Subject: Re: bug#70792: 30.0.50; [PATCH] Add Eshell support for expanding absolute file names within the current remote connection

From: Jim Porter <[email-masked]>
Date: Fri, 10 May 2024 12:35:48 -0700

On 5/9/2024 10:45 PM, Eli Zaretskii wrote:
>> Date: Thu, 9 May 2024 13:30:57 -0700
>> Cc: [email-masked]
>> From: Jim Porter <[email-masked]>
>>
>> Maybe the better solution would simply be for "/local:"[1] to be an
>> Eshell-only syntax that DTRT within Eshell. There shouldn't be any
>> conflict so long as Tramp promises not to add a "local" method. :)
> 
> I agree that it's a better solution: since the problem is Eshell-only,
> having the solution in Eshell has several advantages.

Here's a patch for the first part of this: it just changes the Eshell 
syntax for "run this command on localhost even if cwd is remote" to use 
"/local:program" instead of "/:program". Now, users can instead use "/:" 
in an Eshell command name to *disable* any remote file handlers.

For example "/:/ssh:user@remote:/foo" now means, "Run the program 'foo' 
on the current host, located in the directory '/ssh:user@remote:/'."

Next, I'll work on updating my original patch for this bug to use the 
new syntax (possibly with a new way of handling "/local:" if I need it). 
I wanted to get this ready first though so that Eshell is in a 
release-ready state for the upcoming Emacs 30, even if I don't finish 
the rest of this in time.

From 30ae237516590e53efbe64bada58a1499c5999c5 Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
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