From f41436101706501a7ca8f15ffbd4b34fca31579c Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Sun, 2 Apr 2023 22:41:29 -0700 Subject: [PATCH 3/5] Collect all processes in an Eshell pipeline, not just the head and tail This has the extra benefit that Eshell now only considers a pipeline to be done when *all* of its processes are done (previously, it checked only the last one in the pipeline). * lisp/eshell/esh-util.el (eshell-process-pair-p) (eshell-make-process-pair): Rename to... (eshell-process-list-p, eshell-make-process-list): ... these, and handle lists of processes. Update callers. * lisp/eshell/esh-cmd.el (eshell-head-process): Use 'car'. (eshell-tail-process): Get the last element of the list. (eshell-do-pipelines): Return a list of all processes in the pipeline. (eshell-do-pipelines-synchronously): Return the result of the first command (usually t or nil). (eshell-execute-pipeline): Simplify. (eshell-do-eval): Pass all processes to 'eshell/wait'. --- lisp/eshell/esh-cmd.el | 109 +++++++++++++++++++--------------------- lisp/eshell/esh-util.el | 23 ++++----- 2 files changed, 62 insertions(+), 70 deletions(-) diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index 45176b332d8..a67b8abcc67 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -275,12 +275,9 @@ eshell-last-arguments (defvar eshell-last-command-name nil) (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.") +When executing a pipeline, this is a list of all the pipeline's +processes, with the first usually reading from stdin and last +usually writing to stdout.") (defvar eshell-allow-commands t "If non-nil, allow evaluating command forms (including Lisp forms). @@ -302,12 +299,12 @@ eshell-interactive-process-p (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)) + (car 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)) + (car (last eshell-last-async-procs))) (define-obsolete-function-alias 'eshell-interactive-process 'eshell-tail-process "29.1") @@ -806,54 +803,56 @@ eshell--unmark-deferrable (defmacro eshell-do-pipelines (pipeline &optional notfirst) "Execute the commands in PIPELINE, connecting each to one another. +Returns a list of the processes in the pipeline. + This macro calls itself recursively, with NOTFIRST non-nil." (when (setq pipeline (cadr pipeline)) (eshell--unmark-deferrable (car pipeline)) `(eshell-with-copied-handles - (progn - ,(when (cdr pipeline) - `(let ((nextproc - (eshell-do-pipelines (quote ,(cdr pipeline)) t))) - (eshell-set-output-handle ,eshell-output-handle - 'append nextproc))) - ;; First and last elements in a pipeline may need special treatment. - ;; (Currently only eshell-ls-files uses 'last.) - ;; Affects process-connection-type in eshell-gather-process-output. - (let ((eshell-in-pipeline-p - ,(cond ((not notfirst) (quote 'first)) - ((cdr pipeline) t) - (t (quote 'last))))) - (let ((proc ,(car pipeline))) - (set headproc (or proc (symbol-value headproc))) - (set tailproc (or (symbol-value tailproc) proc)) - proc))) + (let ((next-procs + ,(when (cdr pipeline) + `(eshell-do-pipelines (quote ,(cdr pipeline)) t))) + ;; First and last elements in a pipeline may need special + ;; treatment (currently only `eshell-ls-files' uses + ;; `last'). Affects `process-connection-type' in + ;; `eshell-gather-process-output'. + (eshell-in-pipeline-p + ,(cond ((not notfirst) (quote 'first)) + ((cdr pipeline) t) + (t (quote 'last))))) + ,(when (cdr pipeline) + `(eshell-set-output-handle ,eshell-output-handle + 'append (car next-procs))) + (let ((proc ,(car pipeline))) + (cons proc next-procs))) ;; Steal handles if this is the last item in the pipeline. ,(null (cdr pipeline))))) (defmacro eshell-do-pipelines-synchronously (pipeline) "Execute the commands in PIPELINE in sequence synchronously. -Output of each command is passed as input to the next one in the pipeline. -This is used on systems where async subprocesses are not supported." +This collects the output of each command in turn, passing it as +input to the next one in the pipeline. Returns the result of the +first command invocation in the pipeline (usually t or nil). + +This is used on systems where async subprocesses are not +supported." (when (setq pipeline (cadr pipeline)) ;; FIXME: is deferrable significant here? (eshell--unmark-deferrable (car pipeline)) - `(progn - (eshell-with-copied-handles - (progn - ,(when (cdr pipeline) - `(let ((output-marker ,(point-marker))) - (eshell-set-output-handle ,eshell-output-handle - 'append output-marker))) - (let (;; XXX: `eshell-in-pipeline-p' has a different meaning - ;; for synchronous processes: it's non-nil only when - ;; piping *to* a process. - (eshell-in-pipeline-p ,(and (cdr pipeline) t))) - (let ((result ,(car pipeline))) - ;; `tailproc' gets the result of the last successful - ;; process in the pipeline. - (set tailproc (or result (symbol-value tailproc)))))) - ;; Steal handles if this is the last item in the pipeline. - ,(null (cdr pipeline))) + `(prog1 + (eshell-with-copied-handles + (progn + ,(when (cdr pipeline) + `(let ((output-marker ,(point-marker))) + (eshell-set-output-handle ,eshell-output-handle + 'append output-marker))) + (let (;; XXX: `eshell-in-pipeline-p' has a different + ;; meaning for synchronous processes: it's non-nil + ;; only when piping *to* a process. + (eshell-in-pipeline-p ,(and (cdr pipeline) t))) + ,(car pipeline))) + ;; Steal handles if this is the last item in the pipeline. + ,(null (cdr pipeline))) ,(when (cdr pipeline) `(eshell-do-pipelines-synchronously (quote ,(cdr pipeline))))))) @@ -861,16 +860,10 @@ 'eshell-process-identity (defmacro eshell-execute-pipeline (pipeline) "Execute the commands in PIPELINE, connecting each to one another." - `(let ((headproc (make-symbol "headproc")) - (tailproc (make-symbol "tailproc"))) - (set headproc nil) - (set tailproc nil) - (progn - ,(if eshell-supports-asynchronous-processes - `(eshell-do-pipelines ,pipeline) - `(eshell-do-pipelines-synchronously ,pipeline)) - (eshell-process-identity (cons (symbol-value headproc) - (symbol-value tailproc)))))) + `(eshell-process-identity + ,(if eshell-supports-asynchronous-processes + `(remove nil (eshell-do-pipelines ,pipeline)) + `(eshell-do-pipelines-synchronously ,pipeline)))) (defmacro eshell-as-subcommand (command) "Execute COMMAND as a subcommand. @@ -1021,7 +1014,7 @@ eshell-resume-eval (setq retval (eshell-do-eval eshell-current-command)))))) - (if (eshell-process-pair-p procs) + (if (eshell-process-list-p procs) (ignore (setq eshell-last-async-procs procs)) (cadr retval))))) (error @@ -1228,10 +1221,10 @@ eshell-do-eval (eshell-do-eval form synchronous-p)) (if-let (((memq (car form) eshell-deferrable-commands)) ((not eshell-current-subjob-p)) - (procs (eshell-make-process-pair result))) + (procs (eshell-make-process-list result))) (if synchronous-p - (eshell/wait (cdr procs)) - (eshell-manipulate form "inserting ignore form" + (apply #'eshell/wait procs) + (eshell-manipulate form "inserting ignore form" (setcar form 'ignore) (setcdr form nil)) (throw 'eshell-defer procs)) diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el index d5a75b0d715..5134adeb7fb 100644 --- a/lisp/eshell/esh-util.el +++ b/lisp/eshell/esh-util.el @@ -730,19 +730,18 @@ eshell-processp "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." +(defun eshell-process-list-p (procs) + "Return non-nil if PROCS is a list of process objects." + (and (listp procs) + (seq-every-p #'eshell-processp procs))) + +(defun eshell-make-process-list (procs) + "Make a list of process objects from PROCS if possible. +PROCS can be a single process or a list thereof. If PROCS is +anything else, return nil instead." (pcase procs - ((pred eshell-processp) (cons procs procs)) - ((pred eshell-process-pair-p) procs))) + ((pred eshell-processp) (list procs)) + ((pred eshell-process-list-p) procs))) ;; (defun eshell-copy-file ;; (file newname &optional ok-if-already-exists keep-date) -- 2.25.1