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.
This commit is contained in:
Jim Porter 2024-05-10 12:22:52 -07:00
parent c85294a613
commit e260bf1be7
4 changed files with 50 additions and 36 deletions

View file

@ -1535,13 +1535,18 @@ slash module (@pxref{Electric forward slash}).
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

View file

@ -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.
+++

View file

@ -167,23 +167,23 @@ external version."
(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 @@ This bypasses all Lisp functions and aliases."
(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 @@ An external command simply means external to Emacs."
(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

View file

@ -102,7 +102,7 @@
;; 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