branch: elpa/dirvish
commit 2eb0118681dd65836bfa7e3442f5ed152615b533
Author: Alex Lu <[email protected]>
Commit: Alex Lu <[email protected]>
refactor(rsync): extract rsync code to `dirvish-rsync.el`
---
dirvish.el | 1 +
docs/EXTENSIONS.org | 14 +-
extensions/dirvish-rsync.el | 379 ++++++++++++++++++++++++++++++++++++++++
extensions/dirvish-yank.el | 410 ++++----------------------------------------
4 files changed, 429 insertions(+), 375 deletions(-)
diff --git a/dirvish.el b/dirvish.el
index a23c17a67a..121e1397f7 100644
--- a/dirvish.el
+++ b/dirvish.el
@@ -23,6 +23,7 @@
(require 'dired)
(require 'transient)
+(eval-when-compile (require 'subr-x))
(declare-function ansi-color-apply-on-region "ansi-color")
(declare-function dirvish-fd-find "dirvish-fd")
(declare-function dirvish-tramp-noselect "dirvish-tramp")
diff --git a/docs/EXTENSIONS.org b/docs/EXTENSIONS.org
index 8e53efc111..6bcef682cf 100644
--- a/docs/EXTENSIONS.org
+++ b/docs/EXTENSIONS.org
@@ -44,6 +44,14 @@ B, and paste them with ~dirvish-yank/move/symlink/hardlink~.
See also:
[[https://github.com/alexluigit/dirvish/blob/main/docs/FAQ.org#dired-ranger][comparison
with dired-ranger]]
+* Integration with *rsync* command (dirvish-rsync.el)
+
+This extension introduces =dirvish-rsync=, which requires
[[https://github.com/RsyncProject/rsync][rsync]] executable,
+mirroring the functionality of Alex Bennée's =dired-rsync=. Uniquely,
+=dirvish-rsync= gathers marked files from multiple Dired buffers. It also
+provides a transient menu =dirvish-rsync-menu=, for temporary adjustments to
+=dirvish-rsync-args=.
+
* Group files with custom filter stack (dirvish-emerge.el)
This extension lets you split the file list into different groups by various
@@ -87,7 +95,7 @@ of the peek window.
~dirvish-peek-mode~ currently supports =vertico=, =selectrum=, =ivy= and
=icomplete[-vertical]=.
-* Version-control (git) integration (dirvish-vc.el)
+* Version-control (*git*) integration (dirvish-vc.el)
This extension gives Dirvish the ablity to display version-control data in
different ways. For now we have:
@@ -149,7 +157,7 @@ breeze. No manual editing anymore!
*Figure 3*. left: ~dirvish-quicksort~ right: ~dirvish-ls-switches-menu~
-* Dirvish as the interface of fd (dirvish-fd.el)
+* Dirvish as the interface of *fd* command (dirvish-fd.el)
This is the BEST =fd= frontend, period.
@@ -188,7 +196,7 @@ You can use ~dirvish-subtree-toggle~ to toggle the
directory under the cursor as
subtree. Add ~subtree-state~ to ~dirvish-attributes~ gives you an indicator
about
whether the directory is expanded or not.
-* History navigation commands (dirvish-history.el)
+* History navigation (dirvish-history.el)
|-----------------------------+---------------------------------------|
| Command | Description |
diff --git a/extensions/dirvish-rsync.el b/extensions/dirvish-rsync.el
new file mode 100644
index 0000000000..8b54a92060
--- /dev/null
+++ b/extensions/dirvish-rsync.el
@@ -0,0 +1,379 @@
+;;; dirvish-rsync.el --- Rsync integration for Dirvish -*- lexical-binding: t
-*-
+
+;; Copyright (C) 2021-2025 Alex Lu
+;; Author : Alex Lu <https://github.com/alexluigit>
+;; Version: 2.0.53
+;; Keywords: files, convenience
+;; Homepage: https://github.com/alexluigit/dirvish
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;;; Commentary:
+
+;; This extension introduces `dirvish-rsync' command (which requires `rsync'
+;; executable), mirroring the functionality of Alex Bennée's `dired-rsync'.
+;; Uniquely, `dirvish-rsync' gathers marked files from multiple Dired buffers.
+;; It also provides a transient menu `dirvish-rsync-menu', for temporary
+;; adjustments to `dirvish-rsync-args'.
+
+;;; Code:
+
+(require 'dirvish-yank)
+(require 'tramp)
+
+(define-obsolete-variable-alias 'dirvish-yank-rsync-program
'dirvish-rsync-program "Fed 9, 2025")
+(defcustom dirvish-rsync-program "rsync"
+ "The rsync binary that we are going to use."
+ :type 'string :group 'dirvish)
+
+(define-obsolete-variable-alias 'dirvish-yank-rsync-args 'dirvish-rsync-args
"Fed 9, 2025")
+(defcustom dirvish-rsync-args
+ '("--archive" "--verbose" "--compress" "--info=progress2")
+ "The default options for the rsync command."
+ :type '(repeat string) :group 'dirvish)
+
+(defcustom dirvish-rsync-r2r-ssh-port "22"
+ "Default ssh port of receiver when yanking in remote to remote scenario.
+In this scenario rsync will be run on remote host, so it has no access
+to your ~/.ssh/config file. If you have some settings there you have to
+specify them somehow. One way is to set global default values and other
+way is to set them locally before copying, using rsync-transient menu."
+ :type 'string :group 'dirvish)
+
+(defcustom dirvish-rsync-r2r-ssh-user nil
+ "Default ssh user of receiver when yanking in remote to remote scenario.
+When it is nil, do not specify any user. See
+`dirvish-rsync-r2r-ssh-port' for more details."
+ :type '(choice string (const nil)) :group 'dirvish)
+
+(defcustom dirvish-rsync-r2r-use-direct-connection nil
+ "When t, copy data directly from host1 to host2.
+If this is not possible, for example when host2 is not reacheable from
+host1 set this option to nil. When it is nil the tunnel will be created
+between host1 and host2, using running machine as proxy. For both cases
+make sure that you have passwordless access to both hosts and that
+ssh-agent is properly set-up. For checking that, everything works try
+to execute a command \"ssh -A host1 ssh -o StrictHostKeyChecking=no
+host2 hostname\". Also make sure that ssh-agent Environment variables
+are propagated to Emacs."
+ :type 'boolean :group 'dirvish)
+
+(defcustom dirvish-rsync-shortcut-key-for-yank-menu "R"
+ "A shortcut key added to `dirvish-yank-menu'."
+ :type 'string :group 'dirvish)
+
+(defcustom dirvish-rsync-use-yank-menu t
+ "When t, append a shortcut to invoke `dirvish-rsync' in `dirvish-yank-menu'.
+The shortcut key is denoted by `dirvish-rsync-shortcut-key-for-yank-menu'."
+ :type 'boolean :group 'dirvish
+ :set (lambda (k v)
+ (set k v)
+ (if v (dirvish-yank--menu-setter
+ nil (append dirvish-yank-keys
+ `((,dirvish-rsync-shortcut-key-for-yank-menu
+ "Rsync here" dirvish-rsync))))
+ (dirvish-yank--menu-setter nil dirvish-yank-keys))))
+
+(defvar dirvish-rsync--remote-ssh-args
+ "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
+ "These args will be used for invoking ssh on remote host (in r2r case).")
+(defvar dirvish-rsync--transient-input-history nil
+ "History list of rsync transient input in the minibuffer.")
+(defvar crm-separator)
+
+(defvar-local dirvish-rsync--r2r-direct-conn nil
+ "Local value for enabling direct copy in r2r case.")
+(defvar-local dirvish-rsync--r2r-ssh-recv-host nil
+ "Local value of r2r receiver host.")
+(defvar-local dirvish-rsync--r2r-ssh-recv-port nil
+ "Local value of r2r receiver port.")
+(defvar-local dirvish-rsync--r2r-ssh-recv-user nil
+ "Local value of r2r receiver user.")
+
+(defun dirvish-rsync--get-remote-host ()
+ "Return the remote port we shall use for the reverse port-forward."
+ (+ 50000 (length dirvish-yank-log-buffers)))
+
+(defun dirvish-rsync--filename (file)
+ "Reformat a tramp FILE to one usable for rsync."
+ (if (tramp-tramp-file-p file)
+ (with-parsed-tramp-file-name file tfop
+ (format "%s%s:%s" (if tfop-user (format "%s@" tfop-user) "") tfop-host
+ (shell-quote-argument tfop-localname)))
+ (shell-quote-argument file)))
+
+(defun dirvish-rsync--compose-command ()
+ "Compose rsync command and args into the string.
+Retrieve rsync args from current session or `dirvish-rsync-args'."
+ (format "%s %s"
+ dirvish-rsync-program
+ (string-join
+ (or (dirvish-prop :rsync-switches) dirvish-rsync-args) " ")))
+
+(defun dirvish-rsync--local-ssh-args (host-info)
+ "Compose ssh args used for sshing to source host.
+HOST-INFO is a list of host/user/port parsed from the tramp string."
+ (let* ((port (cl-third host-info))
+ (port-str (if port (concat "-p" port) ""))
+ (user (cl-second host-info))
+ (user-str (if user (concat user "@") "")))
+ (concat port-str " " user-str (cl-first host-info))))
+
+(defun dirvish-rsync--r2r-escape-single-quote (str)
+ "Properly escape all single quotes in STR.
+STR should be processed by `shell-quote-argument' already. Single
+quotes require special care since we wrap remote command with them.
+Bash doesn't allow nesting of single quotes (even escaped ones), so we
+need to turn string into multiple concatenated strings."
+ ;; use string-replace from emacs-28.1 when support of older versions is
dropped
+ (replace-regexp-in-string "'" "'\"'\"'" str t t))
+
+;; Thanks to `dired-rsync.el'
+;; also see:
https://unix.stackexchange.com/questions/183504/how-to-rsync-files-between-two-remotes
+(defun dirvish-rsync--r2r-handler (srcs shost-info dhost-info)
+ "Construct and trigger an rsync run for remote copy.
+This command sync SRCS on SHOST to DEST on DHOST. SHOST-INFO and
+DHOST-INFO are lists containing host,user,port,localname extracted from
+the tramp string."
+ (let* ((srcs (mapcar (lambda (x)
+ (thread-last x file-local-name shell-quote-argument
+ dirvish-rsync--r2r-escape-single-quote))
+ srcs))
+ (src-str (string-join srcs " "))
+ (shost (cl-first shost-info))
+ (dhost (cl-first dhost-info))
+ (dhost-real (or dirvish-rsync--r2r-ssh-recv-host
+ (cl-first dhost-info)))
+ (duser (or dirvish-rsync--r2r-ssh-recv-user
+ (cl-second dhost-info)
+ dirvish-rsync-r2r-ssh-user))
+ (dport (or dirvish-rsync--r2r-ssh-recv-port
+ (cl-third dhost-info)
+ dirvish-rsync-r2r-ssh-port))
+ (dest (thread-last (cl-fourth dhost-info)
+ shell-quote-argument
+ dirvish-rsync--r2r-escape-single-quote))
+ ;; 1. dhost == shost
+ ;; ssh [-p dport] [duser@]dhost 'rsync <rsync-args> <srcs> <dest>'
+ ;; 2. dhost != shost and `dirvish-rsync-r2r-use-direct-connection' ==
t
+ ;; ssh -A [-p sport] [suser@]shost 'rsync <rsync-args> -e "ssh
<ssh-remote-opts> [-p dport]" <srcs> [duser@]dhost:<dest> '
+ ;; 3. dhost != shost and `dirvish-rsync-r2r-use-direct-connection' ==
nil
+ ;; ssh -A -R <bind-addr> [-p sport] [suser@]shost 'rsync <rsync-args>
-e "ssh <ssh-remote-opts> -p <tunnel_port>" <srcs> [duser@]localhost:<dest>'
+ (cmd (cond ((equal shost dhost)
+ (string-join
+ (list "ssh"
+ (dirvish-rsync--local-ssh-args dhost-info)
+ "'"
+ (dirvish-rsync--compose-command)
+ src-str dest "'")
+ " "))
+ ((if dirvish-rsync--r2r-direct-conn
+ (equal dirvish-rsync--r2r-direct-conn "yes")
+ dirvish-rsync-r2r-use-direct-connection)
+ (string-join
+ (list "ssh -A "
+ (dirvish-rsync--local-ssh-args shost-info)
+ " '" (dirvish-rsync--compose-command)
+ (format " -e \"ssh %s %s\" "
+ (if dport (concat "-p" dport) "")
+ dirvish-rsync--remote-ssh-args)
+ src-str " "
+ (if duser
+ (format "%s@%s" duser dhost-real)
+ dhost-real)
+ ":" dest "'")))
+ (t (let* ((port (dirvish-rsync--get-remote-host))
+ (bind-addr (format "localhost:%d:%s:%s"
+ port dhost-real dport)))
+ (string-join
+ (list "ssh -A -R " bind-addr " "
+ (dirvish-rsync--local-ssh-args shost-info)
+ " '" (dirvish-rsync--compose-command)
+ (format " -e \"ssh -p %s %s\" "
+ port dirvish-rsync--remote-ssh-args)
+ src-str
+ " "
+ (if duser
+ (format "%s@localhost" duser)
+ "localhost")
+ ":" dest "'")))))))
+ (dirvish-yank--execute cmd (list (current-buffer) srcs dest 'rsync))))
+
+(defun dirvish-rsync--l2fr-handler (srcs dest)
+ "Execute a local to/from remote rsync command for SRCS and DEST."
+ (let* ((srcs (mapcar #'dirvish-rsync--filename srcs))
+ (dest (dirvish-rsync--filename dest))
+ (rsync-cmd (flatten-tree (list (dirvish-rsync--compose-command)
+ srcs dest)))
+ (cmd (string-join rsync-cmd " ")))
+ (dirvish-yank--execute cmd (list (current-buffer) srcs dest 'rsync))))
+
+;; copied from `dired-rsync'
+(defun dirvish-rsync--extract-host-from-tramp (file-or-path)
+ "Extract the tramp host part of FILE-OR-PATH.
+Returns list that contains (host user port localname)."
+ (with-parsed-tramp-file-name file-or-path tfop
+ (when tfop-hop
+ (user-error "DIRVISH[rsync]: Paths with hop are not supported!"))
+ (list tfop-host tfop-user tfop-port tfop-localname)))
+
+(defun dirvish-rsync--extract-remote (files)
+ "Get string identifying the remote connection of FILES."
+ (cl-loop with hosts = () for f in files for h = (file-remote-p f)
+ do (cl-pushnew h hosts :test #'equal)
+ when (> (length hosts) 1)
+ do (user-error "DIRVISH[rsync]: SOURCEs need to be in the same
host")
+ finally return (car hosts)))
+
+;;;###autoload
+(defun dirvish-rsync (dest)
+ "Rsync marked files to DEST, prompt for DEST if not called with.
+If either the sources or the DEST is located in a remote host, the
+`dirvish-rsync-program' and `dirvish-rsync-args' are used to transfer
+the files.
+
+This command requires proper ssh authentication setup to work correctly
+for file transfer involving remote hosts, because rsync command is
+always run locally, the password prompts may lead to unexpected errors."
+ (interactive (dirvish-yank--read-dest 'rsync))
+ (setq dest (expand-file-name (or dest (dired-current-directory))))
+ (let* ((dvec (and (tramp-tramp-file-p dest) (tramp-dissect-file-name dest)))
+ (srcs (or (and (functionp dirvish-yank-sources)
+ (funcall dirvish-yank-sources))
+ (dirvish-yank--get-srcs dirvish-yank-sources)
+ (user-error "DIRVISH[rsync]: no marked files")))
+ (src-0 (prog1 (car srcs) (dirvish-rsync--extract-remote srcs)))
+ (svec (and (tramp-tramp-file-p src-0) (tramp-dissect-file-name
src-0))))
+ (cond
+ ;; shost and dhost are different remote hosts
+ ((and svec dvec (not (tramp-local-host-p svec))
+ (not (tramp-local-host-p dvec)))
+ (dirvish-rsync--r2r-handler
+ srcs (dirvish-rsync--extract-host-from-tramp src-0)
+ (dirvish-rsync--extract-host-from-tramp dest)))
+ ;; either shost, dhost or both are localhost
+ (t (dirvish-rsync--l2fr-handler srcs dest)))))
+
+(defun dirvish-rsync--transient-init-rsync-switches (obj)
+ "Select initial values for transient suffixes, possibly from OBJ.
+Use values from the local session or Emacs session or saved transient
+values."
+ (or (dirvish-prop :rsync-switches)
+ ;; don't touch if it is alreday set
+ (if (and (slot-boundp obj 'value) (oref obj value))
+ (oref obj value)
+ ;; check saved values
+ (if-let* ((saved (assq (oref obj command) transient-values)))
+ (cdr saved)
+ ;; use default value at last resort
+ dirvish-rsync-args))))
+
+(transient-define-infix dirvish-rsync--r2r-ssh-host ()
+ "Set ssh host of receiver in remote to remote case."
+ :description "Ssh host of receiver"
+ :class 'transient-lisp-variable
+ :variable 'dirvish-rsync--r2r-ssh-recv-host
+ :reader (lambda (_prompt _init _hist)
+ (completing-read
+ "Ssh receiver host: "
+ nil nil nil dirvish-rsync--transient-input-history)))
+
+(transient-define-infix dirvish-rsync--r2r-ssh-port ()
+ "Set ssh port of receiver in remote to remote case."
+ :description "Ssh port of receiver"
+ :class 'transient-lisp-variable
+ :variable 'dirvish-rsync--r2r-ssh-recv-port
+ :reader (lambda (_prompt _init _hist)
+ (completing-read
+ "Ssh receiver port: "
+ nil nil nil dirvish-rsync--transient-input-history)))
+
+(transient-define-infix dirvish-rsync--r2r-ssh-user ()
+ "Set ssh user of receiver in remote to remote case."
+ :description "Ssh user of receiver"
+ :class 'transient-lisp-variable
+ :variable 'dirvish-rsync--r2r-ssh-recv-user
+ :reader (lambda (_prompt _init _hist)
+ (completing-read
+ "Ssh receiver user: "
+ nil nil nil dirvish-rsync--transient-input-history)))
+
+(transient-define-infix dirvish-rsync--r2r-direct-conn ()
+ :class 'transient-lisp-variable
+ :variable 'dirvish-rsync--r2r-direct-conn
+ :reader (lambda (_prompt _init _hist)
+ (completing-read "direct: " '(yes no) nil t)))
+
+(transient-define-prefix dirvish-rsync-transient-configure ()
+ "Configure variables for `dirvish-rsync'."
+ ["Remote to remote"
+ ("rh" "Receiver host" dirvish-rsync--r2r-ssh-host)
+ ("rp" "Receiver port" dirvish-rsync--r2r-ssh-port)
+ ("ru" "Receiver user" dirvish-rsync--r2r-ssh-user)
+ ("rd" "Direct connection" dirvish-rsync--r2r-direct-conn)])
+
+;; inspired by `dired-rsync-transient'
+(define-obsolete-function-alias 'dirvish-rsync-transient #'dirvish-rsync-menu
"Feb 09, 2025")
+;;;###autoload (autoload 'dirvish-rsync-menu "dirvish-rsync" nil t)
+(transient-define-prefix dirvish-rsync-menu ()
+ "Transient command for `dirvish-rsync'."
+ :init-value (lambda (o)
+ (oset o value (dirvish-rsync--transient-init-rsync-switches
o)))
+ ["Common Arguments"
+ ("-a" "archive mode; equals to -rlptgoD" ("-a" "--archive"))
+ ("-s" "no space-splitting; useful when remote filenames contain spaces"
("-s" "--protect-args") :level 4)
+ ("-r" "recurse into directories" ("-r" "--recursive") :level 5)
+ ("-z" "compress file data during the transfer" ("-z" "--compress"))]
+ ["Files selection args"
+ ("-C" "auto-ignore files in the same way CVS does" ("-C" "--cvs-exclude")
:level 4)
+ ("=e" "exclude files matching PATTERN" "--exclude="
+ :multi-value repeat :reader dirvish-rsync--transient-read-multiple
+ :prompt "exclude (e.g. ‘*.git’ or ‘*.bin,*.elc’): ")
+ ("=i" "include files matching PATTERN" "--include="
+ :multi-value repeat :reader dirvish-rsync--transient-read-multiple
+ :prompt "include (e.g. ‘*.pdf’ or ‘*.org,*.el’): " :level 5)]
+ ["Sender specific args"
+ ("-L" "transform symlink into referent file/dir" ("-L" "--copy-links")
:level 4)
+ ("-x" "don't cross filesystem boundaries" ("-x" "--one-file-system") :level
5)
+ ("-l" "copy symlinks as symlinks" ("-l" "--links") :level 5)
+ ("-c" "skip based on checksum, not mod-time & size" ("-c" "--checksum")
:level 6)
+ ("-m" "prune empty directory chains from file-list" ("-m"
"--prune-empty-dirs") :level 6)
+ ("--size-only" "skip files that match in size" "--size-only" :level 6)]
+ ["Receiver specific args"
+ ("-R" "use relative path names" ("-R" "--relative") :level 4)
+ ("-u" "skip files that are newer on the receiver" ("-u" "--update") :level
4)
+ ("=d" "delete extraneous files from dest dirs" "--delete" :level 4)
+ ("-b" "make backups" ("-b" "--backup") :level 5)
+ ("=bs" "backup suffix" "--suffix="
+ :prompt "backup suffix: "
+ :reader (lambda (prompt &optional _initial-input history)
+ (completing-read prompt nil nil nil nil history))
+ :level 5)
+ ("-num" "don't map uid/gid values by user/group name" "--numeric-ids"
:level 5)
+ ("-ex" "skip creating new files on receiver" "--existing" :level 6)
+ ("-K" "treat symlinked dir on receiver as dir" ("-K" "--keep-dirlinks")
:level 6)]
+ ["Information output"
+ ("-v" "increase verbosity" ("-v" "--verbose"))
+ ("-i" "output a change-summary for all updates" "-i" :level 5)
+ ("-h" "output numbers in a human-readable format" "-h" :level 5)
+ ("=I" "per-file (1) or total transfer (2) progress" "--info="
+ :choices ("progress1" "progress2") :level 4)]
+ ["Configure"
+ ("C" "Set variables..." dirvish-rsync-transient-configure)]
+ ["Action"
+ [("RET" "Apply switches and copy" dirvish-rsync--apply-switches-and-copy)]])
+
+(defun dirvish-rsync--transient-read-multiple
+ (prompt &optional _initial-input _history)
+ "Read multiple values after PROMPT with optional INITIAL_INPUT and HISTORY."
+ (let ((crm-separator ","))
+ (completing-read-multiple
+ prompt nil nil nil nil dirvish-rsync--transient-input-history)))
+
+(defun dirvish-rsync--apply-switches-and-copy (args)
+ "Execute rsync command generated by transient ARGS."
+ (interactive (list (transient-args transient-current-command)))
+ (dirvish-prop :rsync-switches args)
+ (call-interactively #'dirvish-rsync))
+
+(provide 'dirvish-rsync)
+;;; dirvish-rsync.el ends here
diff --git a/extensions/dirvish-yank.el b/extensions/dirvish-yank.el
index 94d859f8b5..1197fa5361 100644
--- a/extensions/dirvish-yank.el
+++ b/extensions/dirvish-yank.el
@@ -22,14 +22,11 @@
;; - `dirvish-symlink'
;; - `dirvish-relative-symlink'
;; - `dirvish-hardlink'
-;; - `dirvish-rsync' (requires 'rsync' executable)
-;; - `dirvish-rsync-transient' (requires 'rsync' executable)
;;; Code:
(require 'dired-aux)
(require 'dirvish)
-(require 'tramp)
(defcustom dirvish-yank-sources 'all
"The way to collect source files.
@@ -59,20 +56,26 @@ The value can be a symbol or a function that returns a
fileset."
(const :tag "append INDEX~ to file name" append-to-filename)
(const :tag "prepend INDEX~ to file name"
prepend-to-filename)))
-(defcustom dirvish-yank-rsync-program "rsync"
- "The rsync binary that we are going to use."
- :type 'string :group 'dirvish)
-
-(defcustom dirvish-yank-rsync-args '("--archive" "--verbose" "--compress"
"--info=progress2")
- "The default options for the rsync command."
- :type '(repeat string) :group 'dirvish)
-
(defcustom dirvish-yank-keep-success-log t
"If non-nil then keep logs of all completed yanks.
-By default only logs for yanks that finished with an error are
-kept alive."
+By default only keep the log buffer alive for failed tasks."
:type 'boolean :group 'dirvish)
+(defun dirvish-yank--menu-setter (symbol pairs)
+ "Set key-command PAIRS for SYMBOL `dirvish-yank-menu'."
+ (when symbol (set symbol pairs))
+ (eval
+ `(transient-define-prefix dirvish-yank-menu ()
+ "Yank commands menu."
+ [:description
+ (lambda () (dirvish--format-menu-heading
+ "Select yank operation on marked files:"))
+ ,@pairs]
+ (interactive)
+ (if (derived-mode-p 'dired-mode)
+ (transient-setup 'dirvish-yank-menu)
+ (user-error "Not in a Dirvish buffer")))))
+
;;;###autoload (autoload 'dirvish-yank-menu "dirvish-yank" nil t)
(defcustom dirvish-yank-keys
'(("y" "Yank (paste) here" dirvish-yank)
@@ -83,47 +86,7 @@ kept alive."
"YANK-KEYs for command `dirvish-yank-menu'.
A YANK-KEY is a (KEY DOC CMD) alist where KEY is the key to invoke the
CMD, DOC is the documentation string."
- :group 'dirvish :type 'alist
- :set
- (lambda (k v)
- (set k v)
- (eval
- `(transient-define-prefix dirvish-yank-menu ()
- "Yank commands menu."
- [:description
- (lambda () (dirvish--format-menu-heading "Select yank operation on
marked files:"))
- ,@v]
- (interactive)
- (if (derived-mode-p 'dired-mode)
- (transient-setup 'dirvish-yank-menu)
- (user-error "Not in a Dirvish buffer"))))))
-
-
-(defcustom dirvish-yank-ssh-r2r-default-port "22"
- "Default ssh port of receiver when yanking in remote to remote scenario.
-In this scenario rsync will be run on remote host, so it has no access
-to your ~/.ssh/config file. If you have some settings there you have to
-specify them somehow. One way is to set global default values and other
-way is to set them locally before copying, using rsync-transient menu."
- :type 'string :group 'dirvish)
-
-(defcustom dirvish-yank-ssh-r2r-default-user nil
- "Default ssh user of receiver when yanking in remote to remote scenario.
-When nil do not specify any user. See
-`dirvish-yank-ssh-r2r-default-port' for more details."
- :type 'string :group 'dirvish)
-
-(defcustom dirvish-yank-r2r-default-direct-conn nil
- "When t copy data directly from host1 to host2.
-If this is not possible, for example when host2 is not reacheable from
-host1 set this option to nil. When it is nil the tunnel will be created
-between host1 and host2, using running machine as proxy. For both cases
-make sure that you have passwordless access to both hosts and that
-ssh-agent is properly set-up. For checking that, everything works try
-to execute a command \"ssh -A host1 ssh -o StrictHostKeyChecking=no
-host2 hostname\". Also make sure that ssh-agent Environment variables
-are propagated to Emacs."
- :type 'string :group 'dirvish)
+ :group 'dirvish :type 'alist :set #'dirvish-yank--menu-setter)
(defconst dirvish-yank-fn-string
'((dired-copy-file . "Copying")
@@ -138,27 +101,12 @@ are propagated to Emacs."
"\\`\\(tramp-\\(default\\|connection\\|remote\\)\\|ange-ftp\\)-.*"
"Variables matching this regexp will be loaded on Child Emacs.")
;; matches "Enter passphrase for key ..." (ssh) and "password for ..." (samba)
-(defvar dirvish-passphrase-stall-regex
+(defvar dirvish-yank-passphrase-stall-regex
"\\(Enter \\)?[Pp]ass\\(word\\|phrase\\) for\\( key\\)?"
"A regex to detect passphrase prompts.")
-(defvar dirvish-percent-complete-regex "[[:digit:]]\\{1,3\\}%"
+(defvar dirvish-yank-percent-complete-regex "[[:digit:]]\\{1,3\\}%"
"A regex to extract the % complete from a file.")
-(defvar dirvish-yank--remote-ssh-args
- "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
- "These args will be used for invoking ssh on remote host (in r2r case).")
-
-(defvar dirvish-yank--rsync-transient-input-history nil "History list of rsync
transient input in the minibuffer.")
-
-(defvar-local dirvish-yank--r2r-direct-conn nil "Local value for enabling
direct copy in r2r case.")
-(defvar-local dirvish-yank--ssh-r2r-receiver-host nil "Local value of r2r
receiver host.")
-(defvar-local dirvish-yank--ssh-r2r-receiver-port nil "Local value of r2r
receiver port.")
-(defvar-local dirvish-yank--ssh-r2r-receiver-user nil "Local value of r2r
receiver user.")
-
-(defun dirvish-yank--get-remote-port ()
- "Return the remote port we shall use for the reverse port-forward."
- (+ 50000 (length dirvish-yank-log-buffers)))
-
(defun dirvish-yank--get-srcs (&optional range)
"Get all marked filenames in RANGE.
RANGE can be `buffer', `session', `all'."
@@ -188,14 +136,6 @@ RANGE can be `buffer', `session', `all'."
(dired-dwim-target-directory)
nil nil nil 'file-directory-p))))
-(defun dirvish-yank--filename-for-rsync (file)
- "Reformat a tramp FILE to one usable for rsync."
- (if (tramp-tramp-file-p file)
- (with-parsed-tramp-file-name file tfop
- (format "%s%s:%s" (if tfop-user (format "%s@" tfop-user) "") tfop-host
- (shell-quote-argument tfop-localname)))
- (shell-quote-argument file)))
-
(defun dirvish-yank-proc-sentinel (proc _exit)
"Sentinel for yank task PROC."
(pcase-let ((proc-buf (process-buffer proc))
@@ -235,7 +175,7 @@ RANGE can be `buffer', `session', `all'."
"Filter for yank task PROC's STRING."
(let ((proc-buf (process-buffer proc)))
;; check for passphrase prompt
- (when (string-match dirvish-passphrase-stall-regex string)
+ (when (string-match dirvish-yank-passphrase-stall-regex string)
(process-send-string proc (concat (read-passwd string) "\n")))
;; Answer yes for `large file' prompt
(when (string-match "File .* is large\\(.*\\), really copy" string)
@@ -243,7 +183,7 @@ RANGE can be `buffer', `session', `all'."
(let ((old-process-mark (process-mark proc)))
(when (buffer-live-p proc-buf)
(with-current-buffer proc-buf
- (when (string-match dirvish-percent-complete-regex string)
+ (when (string-match dirvish-yank-percent-complete-regex string)
(dirvish-prop :yank-percent (match-string 0 string))
(force-mode-line-update t))
(let ((moving (= (point) old-process-mark)))
@@ -326,7 +266,8 @@ File `%s' exists, type one of the following keys to
continue.
(if (memq method '(dired-make-relative-symlink make-symbolic-link))
(push (cons src (cdr (dirvish-yank--newbase base dfiles dest)))
result)
- (user-error "Source and target are the same file `%s'" src)))
+ (user-error
+ "DIRVISH[yank]: source and target are the same file `%s'" src)))
(overwrite (push (cons src dest) result))
((and backup collision)
(push (dirvish-yank--newbase base dfiles dest) to-rename)
@@ -347,13 +288,13 @@ File `%s' exists, type one of the following keys to
continue.
(?B (setq backup t)
(push (dirvish-yank--newbase base dfiles dest) to-rename)
(push (cons src dest) result))
- ((?q ?\e) (user-error "Dirvish[info]: yank task aborted"))))
+ ((?q ?\e) (user-error "DIRVISH[yank]: task aborted"))))
(t (push (cons src dest) result)))
finally return
(prog1 result
(cl-loop for (from . to) in to-rename do (rename-file from to)))))
-(defun dirvish-yank-inject-env (include-regexp)
+(defun dirvish-yank--inject-env (include-regexp)
"Return a `setq' form that replicates part of the calling environment.
It sets the value for every variable matching INCLUDE-REGEXP."
`(setq ,@(let (bindings)
@@ -370,116 +311,6 @@ It sets the value for every variable matching
INCLUDE-REGEXP."
bindings (cons sym bindings))))))
bindings)))
-;; Thanks to `dired-rsync.el'
-;; also see:
https://unix.stackexchange.com/questions/183504/how-to-rsync-files-between-two-remotes
-
-(defun dirvish-yank--rsync-args ()
- "Retrieve rsync args for current session or default ones."
- (or (dirvish-prop :rsync-switches)
- dirvish-yank-rsync-args))
-
-(defun dirvish-yank--build-rsync-command ()
- "Compose rsync command and args into the string."
- (format "%s %s" dirvish-yank-rsync-program
- (string-join (dirvish-yank--rsync-args) " ")))
-
-(defun dirvish-yank--build-local-ssh-args (host-info)
- "Compose ssh args used for sshing to source host.
-HOST-INFO is a list of host/user/port parsed from the tramp string."
- (let* ((port (cl-third host-info))
- (port-str (if port (concat "-p" port) ""))
- (user (cl-second host-info))
- (user-str (if user (concat user "@") "")))
- (concat port-str " " user-str (cl-first host-info))))
-
-(defun dirvish-yank--r2r-escape-single-quote (str)
- "Properly escape all single quotes in STR.
-STR should be processed by `shell-quote-argument' already. Single
-quotes require special care since we wrap remote command with them.
-Bash doesn't allow nesting of single quotes (even escaped ones), so we
-need to turn string into multiple concatenated strings."
- ;; use string-replace from emacs-28.1 when support of older versions is
dropped
- (replace-regexp-in-string "'" "'\"'\"'" str t t))
-
-(defun dirvish-yank-r2r-handler (srcs shost-info dhost-info)
- "Construct and trigger an rsync run for remote copy.
-This command sync SRCS on SHOST to DEST on DHOST. SHOST-INFO and
-DHOST-INFO are lists containing host,user,port,localname
-extracted from the tramp string."
- (let* ((srcs (mapcar (lambda (x)
- (thread-last x file-local-name shell-quote-argument
- dirvish-yank--r2r-escape-single-quote))
- srcs))
- (src-str (string-join srcs " "))
- (shost (cl-first shost-info))
- (dhost (cl-first dhost-info))
- (dhost-real (or dirvish-yank--ssh-r2r-receiver-host
- (cl-first dhost-info)))
- (duser (or dirvish-yank--ssh-r2r-receiver-user
- (cl-second dhost-info)
- dirvish-yank-ssh-r2r-default-user))
- (dport (or dirvish-yank--ssh-r2r-receiver-port
- (cl-third dhost-info)
- dirvish-yank-ssh-r2r-default-port))
- (dest (thread-last (cl-fourth dhost-info)
- shell-quote-argument
- dirvish-yank--r2r-escape-single-quote))
-
- ;; 1. dhost == shost
- ;; ssh [-p dport] [duser@]dhost 'rsync <rsync-args> <srcs> <dest>'
- ;; 2. dhost != shost and dirvish-yank-r2r-direct-conn == t
- ;; ssh -A [-p sport] [suser@]shost 'rsync <rsync-args> -e "ssh
<ssh-remote-opts> [-p dport]" <srcs> [duser@]dhost:<dest> '
- ;; 3. dhost != shost and dirvish-yank-r2r-direct-conn == nil
- ;; ssh -A -R <bind-addr> [-p sport] [suser@]shost 'rsync <rsync-args>
-e "ssh <ssh-remote-opts> -p <tunnel_port>" <srcs> [duser@]localhost:<dest>'
- (cmd (cond ((equal shost dhost)
- (string-join
- (list "ssh"
- (dirvish-yank--build-local-ssh-args dhost-info)
- "'"
- (dirvish-yank--build-rsync-command)
- src-str dest "'")
- " "))
- ((if dirvish-yank--r2r-direct-conn
- (equal dirvish-yank--r2r-direct-conn "yes")
- dirvish-yank-r2r-default-direct-conn)
- (string-join
- (list "ssh -A "
- (dirvish-yank--build-local-ssh-args shost-info)
- " '" (dirvish-yank--build-rsync-command)
- (format " -e \"ssh %s %s\" "
- (if dport (concat "-p" dport) "")
- dirvish-yank--remote-ssh-args)
- src-str " "
- (if duser
- (format "%s@%s" duser dhost-real)
- dhost-real)
- ":" dest "'")))
- (t (let* ((port (dirvish-yank--get-remote-port))
- (bind-addr (format "localhost:%d:%s:%s"
- port dhost-real dport)))
- (string-join
- (list "ssh -A -R " bind-addr " "
- (dirvish-yank--build-local-ssh-args shost-info)
- " '" (dirvish-yank--build-rsync-command)
- (format " -e \"ssh -p %s %s\" "
- port dirvish-yank--remote-ssh-args)
- src-str
- " "
- (if duser
- (format "%s@localhost" duser)
- "localhost")
- ":" dest "'")))))))
- (dirvish-yank--execute cmd (list (current-buffer) srcs dest 'rsync))))
-
-(defun dirvish-yank-l2fr-handler (srcs dest)
- "Execute a local to/from remote rsync command for SRCS and DEST."
- (let* ((srcs (mapcar #'dirvish-yank--filename-for-rsync srcs))
- (dest (dirvish-yank--filename-for-rsync dest))
- (rsync-cmd (flatten-tree (list (dirvish-yank--build-rsync-command)
- srcs dest)))
- (cmd (string-join rsync-cmd " ")))
- (dirvish-yank--execute cmd (list (current-buffer) srcs dest 'rsync))))
-
(defun dirvish-yank-default-handler (method srcs dest)
"Execute yank METHOD on SRCS to DEST."
(let* ((pairs (dirvish-yank--filename-pairs method srcs dest))
@@ -487,7 +318,7 @@ extracted from the tramp string."
(cmd `(progn
(require 'dired-aux)
(require 'dired-x)
- ,(dirvish-yank-inject-env dirvish-yank-env-variables-regexp)
+ ,(dirvish-yank--inject-env dirvish-yank-env-variables-regexp)
(cl-loop
with dired-recursive-copies = 'always
with dired-copy-preserve-time = ,dired-copy-preserve-time
@@ -507,30 +338,13 @@ extracted from the tramp string."
(dirvish-yank--execute
(format "%S" cmd) (list (current-buffer) srcs dest method) 'batch)))
-;; copied from `dired-rsync'
-(defun dirvish-yank--extract-host-from-tramp (file-or-path)
- "Extract the tramp host part of FILE-OR-PATH.
-Returns list that contains (host user port localname)."
- (with-parsed-tramp-file-name file-or-path tfop
- (when tfop-hop
- (user-error "Dirvish-yank: Paths with hop are not supported!"))
- (list tfop-host tfop-user tfop-port tfop-localname)))
-
-(defun dirvish-yank--extract-remote (files)
- "Get string identifying the remote connection of FILES."
- (cl-loop with hosts = () for f in files for h = (file-remote-p f)
- do (cl-pushnew h hosts :test #'equal)
- when (> (length hosts) 1)
- do (user-error "Dirvish[error]: SOURCEs need to be in the same
host")
- finally return (car hosts)))
-
(defun dirvish-yank--apply (method dest)
"Apply yank METHOD to DEST."
(setq dest (expand-file-name (or dest (dired-current-directory))))
(let ((srcs (or (and (functionp dirvish-yank-sources)
(funcall dirvish-yank-sources))
(dirvish-yank--get-srcs dirvish-yank-sources)
- (user-error "Dirvish[error]: no marked files"))))
+ (user-error "DIRVISH[yank]: no marked files"))))
(dirvish-yank-default-handler method srcs dest)))
(dirvish-define-mode-line yank
@@ -562,197 +376,49 @@ Returns list that contains (host user port localname)."
(format " %s %s%s "
(propertize (number-to-string number-of-tasks)
'face 'font-lock-keyword-face)
- (propertize "running yank task" 'face 'font-lock-doc-face)
+ (propertize "running tasks" 'face 'font-lock-doc-face)
(propertize (if (> number-of-tasks 1) "s" "")
'face 'font-lock-doc-face))))))
;;;###autoload
(defun dirvish-yank (&optional dest)
"Paste marked files to DEST.
-Prompt for DEST when prefixed with \\[universal-argument], it
-defaults to `dired-current-directory.'"
+Prompt for DEST when prefixed with \\[universal-argument], it defaults
+to `dired-current-directory.'"
(interactive (dirvish-yank--read-dest 'yank))
(dirvish-yank--apply 'dired-copy-file dest))
;;;###autoload
(defun dirvish-move (&optional dest)
"Move marked files to DEST.
-Prompt for DEST when prefixed with \\[universal-argument], it
-defaults to `dired-current-directory'."
+Prompt for DEST when prefixed with \\[universal-argument], it defaults
+to `dired-current-directory'."
(interactive (dirvish-yank--read-dest 'move))
(dirvish-yank--apply 'dired-rename-file dest))
;;;###autoload
(defun dirvish-symlink (&optional dest)
"Symlink marked files to DEST.
-Prompt for DEST when prefixed with \\[universal-argument], it
-defaults to `dired-current-directory'."
+Prompt for DEST when prefixed with \\[universal-argument], it defaults
+to `dired-current-directory'."
(interactive (dirvish-yank--read-dest 'symlink))
(dirvish-yank--apply 'make-symbolic-link dest))
;;;###autoload
(defun dirvish-relative-symlink (&optional dest)
"Similar to `dirvish-symlink', but link files relatively.
-Prompt for DEST when prefixed with \\[universal-argument], it
-defaults to `dired-current-directory'."
+Prompt for DEST when prefixed with \\[universal-argument], it defaults
+to `dired-current-directory'."
(interactive (dirvish-yank--read-dest 'relalink))
(dirvish-yank--apply 'dired-make-relative-symlink dest))
;;;###autoload
(defun dirvish-hardlink (&optional dest)
"Hardlink marked files to DEST.
-Prompt for DEST when prefixed with \\[universal-argument], it
-defaults to `dired-current-directory'."
+Prompt for DEST when prefixed with \\[universal-argument], it defaults
+to `dired-current-directory'."
(interactive (dirvish-yank--read-dest 'hardlink))
(dirvish-yank--apply 'dired-hardlink dest))
-;;;###autoload
-(defun dirvish-rsync (dest)
- "Rsync marked files to DEST, prompt for DEST if not called with.
-If either the sources or the DEST is located in a remote host, the
-`dirvish-yank-rsync-program' and `dirvish-yank-rsync-args' are used to
-transfer the files.
-
-This command requires proper ssh authentication setup to work correctly
-for file transfer involving remote hosts, because rsync command is
-always run locally, the password prompts may lead to unexpected errors."
- (interactive (dirvish-yank--read-dest 'rsync))
- (setq dest (expand-file-name (or dest (dired-current-directory))))
- (let* ((dvec (and (tramp-tramp-file-p dest) (tramp-dissect-file-name dest)))
- (srcs (or (and (functionp dirvish-yank-sources)
- (funcall dirvish-yank-sources))
- (dirvish-yank--get-srcs dirvish-yank-sources)
- (user-error "Dirvish[error]: no marked files")))
- (src-0 (prog1 (car srcs) (dirvish-yank--extract-remote srcs)))
- (svec (and (tramp-tramp-file-p src-0) (tramp-dissect-file-name
src-0))))
- (cond
- ;; shost and dhost are different remote hosts
- ((and svec dvec (not (tramp-local-host-p svec))
- (not (tramp-local-host-p dvec)))
- (dirvish-yank-r2r-handler
- srcs (dirvish-yank--extract-host-from-tramp src-0)
- (dirvish-yank--extract-host-from-tramp dest)))
- ;; either shost, dhost or both are localhost
- (t (dirvish-yank-l2fr-handler srcs dest)))))
-
-(defun dirvish-yank--rsync-transient-init-rsync-switches (obj)
- "Select initial values for transient suffixes, possibly from OBJ.
-Use values from the local session or Emacs session or saved transient
-values."
- (or (dirvish-prop :rsync-switches)
- ;; don't touch if it is alreday set
- (if (and (slot-boundp obj 'value) (oref obj value))
- (oref obj value)
- ;; check saved values
- (if-let* ((saved (assq (oref obj command) transient-values)))
- (cdr saved)
- ;; use default value at last resort
- dirvish-yank-rsync-args))))
-
-(transient-define-infix dirvish-yank--r2r-ssh-host ()
- "Set ssh host of receiver in remote to remote case."
- :description "Ssh host of receiver"
- :class 'transient-lisp-variable
- :variable 'dirvish-yank--ssh-r2r-receiver-host
- :reader (lambda (_prompt _init _hist)
- (completing-read "Ssh receiver host: "
- nil nil nil
dirvish-yank--rsync-transient-input-history)))
-
-(transient-define-infix dirvish-yank--r2r-ssh-port ()
- "Set ssh port of receiver in remote to remote case."
- :description "Ssh port of receiver"
- :class 'transient-lisp-variable
- :variable 'dirvish-yank--ssh-r2r-receiver-port
- :reader (lambda (_prompt _init _hist)
- (completing-read "Ssh receiver port: "
- nil nil nil
dirvish-yank--rsync-transient-input-history)))
-
-(transient-define-infix dirvish-yank--r2r-ssh-user ()
- "Set ssh user of receiver in remote to remote case."
- :description "Ssh user of receiver"
- :class 'transient-lisp-variable
- :variable 'dirvish-yank--ssh-r2r-receiver-user
- :reader (lambda (_prompt _init _hist)
- (completing-read "Ssh receiver user: "
- nil nil nil
dirvish-yank--rsync-transient-input-history)))
-
-(transient-define-infix dirvish-yank--r2r-direct-conn ()
- :class 'transient-lisp-variable
- :variable 'dirvish-yank--r2r-direct-conn
- :reader (lambda (_prompt _init _hist) (completing-read "direct: " '(yes no)
nil t)))
-
-(transient-define-prefix dirvish-rsync-transient-configure ()
- "Configure variables for `dirvish-rsync'."
- ["Remote to remote"
- ("rh" "Receiver host" dirvish-yank--r2r-ssh-host)
- ("rp" "Receiver port" dirvish-yank--r2r-ssh-port)
- ("ru" "Receiver user" dirvish-yank--r2r-ssh-user)
- ("rd" "Direct connection" dirvish-yank--r2r-direct-conn)])
-
-;; inspired by `dired-rsync-transient'
-;;;###autoload (autoload 'dirvish-rsync-transient "dirvish-yank" nil t)
-(transient-define-prefix dirvish-rsync-transient ()
- "Transient command for `dirvish-rsync'."
- :init-value (lambda (o) (oset o value
(dirvish-yank--rsync-transient-init-rsync-switches o)))
- ["Common Arguments"
- ("-a" "archive mode; equals to -rlptgoD" ("-a" "--archive"))
- ("-s" "no space-splitting; useful when remote filenames contain spaces"
("-s" "--protect-args") :level 4)
- ("-r" "recurse into directories" ("-r" "--recursive") :level 5)
- ("-z" "compress file data during the transfer" ("-z" "--compress"))]
-
- ["Files selection args"
- ("-C" "auto-ignore files in the same way CVS does" ("-C" "--cvs-exclude")
:level 4)
- ("=e" "exclude files matching PATTERN" "--exclude="
- :multi-value repeat :reader dirvish-yank--rsync-transient-read-multiple
- :prompt "exclude (e.g. ‘*.git’ or ‘*.bin,*.elc’): ")
- ("=i" "include files matching PATTERN" "--include="
- :multi-value repeat :reader dirvish-yank--rsync-transient-read-multiple
- :prompt "include (e.g. ‘*.pdf’ or ‘*.org,*.el’): " :level 5)]
-
- ["Sender specific args"
- ("-L" "transform symlink into referent file/dir" ("-L" "--copy-links")
:level 4)
- ("-x" "don't cross filesystem boundaries" ("-x" "--one-file-system") :level
5)
- ("-l" "copy symlinks as symlinks" ("-l" "--links") :level 5)
- ("-c" "skip based on checksum, not mod-time & size" ("-c" "--checksum")
:level 6)
- ("-m" "prune empty directory chains from file-list" ("-m"
"--prune-empty-dirs") :level 6)
- ("--size-only" "skip files that match in size" "--size-only" :level 6)]
- ["Receiver specific args"
- ("-R" "use relative path names" ("-R" "--relative") :level 4)
- ("-u" "skip files that are newer on the receiver" ("-u" "--update") :level
4)
- ("=d" "delete extraneous files from dest dirs" "--delete" :level 4)
- ("-b" "make backups" ("-b" "--backup") :level 5)
- ("=bs" "backup suffix" "--suffix="
- :prompt "backup suffix: "
- :reader (lambda (prompt &optional _initial-input history)
- (completing-read prompt nil nil nil nil history))
- :level 5)
- ("-num" "don't map uid/gid values by user/group name" "--numeric-ids"
:level 5)
- ("-ex" "skip creating new files on receiver" "--existing" :level 6)
- ("-K" "treat symlinked dir on receiver as dir" ("-K" "--keep-dirlinks")
:level 6)]
-
- ["Information output"
- ("-v" "increase verbosity" ("-v" "--verbose"))
- ("-i" "output a change-summary for all updates" "-i" :level 5)
- ("-h" "output numbers in a human-readable format" "-h" :level 5)
- ("=I" "per-file (1) or total transfer (2) progress" "--info="
- :choices ("progress1" "progress2") :level 4)]
- ["Configure"
- ("C" "Set variables..." dirvish-rsync-transient-configure)]
- ["Action"
- [("RET" "Apply switches and copy"
dirvish-yank--rsync-apply-switches-and-copy)]])
-
-(defvar crm-separator)
-
-(defun dirvish-yank--rsync-transient-read-multiple (prompt &optional
_initial-input _history)
- "Read multiple values after PROMPT with optional INITIAL_INPUT and HISTORY."
- (let ((crm-separator ","))
- (completing-read-multiple prompt nil nil nil nil
dirvish-yank--rsync-transient-input-history)))
-
-(defun dirvish-yank--rsync-apply-switches-and-copy (args)
- "Execute rsync command generated by transient ARGS."
- (interactive (list (transient-args transient-current-command)))
- (dirvish-prop :rsync-switches args)
- (call-interactively #'dirvish-rsync))
-
(provide 'dirvish-yank)
;;; dirvish-yank.el ends here