Allow using multiple buffers in 'eshell-command'
Provide the same functionality as 'async-shell-command-buffer' but for 'eshell-command'. Co-Authored-By: Jim Porter <jporterbugs@gmail.com> * lisp/eshell/eshell.el (eshell-command-async-buffer): New option... (eshell-command): ... use it. * lisp/eshell/esh-proc.el (eshell-sentinel): Check for buffer liveness in 'finish-io'. * test/lisp/eshell/eshell-tests.el (eshell-test/eshell-command/output-buffer/async-kill): New test. * etc/NEWS: Announce this change (bug#71554).
This commit is contained in:
parent
bd86a6c4fd
commit
7f631a3e2a
4 changed files with 135 additions and 35 deletions
11
etc/NEWS
11
etc/NEWS
|
@ -43,6 +43,17 @@ applies, and please also update docstrings as needed.
|
|||
If 'whitespace-style' includes 'missing-newline-at-eof (which is the
|
||||
default), the 'whitespace-cleanup' function will now add the newline.
|
||||
|
||||
** Eshell
|
||||
|
||||
---
|
||||
*** New option 'eshell-command-async-buffer'.
|
||||
This option lets you tell 'eshell-command' how to respond if its output
|
||||
buffer is already in use by another invocation of 'eshell-command', much
|
||||
like 'async-shell-command-buffer' does for 'shell-command'. By default,
|
||||
this will prompt for confirmation before creating a new buffer when
|
||||
necessary. To restore the previous behavior, set this option to
|
||||
'confirm-kill-process'.
|
||||
|
||||
** SHR
|
||||
|
||||
+++
|
||||
|
|
|
@ -530,30 +530,34 @@ PROC is the process that's exiting. STRING is the exit message."
|
|||
(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))))))
|
||||
|
|
|
@ -216,6 +216,34 @@ named \"*eshell*<2>\"."
|
|||
: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
|
||||
|
@ -283,11 +311,19 @@ information on Eshell, see Info node `(eshell)Top'."
|
|||
(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
|
||||
|
@ -301,18 +337,46 @@ argument), then insert output into the current buffer at point."
|
|||
(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))
|
||||
|
@ -320,7 +384,7 @@ argument), then insert output into the current buffer at point."
|
|||
(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)
|
||||
|
@ -336,7 +400,7 @@ argument), then insert output into the current buffer at point."
|
|||
;; 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
|
||||
|
|
|
@ -117,6 +117,27 @@ This test uses a pipeline for the command."
|
|||
(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
|
||||
|
|
Loading…
Add table
Reference in a new issue