diff options
author | Stefan Monnier <monnier@iro.umontreal.ca> | 2022-09-25 16:15:16 -0400 |
---|---|---|
committer | Stefan Monnier <monnier@iro.umontreal.ca> | 2022-09-25 16:15:16 -0400 |
commit | 650c20f1ca4e07591a727e1cfcc74b3363d15985 (patch) | |
tree | 85d11f6437cde22f410c25e0e5f71a3131ebd07d /lisp/eshell/esh-proc.el | |
parent | 8869332684c2302b5ba1ead4568bbc7ba1c0183e (diff) | |
parent | 4b85ae6a24380fb67a3315eaec9233f17a872473 (diff) | |
download | emacs-650c20f1ca4e07591a727e1cfcc74b3363d15985.tar.gz emacs-650c20f1ca4e07591a727e1cfcc74b3363d15985.tar.bz2 emacs-650c20f1ca4e07591a727e1cfcc74b3363d15985.zip |
Merge 'master' into noverlay
Diffstat (limited to 'lisp/eshell/esh-proc.el')
-rw-r--r-- | lisp/eshell/esh-proc.el | 346 |
1 files changed, 195 insertions, 151 deletions
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el index 3e9ac281a10..7e005a0fc1c 100644 --- a/lisp/eshell/esh-proc.el +++ b/lisp/eshell/esh-proc.el @@ -1,6 +1,6 @@ ;;; esh-proc.el --- process management -*- lexical-binding:t -*- -;; Copyright (C) 1999-2017 Free Software Foundation, Inc. +;; Copyright (C) 1999-2022 Free Software Foundation, Inc. ;; Author: John Wiegley <johnw@gnu.org> @@ -23,9 +23,7 @@ ;;; Code: -(provide 'esh-proc) - -(require 'esh-cmd) +(require 'esh-io) (defgroup eshell-proc nil "When Eshell invokes external commands, it always does so @@ -39,23 +37,19 @@ finish." (defcustom eshell-proc-load-hook nil "A hook that gets run when `eshell-proc' is loaded." :version "24.1" ; removed eshell-proc-initialize - :type 'hook - :group 'eshell-proc) + :type 'hook) (defcustom eshell-process-wait-seconds 0 "The number of seconds to delay waiting for a synchronous process." - :type 'integer - :group 'eshell-proc) + :type 'integer) (defcustom eshell-process-wait-milliseconds 50 "The number of milliseconds to delay waiting for a synchronous process." - :type 'integer - :group 'eshell-proc) + :type 'integer) (defcustom eshell-done-messages-in-minibuffer t "If non-nil, subjob \"Done\" messages will display in minibuffer." - :type 'boolean - :group 'eshell-proc) + :type 'boolean) (defcustom eshell-delete-exited-processes t "If nil, process entries will stick around until `jobs' is run. @@ -74,24 +68,21 @@ subjob is done is that it will no longer appear in the Note that Eshell will have to be restarted for a change in this variable's value to take effect." - :type 'boolean - :group 'eshell-proc) + :type 'boolean) (defcustom eshell-reset-signals "^\\(interrupt\\|killed\\|quit\\|stopped\\)" "If a termination signal matches this regexp, the terminal will be reset." - :type 'regexp - :group 'eshell-proc) + :type 'regexp) (defcustom eshell-exec-hook nil "Called each time a process is exec'd by `eshell-gather-process-output'. It is passed one argument, which is the process that was just started. It is useful for things that must be done each time a process is -executed in a eshell mode buffer (e.g., `process-kill-without-query'). -In contrast, `eshell-mode-hook' is only executed once when the buffer +executed in an eshell mode buffer (e.g., `set-process-query-on-exit-flag'). +In contrast, `eshell-mode-hook' is only executed once, when the buffer is created." - :type 'hook - :group 'eshell-proc) + :type 'hook) (defcustom eshell-kill-hook nil "Called when a process run by `eshell-gather-process-output' has ended. @@ -101,15 +92,31 @@ nil, in which case the user attempted to send a signal, but there was no relevant process. This can be used for displaying help information, for example." :version "24.1" ; removed eshell-reset-after-proc - :type 'hook - :group 'eshell-proc) + :type 'hook) ;;; Internal Variables: (defvar eshell-current-subjob-p nil) (defvar eshell-process-list nil - "A list of the current status of subprocesses.") + "A list of the current status of subprocesses. +Each element has the form (PROC . SUBJOB-P), where PROC is the +process object and SUBJOB-P is non-nil if the process is a +subjob. + +To add or remove elements of this list, see +`eshell-record-process-object' and `eshell-remove-process-entry'.") + +(declare-function eshell-send-eof-to-process "esh-mode") +(declare-function eshell-tail-process "esh-cmd") + +(defvar-keymap eshell-proc-mode-map + "C-c M-i" #'eshell-insert-process + "C-c C-c" #'eshell-interrupt-process + "C-c C-k" #'eshell-kill-process + "C-c C-d" #'eshell-send-eof-to-process + "C-c C-s" #'list-processes + "C-c C-\\" #'eshell-quit-process) ;;; Functions: @@ -118,30 +125,32 @@ information, for example." Runs `eshell-reset-after-proc' and `eshell-kill-hook', passing arguments PROC and STATUS to functions on the latter." ;; Was there till 24.1, but it is not optional. - (if (memq 'eshell-reset-after-proc eshell-kill-hook) - (setq eshell-kill-hook (delq 'eshell-reset-after-proc eshell-kill-hook))) - (eshell-reset-after-proc status) + (remove-hook 'eshell-kill-hook #'eshell-reset-after-proc) + ;; Only reset the prompt if this process is running interactively. + (when (eq proc (eshell-tail-process)) + (eshell-reset-after-proc status)) (run-hook-with-args 'eshell-kill-hook proc status)) -(defun eshell-proc-initialize () +(define-minor-mode eshell-proc-mode + "Minor mode for the proc eshell module. + +\\{eshell-proc-mode-map}" + :keymap eshell-proc-mode-map) + +(defun eshell-proc-initialize () ;Called from `eshell-mode' via intern-soft! "Initialize the process handling code." (make-local-variable 'eshell-process-list) - (define-key eshell-command-map [(meta ?i)] 'eshell-insert-process) - (define-key eshell-command-map [(control ?c)] 'eshell-interrupt-process) - (define-key eshell-command-map [(control ?k)] 'eshell-kill-process) - (define-key eshell-command-map [(control ?d)] 'eshell-send-eof-to-process) -; (define-key eshell-command-map [(control ?q)] 'eshell-continue-process) - (define-key eshell-command-map [(control ?s)] 'list-processes) -; (define-key eshell-command-map [(control ?z)] 'eshell-stop-process) - (define-key eshell-command-map [(control ?\\)] 'eshell-quit-process)) + (eshell-proc-mode)) (defun eshell-reset-after-proc (status) "Reset the command input location after a process terminates. The signals which will cause this to happen are matched by `eshell-reset-signals'." - (if (and (stringp status) - (string-match eshell-reset-signals status)) - (eshell-reset))) + (when (and (stringp status) + (string-match eshell-reset-signals status)) + (require 'esh-mode) + (declare-function eshell-reset "esh-mode" (&optional no-hooks)) + (eshell-reset))) (defun eshell-wait-for-process (&rest procs) "Wait until PROC has successfully completed." @@ -156,9 +165,9 @@ The signals which will cause this to happen are matched by eshell-process-wait-milliseconds)))) (setq procs (cdr procs)))) -(defalias 'eshell/wait 'eshell-wait-for-process) +(defalias 'eshell/wait #'eshell-wait-for-process) -(defun eshell/jobs (&rest args) +(defun eshell/jobs (&rest _args) "List processes, if there are any." (and (fboundp 'process-list) (process-list) @@ -167,7 +176,8 @@ The signals which will cause this to happen are matched by (defun eshell/kill (&rest args) "Kill processes. Usage: kill [-<signal>] <pid>|<process> ... -Accepts PIDs and process objects." +Accepts PIDs and process objects. Optionally accept signals +and signal names." ;; If the first argument starts with a dash, treat it as the signal ;; specifier. (let ((signum 'SIGINT)) @@ -178,12 +188,12 @@ Accepts PIDs and process objects." ((string-match "\\`-[[:digit:]]+\\'" arg) (setq signum (abs (string-to-number arg)))) ((string-match "\\`-\\([[:upper:]]+\\|[[:lower:]]+\\)\\'" arg) - (setq signum (abs (string-to-number arg))))) + (setq signum (intern (substring arg 1))))) (setq args (cdr args)))) (while args (let ((arg (if (eshell-processp (car args)) (process-id (car args)) - (car args)))) + (string-to-number (car args))))) (when arg (cond ((null arg) @@ -198,15 +208,17 @@ Accepts PIDs and process objects." (setq args (cdr args)))) nil) +(put 'eshell/kill 'eshell-no-numeric-conversions t) + (defun eshell-read-process-name (prompt) "Read the name of a process from the minibuffer, using completion. The prompt will be set to PROMPT." (completing-read prompt (mapcar - (function - (lambda (proc) - (cons (process-name proc) t))) - (process-list)) nil t)) + (lambda (proc) + (cons (process-name proc) t)) + (process-list)) + nil t)) (defun eshell-insert-process (process) "Insert the name of PROCESS into the current buffer at point." @@ -217,53 +229,46 @@ The prompt will be set to PROMPT." (defsubst eshell-record-process-object (object) "Record OBJECT as now running." - (if (and (eshell-processp object) - eshell-current-subjob-p) - (eshell-interactive-print - (format "[%s] %d\n" (process-name object) (process-id object)))) - (setq eshell-process-list - (cons (list object eshell-current-handles - eshell-current-subjob-p nil nil) - eshell-process-list))) + (when (and (eshell-processp object) + eshell-current-subjob-p) + (require 'esh-mode) + (declare-function eshell-interactive-print "esh-mode" (string)) + (eshell-interactive-print + (format "[%s] %d\n" (process-name object) (process-id object)))) + (push (cons object eshell-current-subjob-p) eshell-process-list)) (defun eshell-remove-process-entry (entry) "Record the process ENTRY as fully completed." (if (and (eshell-processp (car entry)) - (nth 2 entry) + (cdr entry) eshell-done-messages-in-minibuffer) (message "[%s]+ Done %s" (process-name (car entry)) (process-command (car entry)))) (setq eshell-process-list (delq entry eshell-process-list))) +(defun eshell-record-process-properties (process &optional index) + "Record Eshell bookkeeping properties for PROCESS. +`eshell-insertion-filter' and `eshell-sentinel' will use these to +do their jobs. + +INDEX is the index of the output handle to use for writing; if +nil, write to `eshell-output-handle'." + (process-put process :eshell-handles eshell-current-handles) + (process-put process :eshell-handle-index (or index eshell-output-handle)) + (process-put process :eshell-pending nil) + (process-put process :eshell-busy nil)) + (defvar eshell-scratch-buffer " *eshell-scratch*" "Scratch buffer for holding Eshell's input/output.") (defvar eshell-last-sync-output-start nil "A marker that tracks the beginning of output of the last subprocess. Used only on systems which do not support async subprocesses.") -(defvar eshell-needs-pipe '("bc") - "List of commands which need `process-connection-type' to be nil. -Currently only affects commands in pipelines, and not those at -the front. If an element contains a directory part it must match -the full name of a command, otherwise just the nondirectory part must match.") - -(defun eshell-needs-pipe-p (command) - "Return non-nil if COMMAND needs `process-connection-type' to be nil. -See `eshell-needs-pipe'." - (and eshell-in-pipeline-p - (not (eq eshell-in-pipeline-p 'first)) - ;; FIXME should this return non-nil for anything that is - ;; neither 'first nor 'last? See bug#1388 discussion. - (catch 'found - (dolist (exe eshell-needs-pipe) - (if (string-equal exe (if (string-match "/" exe) - command - (file-name-nondirectory command))) - (throw 'found t)))))) - (defun eshell-gather-process-output (command args) "Gather the output from COMMAND + ARGS." + (require 'esh-var) + (declare-function eshell-environment-variables "esh-var" ()) (unless (and (file-executable-p command) (file-regular-p (file-truename command))) (error "%s: not an executable file" command)) @@ -272,37 +277,54 @@ See `eshell-needs-pipe'." eshell-delete-exited-processes delete-exited-processes)) (process-environment (eshell-environment-variables)) - proc decoding encoding changed) + proc stderr-proc decoding encoding changed) (cond - ((fboundp 'start-file-process) + ((fboundp 'make-process) + (unless (equal (car (aref eshell-current-handles eshell-output-handle)) + (car (aref eshell-current-handles eshell-error-handle))) + (eshell-protect-handles eshell-current-handles) + (setq stderr-proc + (make-pipe-process + :name (concat (file-name-nondirectory command) "-stderr") + :buffer (current-buffer) + :filter (if (eshell-interactive-output-p eshell-error-handle) + #'eshell-output-filter + #'eshell-insertion-filter) + :sentinel #'eshell-sentinel)) + (eshell-record-process-properties stderr-proc eshell-error-handle)) (setq proc - (let ((process-connection-type - (unless (eshell-needs-pipe-p command) - process-connection-type)) - (command (file-local-name command))) - (apply 'start-file-process - (file-name-nondirectory command) nil - ;; `start-process' can't deal with relative filenames. - (append (list (expand-file-name command)) args)))) + (let ((command (file-local-name (expand-file-name command))) + (conn-type (pcase (bound-and-true-p eshell-in-pipeline-p) + ('first '(nil . pipe)) + ('last '(pipe . nil)) + ('t 'pipe) + ('nil nil)))) + (make-process + :name (file-name-nondirectory command) + :buffer (current-buffer) + :command (cons command args) + :filter (if (eshell-interactive-output-p) + #'eshell-output-filter + #'eshell-insertion-filter) + :sentinel #'eshell-sentinel + :connection-type conn-type + :stderr stderr-proc + :file-handler t))) (eshell-record-process-object proc) - (set-process-buffer proc (current-buffer)) - (if (eshell-interactive-output-p) - (set-process-filter proc 'eshell-output-filter) - (set-process-filter proc 'eshell-insertion-filter)) - (set-process-sentinel proc 'eshell-sentinel) + (eshell-record-process-properties proc) (run-hook-with-args 'eshell-exec-hook proc) (when (fboundp 'process-coding-system) (let ((coding-systems (process-coding-system proc))) (setq decoding (car coding-systems) encoding (cdr coding-systems))) - ;; If start-process decided to use some coding system for + ;; If `make-process' decided to use some coding system for ;; decoding data sent from the process and the coding system ;; doesn't specify EOL conversion, we had better convert CRLF ;; to LF. (if (vectorp (coding-system-eol-type decoding)) (setq decoding (coding-system-change-eol-conversion decoding 'dos) changed t)) - ;; Even if start-process left the coding system for encoding + ;; Even if `make-process' left the coding system for encoding ;; data sent from the process undecided, we had better use the ;; same one as what we use for decoding. But, we should ;; suppress EOL conversion. @@ -324,14 +346,14 @@ See `eshell-needs-pipe'." (set-buffer oldbuf) (run-hook-with-args 'eshell-exec-hook command) (setq exit-status - (apply 'call-process-region + (apply #'call-process-region (append (list eshell-last-sync-output-start (point) command t eshell-scratch-buffer nil) args))) ;; When in a pipeline, record the place where the output of ;; this process will begin. - (and eshell-in-pipeline-p + (and (bound-and-true-p eshell-in-pipeline-p) (set-marker eshell-last-sync-output-start (point))) ;; Simulate the effect of the process filter. (when (numberp exit-status) @@ -348,11 +370,16 @@ See `eshell-needs-pipe'." (setq lbeg lend) (set-buffer proc-buf)) (set-buffer oldbuf)) + (require 'esh-mode) + (declare-function eshell-update-markers "esh-mode" (pmark)) + (defvar eshell-last-output-end) ;Defined in esh-mode.el. (eshell-update-markers eshell-last-output-end) ;; Simulate the effect of eshell-sentinel. - (eshell-close-handles (if (numberp exit-status) exit-status -1)) + (eshell-close-handles + (if (numberp exit-status) exit-status -1) + (list 'quote (and (numberp exit-status) (= exit-status 0)))) (eshell-kill-process-function command exit-status) - (or eshell-in-pipeline-p + (or (bound-and-true-p eshell-in-pipeline-p) (setq eshell-last-sync-output-start nil)) (if (not (numberp exit-status)) (error "%s: external command failed: %s" command exit-status)) @@ -365,17 +392,36 @@ PROC is the process for which we're inserting output. STRING is the output." (when (buffer-live-p (process-buffer proc)) (with-current-buffer (process-buffer proc) - (let ((entry (assq proc eshell-process-list))) - (when entry - (setcar (nthcdr 3 entry) - (concat (nth 3 entry) string)) - (unless (nth 4 entry) ; already being handled? - (while (nth 3 entry) - (let ((data (nth 3 entry))) - (setcar (nthcdr 3 entry) nil) - (setcar (nthcdr 4 entry) t) - (eshell-output-object data nil (cadr entry)) - (setcar (nthcdr 4 entry) nil))))))))) + (process-put proc :eshell-pending + (concat (process-get proc :eshell-pending) + string)) + (unless (process-get proc :eshell-busy) ; Already being handled? + (while (process-get proc :eshell-pending) + (let ((handles (process-get proc :eshell-handles)) + (index (process-get proc :eshell-handle-index)) + (data (process-get proc :eshell-pending))) + (process-put proc :eshell-pending nil) + (process-put proc :eshell-busy t) + (unwind-protect + (condition-case nil + (eshell-output-object data index handles) + ;; FIXME: We want to send SIGPIPE to the process + ;; here. However, remote processes don't currently + ;; support that, and not all systems have SIGPIPE in + ;; the first place (e.g. MS Windows). In these + ;; cases, just delete the process; this is + ;; reasonably close to the right behavior, since the + ;; default action for SIGPIPE is to terminate the + ;; process. For use cases where SIGPIPE is truly + ;; needed, using an external pipe operator (`*|') + ;; may work instead (e.g. when working with remote + ;; processes). + (eshell-pipe-broken + (if (or (process-get proc 'remote-pid) + (eq system-type 'windows-nt)) + (delete-process proc) + (signal-process proc 'SIGPIPE)))) + (process-put proc :eshell-busy nil)))))))) (defun eshell-sentinel (proc string) "Generic sentinel for command processes. Reports only signals. @@ -383,32 +429,39 @@ PROC is the process that's exiting. STRING is the exit message." (when (buffer-live-p (process-buffer proc)) (with-current-buffer (process-buffer proc) (unwind-protect - (let* ((entry (assq proc eshell-process-list))) -; (if (not entry) -; (error "Sentinel called for unowned process `%s'" -; (process-name proc)) - (when entry - (unwind-protect - (progn - (unless (string= string "run") - (unless (string-match "^\\(finished\\|exited\\)" string) - (eshell-insertion-filter proc string)) - (let ((handles (nth 1 entry)) - (str (prog1 (nth 3 entry) - (setf (nth 3 entry) nil))) - (status (process-exit-status proc))) - ;; If we're in the middle of handling output - ;; from this process then schedule the EOF for - ;; later. - (letrec ((finish-io - (lambda () - (if (nth 4 entry) - (run-at-time 0 nil finish-io) - (when str (eshell-output-object str nil handles)) - (eshell-close-handles status 'nil handles))))) - (funcall finish-io))))) - (eshell-remove-process-entry entry)))) - (eshell-kill-process-function proc string))))) + (unless (string= string "run") + ;; Write the exit message if the status is abnormal and + ;; the process is already writing to the terminal. + (when (and (eq proc (eshell-tail-process)) + (not (string-match "^\\(finished\\|exited\\)" + string))) + (funcall (process-filter proc) proc string)) + (let* ((handles (process-get proc :eshell-handles)) + (index (process-get proc :eshell-handle-index)) + (data (process-get proc :eshell-pending)) + ;; Only get the status for the primary subprocess, + ;; not the pipe process (if any). + (status (when (= index eshell-output-handle) + (process-exit-status proc)))) + (process-put proc :eshell-pending nil) + ;; If we're in the middle of handling output from this + ;; process then schedule the EOF for later. + (letrec ((finish-io + (lambda () + (if (process-get proc :eshell-busy) + (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))))) + (funcall finish-io)))) + (when-let ((entry (assq proc eshell-process-list))) + (eshell-remove-process-entry entry)) + (eshell-kill-process-function proc string))))) (defun eshell-process-interact (func &optional all query) "Interact with a process, using PROMPT if more than one, via FUNC. @@ -419,7 +472,7 @@ If QUERY is non-nil, query the user with QUERY before calling FUNC." (if (and (memq (process-status (car entry)) '(run stop open closed)) (or all - (not (nth 2 entry))) + (not (cdr entry))) (or (not query) (y-or-n-p (format-message query (process-name (car entry)))))) @@ -436,8 +489,7 @@ If QUERY is non-nil, query the user with QUERY before calling FUNC." (defcustom eshell-kill-process-wait-time 5 "Seconds to wait between sending termination signals to a subprocess." - :type 'integer - :group 'eshell-proc) + :type 'integer) (defcustom eshell-kill-process-signals '(SIGINT SIGQUIT SIGKILL) "Signals used to kill processes when an Eshell buffer exits. @@ -445,8 +497,7 @@ Eshell calls each of these signals in order when an Eshell buffer is killed; if the process is still alive afterwards, Eshell waits a number of seconds defined by `eshell-kill-process-wait-time', and tries the next signal in the list." - :type '(repeat symbol) - :group 'eshell-proc) + :type '(repeat symbol)) (defcustom eshell-kill-processes-on-exit nil "If non-nil, kill active processes when exiting an Eshell buffer. @@ -468,8 +519,7 @@ long to delay between signals." :type '(choice (const :tag "Kill all, don't ask" t) (const :tag "Ask before killing" ask) (const :tag "Ask for each process" every) - (const :tag "Don't kill subprocesses" nil)) - :group 'eshell-proc) + (const :tag "Don't kill subprocesses" nil))) (defun eshell-round-robin-kill (&optional query) "Kill current process by trying various signals in sequence. @@ -477,9 +527,8 @@ See the variable `eshell-kill-processes-on-exit'." (let ((sigs eshell-kill-process-signals)) (while sigs (eshell-process-interact - (function - (lambda (proc) - (signal-process (process-id proc) (car sigs)))) t query) + (lambda (proc) + (signal-process (process-id proc) (car sigs))) t query) (setq query nil) (if (not eshell-process-list) (setq sigs nil) @@ -497,7 +546,7 @@ See the variable `eshell-kill-processes-on-exit'." (buffer-name)))) (eshell-round-robin-kill (if (eq eshell-kill-processes-on-exit 'every) - (format-message "Kill Eshell child process `%s'? ")))) + "Kill Eshell child process `%s'? "))) (let ((buf (get-buffer "*Process List*"))) (if (and buf (buffer-live-p buf)) (kill-buffer buf))) @@ -536,10 +585,5 @@ See the variable `eshell-kill-processes-on-exit'." ; ;; `eshell-resume-eval'. ; (eshell-kill-process-function nil "continue"))) -(defun eshell-send-eof-to-process () - "Send EOF to process." - (interactive) - (eshell-send-input nil nil t) - (eshell-process-interact 'process-send-eof)) - +(provide 'esh-proc) ;;; esh-proc.el ends here |