aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJim Porter2023-09-23 11:36:11 -0700
committerJim Porter2023-10-02 20:49:41 -0700
commit498d31e9f0549189f4e9b140549419dd4e462575 (patch)
treee66ae54cfe9f53d7481df35850a066042e0b6e1d
parent8f2cfe15a72a0c440909faa50a9c436931dcf85e (diff)
downloademacs-498d31e9f0549189f4e9b140549419dd4e462575.tar.gz
emacs-498d31e9f0549189f4e9b140549419dd4e462575.zip
Support Eshell iterative evaluation in the background
This really just generalizes Eshell's previous support for iterative evaluation of a single current command to a list of multiple commands, of which at most one can be in the foreground (bug#66066). * lisp/eshell/esh-cmd.el (eshell-last-async-procs) (eshell-current-command): Make obsolete in favor of... (eshell-foreground-command): ... this (eshell-background-commands): New variable. (eshell-interactive-process-p): Make obsolete. (eshell-head-process, eshell-tail-process): Use 'eshell-foreground-command'. (eshell-cmd-initialize): Initialize new variables. (eshell-add-command, eshell-remove-command) (eshell-commands-for-process): New functions. (eshell-parse-command): Make 'eshell-do-subjob' the outermost call. (eshell-do-subjob): Call 'eshell-resume-eval' to split this command off from its parent forms. (eshell-eval-command): Use 'eshell-add-command'. (eshell-resume-command): Use 'eshell-commands-for-process'. (eshell-resume-eval): Take a COMMAND argument. Return ':eshell-background' form for deferred background commands. (eshell-do-eval): Remove check for 'eshell-current-subjob-p'. This is handled differently now. * lisp/eshell/eshell.el (eshell-command): Wait for all processes to exit when running synchronously. * lisp/eshell/esh-mode.el (eshell-intercept-commands) (eshell-watch-for-password-prompt): * lisp/eshell/em-cmpl.el (eshell-complete-parse-arguments): * lisp/eshell/em-smart.el (eshell-smart-display-move): Use 'eshell-foreground-command'. * test/lisp/eshell/esh-cmd-tests.el (esh-cmd-test/background/simple-command) (esh-cmd-test/background/subcommand): New tests. (esh-cmd-test/throw): Use 'eshell-foreground-command'. * test/lisp/eshell/eshell-tests.el (eshell-test/queue-input): Use 'eshell-foreground-command'. * test/lisp/eshell/em-script-tests.el (em-script-test/source-script/background): Make the test script more complex. * test/lisp/eshell/eshell-tests.el (eshell-test/eshell-command/pipeline-wait): New test. * doc/misc/eshell.texi (Bugs and ideas): Remove implemented feature.
-rw-r--r--doc/misc/eshell.texi2
-rw-r--r--lisp/eshell/em-cmpl.el2
-rw-r--r--lisp/eshell/em-smart.el2
-rw-r--r--lisp/eshell/esh-cmd.el176
-rw-r--r--lisp/eshell/esh-mode.el4
-rw-r--r--lisp/eshell/eshell.el5
-rw-r--r--test/lisp/eshell/em-script-tests.el4
-rw-r--r--test/lisp/eshell/esh-cmd-tests.el29
-rw-r--r--test/lisp/eshell/eshell-tests.el14
9 files changed, 165 insertions, 73 deletions
diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index 8b3eb72aa66..cc94f610615 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -2568,8 +2568,6 @@ A special associate array, which can take references of the form
2568@samp{$=[REGEXP]}. It indexes into the directory ring. 2568@samp{$=[REGEXP]}. It indexes into the directory ring.
2569@end table 2569@end table
2570 2570
2571@item Eshell scripts can't execute in the background
2572
2573@item Support zsh's ``Parameter Expansion'' syntax, i.e., @samp{$@{@var{name}:-@var{val}@}} 2571@item Support zsh's ``Parameter Expansion'' syntax, i.e., @samp{$@{@var{name}:-@var{val}@}}
2574 2572
2575@item Create a mode @code{eshell-browse} 2573@item Create a mode @code{eshell-browse}
diff --git a/lisp/eshell/em-cmpl.el b/lisp/eshell/em-cmpl.el
index 25dccbd695c..61f1237b907 100644
--- a/lisp/eshell/em-cmpl.el
+++ b/lisp/eshell/em-cmpl.el
@@ -343,7 +343,7 @@ to writing a completion function."
343(defun eshell-complete-parse-arguments () 343(defun eshell-complete-parse-arguments ()
344 "Parse the command line arguments for `pcomplete-argument'." 344 "Parse the command line arguments for `pcomplete-argument'."
345 (when (and eshell-no-completion-during-jobs 345 (when (and eshell-no-completion-during-jobs
346 (eshell-interactive-process-p)) 346 eshell-foreground-command)
347 (eshell--pcomplete-insert-tab)) 347 (eshell--pcomplete-insert-tab))
348 (let ((end (point-marker)) 348 (let ((end (point-marker))
349 (begin (save-excursion (beginning-of-line) (point))) 349 (begin (save-excursion (beginning-of-line) (point)))
diff --git a/lisp/eshell/em-smart.el b/lisp/eshell/em-smart.el
index d5002a59d14..4c39a991ec6 100644
--- a/lisp/eshell/em-smart.el
+++ b/lisp/eshell/em-smart.el
@@ -294,7 +294,7 @@ and the end of the buffer are still visible."
294 ((eq this-command 'self-insert-command) 294 ((eq this-command 'self-insert-command)
295 (if (eq last-command-event ? ) 295 (if (eq last-command-event ? )
296 (if (and eshell-smart-space-goes-to-end 296 (if (and eshell-smart-space-goes-to-end
297 eshell-current-command) 297 eshell-foreground-command)
298 (if (not (pos-visible-in-window-p (point-max))) 298 (if (not (pos-visible-in-window-p (point-max)))
299 (setq this-command 'scroll-up) 299 (setq this-command 'scroll-up)
300 (setq this-command 'eshell-smart-goto-end)) 300 (setq this-command 'eshell-smart-goto-end))
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index fc7d54a758d..990d2ca1122 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -263,7 +263,24 @@ command line.")
263 263
264;;; Internal Variables: 264;;; Internal Variables:
265 265
266(defvar eshell-current-command nil) 266;; These variables have been merged into `eshell-foreground-command'.
267;; Outside of this file, the most-common use for them is to check
268;; whether they're nil.
269(define-obsolete-variable-alias 'eshell-last-async-procs
270 'eshell-foreground-command "30.1")
271(define-obsolete-variable-alias 'eshell-current-command
272 'eshell-foreground-command "30.1")
273
274(defvar eshell-foreground-command nil
275 "The currently-running foreground command, if any.
276This is a list of the form (FORM PROCESSES). FORM is the Eshell
277command form. PROCESSES is a list of processes that deferred the
278command.")
279(defvar eshell-background-commands nil
280 "A list of currently-running deferred commands.
281Each element is of the form (FORM PROCESSES), as with
282`eshell-foreground-command' (which see).")
283
267(defvar eshell-command-name nil) 284(defvar eshell-command-name nil)
268(defvar eshell-command-arguments nil) 285(defvar eshell-command-arguments nil)
269(defvar eshell-in-pipeline-p nil 286(defvar eshell-in-pipeline-p nil
@@ -273,11 +290,6 @@ otherwise t.")
273(defvar eshell-in-subcommand-p nil) 290(defvar eshell-in-subcommand-p nil)
274(defvar eshell-last-arguments nil) 291(defvar eshell-last-arguments nil)
275(defvar eshell-last-command-name nil) 292(defvar eshell-last-command-name nil)
276(defvar eshell-last-async-procs nil
277 "The currently-running foreground process(es).
278When executing a pipeline, this is a list of all the pipeline's
279processes, with the first usually reading from stdin and last
280usually writing to stdout.")
281 293
282(defvar eshell-allow-commands t 294(defvar eshell-allow-commands t
283 "If non-nil, allow evaluating command forms (including Lisp forms). 295 "If non-nil, allow evaluating command forms (including Lisp forms).
@@ -294,29 +306,30 @@ also `eshell-complete-parse-arguments'.")
294 306
295(defsubst eshell-interactive-process-p () 307(defsubst eshell-interactive-process-p ()
296 "Return non-nil if there is a currently running command process." 308 "Return non-nil if there is a currently running command process."
297 eshell-last-async-procs) 309 (declare (obsolete 'eshell-foreground-command "30.1"))
310 eshell-foreground-command)
298 311
299(defsubst eshell-head-process () 312(defsubst eshell-head-process ()
300 "Return the currently running process at the head of any pipeline. 313 "Return the currently running process at the head of any pipeline.
301This only returns external (non-Lisp) processes." 314This only returns external (non-Lisp) processes."
302 (car eshell-last-async-procs)) 315 (caadr eshell-foreground-command))
303 316
304(defsubst eshell-tail-process () 317(defsubst eshell-tail-process ()
305 "Return the currently running process at the tail of any pipeline. 318 "Return the currently running process at the tail of any pipeline.
306This only returns external (non-Lisp) processes." 319This only returns external (non-Lisp) processes."
307 (car (last eshell-last-async-procs))) 320 (car (last (cadr eshell-foreground-command))))
308 321
309(define-obsolete-function-alias 'eshell-interactive-process 322(define-obsolete-function-alias 'eshell-interactive-process
310 'eshell-tail-process "29.1") 323 'eshell-tail-process "29.1")
311 324
312(defun eshell-cmd-initialize () ;Called from `eshell-mode' via intern-soft! 325(defun eshell-cmd-initialize () ;Called from `eshell-mode' via intern-soft!
313 "Initialize the Eshell command processing module." 326 "Initialize the Eshell command processing module."
314 (setq-local eshell-current-command nil) 327 (setq-local eshell-foreground-command nil)
328 (setq-local eshell-background-commands nil)
315 (setq-local eshell-command-name nil) 329 (setq-local eshell-command-name nil)
316 (setq-local eshell-command-arguments nil) 330 (setq-local eshell-command-arguments nil)
317 (setq-local eshell-last-arguments nil) 331 (setq-local eshell-last-arguments nil)
318 (setq-local eshell-last-command-name nil) 332 (setq-local eshell-last-command-name nil)
319 (setq-local eshell-last-async-procs nil)
320 333
321 (add-hook 'eshell-kill-hook #'eshell-resume-command nil t) 334 (add-hook 'eshell-kill-hook #'eshell-resume-command nil t)
322 (add-hook 'eshell-parse-argument-hook 335 (add-hook 'eshell-parse-argument-hook
@@ -337,6 +350,47 @@ This only returns external (non-Lisp) processes."
337 (throw 'pcomplete-completions 350 (throw 'pcomplete-completions
338 (all-completions pcomplete-stub obarray 'boundp))))) 351 (all-completions pcomplete-stub obarray 'boundp)))))
339 352
353;; Current command management
354
355(defun eshell-add-command (form &optional background)
356 "Add a command FORM to our list of known commands and return the new entry.
357If non-nil, BACKGROUND indicates that this is a command running
358in the background. The result is a command entry in the
359form (BACKGROUND FORM PROCESSES), where PROCESSES is initially
360nil."
361 (cons (when background 'background)
362 (if background
363 (car (push (list form nil) eshell-background-commands))
364 (cl-assert (null eshell-foreground-command))
365 (setq eshell-foreground-command (list form nil)))))
366
367(defun eshell-remove-command (command)
368 "Remove COMMAND from our list of known commands.
369COMMAND should be a list of the form (BACKGROUND FORM PROCESSES),
370as returned by `eshell-add-command' (which see)."
371 (let ((background (car command))
372 (entry (cdr command)))
373 (if background
374 (setq eshell-background-commands
375 (delq entry eshell-background-commands))
376 (cl-assert (eq eshell-foreground-command entry))
377 (setq eshell-foreground-command nil))))
378
379(defun eshell-commands-for-process (process)
380 "Return all commands associated with a PROCESS.
381Each element will have the form (BACKGROUND FORM PROCESSES), as
382returned by `eshell-add-command' (which see).
383
384Usually, there should only be one element in this list, but it's
385theoretically possible to have more than one associated command
386for a given process."
387 (nconc (when (memq process (cadr eshell-foreground-command))
388 (list (cons nil eshell-foreground-command)))
389 (seq-keep (lambda (cmd)
390 (when (memq process (cadr cmd))
391 (cons 'background cmd)))
392 eshell-background-commands)))
393
340;; Command parsing 394;; Command parsing
341 395
342(defsubst eshell--region-p (object) 396(defsubst eshell--region-p (object)
@@ -407,8 +461,6 @@ command hooks should be run before and after the command."
407 (lambda (cmd) 461 (lambda (cmd)
408 (let ((sep (pop sep-terms))) 462 (let ((sep (pop sep-terms)))
409 (setq cmd (eshell-parse-pipeline cmd)) 463 (setq cmd (eshell-parse-pipeline cmd))
410 (when (equal sep "&")
411 (setq cmd `(eshell-do-subjob (cons :eshell-background ,cmd))))
412 (unless eshell-in-pipeline-p 464 (unless eshell-in-pipeline-p
413 (setq cmd `(eshell-trap-errors ,cmd))) 465 (setq cmd `(eshell-trap-errors ,cmd)))
414 ;; Copy I/O handles so each full statement can manipulate 466 ;; Copy I/O handles so each full statement can manipulate
@@ -416,6 +468,8 @@ command hooks should be run before and after the command."
416 ;; command in the list; we won't use the originals again 468 ;; command in the list; we won't use the originals again
417 ;; anyway. 469 ;; anyway.
418 (setq cmd `(eshell-with-copied-handles ,cmd ,(not sep))) 470 (setq cmd `(eshell-with-copied-handles ,cmd ,(not sep)))
471 (when (equal sep "&")
472 (setq cmd `(eshell-do-subjob ,cmd)))
419 cmd)) 473 cmd))
420 sub-chains))) 474 sub-chains)))
421 (if toplevel 475 (if toplevel
@@ -740,13 +794,13 @@ if none)."
740 794
741(defmacro eshell-do-subjob (object) 795(defmacro eshell-do-subjob (object)
742 "Evaluate a command OBJECT as a subjob. 796 "Evaluate a command OBJECT as a subjob.
743We indicate that the process was run in the background by returning it 797We indicate that the process was run in the background by
744ensconced in a list." 798returning it as (:eshell-background . PROCESSES)."
745 `(let ((eshell-current-subjob-p t) 799 `(let ((eshell-current-subjob-p t)
746 ;; Print subjob messages. This could have been cleared 800 ;; Print subjob messages. This could have been cleared
747 ;; (e.g. by `eshell-source-file', which see). 801 ;; (e.g. by `eshell-source-file', which see).
748 (eshell-subjob-messages t)) 802 (eshell-subjob-messages t))
749 ,object)) 803 (eshell-resume-eval (eshell-add-command ',object 'background))))
750 804
751(defmacro eshell-commands (object &optional silent) 805(defmacro eshell-commands (object &optional silent)
752 "Place a valid set of handles, and context, around command OBJECT." 806 "Place a valid set of handles, and context, around command OBJECT."
@@ -980,12 +1034,12 @@ Return the process (or head and tail processes) created by
980COMMAND, if any. If COMMAND is a background command, return the 1034COMMAND, if any. If COMMAND is a background command, return the
981process(es) in a cons cell like: 1035process(es) in a cons cell like:
982 1036
983 (:eshell-background . PROCESS)" 1037 (:eshell-background . PROCESSES)"
984 (if eshell-current-command 1038 (if eshell-foreground-command
985 (progn 1039 (progn
986 ;; We can just stick the new command at the end of the current 1040 ;; We can just stick the new command at the end of the current
987 ;; one, and everything will happen as it should. 1041 ;; one, and everything will happen as it should.
988 (setcdr (last (cdr eshell-current-command)) 1042 (setcdr (last (cdar eshell-foreground-command))
989 (list `(let ((here (and (eobp) (point)))) 1043 (list `(let ((here (and (eobp) (point))))
990 ,(and input 1044 ,(and input
991 `(insert-and-inherit ,(concat input "\n"))) 1045 `(insert-and-inherit ,(concat input "\n")))
@@ -994,56 +1048,61 @@ process(es) in a cons cell like:
994 (eshell-do-eval ',command)))) 1048 (eshell-do-eval ',command))))
995 (eshell-debug-command 'form 1049 (eshell-debug-command 'form
996 "enqueued command form for %S\n\n%s" 1050 "enqueued command form for %S\n\n%s"
997 (or input "<no string>") (eshell-stringify eshell-current-command))) 1051 (or input "<no string>")
1052 (eshell-stringify (car eshell-foreground-command))))
998 (eshell-debug-command-start input) 1053 (eshell-debug-command-start input)
999 (setq eshell-current-command command)
1000 (let* (result 1054 (let* (result
1001 (delim (catch 'eshell-incomplete 1055 (delim (catch 'eshell-incomplete
1002 (ignore (setq result (eshell-resume-eval)))))) 1056 (ignore (setq result (eshell-resume-eval
1057 (eshell-add-command command)))))))
1003 (when delim 1058 (when delim
1004 (error "Unmatched delimiter: %S" delim)) 1059 (error "Unmatched delimiter: %S" delim))
1005 result))) 1060 result)))
1006 1061
1007(defun eshell-resume-command (proc status) 1062(defun eshell-resume-command (proc status)
1008 "Resume the current command when a pipeline ends." 1063 "Resume the current command when a pipeline ends.
1009 (when (and proc 1064PROC is the process that invoked this from its sentinel, and
1010 ;; Make sure PROC is one of our foreground processes and 1065STATUS is its status."
1011 ;; that all of those processes are now dead. 1066 (when proc
1012 (member proc eshell-last-async-procs) 1067 (dolist (command (eshell-commands-for-process proc))
1013 (not (seq-some #'eshell-process-active-p eshell-last-async-procs))) 1068 (unless (seq-some #'eshell-process-active-p (nth 2 command))
1014 (if (and ;; Check STATUS to determine whether we want to resume or 1069 (setf (nth 2 command) nil) ; Clear processes from command.
1015 ;; abort the command. 1070 (if (and ;; Check STATUS to determine whether we want to resume or
1016 (stringp status) 1071 ;; abort the command.
1017 (not (string= "stopped" status)) 1072 (stringp status)
1018 (not (string-match eshell-reset-signals status))) 1073 (not (string= "stopped" status))
1019 (eshell-resume-eval) 1074 (not (string-match eshell-reset-signals status)))
1020 (setq eshell-last-async-procs nil) 1075 (eshell-resume-eval command)
1021 (setq eshell-current-command nil) 1076 (eshell-remove-command command)
1022 (declare-function eshell-reset "esh-mode" (&optional no-hooks)) 1077 (declare-function eshell-reset "esh-mode" (&optional no-hooks))
1023 (eshell-reset)))) 1078 (eshell-reset))))))
1024 1079
1025(defun eshell-resume-eval () 1080(defun eshell-resume-eval (command)
1026 "Destructively evaluate a form which may need to be deferred." 1081 "Destructively evaluate a COMMAND which may need to be deferred.
1027 (setq eshell-last-async-procs nil) 1082COMMAND is a command entry of the form (BACKGROUND FORM
1028 (when eshell-current-command 1083PROCESSES) (see `eshell-add-command').
1029 (eshell-condition-case err 1084
1030 (let (retval procs) 1085Return the result of COMMAND's FORM if it wasn't deferred. If
1031 (unwind-protect 1086BACKGROUND is non-nil and Eshell defers COMMAND, return a list of
1032 (progn 1087the form (:eshell-background . PROCESSES)."
1033 (setq procs (catch 'eshell-defer 1088 (eshell-condition-case err
1034 (ignore (setq retval 1089 (let (retval procs)
1035 (eshell-do-eval 1090 (unwind-protect
1036 eshell-current-command))))) 1091 (progn
1037 (when retval 1092 (setq procs
1038 (cadr retval))) 1093 (catch 'eshell-defer
1039 (setq eshell-last-async-procs procs) 1094 (ignore (setq retval (eshell-do-eval (cadr command))))))
1095 (cond
1096 (retval (cadr retval))
1097 ((car command) (cons :eshell-background procs))))
1098 (if procs
1099 (setf (nth 2 command) procs)
1040 ;; If we didn't defer this command, clear it out. This 1100 ;; If we didn't defer this command, clear it out. This
1041 ;; applies both when the command has finished normally, 1101 ;; applies both when the command has finished normally,
1042 ;; and when a signal or thrown value causes us to unwind. 1102 ;; and when a signal or thrown value causes us to unwind.
1043 (unless procs 1103 (eshell-remove-command command))))
1044 (setq eshell-current-command nil)))) 1104 (error
1045 (error 1105 (error (error-message-string err)))))
1046 (error (error-message-string err))))))
1047 1106
1048(defmacro eshell-manipulate (form tag &rest body) 1107(defmacro eshell-manipulate (form tag &rest body)
1049 "Manipulate a command FORM with BODY, using TAG as a debug identifier." 1108 "Manipulate a command FORM with BODY, using TAG as a debug identifier."
@@ -1272,7 +1331,6 @@ have been replaced by constants."
1272 (setcdr form (cdr new-form))) 1331 (setcdr form (cdr new-form)))
1273 (eshell-do-eval form synchronous-p)) 1332 (eshell-do-eval form synchronous-p))
1274 (if-let (((memq (car form) eshell-deferrable-commands)) 1333 (if-let (((memq (car form) eshell-deferrable-commands))
1275 ((not eshell-current-subjob-p))
1276 (procs (eshell-make-process-list result))) 1334 (procs (eshell-make-process-list result)))
1277 (if synchronous-p 1335 (if synchronous-p
1278 (apply #'eshell/wait procs) 1336 (apply #'eshell/wait procs)
diff --git a/lisp/eshell/esh-mode.el b/lisp/eshell/esh-mode.el
index 0c381dbb86a..2b560afb92c 100644
--- a/lisp/eshell/esh-mode.el
+++ b/lisp/eshell/esh-mode.el
@@ -453,7 +453,7 @@ and the hook `eshell-exit-hook'."
453 last-command-event)))) 453 last-command-event))))
454 454
455(defun eshell-intercept-commands () 455(defun eshell-intercept-commands ()
456 (when (and (eshell-interactive-process-p) 456 (when (and eshell-foreground-command
457 (not (and (integerp last-input-event) 457 (not (and (integerp last-input-event)
458 (memq last-input-event '(?\C-x ?\C-c))))) 458 (memq last-input-event '(?\C-x ?\C-c)))))
459 (let ((possible-events (where-is-internal this-command)) 459 (let ((possible-events (where-is-internal this-command))
@@ -967,7 +967,7 @@ buffer's process if STRING contains a password prompt defined by
967`eshell-password-prompt-regexp'. 967`eshell-password-prompt-regexp'.
968 968
969This function could be in the list `eshell-output-filter-functions'." 969This function could be in the list `eshell-output-filter-functions'."
970 (when (eshell-interactive-process-p) 970 (when eshell-foreground-command
971 (save-excursion 971 (save-excursion
972 (let ((case-fold-search t)) 972 (let ((case-fold-search t))
973 (goto-char eshell-last-output-block-begin) 973 (goto-char eshell-last-output-block-begin)
diff --git a/lisp/eshell/eshell.el b/lisp/eshell/eshell.el
index a3f80f453eb..8765ba499a1 100644
--- a/lisp/eshell/eshell.el
+++ b/lisp/eshell/eshell.el
@@ -315,9 +315,8 @@ argument), then insert output into the current buffer at point."
315 ;; make the output as attractive as possible, with no 315 ;; make the output as attractive as possible, with no
316 ;; extraneous newlines 316 ;; extraneous newlines
317 (when intr 317 (when intr
318 (if (eshell-interactive-process-p) 318 (apply #'eshell-wait-for-process (cadr eshell-foreground-command))
319 (eshell-wait-for-process (eshell-tail-process))) 319 (cl-assert (not eshell-foreground-command))
320 (cl-assert (not (eshell-interactive-process-p)))
321 (goto-char (point-max)) 320 (goto-char (point-max))
322 (while (and (bolp) (not (bobp))) 321 (while (and (bolp) (not (bobp)))
323 (delete-char -1))) 322 (delete-char -1)))
diff --git a/test/lisp/eshell/em-script-tests.el b/test/lisp/eshell/em-script-tests.el
index 191755dcc3e..02e4125d827 100644
--- a/test/lisp/eshell/em-script-tests.el
+++ b/test/lisp/eshell/em-script-tests.el
@@ -67,14 +67,14 @@
67 "Test sourcing a script in the background." 67 "Test sourcing a script in the background."
68 (skip-unless (executable-find "echo")) 68 (skip-unless (executable-find "echo"))
69 (ert-with-temp-file temp-file 69 (ert-with-temp-file temp-file
70 :text "*echo hi" 70 :text "*echo hi\nif {[ foo = foo ]} {*echo bye}"
71 (eshell-with-temp-buffer bufname "old" 71 (eshell-with-temp-buffer bufname "old"
72 (with-temp-eshell 72 (with-temp-eshell
73 (eshell-match-command-output 73 (eshell-match-command-output
74 (format "source %s > #<%s> &" temp-file bufname) 74 (format "source %s > #<%s> &" temp-file bufname)
75 "\\`\\'") 75 "\\`\\'")
76 (eshell-wait-for-subprocess t)) 76 (eshell-wait-for-subprocess t))
77 (should (equal (buffer-string) "hi\n"))))) 77 (should (equal (buffer-string) "hi\nbye\n")))))
78 78
79(ert-deftest em-script-test/source-script/arg-vars () 79(ert-deftest em-script-test/source-script/arg-vars ()
80 "Test sourcing script with $0, $1, ... variables." 80 "Test sourcing script with $0, $1, ... variables."
diff --git a/test/lisp/eshell/esh-cmd-tests.el b/test/lisp/eshell/esh-cmd-tests.el
index 643038f89ff..e0783b26ad6 100644
--- a/test/lisp/eshell/esh-cmd-tests.el
+++ b/test/lisp/eshell/esh-cmd-tests.el
@@ -104,6 +104,32 @@ bug#59469."
104 "value\nexternal\nvalue\n"))) 104 "value\nexternal\nvalue\n")))
105 105
106 106
107;; Background command invocation
108
109(ert-deftest esh-cmd-test/background/simple-command ()
110 "Test invocation with a simple background command."
111 (skip-unless (executable-find "echo"))
112 (eshell-with-temp-buffer bufname ""
113 (with-temp-eshell
114 (eshell-match-command-output
115 (format "*echo hi > #<%s> &" bufname)
116 (rx "[echo" (? ".exe") "] " (+ digit) "\n"))
117 (eshell-wait-for-subprocess t))
118 (should (equal (buffer-string) "hi\n"))))
119
120(ert-deftest esh-cmd-test/background/subcommand ()
121 "Test invocation with a background command containing subcommands."
122 (skip-unless (and (executable-find "echo")
123 (executable-find "rev")))
124 (eshell-with-temp-buffer bufname ""
125 (with-temp-eshell
126 (eshell-match-command-output
127 (format "*echo ${*echo hello | rev} > #<%s> &" bufname)
128 (rx "[echo" (? ".exe") "] " (+ digit) "\n"))
129 (eshell-wait-for-subprocess t))
130 (should (equal (buffer-string) "olleh\n"))))
131
132
107;; Lisp forms 133;; Lisp forms
108 134
109(ert-deftest esh-cmd-test/quoted-lisp-form () 135(ert-deftest esh-cmd-test/quoted-lisp-form ()
@@ -453,8 +479,7 @@ This tests when `eshell-lisp-form-nil-is-failure' is nil."
453 "echo hi; (throw 'tag 42); echo bye")) 479 "echo hi; (throw 'tag 42); echo bye"))
454 42)) 480 42))
455 (should (eshell-match-output "\\`hi\n\\'")) 481 (should (eshell-match-output "\\`hi\n\\'"))
456 (should-not eshell-current-command) 482 (should-not eshell-foreground-command)
457 (should-not eshell-last-async-procs)
458 ;; Make sure we can call another command after throwing. 483 ;; Make sure we can call another command after throwing.
459 (eshell-match-command-output "echo again" "\\`again\n"))) 484 (eshell-match-command-output "echo again" "\\`again\n")))
460 485
diff --git a/test/lisp/eshell/eshell-tests.el b/test/lisp/eshell/eshell-tests.el
index 25c8cfd389c..b02e5fca592 100644
--- a/test/lisp/eshell/eshell-tests.el
+++ b/test/lisp/eshell/eshell-tests.el
@@ -58,6 +58,18 @@ This test uses a pipeline for the command."
58 (eshell-command "*echo hi | *cat" t) 58 (eshell-command "*echo hi | *cat" t)
59 (should (equal (buffer-string) "hi\n")))))) 59 (should (equal (buffer-string) "hi\n"))))))
60 60
61(ert-deftest eshell-test/eshell-command/pipeline-wait ()
62 "Check that `eshell-command' waits for all its processes before returning."
63 (skip-unless (and (executable-find "echo")
64 (executable-find "sh")
65 (executable-find "rev")))
66 (ert-with-temp-directory eshell-directory-name
67 (let ((eshell-history-file-name nil))
68 (with-temp-buffer
69 (eshell-command
70 "*echo hello | sh -c 'sleep 1; rev' 1>&2 | *echo goodbye" t)
71 (should (equal (buffer-string) "goodbye\nolleh\n"))))))
72
61(ert-deftest eshell-test/eshell-command/background () 73(ert-deftest eshell-test/eshell-command/background ()
62 "Test that `eshell-command' works for background commands." 74 "Test that `eshell-command' works for background commands."
63 (skip-unless (executable-find "echo")) 75 (skip-unless (executable-find "echo"))
@@ -132,7 +144,7 @@ insert the queued one at the next prompt, and finally run it."
132 (eshell-insert-command "sleep 1; echo slept") 144 (eshell-insert-command "sleep 1; echo slept")
133 (eshell-insert-command "echo alpha" #'eshell-queue-input) 145 (eshell-insert-command "echo alpha" #'eshell-queue-input)
134 (let ((start (marker-position (eshell-beginning-of-output)))) 146 (let ((start (marker-position (eshell-beginning-of-output))))
135 (eshell-wait-for (lambda () (not eshell-current-command))) 147 (eshell-wait-for (lambda () (not eshell-foreground-command)))
136 (should (string-match "^slept\n.*echo alpha\nalpha\n$" 148 (should (string-match "^slept\n.*echo alpha\nalpha\n$"
137 (buffer-substring-no-properties 149 (buffer-substring-no-properties
138 start (eshell-end-of-output))))))) 150 start (eshell-end-of-output)))))))