Collect all processes in an Eshell pipeline, not just the head and tail
authorJim Porter <jporterbugs@gmail.com>
Mon, 3 Apr 2023 05:41:29 +0000 (22:41 -0700)
committerJim Porter <jporterbugs@gmail.com>
Sun, 10 Sep 2023 17:38:25 +0000 (10:38 -0700)
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
lisp/eshell/esh-util.el

index 45176b332d8e2350b92d6dea27bb6dca56534a03..a67b8abcc67f35419051f7d5927fa8c423cf6b42 100644 (file)
@@ -275,12 +275,9 @@ otherwise t.")
 (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 @@ also `eshell-complete-parse-arguments'.")
 (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 @@ that Eshell doesn't erroneously allow deferring it.  For example,
 
 (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 @@ 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 ((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 @@ process(es) in a cons cell like:
                          (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 @@ have been replaced by constants."
                  (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))
index d5a75b0d7157b44e55d8bc0b29e31f890b76f371..5134adeb7fbc6c2818b0fb5a82ddd2b368da2890 100644 (file)
@@ -730,19 +730,18 @@ 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."
+(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)