summaryrefslogtreecommitdiff
path: root/lisp/shell.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/shell.el')
-rw-r--r--lisp/shell.el726
1 files changed, 392 insertions, 334 deletions
diff --git a/lisp/shell.el b/lisp/shell.el
index 64c069bf05d..9198a11c535 100644
--- a/lisp/shell.el
+++ b/lisp/shell.el
@@ -1,6 +1,6 @@
-;; -*-Emacs-Lisp-*- run a shell in an Emacs window
-;; Copyright (C) 1985, 1986, 1987, 1990 Free Software Foundation, Inc.
-
+;; Run subshell under Emacs
+;; Copyright (C) 1985, 1986, 1987, 1988 Free Software Foundation, Inc.
+
;; This file is part of GNU Emacs.
;; GNU Emacs is free software; you can redistribute it and/or modify
@@ -17,23 +17,18 @@
;; along with GNU Emacs; see the file COPYING. If not, write to
;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
-;;; Hacked from tea.el and shell.el by Olin Shivers (shivers@cs.cmu.edu). 8/88
-;;; Since this mode is built on top of the general command-interpreter-in-
-;;; a-buffer mode (comint mode), it shares a common base functionality,
-;;; and a common set of bindings, with all modes derived from comint mode.
+(provide 'shell)
-;;; For documentation on the functionality provided by comint mode, and
-;;; the hooks available for customising it, see the file comint.el.
+(defvar last-input-start nil
+ "In a shell-mode buffer, marker for start of last unit of input.")
+(defvar last-input-end nil
+ "In a shell-mode buffer, marker for end of last unit of input.")
-;;; Needs fixin:
-;;; When sending text from a source file to a subprocess, the process-mark can
-;;; move off the window, so you can lose sight of the process interactions.
-;;; Maybe I should ensure the process mark is in the window when I send
-;;; text to the process? Switch selectable?
+(defvar shell-mode-map nil)
-(require 'comint)
-(provide 'shell)
+(defvar shell-directory-stack nil
+ "List of directories saved by pushd in this buffer's shell.")
(defvar shell-popd-regexp "popd"
"*Regexp to match subshell commands equivalent to popd.")
@@ -47,346 +42,409 @@
(defvar explicit-shell-file-name nil
"*If non-nil, is file name to use for explicitly requested inferior shell.")
-(defvar explicit-csh-args
- (if (eq system-type 'hpux)
- ;; -T persuades HP's csh not to think it is smarter
- ;; than us about what terminal modes to use.
- '("-i" "-T")
- '("-i"))
- "*Args passed to inferior shell by M-x shell, if the shell is csh.
-Value is a list of strings, which may be nil.")
-
-(defvar shell-dirstack nil
- "List of directories saved by pushd in this buffer's shell.")
-
-(defvar shell-dirstack-query "dirs"
- "Command used by shell-resync-dirlist to query shell.")
-
-(defvar shell-mode-map ())
-(cond ((not shell-mode-map)
- (setq shell-mode-map (copy-keymap comint-mode-map))
- (define-key shell-mode-map "\t" 'comint-dynamic-complete)
- (define-key shell-mode-map "\M-?" 'comint-dynamic-list-completions)))
-
-(defvar shell-mode-hook '()
- "*Hook for customising shell mode")
-
-
-;;; Basic Procedures
-;;; ===========================================================================
-;;;
+;In loaddefs.el now.
+;(defconst shell-prompt-pattern
+; "^[^#$%>]*[#$%>] *"
+; "*Regexp used by Newline command to match subshell prompts.
+;Anything from beginning of line up to the end of what this pattern matches
+;is deemed to be prompt, and is not reexecuted.")
(defun shell-mode ()
"Major mode for interacting with an inferior shell.
-Return after the end of the process' output sends the text from the
- end of process to the end of the current line.
-Return before end of process output copies rest of line to end (skipping
- the prompt) and sends it.
-M-x send-invisible reads a line of text without echoing it, and sends it to
- the shell.
-
-If you accidentally suspend your process, use \\[comint-continue-subjob]
-to continue it.
-
-cd, pushd and popd commands given to the shell are watched by Emacs to keep
-this buffer's default directory the same as the shell's working directory.
-M-x dirs queries the shell and resyncs Emacs' idea of what the current
- directory stack is.
-M-x dirtrack-toggle turns directory tracking on and off.
+Shell name is same as buffer name, sans the asterisks.
+Return at end of buffer sends line as input.
+Return not at end copies rest of line to end and sends it.
+The following commands imitate the usual Unix interrupt and
+editing control characters:
\\{shell-mode-map}
-Customisation: Entry to this mode runs the hooks on comint-mode-hook and
-shell-mode-hook (in that order).
-Variables shell-cd-regexp, shell-pushd-regexp and shell-popd-regexp are used
-to match their respective commands."
+Entry to this mode calls the value of shell-mode-hook with no args,
+if that value is non-nil.
+
+cd, pushd and popd commands given to the shell are watched
+by Emacs to keep this buffer's default directory
+the same as the shell's working directory.
+Variables shell-cd-regexp, shell-pushd-regexp and shell-popd-regexp
+are used to match these command names.
+
+You can send text to the shell (or its subjobs) from other buffers
+using the commands process-send-region, process-send-string
+and lisp-send-defun."
(interactive)
- (comint-mode)
- (setq major-mode 'shell-mode
- mode-name "Shell"
- comint-prompt-regexp shell-prompt-pattern
- comint-input-sentinel 'shell-directory-tracker)
+ (kill-all-local-variables)
+ (setq major-mode 'shell-mode)
+ (setq mode-name "Shell")
+ (setq mode-line-process '(": %s"))
(use-local-map shell-mode-map)
- (make-local-variable 'shell-dirstack)
- (set (make-local-variable 'shell-dirtrackp) t)
+ (make-local-variable 'shell-directory-stack)
+ (setq shell-directory-stack nil)
+ (make-local-variable 'last-input-start)
+ (setq last-input-start (make-marker))
+ (make-local-variable 'last-input-end)
+ (setq last-input-end (make-marker))
(run-hooks 'shell-mode-hook))
+(if shell-mode-map
+ nil
+ (setq shell-mode-map (make-sparse-keymap))
+ (define-key shell-mode-map "\C-m" 'shell-send-input)
+ (define-key shell-mode-map "\C-c\C-d" 'shell-send-eof)
+ (define-key shell-mode-map "\C-c\C-u" 'kill-shell-input)
+ (define-key shell-mode-map "\C-c\C-w" 'backward-kill-word)
+ (define-key shell-mode-map "\C-c\C-c" 'interrupt-shell-subjob)
+ (define-key shell-mode-map "\C-c\C-z" 'stop-shell-subjob)
+ (define-key shell-mode-map "\C-c\C-\\" 'quit-shell-subjob)
+ (define-key shell-mode-map "\C-c\C-o" 'kill-output-from-shell)
+ (define-key shell-mode-map "\C-c\C-r" 'show-output-from-shell)
+ (define-key shell-mode-map "\C-c\C-y" 'copy-last-shell-input))
+(defvar explicit-csh-args
+ (if (eq system-type 'hpux)
+ ;; -T persuades HP's csh not to think it is smarter
+ ;; than us about what terminal modes to use.
+ '("-i" "-T")
+ '("-i"))
+ "Args passed to inferior shell by M-x shell, if the shell is csh.
+Value is a list of strings, which may be nil.")
+
(defun shell ()
"Run an inferior shell, with I/O through buffer *shell*.
If buffer exists but shell process is not running, make new shell.
-If buffer exists and shell process is running, just switch to buffer *shell*.
-
-The shell to use comes from the first non-nil variable found from these:
-explicit-shell-file-name in Emacs, ESHELL in the environment or SHELL in the
-environment. If none is found, /bin/sh is used.
-
-If a file ~/.emacs_SHELLNAME exists, it is given as initial input, simulating
-a start-up file for the shell like .profile or .cshrc. Note that this may
-lose due to a timing error if the shell discards input when it starts up.
-
+Program used comes from variable explicit-shell-file-name,
+ or (if that is nil) from the ESHELL environment variable,
+ or else from SHELL if there is no ESHELL.
+If a file ~/.emacs_SHELLNAME exists, it is given as initial input
+ (Note that this may lose due to a timing error if the shell
+ discards input when it starts up.)
The buffer is put in shell-mode, giving commands for sending input
-and controlling the subjobs of the shell.
+and controlling the subjobs of the shell. See shell-mode.
+See also variable shell-prompt-pattern.
-The shell file name, sans directories, is used to make a symbol name
+The shell file name (sans directories) is used to make a symbol name
such as `explicit-csh-arguments'. If that symbol is a variable,
its value is used as a list of arguments when invoking the shell.
Otherwise, one argument `-i' is passed to the shell.
-\(Type \\[describe-mode] in the shell buffer for a list of commands.)"
+Note that many people's .cshrc files unconditionally clear the prompt.
+If yours does, you will probably want to change it."
+ (interactive)
+ (let* ((prog (or explicit-shell-file-name
+ (getenv "ESHELL")
+ (getenv "SHELL")
+ "/bin/sh"))
+ (name (file-name-nondirectory prog)))
+ (switch-to-buffer
+ (apply 'make-shell "shell" prog
+ (if (file-exists-p (concat "~/.emacs_" name))
+ (concat "~/.emacs_" name))
+ (let ((symbol (intern-soft (concat "explicit-" name "-args"))))
+ (if (and symbol (boundp symbol))
+ (symbol-value symbol)
+ '("-i")))))))
+
+(defun make-shell (name program &optional startfile &rest switches)
+ (let ((buffer (get-buffer-create (concat "*" name "*")))
+ proc status size)
+ (setq proc (get-buffer-process buffer))
+ (if proc (setq status (process-status proc)))
+ (save-excursion
+ (set-buffer buffer)
+ ;; (setq size (buffer-size))
+ (if (memq status '(run stop))
+ nil
+ (if proc (delete-process proc))
+ (setq proc (apply 'start-process name buffer
+ (concat exec-directory "env")
+ (format "TERMCAP=emacs:co#%d:tc=unknown:"
+ (screen-width))
+ "TERM=emacs"
+ "EMACS=t"
+ "-"
+ (or program explicit-shell-file-name
+ (getenv "ESHELL")
+ (getenv "SHELL")
+ "/bin/sh")
+ switches))
+ (cond (startfile
+ ;;This is guaranteed to wait long enough
+ ;;but has bad results if the shell does not prompt at all
+ ;; (while (= size (buffer-size))
+ ;; (sleep-for 1))
+ ;;I hope 1 second is enough!
+ (sleep-for 1)
+ (goto-char (point-max))
+ (insert-file-contents startfile)
+ (setq startfile (buffer-substring (point) (point-max)))
+ (delete-region (point) (point-max))
+ (process-send-string proc startfile)))
+ (setq name (process-name proc)))
+ (goto-char (point-max))
+ (set-marker (process-mark proc) (point))
+ (or (eq major-mode 'shell-mode) (shell-mode)))
+ buffer))
+
+(defvar shell-set-directory-error-hook 'ignore
+ "Function called with no arguments when shell-send-input
+recognizes a change-directory command but gets an error
+trying to change Emacs's default directory.")
+
+(defun shell-send-input ()
+ "Send input to subshell.
+At end of buffer, sends all text after last output
+ as input to the subshell, including a newline inserted at the end.
+When not at end, copies current line to the end of the buffer and sends it,
+after first attempting to discard any prompt at the beginning of the line
+by matching the regexp that is the value of shell-prompt-pattern if possible.
+This regexp should start with \"^\"."
+ (interactive)
+ (or (get-buffer-process (current-buffer))
+ (error "Current buffer has no process"))
+ (end-of-line)
+ (if (eobp)
+ (progn
+ (move-marker last-input-start
+ (process-mark (get-buffer-process (current-buffer))))
+ (insert ?\n)
+ (move-marker last-input-end (point)))
+ (beginning-of-line)
+ ;; Exclude the shell prompt, if any.
+ (re-search-forward shell-prompt-pattern
+ (save-excursion (end-of-line) (point))
+ t)
+ (let ((copy (buffer-substring (point)
+ (progn (forward-line 1) (point)))))
+ (goto-char (point-max))
+ (move-marker last-input-start (point))
+ (insert copy)
+ (move-marker last-input-end (point))))
+ ;; Even if we get an error trying to hack the working directory,
+ ;; still send the input to the subshell.
+ (condition-case ()
+ (save-excursion
+ (goto-char last-input-start)
+ (shell-set-directory))
+ (error (funcall shell-set-directory-error-hook)))
+ (let ((process (get-buffer-process (current-buffer))))
+ (process-send-region process last-input-start last-input-end)
+ (set-marker (process-mark process) (point))))
+
+;;; If this code changes (shell-send-input and shell-set-directory),
+;;; the customization tutorial in
+;;; info/customizing-tutorial must also change, since it explains this
+;;; code. Please let marick@gswd-vms.arpa know of any changes you
+;;; make.
+
+(defun shell-set-directory ()
+ (cond ((and (looking-at shell-popd-regexp)
+ (memq (char-after (match-end 0)) '(?\; ?\n)))
+ (if shell-directory-stack
+ (progn
+ (cd (car shell-directory-stack))
+ (setq shell-directory-stack (cdr shell-directory-stack)))))
+ ((looking-at shell-pushd-regexp)
+ (cond ((memq (char-after (match-end 0)) '(?\; ?\n))
+ (if shell-directory-stack
+ (let ((old default-directory))
+ (cd (car shell-directory-stack))
+ (setq shell-directory-stack
+ (cons old (cdr shell-directory-stack))))))
+ ((memq (char-after (match-end 0)) '(?\ ?\t))
+ (let (dir)
+ (skip-chars-forward "^ ")
+ (skip-chars-forward " \t")
+ (if (file-directory-p
+ (setq dir
+ (expand-file-name
+ (substitute-in-file-name
+ (buffer-substring
+ (point)
+ (progn
+ (skip-chars-forward "^\n \t;")
+ (point)))))))
+ (progn
+ (setq shell-directory-stack
+ (cons default-directory shell-directory-stack))
+ (cd dir)))))))
+ ((looking-at shell-cd-regexp)
+ (cond ((memq (char-after (match-end 0)) '(?\; ?\n))
+ (cd (getenv "HOME")))
+ ((memq (char-after (match-end 0)) '(?\ ?\t))
+ (let (dir)
+ (forward-char 3)
+ (skip-chars-forward " \t")
+ (if (file-directory-p
+ (setq dir
+ (expand-file-name
+ (substitute-in-file-name
+ (buffer-substring
+ (point)
+ (progn
+ (skip-chars-forward "^\n \t;")
+ (point)))))))
+ (cd dir))))))))
+
+(defun shell-send-eof ()
+ "Send eof to subshell (or to the program running under it)."
+ (interactive)
+ (process-send-eof))
+
+(defun kill-output-from-shell ()
+ "Kill all output from shell since last input."
+ (interactive)
+ (goto-char (point-max))
+ (beginning-of-line)
+ (kill-region last-input-end (point))
+ (insert "*** output flushed ***\n")
+ (goto-char (point-max)))
+
+(defun show-output-from-shell ()
+ "Display start of this batch of shell output at top of window.
+Also put cursor there."
+ (interactive)
+ (set-window-start (selected-window) last-input-end)
+ (goto-char last-input-end))
+
+(defun copy-last-shell-input ()
+ "Copy previous shell input, sans newline, and insert before point."
(interactive)
- (cond ((not (comint-check-proc "*shell*"))
- (let* ((prog (or explicit-shell-file-name
- (getenv "ESHELL")
- (getenv "SHELL")
- "/bin/sh"))
- (name (file-name-nondirectory prog))
- (startfile (concat "~/.emacs_" name))
- (xargs-name (intern-soft (concat "explicit-" name "-args"))))
- (set-buffer (apply 'make-comint "shell" prog
- (if (file-exists-p startfile) startfile)
- (if (and xargs-name (boundp xargs-name))
- (symbol-value xargs-name)
- '("-i"))))
- (shell-mode))))
- (switch-to-buffer "*shell*"))
+ (insert (buffer-substring last-input-end last-input-start))
+ (delete-char -1))
+(defun interrupt-shell-subjob ()
+ "Interrupt this shell's current subjob."
+ (interactive)
+ (interrupt-process nil t))
+
+(defun kill-shell-subjob ()
+ "Send kill signal to this shell's current subjob."
+ (interactive)
+ (kill-process nil t))
+
+(defun quit-shell-subjob ()
+ "Send quit signal to this shell's current subjob."
+ (interactive)
+ (quit-process nil t))
+
+(defun stop-shell-subjob ()
+ "Stop this shell's current subjob."
+ (interactive)
+ (stop-process nil t))
+
+(defun kill-shell-input ()
+ "Kill all text since last stuff output by the shell or its subjobs."
+ (interactive)
+ (kill-region (process-mark (get-buffer-process (current-buffer)))
+ (point)))
-;;; Directory tracking
-;;; ===========================================================================
-;;; This code provides the shell mode input sentinel
-;;; SHELL-DIRECTORY-TRACKER
-;;; that tracks cd, pushd, and popd commands issued to the shell, and
-;;; changes the current directory of the shell buffer accordingly.
-;;;
-;;; This is basically a fragile hack, although it's more accurate than
-;;; the original version in shell.el. It has the following failings:
-;;; 1. It doesn't know about the cdpath shell variable.
-;;; 2. It only spots the first command in a command sequence. E.g., it will
-;;; miss the cd in "ls; cd foo"
-;;; 3. More generally, any complex command (like ";" sequencing) is going to
-;;; throw it. Otherwise, you'd have to build an entire shell interpreter in
-;;; emacs lisp. Failing that, there's no way to catch shell commands where
-;;; cd's are buried inside conditional expressions, aliases, and so forth.
-;;;
-;;; The whole approach is a crock. Shell aliases mess it up. File sourcing
-;;; messes it up. You run other processes under the shell; these each have
-;;; separate working directories, and some have commands for manipulating
-;;; their w.d.'s (e.g., the lcd command in ftp). Some of these programs have
-;;; commands that do *not* effect the current w.d. at all, but look like they
-;;; do (e.g., the cd command in ftp). In shells that allow you job
-;;; control, you can switch between jobs, all having different w.d.'s. So
-;;; simply saying %3 can shift your w.d..
-;;;
-;;; The solution is to relax, not stress out about it, and settle for
-;;; a hack that works pretty well in typical circumstances. Remember
-;;; that a half-assed solution is more in keeping with the spirit of Unix,
-;;; anyway. Blech.
-;;;
-;;; One good hack not implemented here for users of programmable shells
-;;; is to program up the shell w.d. manipulation commands to output
-;;; a coded command sequence to the tty. Something like
-;;; ESC | <cwd> |
-;;; where <cwd> is the new current working directory. Then trash the
-;;; directory tracking machinery currently used in this package, and
-;;; replace it with a process filter that watches for and strips out
-;;; these messages.
-
-;;; REGEXP is a regular expression. STR is a string. START is a fixnum.
-;;; Returns T if REGEXP matches STR where the match is anchored to start
-;;; at position START in STR. Sort of like LOOKING-AT for strings.
-(defun shell-front-match (regexp str start)
- (eq start (string-match regexp str start)))
-
-(defun shell-directory-tracker (str)
- "Tracks cd, pushd and popd commands issued to the shell.
-This function is called on each input passed to the shell.
-It watches for cd, pushd and popd commands and sets the buffer's
-default directory to track these commands.
-
-You may toggle this tracking on and off with M-x dirtrack-toggle.
-If emacs gets confused, you can resync with the shell with M-x dirs.
-
-See variables shell-cd-regexp, shell-pushd-regexp, and shell-popd-regexp.
-Environment variables are expanded, see function substitute-in-file-name."
- (condition-case err
- (cond (shell-dirtrackp
- (string-match "^\\s *" str) ; skip whitespace
- (let ((bos (match-end 0))
- (x nil))
- (cond ((setq x (shell-match-cmd-w/optional-arg shell-popd-regexp
- str bos))
- (shell-process-popd (substitute-in-file-name x)))
- ((setq x (shell-match-cmd-w/optional-arg shell-pushd-regexp
- str bos))
- (shell-process-pushd (substitute-in-file-name x)))
- ((setq x (shell-match-cmd-w/optional-arg shell-cd-regexp
- str bos))
- (shell-process-cd (substitute-in-file-name x)))))))
- (error (message (car (cdr err))))))
-
-
-;;; Try to match regexp CMD to string, anchored at position START.
-;;; CMD may be followed by a single argument. If a match, then return
-;;; the argument, if there is one, or the empty string if not. If
-;;; no match, return nil.
-
-(defun shell-match-cmd-w/optional-arg (cmd str start)
- (and (shell-front-match cmd str start)
- (let ((eoc (match-end 0))) ; end of command
- (cond ((shell-front-match "\\s *\\(\;\\|$\\)" str eoc)
- "") ; no arg
- ((shell-front-match "\\s +\\([^ \t\;]+\\)\\s *\\(\;\\|$\\)"
- str eoc)
- (substring str (match-beginning 1) (match-end 1))) ; arg
- (t nil))))) ; something else.
-;;; The first regexp is [optional whitespace, (";" or the end of string)].
-;;; The second regexp is [whitespace, (an arg), optional whitespace,
-;;; (";" or end of string)].
-
-
-;;; popd [+n]
-(defun shell-process-popd (arg)
- (let ((num (if (zerop (length arg)) 0 ; no arg means +0
- (shell-extract-num arg))))
- (if (and num (< num (length shell-dirstack)))
- (if (= num 0) ; condition-case because the CD could lose.
- (condition-case nil (progn (cd (car shell-dirstack))
- (setq shell-dirstack
- (cdr shell-dirstack))
- (shell-dirstack-message))
- (error (message "Couldn't cd.")))
- (let* ((ds (cons nil shell-dirstack))
- (cell (nthcdr (- num 1) ds)))
- (rplacd cell (cdr (cdr cell)))
- (setq shell-dirstack (cdr ds))
- (shell-dirstack-message)))
- (message "Bad popd."))))
-
-
-;;; cd [dir]
-(defun shell-process-cd (arg)
- (condition-case nil (progn (cd (if (zerop (length arg)) (getenv "HOME")
- arg))
- (shell-dirstack-message))
- (error (message "Couldn't cd."))))
-
-
-;;; pushd [+n | dir]
-(defun shell-process-pushd (arg)
- (if (zerop (length arg))
- ;; no arg -- swap pwd and car of shell stack
- (condition-case nil (if shell-dirstack
- (let ((old default-directory))
- (cd (car shell-dirstack))
- (setq shell-dirstack
- (cons old (cdr shell-dirstack)))
- (shell-dirstack-message))
- (message "Directory stack empty."))
- (message "Couldn't cd."))
-
- (let ((num (shell-extract-num arg)))
- (if num ; pushd +n
- (if (> num (length shell-dirstack))
- (message "Directory stack not that deep.")
- (let* ((ds (cons default-directory shell-dirstack))
- (dslen (length ds))
- (front (nthcdr num ds))
- (back (reverse (nthcdr (- dslen num) (reverse ds))))
- (new-ds (append front back)))
- (condition-case nil
- (progn (cd (car new-ds))
- (setq shell-dirstack (cdr new-ds))
- (shell-dirstack-message))
- (error (message "Couldn't cd.")))))
-
- ;; pushd <dir>
- (let ((old-wd default-directory))
- (condition-case nil
- (progn (cd arg)
- (setq shell-dirstack
- (cons old-wd shell-dirstack))
- (shell-dirstack-message))
- (error (message "Couldn't cd."))))))))
-
-;; If STR is of the form +n, for n>0, return n. Otherwise, nil.
-(defun shell-extract-num (str)
- (and (string-match "^\\+[1-9][0-9]*$" str)
- (string-to-int str)))
-
-
-(defun shell-dirtrack-toggle ()
- "Turn directory tracking on and off in a shell buffer."
+(defvar inferior-lisp-mode-map nil)
+(if inferior-lisp-mode-map
+ nil
+ (setq inferior-lisp-mode-map (copy-alist shell-mode-map))
+ (lisp-mode-commands inferior-lisp-mode-map)
+ (define-key inferior-lisp-mode-map "\e\C-x" 'lisp-send-defun))
+
+(defvar inferior-lisp-program "lisp"
+ "*Program name for invoking an inferior Lisp with `run-lisp'.")
+
+(defvar inferior-lisp-load-command "(load \"%s\")\n"
+ "*Format-string for building a Lisp expression to load a file.
+This format string should use %s to substitute a file name
+and should result in a Lisp expression that will command the inferior Lisp
+to load that file. The default works acceptably on most Lisps.
+The string \"(progn (load \\\"%s\\\" :verbose nil :print t) (values))\\\n\"
+produces cosmetically superior output for this application,
+but it works only in Common Lisp.")
+
+(defvar inferior-lisp-prompt "^.*>:? *$"
+ "*Regexp to recognize prompts from the inferior Lisp.
+Default is right for Franz Lisp and kcl.")
+
+(defun inferior-lisp-mode ()
+ "Major mode for interacting with an inferior Lisp process.
+Runs a Lisp interpreter as a subprocess of Emacs, with Lisp I/O
+through an Emacs buffer. Variable inferior-lisp-program controls
+which Lisp interpreter is run. Variables inferior-lisp-prompt
+and inferior-lisp-load-command can customize this mode for different
+Lisp interpreters.
+
+Commands:
+DELETE converts tabs to spaces as it moves back.
+TAB indents for Lisp; with argument, shifts rest
+ of expression rigidly with the current line.
+Meta-Control-Q does TAB on each line starting within following expression.
+Paragraphs are separated only by blank lines. Semicolons start comments.
+
+Return at end of buffer sends line as input.
+Return not at end copies rest of line to end and sends it.
+
+The following commands imitate the usual Unix interrupt and
+editing control characters:
+\\{shell-mode-map}
+
+Entry to this mode calls the value of lisp-mode-hook with no arguments,
+if that value is non-nil. Likewise with the value of shell-mode-hook.
+lisp-mode-hook is called after shell-mode-hook.
+
+You can send text to the inferior Lisp from other buffers
+using the commands process-send-region, process-send-string
+and \\[lisp-send-defun]."
+ (interactive)
+ (kill-all-local-variables)
+ (setq major-mode 'inferior-lisp-mode)
+ (setq mode-name "Inferior Lisp")
+ (setq mode-line-process '(": %s"))
+ (lisp-mode-variables t)
+ (use-local-map inferior-lisp-mode-map)
+ (make-local-variable 'last-input-start)
+ (setq last-input-start (make-marker))
+ (make-local-variable 'last-input-end)
+ (setq last-input-end (make-marker))
+ (run-hooks 'shell-mode-hook 'lisp-mode-hook))
+
+(defun run-lisp ()
+ "Run an inferior Lisp process, input and output via buffer *lisp*."
(interactive)
- (setq shell-dirtrackp (not shell-dirtrackp))
- (message "directory tracking %s."
- (if shell-dirtrackp "ON" "OFF")))
-
-;;; For your typing convenience:
-(fset 'dirtrack-toggle 'shell-dirtrack-toggle)
-
-
-(defun shell-resync-dirs ()
- "Resync the buffer's idea of the current directory stack.
-This command queries the shell with the command bound to
-shell-dirstack-query (default \"dirs\"), reads the next
-line output and parses it to form the new directory stack.
-DON'T issue this command unless the buffer is at a shell prompt.
-Also, note that if some other subprocess decides to do output
-immediately after the query, its output will be taken as the
-new directory stack -- you lose. If this happens, just do the
-command again."
+ (switch-to-buffer (make-shell "lisp" inferior-lisp-program))
+ (inferior-lisp-mode))
+
+(defun lisp-send-defun (display-flag)
+ "Send the current defun to the Lisp process made by M-x run-lisp.
+With argument, force redisplay and scrolling of the *lisp* buffer.
+Variable `inferior-lisp-load-command' controls formatting of
+the `load' form that is set to the Lisp process."
+ (interactive "P")
+ (or (get-process "lisp")
+ (error "No current lisp process"))
+ (save-excursion
+ (end-of-defun)
+ (let ((end (point))
+ (filename (format "/tmp/emlisp%d" (process-id (get-process "lisp")))))
+ (beginning-of-defun)
+ (write-region (point) end filename nil 'nomessage)
+ (process-send-string "lisp" (format inferior-lisp-load-command filename)))
+ (if display-flag
+ (let* ((process (get-process "lisp"))
+ (buffer (process-buffer process))
+ (w (or (get-buffer-window buffer) (display-buffer buffer)))
+ (height (window-height w))
+ (end))
+ (save-excursion
+ (set-buffer buffer)
+ (setq end (point-max))
+ (while (progn
+ (accept-process-output process)
+ (goto-char (point-max))
+ (beginning-of-line)
+ (or (= (point-max) end)
+ (not (looking-at inferior-lisp-prompt)))))
+ (setq end (point-max))
+ (vertical-motion (- 4 height))
+ (set-window-start w (point)))
+ (set-window-point w end)))))
+
+(defun lisp-send-defun-and-go ()
+ "Send the current defun to the inferior Lisp, and switch to *lisp* buffer."
(interactive)
- (let* ((proc (get-buffer-process (current-buffer)))
- (pmark (process-mark proc)))
- (goto-char pmark)
- (insert shell-dirstack-query) (insert "\n")
- (sit-for 0) ; force redisplay
- (comint-send-string proc shell-dirstack-query)
- (comint-send-string proc "\n")
- (set-marker pmark (point))
- (let ((pt (point))) ; wait for 1 line
- ;; This extra newline prevents the user's pending input from spoofing us.
- (insert "\n") (backward-char 1)
- (while (not (looking-at ".+\n"))
- (accept-process-output proc)
- (goto-char pt)))
- (goto-char pmark) (delete-char 1) ; remove the extra newline
- ;; That's the dirlist. grab it & parse it.
- (let* ((dl (buffer-substring (match-beginning 0) (- (match-end 0) 1)))
- (dl-len (length dl))
- (ds '()) ; new dir stack
- (i 0))
- (while (< i dl-len)
- ;; regexp = optional whitespace, (non-whitespace), optional whitespace
- (string-match "\\s *\\(\\S +\\)\\s *" dl i) ; pick off next dir
- (setq ds (cons (substring dl (match-beginning 1) (match-end 1))
- ds))
- (setq i (match-end 0)))
- (let ((ds (reverse ds)))
- (condition-case nil
- (progn (cd (car ds))
- (setq shell-dirstack (cdr ds))
- (shell-dirstack-message))
- (error (message "Couldn't cd.")))))))
-
-;;; For your typing convenience:
-(fset 'dirs 'shell-resync-dirs)
-
-
-;;; Show the current dirstack on the message line.
-;;; Pretty up dirs a bit by changing "/usr/jqr/foo" to "~/foo".
-;;; (This isn't necessary if the dirlisting is generated with a simple "dirs".)
-;;; All the commands that mung the buffer's dirstack finish by calling
-;;; this guy.
-(defun shell-dirstack-message ()
- (let ((msg "")
- (ds (cons default-directory shell-dirstack)))
- (while ds
- (let ((dir (car ds)))
- (if (string-match (format "^%s\\(/\\|$\\)" (getenv "HOME")) dir)
- (setq dir (concat "~/" (substring dir (match-end 0)))))
- (if (string-equal dir "~/") (setq dir "~"))
- (setq msg (concat msg dir " "))
- (setq ds (cdr ds))))
- (message msg)))
+ (lisp-send-defun nil)
+ (switch-to-buffer "*lisp*"))