jgk@osc.COM (Joe Keane) (08/29/90)
Here is the latest and greatest version of background mode for GNU Emacs. Basically, as they name says, it lets you run commands in the background. It's got all sorts of nifty goodies, but you'll just have to try it to find them. I don't know of any bugs, but you can consider this a beta-test version. All comments are appreciated. To use it, stick the following stuff in a file called `background.el' somewhere in your load path. Then put something like this in your `.emacs': (autoload 'background "background" nil t) (global-set-key "\M-!" 'background) - - - everything after this line goes in `background.el' - - - ;; Background jobs in GNU Emacs ;; Copyright (C) 1990 Joe Keane <jgk@osc.com> ;; Refer to the GNU Emacs General Public License for copyright info (require 'shell) (provide 'background) ;; define global variables (defvar background-minibuffer-map (make-keymap) "The keymap to use when prompting the user for a background command.") (defvar background-history-size 0 "The number of commands in the background command history.") (defvar background-history-vector (make-vector 16 nil) "A vector containing the background command history. Position zero is unused.") (defvar background-search-last-string "" "Last string searched for by a background search.") ;; add bindings to shell-mode-map (define-key shell-mode-map "\C-c\C-k" 'kill-shell-subjob) (define-key shell-mode-map "\C-c\C-m" 'continue-shell-subjob) (define-key shell-mode-map "\C-c\C-z" 'stop-shell-subjob) ;; add bindings to background-minibuffer-map (define-key background-minibuffer-map "\C-g" 'abort-recursive-edit) (define-key background-minibuffer-map "\C-m" 'exit-minibuffer) (define-key background-minibuffer-map "\C-n" 'background-next-command) (define-key background-minibuffer-map "\C-p" 'background-previous-command) (define-key background-minibuffer-map "\C-r" 'background-search-backward) (define-key background-minibuffer-map "\C-s" 'background-search-forward) ;; missing from shell-mode (defun continue-shell-subjob () "Send continue signal to this shell's current subjob." (interactive) (continue-process nil t)) ;; move around in command history (defun background-next-command (arg) "Go to the ARG'th next background command." (interactive "p") (background-previous-command (- arg))) (defun background-previous-command (arg) "Go to ARG'th previous background command." (interactive "p") (setq scrolling t) (setq history-index (if (>= arg 0) (let ((index (- (or history-index (1+ background-history-size)) arg))) (and (<= index 0) (error "Beginning of command history")) index) (let ((index (- (or history-index 0) arg))) (and (> index background-history-size) (error "End of command history")) index))) (exit-minibuffer)) ;; search in command history (defun background-search-backward () "Search backward through the background command history. If you know how to use isearch you should be able to figure this out." (interactive) (background-search nil)) (defun background-search-forward () "Same as background-search-backward except forward." (interactive) (background-search t)) (defun background-search (forward) "Function to do the work of searching through background command history." (let ((search-string "") failing wrapped (search-index (or history-index (if forward 1 background-history-size))) loop-index) (while (progn (message "%s%s%s\"%s\" %d%% %s" (if failing "F" "") (if wrapped "W" "") (if forward "S" "R") search-string search-index (aref background-history-vector search-index)) (setq loop-index search-index) (let ((char (read-char)) (continue t)) (cond ((= char search-exit-char) (setq continue nil)) ((= char search-reverse-char) (and (string-equal search-string "") (setq search-string background-search-last-string)) (setq loop-index (if (and failing (not forward)) (progn (setq wrapped t) background-history-size) (1- search-index))) (setq forward nil)) ((= char search-repeat-char) (and (string-equal search-string "") (setq search-string background-search-last-string)) (setq loop-index (if (and failing forward) (progn (setq wrapped t) 1) (1+ search-index))) (setq forward t)) ((= char search-delete-char) (setq failing nil) (setq search-string (if (string-equal search-string "") background-search-last-string (substring search-string 0 -1)))) ((= char search-quote-char) (message "quote") (setq search-string (concat search-string (char-to-string (read-quoted-char))))) ((or (< char 32) (>= char 128)) (setq unread-command-char char) (setq continue nil)) (t (setq search-string (concat search-string (char-to-string char))))) continue)) (if forward (while (if (> loop-index background-history-size) (progn (setq failing t) nil) (or (not (string-match search-string (aref background-history-vector loop-index))) (progn (setq failing nil) (setq search-index loop-index) nil))) (setq loop-index (1+ loop-index))) (while (if (<= loop-index 0) (progn (setq failing t) nil) (or (not (string-match search-string (aref background-history-vector loop-index))) (progn (setq failing nil) (setq search-index loop-index) nil))) (setq loop-index (1- loop-index))))) (setq scrolling t) (setq history-index search-index) (setq background-search-last-string search-string)) (exit-minibuffer)) ;; main function (defun background (command) "Run COMMAND as a background job like csh. When entering the command, ^P and ^N and ^R and ^S do interesting things with the command history. Some bang commands are interpreted. A message is displayed when the job starts and finishes, or otherwise changes state. The job's buffer is in shell mode, so you can send input and signals to the job. A prefix argument suggest a job number, which is useful to store output." (interactive (list (let ((prompt "% ") default-command command history-index) (while (progn (while (let (scrolling) (setq command (read-from-minibuffer prompt default-command background-minibuffer-map)) scrolling) (setq prompt (concat history-index "% ")) (setq default-command (aref background-history-vector history-index))) (string-match "^!" command)) (cond ((string-match "^!!" command) (setq history-index background-history-size)) ((string-match "^!-?[0-9]+" command) (let ((index (if (string-match "^!-" command) (- (1+ background-history-size) (string-to-int (substring command 2))) (string-to-int (substring command 1))))) (if (and (> index 0) (<= index background-history-size)) (setq history-index index) (progn (message "Bad history index") (sit-for 1))))) (t (let* ((index background-history-size) (flag (string-match "^!\\?" command)) (event (substring command (if flag 2 1))) (pattern (if flag event (concat "^" event)))) (while (if (<= index 0) (progn (message "%s: Event not found" event) (sit-for 1) nil) (or (not (string-match pattern (aref background-history-vector index))) (progn (setq history-index index) nil))) (setq index (1- index)))))) (and history-index (progn (setq prompt (concat history-index "% ")) (setq default-command (aref background-history-vector history-index))))) command))) (prog1 (let* ((job-number (if (numberp current-prefix-arg) current-prefix-arg 1)) (process (let (job-name) (while (get-process (setq job-name (concat "%" job-number))) (setq job-number (1+ job-number))) (setq default-directory (prog1 (if (not (string-match "^[\t ]*cd[\t ]+\\([^\t ;]+\\)[\t ]*;[\t ]*" command)) default-directory (prog1 (file-name-as-directory (expand-file-name (substring command (match-beginning 1) (match-end 1)))) (setq command (substring command (match-end 0))))) (if (equal current-prefix-arg '(4)) (pop-to-buffer job-name) (or (numberp current-prefix-arg) (with-output-to-temp-buffer job-name)) (set-buffer (get-buffer-create job-name))) (erase-buffer))) (start-process job-name (current-buffer) shell-file-name "-c" command)))) (insert "*** Start \"" command "\" in " default-directory " at " (substring (current-time-string) 11 19) ?\n) (set-marker (process-mark process) (point)) (set-process-sentinel process (function (lambda (process info) (message "[%s] %s %s" (substring (process-name process) 1) (setq info (cond ((string-equal info "finished\n") "Done") ((string-match "^exit" info) (concat "Exit " (process-exit-status process))) ((string-equal info "") "Continuing") (t (concat (upcase (substring info 0 1)) (substring info 1 -1))))) (nth 2 (process-command process))) (let ((buffer (process-buffer process))) (if (null (buffer-name buffer)) (set-process-buffer process nil) (and (memq (process-status process) '(signal exit)) (save-excursion (set-buffer buffer) (goto-char (point-max)) (insert "*** " info " at " (substring (current-time-string) 11 19) ?\n) (set-buffer-modified-p nil) (undo-boundary)))))))) (message "[%d] %d" job-number (process-id process)) process) (shell-mode) (setq mode-name "Background") (or (let ((previous-command (aref background-history-vector background-history-size))) (and previous-command (string-equal command previous-command))) (progn (let ((capacity (length background-history-vector))) (and (>= (setq background-history-size (1+ background-history-size)) capacity) (progn (let* ((history-index 0) (new-vector (make-vector (* 2 capacity) nil))) (while (progn (aset new-vector history-index (aref background-history-vector history-index)) (< (setq history-index (1+ history-index)) background-history-size))) (setq background-history-vector new-vector))))) (aset background-history-vector background-history-size command)))))