unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Jim Porter <jporterbugs@gmail.com>
To: emacs-devel@gnu.org
Cc: michael.albinus@gmx.de
Subject: [RFC] Explicity-remote commands in Eshell
Date: Sun, 5 Mar 2023 18:35:43 -0800	[thread overview]
Message-ID: <0298a618-645c-a0c0-172b-9a0491462959@gmail.com> (raw)

[-- Attachment #1: Type: text/plain, Size: 1565 bytes --]

One of the nice features of Eshell is that since it's aware of Tramp, 
connecting to a different machine (or changing users on a machine) is 
just a matter of cd'ing into a remote directory:

   cd /ssh:user@somewhere:~/some/dir

In addition, Eshell routes data in pipelines through buffers. In 
principle, this means it should be easy to build cross-host pipelines in 
Eshell, where the output of a command on host A is piped into the input 
of a command on host B. However, it's not easy to spell this in Eshell; 
the best I can come up with is to use a subshell like this:

   {cd /ssh:user@somewhere: && remote-command} | local-command

Instead, I think we could make this easier:

   /ssh:user@somewhere:remote-command | local-command

What do people think of the above syntax? It's a little bit different 
from the usual Tramp syntax, since the local part shouldn't be expanded 
using 'expand-file-name'; it's an executable somewhere on the remote 
$PATH. I can't think of any problems with supporting this syntax in 
Eshell[1], but I'm interested to hear others' thoughts.

Attached is a patch demonstrating this. The only non-Eshell change I had 
to make was to enhance 'file-remote-p' so that I can tell Tramp not to 
call 'expand-file-name'.

As a footnote: you might wonder how to execute a program on your local 
host when the current Eshell directory is remote. In my patch, I chose 
to do this via quoted file names:

   /:local-command

[1] Tramp shouldn't need to know about this syntax in other places. It 
can just be something Eshell understands.

[-- Attachment #2: 0001-Add-support-for-explicitly-remote-commands-in-Eshell.patch --]
[-- Type: text/plain, Size: 6000 bytes --]

From 676659ebcbf2cabc5e606620fdd58e3f498225f9 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 22 Jan 2023 22:45:40 -0800
Subject: [PATCH] Add support for explicitly-remote commands in Eshell

* lisp/files.el (file-remote-p): Document 'never' for CONNECTED
argument.

* lisp/net/tramp.el (tramp-handle-file-remote-p): Handle CONNECTED
value of 'never'.

* lisp/eshell/esh-ext.el (eshell-explicit-remote-commands): New
option.
(eshell-ext-initialize): Apply 'eshell-handle-remote-command' when
requested.
(eshell-handle-remote-command): New function.
(eshell-remote-command): Reimplement this function and dispatch to
'eshell-external-command', which can handle remote processes on its
own.
---
 lisp/eshell/esh-ext.el | 58 ++++++++++++++++++++++++++----------------
 lisp/files.el          |  4 ++-
 lisp/net/tramp.el      |  5 ++--
 3 files changed, 42 insertions(+), 25 deletions(-)

diff --git a/lisp/eshell/esh-ext.el b/lisp/eshell/esh-ext.el
index f350622e78c..d720cde0dd2 100644
--- a/lisp/eshell/esh-ext.el
+++ b/lisp/eshell/esh-ext.el
@@ -168,11 +168,23 @@ eshell-explicit-command-char
   :type 'character
   :group 'eshell-ext)
 
+(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\"."
+  :type 'boolean
+  :group 'eshell-ext)
+
 ;;; Functions:
 
 (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))
+  (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)))
 
 (defun eshell-explicit-command (command args)
   "If a command name begins with `*', call it externally always.
@@ -186,30 +198,32 @@ 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-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.
-
-Note that this function is very crude at the moment.  It gathers up
-all the output from the remote command, and sends it all at once,
-causing the user to wonder if anything's really going on..."
-  (let ((outbuf (generate-new-buffer " *eshell remote output*"))
-	(errbuf (generate-new-buffer " *eshell remote error*"))
-	(command (file-local-name command))
-	(exitcode 1))
-    (unwind-protect
-	(progn
-	  (setq exitcode
-		(shell-command
-		 (mapconcat #'shell-quote-argument
-			    (append (list command) args) " ")
-		 outbuf errbuf))
-	  (eshell-print (with-current-buffer outbuf (buffer-string)))
-	  (eshell-error (with-current-buffer errbuf (buffer-string))))
-      (eshell-close-handles exitcode 'nil)
-      (kill-buffer outbuf)
-      (kill-buffer errbuf))))
+An external command simply means external to Emacs."
+  (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))
+         (command-localname (file-remote-p command 'localname 'never)))
+    (unless command-connection
+      (error "%s: not a remote command" command))
+    (eshell-external-command command-localname args)))
 
 (defun eshell-external-command (command args)
   "Insert output from an external COMMAND, using ARGS."
diff --git a/lisp/files.el b/lisp/files.el
index 387a3b5dc66..e5cb775eb6d 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -1267,7 +1267,9 @@ file-remote-p
 
 If CONNECTED is non-nil, return an identification only if FILE is
 located on a remote system and a connection is established to
-that remote system.
+that remote system.  If CONNECTED is `never', never use an
+existing connection to return the identification (this is
+otherwise like a value of nil).
 
 Tip: You can use this expansion of remote identifier components
      to derive a new remote file name from an existing one.  For
diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el
index 47173b95bea..e2b0e6bffa0 100644
--- a/lisp/net/tramp.el
+++ b/lisp/net/tramp.el
@@ -4269,13 +4269,14 @@ tramp-handle-file-remote-p
   (let ((tramp-verbose (min tramp-verbose 3)))
     (when (tramp-tramp-file-p filename)
       (let* ((o (tramp-dissect-file-name filename))
-	     (p (tramp-get-connection-process o))
+	     (p (and (not (eq connected 'never))
+                     (tramp-get-connection-process o)))
 	     (c (and (process-live-p p)
 		     (tramp-get-connection-property p "connected"))))
 	;; We expand the file name only, if there is already a connection.
 	(with-parsed-tramp-file-name
 	    (if c (expand-file-name filename) filename) nil
-	  (and (or (not connected) c)
+	  (and (or (memq connected '(nil never)) c)
 	       (cond
 		((eq identification 'method) method)
 		;; Domain and port are appended to user and host,
-- 
2.25.1


             reply	other threads:[~2023-03-06  2:35 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-03-06  2:35 Jim Porter [this message]
2023-03-06  8:09 ` [RFC] Explicity-remote commands in Eshell Michael Albinus
2023-03-06 16:10   ` John Wiegley
2023-03-06 22:07   ` Jim Porter

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=0298a618-645c-a0c0-172b-9a0491462959@gmail.com \
    --to=jporterbugs@gmail.com \
    --cc=emacs-devel@gnu.org \
    --cc=michael.albinus@gmx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).