From 30ae237516590e53efbe64bada58a1499c5999c5 Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Fri, 10 May 2024 12:22:52 -0700 Subject: [PATCH] Use "/local:" prefix in Eshell to run local commands when cwd is remote * lisp/eshell/esh-ext.el (eshell-explicit-remote-commands) (eshell-explicit-command): Update docstrings. (eshell--local-prefix): New constant. (eshell-handle-remote-command): Remove. (eshell-quoted-file-command): New function... (eshell-ext-initialize): ... add it as a hook. (eshell-remote-command): Support running commands on localhost. (eshell-connection-local-command): Rename from 'eshell-external-command'. (eshell-external-command): New implementation calling 'eshell-remote-command' or 'eshell-connection-local-command' as appropriate. * test/lisp/eshell/esh-ext-tests.el (esh-ext-test/explicitly-local-command): Update test. * doc/misc/eshell.texi (Remote Access): Update documentation. * etc/NEWS: Update announcement. --- doc/misc/eshell.texi | 17 +++++--- etc/NEWS | 2 +- lisp/eshell/esh-ext.el | 65 ++++++++++++++++++------------- test/lisp/eshell/esh-ext-tests.el | 2 +- 4 files changed, 50 insertions(+), 36 deletions(-) diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index 30c85da795b..8cb73d4077b 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -1535,13 +1535,18 @@ Remote Access When running commands, you can also make them explicitly remote by prefixing the command name with a remote identifier, e.g.@: @samp{/ssh:user@@remote:whoami}. This runs the command @code{whoami} -over the SSH connection for @code{user@@remote}, no matter your -current directory. If you want to explicitly run a @emph{local} -command even when in a remote directory, you can prefix the command -name with @kbd{/:}, like @samp{/:whoami}. In either case, you can +over the SSH connection for @code{user@@remote}, no matter your current +directory. If you want to explicitly run a command on your @emph{local} +machine even when in a remote directory, you can prefix the command name +with @kbd{/local:}, like @samp{/local:whoami}. In either case, you can also specify the absolute path to the program, e.g.@: -@samp{/ssh:user@@remote:/usr/bin/whoami}. To disable this syntax, set -the option @code{eshell-explicit-remote-commands} to @code{nil}. +@samp{/ssh:user@@remote:/usr/bin/whoami}. If you need to refer to a +program whose file name would be interpreted as an explicitly-remote +command, you can use @kbd{/:} to quote the name, e.g.@: +@samp{/:/ssh:user@@remote:whoami} (@pxref{Quoted File Names,,, emacs, +The GNU Emacs Manual}). To disable explicity-remote commands entirely, +you can set the option @code{eshell-explicit-remote-commands} to +@code{nil}. @node History @section History diff --git a/etc/NEWS b/etc/NEWS index bd68cd6d751..3178a9c3032 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -915,7 +915,7 @@ By prefixing a command name in Eshell with a remote identifier, like "/ssh:user@remote:whoami", you can now run commands on a particular host no matter your current directory. Likewise, you can run a command on your local system no matter your current directory via -"/:whoami". For more information, see the "(eshell) Remote Access" +"/local:whoami". For more information, see the "(eshell) Remote Access" node in the Eshell manual. +++ diff --git a/lisp/eshell/esh-ext.el b/lisp/eshell/esh-ext.el index b4fce7a82a2..c1f2d6d14ac 100644 --- a/lisp/eshell/esh-ext.el +++ b/lisp/eshell/esh-ext.el @@ -167,23 +167,23 @@ eshell-explicit-command-char (defcustom eshell-explicit-remote-commands t "If non-nil, support explicitly-remote commands. These are commands with a full remote file name, such as -\"/ssh:host:whoami\". If this is enabled, you can also run -explicitly-local commands by using a quoted file name, like -\"/:whoami\"." +\"/ssh:host:whoami\". If this is enabled, you can also explicitly run +commands on your local host by using the \"/local:\" prefix, like +\"/local:whoami\"." :type 'boolean :group 'eshell-ext) ;;; Functions: +(defconst eshell--local-prefix "/local:") + (defun eshell-ext-initialize () ;Called from `eshell-mode' via intern-soft! "Initialize the external command handling code." - (add-hook 'eshell-named-command-hook #'eshell-explicit-command nil t) - (when eshell-explicit-remote-commands - (add-hook 'eshell-named-command-hook - #'eshell-handle-remote-command nil t))) + (add-hook 'eshell-named-command-hook #'eshell-quoted-file-command nil t) + (add-hook 'eshell-named-command-hook #'eshell-explicit-command nil t)) (defun eshell-explicit-command (command args) - "If a command name begins with `*', call it externally always. + "If a command name begins with \"*\", always call it externally. This bypasses all Lisp functions and aliases." (when (and (> (length command) 1) (eq (aref command 0) eshell-explicit-command-char)) @@ -194,39 +194,35 @@ eshell-explicit-command (error "%s: external command not found" (substring command 1)))))) -(defun eshell-handle-remote-command (command args) - "Handle remote (or quoted) COMMAND names, using ARGS. -This calls the appropriate function for commands that aren't on -the connection associated with `default-directory'. (See -`eshell-explicit-remote-commands'.)" - (if (file-name-quoted-p command) - (let ((default-directory (if (file-remote-p default-directory) - (expand-file-name "~") - default-directory))) - (eshell-external-command (file-name-unquote command) args)) - (when (file-remote-p command) - (eshell-remote-command command args)))) +(defun eshell-quoted-file-command (command args) + "If a command name begins with \"/:\", always call it externally. +Similar to `eshell-explicit-command', this bypasses all Lisp functions +and aliases, but it also ignores file name handlers." + (when (file-name-quoted-p command) + (eshell-external-command (file-name-unquote command) args))) (defun eshell-remote-command (command args) "Insert output from a remote COMMAND, using ARGS. -A remote command is something that executes on a different machine. -An external command simply means external to Emacs." +A \"remote\" command in Eshell is something that executes on a different +machine. If COMMAND is a remote file name, run it on the host for that +file; if COMMAND is a local file name, run it locally." (let* ((cwd-connection (file-remote-p default-directory)) (command-connection (file-remote-p command)) (default-directory (if (equal cwd-connection command-connection) default-directory - command-connection)) + (or command-connection (expand-file-name "~")))) ;; Never use the remote connection here. We don't want to ;; expand the local name! Instead, we want it as the user ;; typed, so that if COMMAND is "/ssh:host:cat", we just get ;; "cat" as the result. - (command-localname (file-remote-p command 'localname 'never))) - (unless command-connection - (error "%s: not a remote command" command)) + (command-localname (or (file-remote-p command 'localname 'never) + command))) (eshell-external-command command-localname args))) -(defun eshell-external-command (command args) - "Insert output from an external COMMAND, using ARGS." +(defun eshell-connection-local-command (command args) + "Insert output from an external COMMAND, using ARGS. +This always runs COMMAND using the connection associated with the +current working directory." (setq args (eshell-stringify-list (flatten-tree args))) (let ((interp (eshell-find-interpreter command @@ -243,6 +239,19 @@ eshell-external-command (eshell-gather-process-output (car interp) (append (cdr interp) args))))) +(defun eshell-external-command (command args) + "Insert output from an external COMMAND, using ARGS." + (cond + ((and eshell-explicit-remote-commands + (file-remote-p command)) + (eshell-remote-command command args)) + ((and eshell-explicit-remote-commands + (string-prefix-p eshell--local-prefix command)) + (eshell-remote-command + (substring command (length eshell--local-prefix)) args)) + (t + (eshell-connection-local-command command args)))) + (defun eshell/addpath (&rest args) "Add a set of paths to PATH." (eshell-eval-using-options diff --git a/test/lisp/eshell/esh-ext-tests.el b/test/lisp/eshell/esh-ext-tests.el index 8abbd74f737..ce958d788cc 100644 --- a/test/lisp/eshell/esh-ext-tests.el +++ b/test/lisp/eshell/esh-ext-tests.el @@ -102,7 +102,7 @@ esh-ext-test/explicitly-local-command ;; Check the value of $INSIDE_EMACS using `sh' in order to ;; delay variable expansion. (eshell-match-command-output - (format "/:%s -c 'echo $INSIDE_EMACS'" cmd) + (format "/local:%s -c 'echo $INSIDE_EMACS'" cmd) "eshell\n")))))) ;; esh-ext-tests.el ends here -- 2.25.1