summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJim Porter <jporterbugs@gmail.com>2022-01-30 18:53:53 -0800
committerLars Ingebrigtsen <larsi@gnus.org>2022-02-03 20:02:22 +0100
commit3a388ab4a604f868ab2c1f9b3c63a7f7a3221f7f (patch)
treee0c4cb817c5dc16179305307ec0b56e527d7f36d
parent2e60ef696a3cf16b0eb297948e27ba0cb32a125e (diff)
downloademacs-3a388ab4a604f868ab2c1f9b3c63a7f7a3221f7f.tar.gz
When executing an Eshell pipeline, send input to the first process
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.
-rw-r--r--lisp/eshell/em-cmpl.el2
-rw-r--r--lisp/eshell/em-rebind.el2
-rw-r--r--lisp/eshell/em-term.el2
-rw-r--r--lisp/eshell/esh-cmd.el66
-rw-r--r--lisp/eshell/esh-io.el2
-rw-r--r--lisp/eshell/esh-mode.el28
-rw-r--r--lisp/eshell/esh-proc.el11
-rw-r--r--lisp/eshell/esh-util.el14
-rw-r--r--lisp/eshell/eshell.el6
-rw-r--r--test/lisp/eshell/eshell-tests-helpers.el2
-rw-r--r--test/lisp/eshell/eshell-tests.el11
11 files changed, 97 insertions, 49 deletions
diff --git a/lisp/eshell/em-cmpl.el b/lisp/eshell/em-cmpl.el
index c6a51b1793e..b79475f6e07 100644
--- a/lisp/eshell/em-cmpl.el
+++ b/lisp/eshell/em-cmpl.el
@@ -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))
diff --git a/lisp/eshell/em-rebind.el b/lisp/eshell/em-rebind.el
index f24758d4e34..2b56c9e8444 100644
--- a/lisp/eshell/em-rebind.el
+++ b/lisp/eshell/em-rebind.el
@@ -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)
diff --git a/lisp/eshell/em-term.el b/lisp/eshell/em-term.el
index e34c5ae47ce..d150c07b030 100644
--- a/lisp/eshell/em-term.el
+++ b/lisp/eshell/em-term.el
@@ -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
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 14139896dd4..e702de03a03 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -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
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index 2e0f312f4a6..8e6463eac27 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -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)
diff --git a/lisp/eshell/esh-mode.el b/lisp/eshell/esh-mode.el
index 8302eefe1e6..59c8f8034fe 100644
--- a/lisp/eshell/esh-mode.el
+++ b/lisp/eshell/esh-mode.el
@@ -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)
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 5ed692fb5a3..bb2136c06cc 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -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
diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el
index 0e04dbc7c9f..788404fc43a 100644
--- a/lisp/eshell/esh-util.el
+++ b/lisp/eshell/esh-util.el
@@ -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'."
diff --git a/lisp/eshell/eshell.el b/lisp/eshell/eshell.el
index 5c356e89289..2c472a2afad 100644
--- a/lisp/eshell/eshell.el
+++ b/lisp/eshell/eshell.el
@@ -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)))
diff --git a/test/lisp/eshell/eshell-tests-helpers.el b/test/lisp/eshell/eshell-tests-helpers.el
index 77f5313d57a..f3fbe90356a 100644
--- a/test/lisp/eshell/eshell-tests-helpers.el
+++ b/test/lisp/eshell/eshell-tests-helpers.el
@@ -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"))
diff --git a/test/lisp/eshell/eshell-tests.el b/test/lisp/eshell/eshell-tests.el
index 7658d5f5517..3b1bbe7188b 100644
--- a/test/lisp/eshell/eshell-tests.el
+++ b/test/lisp/eshell/eshell-tests.el
@@ -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)")))