]> git.eshelyaron.com Git - emacs.git/commitdiff
When executing an Eshell pipeline, send input to the first process
authorJim Porter <jporterbugs@gmail.com>
Mon, 31 Jan 2022 02:53:53 +0000 (18:53 -0800)
committerLars Ingebrigtsen <larsi@gnus.org>
Thu, 3 Feb 2022 19:02:22 +0000 (20:02 +0100)
Previously, input was sent to the last process in the pipeline,
resulting in unexpected behavior when running commands like
'tr a-z A-Z | rev'.

* lisp/eshell/esh-util.el (eshell-process-pair-p)
(eshell-make-process-pair): New functions.

* lisp/eshell/esh-cmd.el (eshell-last-async-proc): Rename to...
(eshell-last-async-procs): ... this, and store a pair of processes.
(eshell-interactive-process): Replace with...
(eshell-interactive-process-p, eshell-head-process)
(eshell-tail-process): ... these.
(eshell-cmd-initialize): Set 'eshell-last-async-procs'.
(eshell-do-pipelines): Set 'headproc'.
(eshell-execute-pipeline): Return 'headproc' and 'tailproc'.
(eshell-resume-eval): Use 'eshell-last-async-procs'.
(eshell-do-eval): Make sure we work with a pair of processes.

* lisp/eshell/esh-proc.el (eshell-send-eof-to-process): Move from
here...
* lisp/eshell/esh-mode.el (eshell-send-eof-to-process): ... to here,
and only send EOF to the head process.

* lisp/eshell/em-cmpl.el (eshell-complete-parse-arguments)
* lisp/eshell/esh-mode.el (eshell-intercept-commands)
(eshell-watch-for-password-prompt):
Use 'eshell-interactive-process-p'.

* lisp/eshell/em-rebind.el (eshell-delchar-or-maybe-eof)
* lisp/eshell/em-term.el (eshell-term-send-raw-string)
* lisp/eshell/esh-mode.el (eshell-self-insert-command)
(eshell-send-input, eshell-send-invisible):
Use 'eshell-head-process'.

* lisp/eshell/esh-cmd.el (eshell-as-subcommand):
Use 'eshell-tail-process'.

* lisp/eshell/eshell.el (eshell-command):
* test/lisp/eshell/eshell-tests-helpers.el
(eshell-wait-for-subprocess):
Use 'eshell-interactive-process-p' and 'eshell-tail-process'.

* test/lisp/eshell/eshell-tests.el (eshell-test/pipe-headproc-stdin):
New test.

lisp/eshell/em-cmpl.el
lisp/eshell/em-rebind.el
lisp/eshell/em-term.el
lisp/eshell/esh-cmd.el
lisp/eshell/esh-io.el
lisp/eshell/esh-mode.el
lisp/eshell/esh-proc.el
lisp/eshell/esh-util.el
lisp/eshell/eshell.el
test/lisp/eshell/eshell-tests-helpers.el
test/lisp/eshell/eshell-tests.el

index c6a51b1793e31812badd56aaac6b29ba3ba72d72..b79475f6e07576598ecbd98652a19f9c7902ec5f 100644 (file)
@@ -314,7 +314,7 @@ to writing a completion function."
 (defun eshell-complete-parse-arguments ()
   "Parse the command line arguments for `pcomplete-argument'."
   (when (and eshell-no-completion-during-jobs
-            (eshell-interactive-process))
+            (eshell-interactive-process-p))
     (insert-and-inherit "\t")
     (throw 'pcompleted t))
   (let ((end (point-marker))
index f24758d4e34bda50a42620dfb3e8a7bdb57f4256..2b56c9e84447e95c6eb7fd9f5f2bb3b0d0ca3342 100644 (file)
@@ -238,7 +238,7 @@ lock it at that."
 Sends an EOF only if point is at the end of the buffer and there is no
 input."
   (interactive "p")
-  (let ((proc (eshell-interactive-process)))
+  (let ((proc (eshell-head-process)))
     (if (eobp)
        (cond
         ((/= (point) eshell-last-output-end)
index e34c5ae47ce06622ba4904df8c07ccdd12f9ccdd..d150c07b030063a0fcbc4da8bd2f56d48030286f 100644 (file)
@@ -224,7 +224,7 @@ the buffer."
 
 ; (defun eshell-term-send-raw-string (chars)
 ;   (goto-char eshell-last-output-end)
-;   (process-send-string (eshell-interactive-process) chars))
+;   (process-send-string (eshell-head-process) chars))
 
 ; (defun eshell-term-send-raw ()
 ;   "Send the last character typed through the terminal-emulator
index 14139896dd453247a32df12735ed6fd5241419bb..e702de03a0326f4e2738e115c53fa1efe93c81fb 100644 (file)
@@ -279,14 +279,33 @@ otherwise t.")
 (defvar eshell-in-subcommand-p nil)
 (defvar eshell-last-arguments nil)
 (defvar eshell-last-command-name nil)
-(defvar eshell-last-async-proc nil
-  "When this foreground process completes, resume command evaluation.")
+(defvar eshell-last-async-procs nil
+  "The currently-running foreground process(es).
+When executing a pipeline, this is a cons cell whose CAR is the
+first process (usually reading from stdin) and whose CDR is the
+last process (usually writing to stdout).  Otherwise, the CAR and
+CDR are the same process.
+
+When the process in the CDR completes, resume command evaluation.")
 
 ;;; Functions:
 
-(defsubst eshell-interactive-process ()
-  "Return currently running command process, if non-Lisp."
-  eshell-last-async-proc)
+(defsubst eshell-interactive-process-p ()
+  "Return non-nil if there is a currently running command process."
+  eshell-last-async-procs)
+
+(defsubst eshell-head-process ()
+  "Return the currently running process at the head of any pipeline.
+This only returns external (non-Lisp) processes."
+  (car-safe eshell-last-async-procs))
+
+(defsubst eshell-tail-process ()
+  "Return the currently running process at the tail of any pipeline.
+This only returns external (non-Lisp) processes."
+  (cdr-safe eshell-last-async-procs))
+
+(define-obsolete-function-alias 'eshell-interactive-process
+  'eshell-tail-process "29.1")
 
 (defun eshell-cmd-initialize ()     ;Called from `eshell-mode' via intern-soft!
   "Initialize the Eshell command processing module."
@@ -295,7 +314,7 @@ otherwise t.")
   (setq-local eshell-command-arguments nil)
   (setq-local eshell-last-arguments nil)
   (setq-local eshell-last-command-name nil)
-  (setq-local eshell-last-async-proc nil)
+  (setq-local eshell-last-async-procs nil)
 
   (add-hook 'eshell-kill-hook #'eshell-resume-command nil t)
 
@@ -306,7 +325,7 @@ otherwise t.")
   (add-hook 'eshell-post-command-hook
             (lambda ()
               (setq eshell-current-command nil
-                    eshell-last-async-proc nil))
+                    eshell-last-async-procs nil))
             nil t)
 
   (add-hook 'eshell-parse-argument-hook
@@ -781,6 +800,8 @@ This macro calls itself recursively, with NOTFIRST non-nil."
                      ((cdr pipeline) t)
                      (t (quote 'last)))))
           (let ((proc ,(car pipeline)))
+            ,(unless notfirst
+               '(setq headproc proc))
             (setq tailproc (or tailproc proc))
             proc))))))
 
@@ -823,7 +844,7 @@ This is used on systems where async subprocesses are not supported."
 
 (defmacro eshell-execute-pipeline (pipeline)
   "Execute the commands in PIPELINE, connecting each to one another."
-  `(let ((eshell-in-pipeline-p t) tailproc)
+  `(let ((eshell-in-pipeline-p t) headproc tailproc)
      (progn
        ,(if (fboundp 'make-process)
            `(eshell-do-pipelines ,pipeline)
@@ -833,7 +854,7 @@ This is used on systems where async subprocesses are not supported."
                                (car (aref eshell-current-handles
                                           ,eshell-error-handle)) nil)))
             (eshell-do-pipelines-synchronously ,pipeline)))
-       (eshell-process-identity tailproc))))
+       (eshell-process-identity (cons headproc tailproc)))))
 
 (defmacro eshell-as-subcommand (command)
   "Execute COMMAND using a temp buffer.
@@ -993,24 +1014,24 @@ produced by `eshell-parse-command'."
     (unless (or (not (stringp status))
                (string= "stopped" status)
                (string-match eshell-reset-signals status))
-      (if (eq proc (eshell-interactive-process))
+      (if (eq proc (eshell-tail-process))
          (eshell-resume-eval)))))
 
 (defun eshell-resume-eval ()
   "Destructively evaluate a form which may need to be deferred."
   (eshell-condition-case err
       (progn
-       (setq eshell-last-async-proc nil)
+       (setq eshell-last-async-procs nil)
        (when eshell-current-command
          (let* (retval
-                (proc (catch 'eshell-defer
+                (procs (catch 'eshell-defer
                         (ignore
                          (setq retval
                                (eshell-do-eval
                                 eshell-current-command))))))
-           (if (eshell-processp proc)
-               (ignore (setq eshell-last-async-proc proc))
-             (cadr retval)))))
+           (if (eshell-process-pair-p procs)
+               (ignore (setq eshell-last-async-procs procs))
+             (cadr retval)))))
     (error
      (error (error-message-string err)))))
 
@@ -1173,17 +1194,16 @@ be finished later after the completion of an asynchronous subprocess."
                    (setcar form (car new-form))
                    (setcdr form (cdr new-form)))
                  (eshell-do-eval form synchronous-p))
-             (if (and (memq (car form) eshell-deferrable-commands)
-                      (not eshell-current-subjob-p)
-                      result
-                      (eshell-processp result))
-                 (if synchronous-p
-                     (eshell/wait result)
+              (if-let (((memq (car form) eshell-deferrable-commands))
+                       ((not eshell-current-subjob-p))
+                       (procs (eshell-make-process-pair result)))
+                  (if synchronous-p
+                     (eshell/wait (cdr procs))
                    (eshell-manipulate "inserting ignore form"
                      (setcar form 'ignore)
                      (setcdr form nil))
-                   (throw 'eshell-defer result))
-               (list 'quote result))))))))))))
+                   (throw 'eshell-defer procs))
+                (list 'quote result))))))))))))
 
 ;; command invocation
 
index 2e0f312f4a61844ad44932d8b67a81105a536e87..8e6463eac27d06b7d277d0cf2de9932882e15645 100644 (file)
@@ -485,7 +485,7 @@ Returns what was actually sent, or nil if nothing was sent."
    ((eshell-processp target)
     (when (eq (process-status target) 'run)
       (unless (stringp object)
-       (setq object (eshell-stringify object)))
+       (setq object (eshell-stringify object)))
       (process-send-string target object)))
 
    ((consp target)
index 8302eefe1e605ce1678bdc8fc433a71f37565330..59c8f8034fe17647109d392e780faa3c5609af4d 100644 (file)
@@ -423,13 +423,13 @@ and the hook `eshell-exit-hook'."
 (defun eshell-self-insert-command ()
   (interactive)
   (process-send-string
-   (eshell-interactive-process)
+   (eshell-head-process)
    (char-to-string (if (symbolp last-command-event)
                       (get last-command-event 'ascii-character)
                     last-command-event))))
 
 (defun eshell-intercept-commands ()
-  (when (and (eshell-interactive-process)
+  (when (and (eshell-interactive-process-p)
             (not (and (integerp last-input-event)
                       (memq last-input-event '(?\C-x ?\C-c)))))
     (let ((possible-events (where-is-internal this-command))
@@ -595,13 +595,13 @@ If NO-NEWLINE is non-nil, the input is sent without an implied final
 newline."
   (interactive "P")
   ;; Note that the input string does not include its terminal newline.
-  (let ((proc-running-p (and (eshell-interactive-process)
+  (let ((proc-running-p (and (eshell-head-process)
                             (not queue-p)))
        (inhibit-point-motion-hooks t)
        (inhibit-modification-hooks t))
     (unless (and proc-running-p
                 (not (eq (process-status
-                          (eshell-interactive-process))
+                          (eshell-head-process))
                           'run)))
       (if (or proc-running-p
              (>= (point) eshell-last-output-end))
@@ -627,8 +627,8 @@ newline."
            (if (or eshell-send-direct-to-subprocesses
                    (= eshell-last-input-start eshell-last-input-end))
                (unless no-newline
-                 (process-send-string (eshell-interactive-process) "\n"))
-             (process-send-region (eshell-interactive-process)
+                 (process-send-string (eshell-head-process) "\n"))
+             (process-send-region (eshell-head-process)
                                   eshell-last-input-start
                                   eshell-last-input-end)))
        (if (= eshell-last-output-end (point))
@@ -665,6 +665,16 @@ newline."
               (run-hooks 'eshell-post-command-hook)
               (insert-and-inherit input)))))))))
 
+(defun eshell-send-eof-to-process ()
+  "Send EOF to the currently-running \"head\" process."
+  (interactive)
+  (require 'esh-mode)
+  (declare-function eshell-send-input "esh-mode"
+                    (&optional use-region queue-p no-newline))
+  (eshell-send-input nil nil t)
+  (when (eshell-head-process)
+    (process-send-eof (eshell-head-process))))
+
 (defsubst eshell-kill-new ()
   "Add the last input text to the kill ring."
   (kill-ring-save eshell-last-input-start eshell-last-input-end))
@@ -924,9 +934,9 @@ Then send it to the process running in the current buffer."
   (interactive) ; Don't pass str as argument, to avoid snooping via C-x ESC ESC
   (let ((str (read-passwd
              (format "%s Password: "
-                     (process-name (eshell-interactive-process))))))
+                     (process-name (eshell-head-process))))))
     (if (stringp str)
-       (process-send-string (eshell-interactive-process)
+       (process-send-string (eshell-head-process)
                             (concat str "\n"))
       (message "Warning: text will be echoed"))))
 
@@ -937,7 +947,7 @@ buffer's process if STRING contains a password prompt defined by
 `eshell-password-prompt-regexp'.
 
 This function could be in the list `eshell-output-filter-functions'."
-  (when (eshell-interactive-process)
+  (when (eshell-interactive-process-p)
     (save-excursion
       (let ((case-fold-search t))
        (goto-char eshell-last-output-block-begin)
index 5ed692fb5a3cc527e921ce95b5952488d339af52..bb2136c06ccb617217343454c30af28a68b34eba 100644 (file)
@@ -101,6 +101,8 @@ information, for example."
 (defvar eshell-process-list nil
   "A list of the current status of subprocesses.")
 
+(declare-function eshell-send-eof-to-process "esh-mode")
+
 (defvar-keymap eshell-proc-mode-map
   "C-c M-i"  #'eshell-insert-process
   "C-c C-c"  #'eshell-interrupt-process
@@ -542,14 +544,5 @@ See the variable `eshell-kill-processes-on-exit'."
 ;    ;; `eshell-resume-eval'.
 ;    (eshell-kill-process-function nil "continue")))
 
-(defun eshell-send-eof-to-process ()
-  "Send EOF to process."
-  (interactive)
-  (require 'esh-mode)
-  (declare-function eshell-send-input "esh-mode"
-                    (&optional use-region queue-p no-newline))
-  (eshell-send-input nil nil t)
-  (eshell-process-interact 'process-send-eof))
-
 (provide 'esh-proc)
 ;;; esh-proc.el ends here
index 0e04dbc7c9f1e799146f0caf8b7a71348faadad4..788404fc43ac06125fa22ee68b995843aa0789d2 100644 (file)
@@ -609,6 +609,20 @@ gid format.  Valid values are `string' and `integer', defaulting to
   "If the `processp' function does not exist, PROC is not a process."
   (and (fboundp 'processp) (processp proc)))
 
+(defun eshell-process-pair-p (procs)
+  "Return non-nil if PROCS is a pair of process objects."
+  (and (consp procs)
+       (eshell-processp (car procs))
+       (eshell-processp (cdr procs))))
+
+(defun eshell-make-process-pair (procs)
+  "Make a pair of process objects from PROCS if possible.
+This represents the head and tail of a pipeline of processes,
+where the head and tail may be the same process."
+  (pcase procs
+    ((pred eshell-processp) (cons procs procs))
+    ((pred eshell-process-pair-p) procs)))
+
 ;; (defun eshell-copy-file
 ;;   (file newname &optional ok-if-already-exists keep-date)
 ;;   "Copy FILE to NEWNAME.  See docs for `copy-file'."
index 5c356e89289ff9fe400650ad79d47fae3cdea883..2c472a2afad5bbbe569bd1be82425795178a7ebd 100644 (file)
@@ -332,9 +332,9 @@ With prefix ARG, insert output into the current buffer at point."
        ;; make the output as attractive as possible, with no
        ;; extraneous newlines
        (when intr
-         (if (eshell-interactive-process)
-             (eshell-wait-for-process (eshell-interactive-process)))
-         (cl-assert (not (eshell-interactive-process)))
+         (if (eshell-interactive-process-p)
+             (eshell-wait-for-process (eshell-tail-process)))
+         (cl-assert (not (eshell-interactive-process-p)))
          (goto-char (point-max))
          (while (and (bolp) (not (bobp)))
            (delete-char -1)))
index 77f5313d57a4fbd0faaf0dd590928072bd6406e3..f3fbe90356a95c176c4f715cb1f9b6b9d0b4561a 100644 (file)
@@ -53,7 +53,7 @@ See `eshell-wait-for-subprocess'.")
 If this takes longer than `eshell-test--max-subprocess-time',
 raise an error."
   (let ((start (current-time)))
-    (while (eshell-interactive-process)
+    (while (eshell-interactive-process-p)
       (when (> (float-time (time-since start))
                eshell-test--max-subprocess-time)
         (error "timed out waiting for subprocess"))
index 7658d5f5517e8a6a5595c69b8349df2c5964cba6..3b1bbe7188bdd5f8d48edac9bb4c62e56646b1a8 100644 (file)
@@ -136,6 +136,17 @@ e.g. \"{(+ 1 2)} 3\" => 3"
    (eshell-command-result-p "*echo hi | echo bye"
                             "bye\nhi\n")))
 
+(ert-deftest eshell-test/pipe-headproc-stdin ()
+  "Check that standard input is sent to the head process in a pipeline"
+  (skip-unless (and (executable-find "tr")
+                    (executable-find "rev")))
+  (with-temp-eshell
+   (eshell-insert-command "tr a-z A-Z | rev")
+   (eshell-insert-command "hello")
+   (eshell-send-eof-to-process)
+   (eshell-wait-for-subprocess)
+   (eshell-match-result "OLLEH\n")))
+
 (ert-deftest eshell-test/window-height ()
   "$LINES should equal (window-height)"
   (should (eshell-test-command-result "= $LINES (window-height)")))