Fix using background commands in 'eshell-command'

This regressed due to the patch for bug#53715, which changed how
Eshell pipelines return the processes in the pipeline (bug#62556).

* lisp/eshell/esh-cmd.el (eshell-parse-command): When creating
background commands, wrap the process(es) in a cons cell whose CAR is
':eshell-background'.  This lets us use fewer heuristics...
(eshell-eval-command): ... here.  Additionally, keep the result and
the incomplete delimiter separate.

* lisp/eshell/eshell.el (eshell-command): Check ':eshell-background'
and use a more-robust method for setting the output target.

* test/lisp/eshell/eshell-tests.el (eshell-test/eshell-command/simple)
(eshell-test/eshell-command/pipeline)
(eshell-test/eshell-command/background)
(eshell-test/eshell-command/background-pipeline): New tests.
This commit is contained in:
Jim Porter 2023-03-30 19:31:30 -07:00
parent 6df2941c1b
commit 267fca267f
3 changed files with 65 additions and 30 deletions

View file

@ -421,7 +421,8 @@ hooks should be run before and after the command."
(string= (car eshell--sep-terms) ";"))
(eshell-parse-pipeline cmd)
`(eshell-do-subjob
(list ,(eshell-parse-pipeline cmd)))))
(cons :eshell-background
,(eshell-parse-pipeline cmd)))))
(setq eshell--sep-terms (cdr eshell--sep-terms))
(if eshell-in-pipeline-p
cmd
@ -1036,7 +1037,12 @@ produced by `eshell-parse-command'."
(cadr result)))
(defun eshell-eval-command (command &optional input)
"Evaluate the given COMMAND iteratively."
"Evaluate the given COMMAND iteratively.
Return the process (or head and tail processes) created by
COMMAND, if any. If COMMAND is a background command, return the
process(es) in a cons cell like:
(:eshell-background . PROCESS)"
(if eshell-current-command
;; We can just stick the new command at the end of the current
;; one, and everything will happen as it should.
@ -1052,20 +1058,12 @@ produced by `eshell-parse-command'."
(erase-buffer)
(insert "command: \"" input "\"\n")))
(setq eshell-current-command command)
(let* ((delim (catch 'eshell-incomplete
(eshell-resume-eval)))
(val (car-safe delim)))
;; If the return value of `eshell-resume-eval' is wrapped in a
;; list, it indicates that the command was run asynchronously.
;; In that case, unwrap the value before checking the delimiter
;; value.
(if (and val
(not (eshell-processp val))
(not (eq val t)))
(error "Unmatched delimiter: %S" val)
;; Eshell-command expect a list like (<process>) to know if the
;; command should be async or not.
(or (and (eshell-processp val) delim) val)))))
(let* (result
(delim (catch 'eshell-incomplete
(ignore (setq result (eshell-resume-eval))))))
(when delim
(error "Unmatched delimiter: %S" delim))
result)))
(defun eshell-resume-command (proc status)
"Resume the current command when a process ends."

View file

@ -290,25 +290,18 @@ With prefix ARG, insert output into the current buffer at point."
(eshell-add-input-to-history command)))))
(unless command
(error "No command specified!"))
;; redirection into the current buffer is achieved by adding an
;; output redirection to the end of the command, of the form
;; 'COMMAND >>> #<buffer BUFFER>'. This will not interfere with
;; other redirections, since multiple redirections merely cause the
;; output to be copied to multiple target locations
(if arg
(setq command
(concat command
(format " >>> #<buffer %s>"
(buffer-name (current-buffer))))))
(save-excursion
(let ((buf (set-buffer (generate-new-buffer " *eshell cmd*")))
(let ((stdout (if arg (current-buffer) t))
(buf (set-buffer (generate-new-buffer " *eshell cmd*")))
(eshell-non-interactive-p t))
(eshell-mode)
(let* ((proc (eshell-eval-command
(list 'eshell-commands
(eshell-parse-command command))))
`(let ((eshell-current-handles
(eshell-create-handles ,stdout 'insert))
(eshell-current-subjob-p))
,(eshell-parse-command command))))
intr
(bufname (if (and proc (listp proc))
(bufname (if (eq (car-safe proc) :eshell-background)
"*Eshell Async Command Output*"
(setq intr t)
"*Eshell Command Output*")))

View file

@ -107,6 +107,50 @@
(format template "format \"%s\" eshell-in-pipeline-p")
"nil")))
(ert-deftest eshell-test/eshell-command/simple ()
"Test that the `eshell-command' function writes to the current buffer."
(skip-unless (executable-find "echo"))
(ert-with-temp-directory eshell-directory-name
(let ((eshell-history-file-name nil))
(with-temp-buffer
(eshell-command "*echo hi" t)
(should (equal (buffer-string) "hi\n"))))))
(ert-deftest eshell-test/eshell-command/pipeline ()
"Test that the `eshell-command' function writes to the current buffer.
This test uses a pipeline for the command."
(skip-unless (and (executable-find "echo")
(executable-find "cat")))
(ert-with-temp-directory eshell-directory-name
(let ((eshell-history-file-name nil))
(with-temp-buffer
(eshell-command "*echo hi | *cat" t)
(should (equal (buffer-string) "hi\n"))))))
(ert-deftest eshell-test/eshell-command/background ()
"Test that `eshell-command' works for background commands."
(skip-unless (executable-find "echo"))
(ert-with-temp-directory eshell-directory-name
(let ((orig-processes (process-list))
(eshell-history-file-name nil))
(with-temp-buffer
(eshell-command "*echo hi &" t)
(eshell-wait-for (lambda () (equal (process-list) orig-processes)))
(should (equal (buffer-string) "hi\n"))))))
(ert-deftest eshell-test/eshell-command/background-pipeline ()
"Test that `eshell-command' works for background commands.
This test uses a pipeline for the command."
(skip-unless (and (executable-find "echo")
(executable-find "cat")))
(ert-with-temp-directory eshell-directory-name
(let ((orig-processes (copy-tree (process-list)))
(eshell-history-file-name nil))
(with-temp-buffer
(eshell-command "*echo hi | *cat &" t)
(eshell-wait-for (lambda () (equal (process-list) orig-processes)))
(should (equal (buffer-string) "hi\n"))))))
(ert-deftest eshell-test/command-running-p ()
"Modeline should show no command running"
(with-temp-eshell