;;; esh-proc --- process management;; Copyright (C) 1999, 2000 Free Software Foundation;; Author: John Wiegley <johnw@gnu.org>;; This file is part of GNU Emacs.;; GNU Emacs is free software; you can redistribute it and/or modify;; it under the terms of the GNU General Public License as published by;; the Free Software Foundation; either version 2, or (at your option);; any later version.;; GNU Emacs is distributed in the hope that it will be useful,;; but WITHOUT ANY WARRANTY; without even the implied warranty of;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the;; GNU General Public License for more details.;; You should have received a copy of the GNU General Public License;; along with GNU Emacs; see the file COPYING. If not, write to the;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,;; Boston, MA 02111-1307, USA.(provide'esh-proc)(eval-when-compile(require'esh-maint))(defgroupeshell-procnil"When Eshell invokes external commands, it always does soasynchronously, so that Emacs isn't tied up waiting for the process tofinish.":tag"Process management":group'eshell);;; Commentary:;;; User Variables:(defcustomeshell-proc-load-hook'(eshell-proc-initialize)"*A hook that gets run when `eshell-proc' is loaded.":type'hook:group'eshell-proc)(defcustomeshell-process-wait-seconds0"*The number of seconds to delay waiting for a synchronous process.":type'integer:group'eshell-proc)(defcustomeshell-process-wait-milliseconds50"*The number of milliseconds to delay waiting for a synchronous process.":type'integer:group'eshell-proc)(defcustomeshell-done-messages-in-minibuffert"*If non-nil, subjob \"Done\" messages will display in minibuffer.":type'boolean:group'eshell-proc)(defcustomeshell-delete-exited-processest"*If nil, process entries will stick around until `jobs' is run.This variable sets the buffer-local value of `delete-exited-processes'in Eshell buffers.This variable causes Eshell to mimic the behavior of bash when set tonil. It allows the user to view the exit status of a completed subjob\(process) at their leisure, because the process entry remains inmemory until the user examines it using \\[list-processes].Otherwise, if `eshell-done-messages-in-minibuffer' is nil, and thisvariable is set to t, the only indication the user will have that asubjob is done is that it will no longer appear in the\\[list-processes\\] display.Note that Eshell will have to be restarted for a change in thisvariable's value to take effect.":type'boolean:group'eshell-proc)(defcustomeshell-reset-signals"^\\(interrupt\\|killed\\|quit\\|stopped\\)""*If a termination signal matches this regexp, the terminal will be reset.":type'regexp:group'eshell-proc)(defcustomeshell-exec-hooknil"*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 isexecuted in a eshell mode buffer (e.g., `process-kill-without-query').In contrast, `eshell-mode-hook' is only executed once when the bufferis created.":type'hook:group'eshell-proc)(defcustomeshell-kill-hook'(eshell-reset-after-proc)"*Called when a process run by `eshell-gather-process-output' has ended.It is passed two arguments: the process that was just ended, and thetermination status (as a string). Note that the first argument may benil, in which case the user attempted to send a signal, but there wasno relevant process. This can be used for displaying helpinformation, for example.":type'hook:group'eshell-proc);;; Internal Variables:(defvareshell-current-subjob-pnil)(defvareshell-process-listnil"A list of the current status of subprocesses.");;; Functions:(defuneshell-proc-initialize()"Initialize the process handling code."(make-local-variable'eshell-process-list)(define-keyeshell-command-map[(meta?i)]'eshell-insert-process)(define-keyeshell-command-map[(control?c)]'eshell-interrupt-process)(define-keyeshell-command-map[(control?k)]'eshell-kill-process)(define-keyeshell-command-map[(control?d)]'eshell-send-eof-to-process)(define-keyeshell-command-map[(control?q)]'eshell-continue-process)(define-keyeshell-command-map[(control?s)]'list-processes)(define-keyeshell-command-map[(control?z)]'eshell-stop-process)(define-keyeshell-command-map[(control?\\)]'eshell-quit-process))(defuneshell-reset-after-proc(procstatus)"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(stringpstatus)(string-matcheshell-reset-signalsstatus))(eshell-reset)))(defuneshell-wait-for-process(&restprocs)"Wait until PROC has successfully completed."(whileprocs(let((proc(carprocs)))(when(eshell-processpproc);; NYI: If the process gets stopped here, that's bad.(while(assqproceshell-process-list)(if(input-pending-p)(discard-input))(sit-foreshell-process-wait-secondseshell-process-wait-milliseconds))))(setqprocs(cdrprocs))))(defalias'eshell/wait'eshell-wait-for-process)(defuneshell/jobs(&restargs)"List processes, if there are any."(and(fboundp'process-list)(process-list)(list-processes)))(defuneshell/kill(&restargs)"Kill processes, buffers, symbol or files."(let((ptrargs)(signum'SIGINT))(whileptr(if(or(eshell-processp(carptr))(and(stringp(carptr))(string-match"^[A-Za-z/][A-Za-z0-9<>/]+$"(carptr))));; What about when $lisp-variable is possible here?;; It could very well name a process.(setcarptr(get-process(carptr))))(setqptr(cdrptr)))(whileargs(let((id(if(eshell-processp(carargs))(process-id(carargs))(carargs))))(whenid(cond((nullid)(error"kill: bad signal spec"))((and(numberpid)(=id0))(error"kill: bad signal spec `%d'"id))((and(stringpid)(string-match"^-?[0-9]+$"id))(setqsignum(abs(string-to-numberid))))((stringpid)(let(case-fold-search)(if(string-match"^-\\([A-Z]+\\)$"id)(setqsignum(intern(concat"SIG"(match-string1id))))(error"kill: bad signal spec `%s'"id))))((<id0)(setqsignum(absid)))(t(signal-processidsignum)))))(setqargs(cdrargs)))nil))(defuneshell-read-process-name(prompt)"Read the name of a process from the minibuffer, using completion.The prompt will be set to PROMPT."(completing-readprompt(mapcar(function(lambda(proc)(cons(process-nameproc)t)))(process-list))nilt))(defuneshell-insert-process(process)"Insert the name of PROCESS into the current buffer at point."(interactive(list(get-process(eshell-read-process-name"Name of process: "))))(insert-and-inherit"#<process "(process-nameprocess)">"))(defsubsteshell-record-process-object(object)"Record OBJECT as now running."(if(and(eshell-processpobject)eshell-current-subjob-p)(eshell-interactive-print(format"[%s] %d\n"(process-nameobject)(process-idobject))))(setqeshell-process-list(cons(listobjecteshell-current-handleseshell-current-subjob-pnilnil)eshell-process-list)))(defuneshell-remove-process-entry(entry)"Record the process ENTRY as fully completed."(if(and(eshell-processp(carentry))(nth2entry)eshell-done-messages-in-minibuffer)(message(format"[%s]+ Done %s"(process-name(carentry))(process-command(carentry)))))(setqeshell-process-list(delqentryeshell-process-list)))(defvareshell-scratch-buffer" *eshell-scratch*""Scratch buffer for holding Eshell's input/output.")(defvareshell-last-sync-output-startnil"A marker that tracks the beginning of output of the last subprocess.Used only on systems which do not support async subprocesses.")(defuneshell-gather-process-output(commandargs)"Gather the output from COMMAND + ARGS."(unless(and(file-executable-pcommand)(file-regular-pcommand))(error"%s: not an executable file"command))(let*((delete-exited-processes(ifeshell-current-subjob-peshell-delete-exited-processesdelete-exited-processes))(process-environment(eshell-environment-variables))procdecodingencodingchanged)(cond((fboundp'start-process)(setqproc(apply'start-process(file-name-nondirectorycommand)nil;; `start-process' can't deal with relative;; filenames(append(list(expand-file-namecommand))args)))(eshell-record-process-objectproc)(set-process-bufferproc(current-buffer))(if(eshell-interactive-output-p)(set-process-filterproc'eshell-output-filter)(set-process-filterproc'eshell-insertion-filter))(set-process-sentinelproc'eshell-sentinel)(run-hook-with-args'eshell-exec-hookproc)(when(fboundp'process-coding-system)(let((coding-systems(process-coding-systemproc)))(setqdecoding(carcoding-systems)encoding(cdrcoding-systems)));; If start-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-typedecoding))(setqdecoding(coding-system-change-eol-conversiondecoding'dos)changedt));; Even if start-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.(if(anddecoding(notencoding))(setqencoding(coding-system-change-eol-conversiondecoding'unix)changedt))(ifchanged(set-process-coding-systemprocdecodingencoding))))(t;; No async subprocesses...(let((oldbuf(current-buffer))(interact-p(eshell-interactive-output-p))lbeglendlineproc-bufexit-status)(and(not(markerpeshell-last-sync-output-start))(setqeshell-last-sync-output-start(point-marker)))(setqproc-buf(set-buffer(get-buffer-createeshell-scratch-buffer)))(erase-buffer)(set-bufferoldbuf)(run-hook-with-args'eshell-exec-hookcommand)(setqexit-status(apply'call-process-region(append(listeshell-last-sync-output-start(point)commandteshell-scratch-buffernil)args)));; When in a pipeline, record the place where the output of;; this process will begin.(andeshell-in-pipeline-p(set-markereshell-last-sync-output-start(point)));; Simulate the effect of the process filter.(when(numberpexit-status)(set-bufferproc-buf)(goto-char(point-min))(setqlbeg(point))(while(eq0(forward-line1))(setqlend(point)line(buffer-substring-no-propertieslbeglend))(set-bufferoldbuf)(ifinteract-p(eshell-output-filternilline)(eshell-output-objectline))(setqlbeglend)(set-bufferproc-buf))(set-bufferoldbuf))(eshell-update-markerseshell-last-output-end);; Simulate the effect of eshell-sentinel.(eshell-close-handles(if(numberpexit-status)exit-status-1))(run-hook-with-args'eshell-kill-hookcommandexit-status)(oreshell-in-pipeline-p(setqeshell-last-sync-output-startnil))(if(not(numberpexit-status))(error"%s: external command failed: %s"commandexit-status))(setqproct))))proc))(defuneshell-insertion-filter(procstring)"Insert a string into the eshell buffer, or a process/file/buffer.PROC is the process for which we're inserting output. STRING is theoutput."(when(buffer-live-p(process-bufferproc))(set-buffer(process-bufferproc))(let((entry(assqproceshell-process-list)))(whenentry(setcar(nthcdr3entry)(concat(nth3entry)string))(unless(nth4entry); already being handled?(while(nth3entry)(let((data(nth3entry)))(setcar(nthcdr3entry)nil)(setcar(nthcdr4entry)t)(eshell-output-objectdatanil(cadrentry))(setcar(nthcdr4entry)nil))))))))(defuneshell-sentinel(procstring)"Generic sentinel for command processes. Reports only signals.PROC is the process that's exiting. STRING is the exit message."(when(buffer-live-p(process-bufferproc))(set-buffer(process-bufferproc))(unwind-protect(let*((entry(assqproceshell-process-list))); (if (not entry); (error "Sentinel called for unowned process `%s'"; (process-name proc))(whenentry(unwind-protect(progn(unless(string=string"run")(unless(string-match"^\\(finished\\|exited\\)"string)(eshell-insertion-filterprocstring))(eshell-close-handles(process-exit-statusproc)'nil(cadrentry))))(eshell-remove-process-entryentry))))(run-hook-with-args'eshell-kill-hookprocstring))))(defuneshell-process-interact(func&optionalallquery)"Interact with a process, using PROMPT if more than one, via FUNC.If ALL is non-nil, background processes will be interacted with as well.If QUERY is non-nil, query the user with QUERY before calling FUNC."(let(defunctresult)(eshell-forentryeshell-process-list(if(and(memq(process-status(carentry))'(runstopopenclosed))(orall(not(nth2entry)))(or(notquery)(y-or-n-p(formatquery(process-name(carentry))))))(setqresult(funcallfunc(carentry))))(unless(memq(process-status(carentry))'(runstopopenclosed))(setqdefunct(consentrydefunct))));; clean up the process list; this can get dirty if an error;; occurred that brought the user into the debugger, and then they;; quit, so that the sentinel was never called.(eshell-forddefunct(eshell-remove-process-entryd))result))(defcustomeshell-kill-process-wait-time5"*Seconds to wait between sending termination signals to a subprocess.":type'integer:group'eshell-proc)(defcustomeshell-kill-process-signals'(SIGINTSIGQUITSIGKILL)"*Signals used to kill processes when an Eshell buffer exits.Eshell calls each of these signals in order when an Eshell buffer iskilled; if the process is still alive afterwards, Eshell waits anumber of seconds defined by `eshell-kill-process-wait-time', andtries the next signal in the list.":type'(repeatsymbol):group'eshell-proc)(defcustomeshell-kill-processes-on-exitnil"*If non-nil, kill active processes when exiting an Eshell buffer.Emacs will only kill processes owned by that Eshell buffer.If nil, ownership of background and foreground processes reverts toEmacs itself, and will die only if the user exits Emacs, calls`kill-process', or terminates the processes externally.If `ask', Emacs prompts the user before killing any processes.If `every', it prompts once for every process.If t, it kills all buffer-owned processes without asking.Processes are first sent SIGHUP, then SIGINT, then SIGQUIT, thenSIGKILL. The variable `eshell-kill-process-wait-time' specifies howlong 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)(defuneshell-round-robin-kill(&optionalquery)"Kill current process by trying various signals in sequence.See the variable `eshell-kill-processes-on-exit'."(let((sigseshell-kill-process-signals))(whilesigs(eshell-process-interact(function(lambda(proc)(signal-process(process-idproc)(carsigs))))tquery)(setqquerynil)(if(noteshell-process-list)(setqsigsnil)(sleep-foreshell-kill-process-wait-time)(setqsigs(cdrsigs))))))(defuneshell-query-kill-processes()"Kill processes belonging to the current Eshell buffer, possibly w/ query."(when(andeshell-kill-processes-on-exiteshell-process-list)(save-window-excursion(list-processes)(if(or(not(eqeshell-kill-processes-on-exit'ask))(y-or-n-p(format"Kill processes owned by `%s'? "(buffer-name))))(eshell-round-robin-kill(if(eqeshell-kill-processes-on-exit'every)"Kill Eshell child process `%s'? ")))(let((buf(get-buffer"*Process List*")))(if(andbuf(buffer-live-pbuf))(kill-bufferbuf)))(messagenil))))(custom-add-option'eshell-exit-hook'eshell-query-kill-processes)(defuneshell-interrupt-process()"Interrupt a process."(interactive)(unless(eshell-process-interact'interrupt-process)(run-hook-with-args'eshell-kill-hooknil"interrupt")))(defuneshell-kill-process()"Kill a process."(interactive)(unless(eshell-process-interact'kill-process)(run-hook-with-args'eshell-kill-hooknil"killed")))(defuneshell-quit-process()"Send quit signal to process."(interactive)(unless(eshell-process-interact'quit-process)(run-hook-with-args'eshell-kill-hooknil"quit")))(defuneshell-stop-process()"Send STOP signal to process."(interactive)(unless(eshell-process-interact'stop-process)(run-hook-with-args'eshell-kill-hooknil"stopped")))(defuneshell-continue-process()"Send CONTINUE signal to process."(interactive)(unless(eshell-process-interact'continue-process);; jww (1999-09-17): this signal is not dealt with yet. For;; example, `eshell-reset' will be called, and so will;; `eshell-resume-eval'.(run-hook-with-args'eshell-kill-hooknil"continue")))(defuneshell-send-eof-to-process()"Send EOF to process."(interactive)(eshell-send-inputnilnilt)(eshell-process-interact'process-send-eof));;; Code:;;; esh-proc.el ends here