diff options
| author | Jim Porter | 2023-09-23 11:36:11 -0700 |
|---|---|---|
| committer | Jim Porter | 2023-10-02 20:49:41 -0700 |
| commit | 498d31e9f0549189f4e9b140549419dd4e462575 (patch) | |
| tree | e66ae54cfe9f53d7481df35850a066042e0b6e1d | |
| parent | 8f2cfe15a72a0c440909faa50a9c436931dcf85e (diff) | |
| download | emacs-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.texi | 2 | ||||
| -rw-r--r-- | lisp/eshell/em-cmpl.el | 2 | ||||
| -rw-r--r-- | lisp/eshell/em-smart.el | 2 | ||||
| -rw-r--r-- | lisp/eshell/esh-cmd.el | 176 | ||||
| -rw-r--r-- | lisp/eshell/esh-mode.el | 4 | ||||
| -rw-r--r-- | lisp/eshell/eshell.el | 5 | ||||
| -rw-r--r-- | test/lisp/eshell/em-script-tests.el | 4 | ||||
| -rw-r--r-- | test/lisp/eshell/esh-cmd-tests.el | 29 | ||||
| -rw-r--r-- | test/lisp/eshell/eshell-tests.el | 14 |
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. | ||
| 276 | This is a list of the form (FORM PROCESSES). FORM is the Eshell | ||
| 277 | command form. PROCESSES is a list of processes that deferred the | ||
| 278 | command.") | ||
| 279 | (defvar eshell-background-commands nil | ||
| 280 | "A list of currently-running deferred commands. | ||
| 281 | Each 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). | ||
| 278 | When executing a pipeline, this is a list of all the pipeline's | ||
| 279 | processes, with the first usually reading from stdin and last | ||
| 280 | usually 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. |
| 301 | This only returns external (non-Lisp) processes." | 314 | This 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. |
| 306 | This only returns external (non-Lisp) processes." | 319 | This 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. | ||
| 357 | If non-nil, BACKGROUND indicates that this is a command running | ||
| 358 | in the background. The result is a command entry in the | ||
| 359 | form (BACKGROUND FORM PROCESSES), where PROCESSES is initially | ||
| 360 | nil." | ||
| 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. | ||
| 369 | COMMAND should be a list of the form (BACKGROUND FORM PROCESSES), | ||
| 370 | as 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. | ||
| 381 | Each element will have the form (BACKGROUND FORM PROCESSES), as | ||
| 382 | returned by `eshell-add-command' (which see). | ||
| 383 | |||
| 384 | Usually, there should only be one element in this list, but it's | ||
| 385 | theoretically possible to have more than one associated command | ||
| 386 | for 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. |
| 743 | We indicate that the process was run in the background by returning it | 797 | We indicate that the process was run in the background by |
| 744 | ensconced in a list." | 798 | returning 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 | |||
| 980 | COMMAND, if any. If COMMAND is a background command, return the | 1034 | COMMAND, if any. If COMMAND is a background command, return the |
| 981 | process(es) in a cons cell like: | 1035 | process(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 | 1064 | PROC is the process that invoked this from its sentinel, and |
| 1010 | ;; Make sure PROC is one of our foreground processes and | 1065 | STATUS 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) | 1082 | COMMAND is a command entry of the form (BACKGROUND FORM |
| 1028 | (when eshell-current-command | 1083 | PROCESSES) (see `eshell-add-command'). |
| 1029 | (eshell-condition-case err | 1084 | |
| 1030 | (let (retval procs) | 1085 | Return the result of COMMAND's FORM if it wasn't deferred. If |
| 1031 | (unwind-protect | 1086 | BACKGROUND is non-nil and Eshell defers COMMAND, return a list of |
| 1032 | (progn | 1087 | the 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 | ||
| 969 | This function could be in the list `eshell-output-filter-functions'." | 969 | This 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))))))) |