(not (process-live-p proc))))
(finish-io
(lambda ()
- (with-current-buffer (process-buffer proc)
- (if (or (process-get proc :eshell-busy)
- (and wait-for-stderr (car stderr-live)))
- (progn
+ (if (buffer-live-p (process-buffer proc))
+ (with-current-buffer (process-buffer proc)
+ (if (or (process-get proc :eshell-busy)
+ (and wait-for-stderr (car stderr-live)))
+ (progn
+ (eshell-debug-command 'process
+ "i/o busy for process `%s'" proc)
+ (run-at-time 0 nil finish-io))
+ (when data
+ (ignore-error eshell-pipe-broken
+ (eshell-output-object
+ data index handles)))
+ (eshell-close-handles
+ status
+ (when status (list 'quote (= status 0)))
+ handles)
+ ;; Clear the handles to mark that we're 100%
+ ;; finished with the I/O for this process.
+ (process-put proc :eshell-handles nil)
(eshell-debug-command 'process
- "i/o busy for process `%s'" proc)
- (run-at-time 0 nil finish-io))
- (when data
- (ignore-error eshell-pipe-broken
- (eshell-output-object
- data index handles)))
- (eshell-close-handles
- status
- (when status (list 'quote (= status 0)))
- handles)
- ;; Clear the handles to mark that we're 100%
- ;; finished with the I/O for this process.
- (process-put proc :eshell-handles nil)
- (eshell-debug-command 'process
- "finished external process `%s'" proc)
- (if primary
- (run-hook-with-args 'eshell-kill-hook
- proc string)
- (setcar stderr-live nil)))))))
+ "finished external process `%s'" proc)
+ (if primary
+ (run-hook-with-args 'eshell-kill-hook
+ proc string)
+ (setcar stderr-live nil))))
+ (eshell-debug-command 'process
+ "buffer for external process `%s' already killed"
+ proc)))))
(funcall finish-io)))
(when-let ((entry (assq proc eshell-process-list)))
(eshell-remove-process-entry entry))))))
:type 'string
:group 'eshell)
+(defcustom eshell-command-async-buffer 'confirm-new-buffer
+ "What to do when the output buffer is used by another shell command.
+This option specifies how to resolve the conflict where a new command
+wants to direct its output to the buffer whose name is stored
+in `eshell-command-buffer-name-async', but that buffer is already
+taken by another running shell command.
+
+The value `confirm-kill-process' is used to ask for confirmation before
+killing the already running process and running a new process in the
+same buffer, `confirm-new-buffer' for confirmation before running the
+command in a new buffer with a name other than the default buffer name,
+`new-buffer' for doing the same without confirmation,
+`confirm-rename-buffer' for confirmation before renaming the existing
+output buffer and running a new command in the default buffer,
+`rename-buffer' for doing the same without confirmation."
+ :type '(choice (const :tag "Confirm killing of running command"
+ confirm-kill-process)
+ (const :tag "Confirm creation of a new buffer"
+ confirm-new-buffer)
+ (const :tag "Create a new buffer"
+ new-buffer)
+ (const :tag "Confirm renaming of existing buffer"
+ confirm-rename-buffer)
+ (const :tag "Rename the existing buffer"
+ rename-buffer))
+ :group 'eshell
+ :version "31.1")
+
;;;_* Running Eshell
;;
;; There are only three commands used to invoke Eshell. The first two
(eshell-command-mode +1))
(read-from-minibuffer prompt))))
+(defvar eshell-command-buffer-name-async "*Eshell Async Command Output*")
+(defvar eshell-command-buffer-name-sync "*Eshell Command Output*")
+
;;;###autoload
(defun eshell-command (command &optional to-current-buffer)
"Execute the Eshell command string COMMAND.
If TO-CURRENT-BUFFER is non-nil (interactively, with the prefix
-argument), then insert output into the current buffer at point."
+argument), then insert output into the current buffer at point.
+
+When \"&\" is added at end of command, the command is async and its output
+appears in a specific buffer. You can customize
+`eshell-command-async-buffer' to specify what to do when this output
+buffer is already taken by another running shell command."
(interactive (list (eshell-read-command)
current-prefix-arg))
(save-excursion
(eshell-current-subjob-p))
,(eshell-parse-command command))
command))
- intr
- (bufname (if (eq (car-safe proc) :eshell-background)
- "*Eshell Async Command Output*"
- (setq intr t)
- "*Eshell Command Output*")))
- (if (buffer-live-p (get-buffer bufname))
- (kill-buffer bufname))
- (rename-buffer bufname)
+ (async (eq (car-safe proc) :eshell-background))
+ (bufname (cond
+ (to-current-buffer nil)
+ (async eshell-command-buffer-name-async)
+ (t eshell-command-buffer-name-sync)))
+ unique)
+ (when bufname
+ (when (buffer-live-p (get-buffer bufname))
+ (cond
+ ((with-current-buffer bufname
+ (and (null eshell-foreground-command)
+ (null eshell-background-commands)))
+ ;; The old buffer is done executing; kill it so we can
+ ;; take its place.
+ (kill-buffer bufname))
+ ((eq eshell-command-async-buffer 'confirm-kill-process)
+ (shell-command--same-buffer-confirm "Kill it")
+ (with-current-buffer bufname
+ ;; Stop all the processes in the old buffer (there may
+ ;; be several).
+ (eshell-process-interact #'interrupt-process t))
+ (accept-process-output)
+ (kill-buffer bufname))
+ ((eq eshell-command-async-buffer 'confirm-new-buffer)
+ (shell-command--same-buffer-confirm "Use a new buffer")
+ (setq unique t))
+ ((eq eshell-command-async-buffer 'new-buffer)
+ (setq unique t))
+ ((eq eshell-command-async-buffer 'confirm-rename-buffer)
+ (shell-command--same-buffer-confirm "Rename it")
+ (with-current-buffer bufname
+ (rename-uniquely)))
+ ((eq eshell-command-async-buffer 'rename-buffer)
+ (with-current-buffer bufname
+ (rename-uniquely)))))
+ (rename-buffer bufname unique))
;; things get a little coarse here, since the desire is to
;; make the output as attractive as possible, with no
;; extraneous newlines
- (when intr
+ (unless async
(apply #'eshell-wait-for-process (cadr eshell-foreground-command))
(cl-assert (not eshell-foreground-command))
(goto-char (point-max))
(delete-char -1)))
(cl-assert (and buf (buffer-live-p buf)))
(unless to-current-buffer
- (let ((len (if (not intr) 2
+ (let ((len (if async 2
(count-lines (point-min) (point-max)))))
(cond
((= len 0)
;; cause the output buffer to take up as little screen
;; real-estate as possible, if temp buffer resizing is
;; enabled
- (and intr temp-buffer-resize-mode
+ (and (not async) temp-buffer-resize-mode
(resize-temp-buffer-window)))))))))))
;;;###autoload
(forward-line)
(should (looking-at "hi\n"))))))
+(ert-deftest eshell-test/eshell-command/output-buffer/async-kill ()
+ "Test that the `eshell-command' function kills the old process when told to."
+ (skip-unless (executable-find "echo"))
+ (ert-with-temp-directory eshell-directory-name
+ (let ((orig-processes (process-list))
+ (eshell-history-file-name nil)
+ (eshell-command-async-buffer 'confirm-kill-process))
+ (eshell-command "sleep 5 | *echo hi &")
+ (cl-letf* ((result t)
+ ;; Say "yes" only once: for the `confirm-kill-process'
+ ;; prompt. If there are any other prompts (e.g. from
+ ;; `kill-buffer'), say "no" to make the test fail.
+ ((symbol-function 'yes-or-no-p)
+ (lambda (_prompt) (prog1 result (setq result nil)))))
+ (eshell-command "*echo bye &"))
+ (eshell-wait-for (lambda () (equal (process-list) orig-processes)))
+ (with-current-buffer "*Eshell Async Command Output*"
+ (goto-char (point-min))
+ (forward-line)
+ (should (looking-at "bye\n"))))))
+
(ert-deftest eshell-test/command-running-p ()
"Modeline should show no command running"
(with-temp-eshell