Xref: utzoo comp.emacs:5285 gnu.emacs:435 Path: utzoo!utgpu!jarvis.csri.toronto.edu!mailrus!bbn!rochester!pt.cs.cmu.edu!centro.soar.cs.cmu.edu!shivers From: shivers@centro.soar.cs.cmu.edu (Olin Shivers) Newsgroups: comp.emacs,gnu.emacs Subject: New process modes for shell, Lisp, Scheme, T, TeX (part 2 of 3) Message-ID: <4236@pt.cs.cmu.edu> Date: 9 Feb 89 21:30:10 GMT Organization: Carnegie-Mellon University, CS/RI Lines: 835 This message contains: 1. The standard anouncement header 2. The source for cmushell.el (The entire package must be split -- otherwise notesfiles sites will not be able to handle it.) ------------------------------------------------------------------------------- I have written new gnu-emacs packages that provide shell, inferior lisp, inferior scheme, and inferior T modes. These packages have the following advantages over the standard released gnu packages: - Input history is kept in all modes, traversed with M-p and M-n, just like on LispM's and various fancy shells. - Filename completion and query is available in all modes. - Keybindings are cross-compatible among all modes. - Keybindings are compatible with Zwei and Hemlock. - Directory tracking is more robust in shell mode, and is *only* active in shell mode. (Try entering "cd /" to an inferior lisp in the old package: Lisp barfs, but the inferior lisp mode goes ahead and changes the buffer's directory.) - Non-echoed text entry (for entering passwords) is available in all modes. - Shell and inferior Lisp modes are backwards compatible with the standard gnu modes. - One source for the inferior Lisp mode works in both emacs releases 18.4x and 18.5x. This has been the cause of confusing bugs for users who unwittingly tried to use an 18.4x version inferior Lisp mode in an 18.5x version emacs, and vice-versa. - A full set of eval/compile-region/defun commands for the inferior Lisp, Scheme, and T modes. - New modes are easily created for new types of processes. =============================================================================== THE BAD NEWS: It would be nice if the shell & inferior lisp package, cmushell.el, was completely plug-compatible with the old package in shell.el -- if you could just name the new version shell.el, and have it transparently replace the old one. But you can't. Several other packages (tex-mode, background, dbx, gdb, kermit, monkey, prolog, telnet) are also clients of shell mode. These packages assume detailed knowledge of shell mode internals in ways that are incompatible with the new mode (mostly because of the new mode's greater functionality). So, unless we are willing to port all of these packages, we can't have the new shell package be a complete replacement for shell.el -- that is, we can't name the file shell.el, and its main entry point (shell), because dbx.el will break when it loads it in and tries to use it. There are two ways to fix this. One: rewrite these other modes to use the new package. This is a win, but can't be assumed. The other, backwards compatible route, is to make this package non-conflict with shell.el, so both files can be loaded in at the same time. This is what I have done. So the mode names and major functions have different names, e.g: shell.el cmushell.el -------- ---------- M-x shell M-x cmushell -- Fire up a shell M-x run-lisp M-x cmulisp -- Fire up a lisp shell-mode-map cmushell-mode-map -- Keybindings for [cmu]shell mode All the names have been carefully chosen so that shell.el and cmushell.el won't tromp on each other -- that way dbx.el and friends can happily load in shell.el without breaking the cmushell.el package, and vice versa. With the exception of M-x cmushell and M-x cmulisp, however, most of the name changes are invisible to the user. Further, most of the customising variables that are common in functionality have the same name: inferior-lisp-program, explicit-shell-file-name, et al. Hook variables are different, so you can customise shell-mode and cmushell-mode differently, for instance. By the way, it is rather easy to port the shell.el-dependent packages to use the new stuff. There are fairly complete comments in the relevant source files showing how to do this. Note that this backwards-compatibility hassle *only* affects shell and inferior lisp mode; the other process-in-a-buffer modes (Scheme, T, etc.) do not have this problem. =============================================================================== GENERALIA: The implementation strategy was to factor common process functionality into a general command interpreter mode -- comint mode -- and then to build all the specific modes on top. This provides uniform, integrated functionality and interface across all the derived modes. Comint mode provides the input history, filename completion and query, non-echoed text entry, input editing, and process signalling (e.g., ^z, ^c, ...) commands. *Any* mode built on comint mode gets all this stuff automatically. Additionally, comint mode has general hooks for customising it to specific command interpreters, such as Lisp, Scheme, csh, ML, etc.. This release includes the following files: - comint.el comint mode - cmushell.el cmushell and cmulisp modes, built on comint mode. - cmuscheme.el inferior Scheme mode, built on comint mode. - tea.el inferior T mode, built on comint mode. - tea2.el Variant of tea.el - cmutex.el tex-mode.el with rewritten process interaction code. Some bugs also fixed. These packages have been in daily use by a user community of about 10-20 at CMU since August; most bugs have been shaken out. cmutex.el is less tested. Please notify me of bugs. The files are *extensively* commented; this should serve as sufficient documentation. Each file includes suggestions for your .emacs file in comments at the top. On-line documentation (C-h C-m, C-h C-f, C-h C-v) is available for modes, commands, and variables. This source is available on an FSF-style basis: use it any way you like as long as you don't charge money for it or change the basis of its availability; I assume no liability for its use. =============================================================================== INPUT HISTORY: There are actually two different ways to retrieve old commands for resubmission to a process. The standard way is to use the input history mechanism. An internal list is kept in each process buffer of the last n inputs (default: 30). The commands M-p and M-n move through this list. This is similar in functionality to the input history mechanisms provided by the LispM, Hemlock, and various fancy shells (tcsh, cmucsh, ksh,...). There is also a command, bound to C-c r, which searches backwards through the input history looking for a substring match. RMS doesn't like this mechanism. He has suggested an input history mechanism that operates by searching backwards (and forwards) through the buffer for occurrences of the prompt. The user can then resubmit the input by hitting return. I do not like this mechanism. If the prompt changes dynamically, you can miss a command. False positives are also annoying. The screen jumps around a lot as you scroll through your history. If you run a subprogram that has a null prompt (like dc), prompt search will miss all its inputs. Etc. However, you may try either of these mechanisms, and go with the style you prefer. The RMS-style prompt-search stuff is available on M-N and M-P (meta-shift-n and meta-shift-P); C-c R is bound to a command that searches for specific commands (analogous to C-c r). If you use this stuff, you will probably want to sharpen up the regular expression used to match the prompt in each mode to match your particular prompt -- the default, general regexp used in shell mode generates too many annoying false positives. (It's local variable comint-prompt-regexp -- you should set it in a hook). =============================================================================== Source code follows. Please report bugs to me: shivers@cs.cmu.edu. -Olin ------ ;;; -*-Emacs-Lisp-*- General command interpreter in a window stuff ;;; Copyright Olin Shivers (1988). ;;; Please imagine a long, tedious, legalistic 5-page gnu-style copyright ;;; notice appearing here to the effect that you may use this code any ;;; way you like, as long as you don't charge money for it, remove this ;;; notice, or hold me liable for its results. ;;; This replaces the standard shell and inferior-lisp modes. ;;; Hacked from tea.el and shell.el by Olin Shivers (shivers@cs.cmu.edu). 8/88 ;;; Please send me bug reports, bug fixes, and extensions, so that I can ;;; merge them into the master source. ;;; This file defines two packages: ;;; - A shell-in-a-buffer package (cmushell mode) built on top of comint mode. ;;; - A lisp-in-a-buffer package (cmulisp mode) built on top of comint ;;; mode. ;;; ;;; Cmushell and cmulisp modes are similar to, and intended to replace, ;;; their counterparts in the standard gnu emacs release. These replacements ;;; are more featureful, robust, and uniform than the released versions. ;;; The key bindings in lisp mode are also more compatible with the bindings ;;; of Hemlock and Zwei (the Lisp Machine emacs). ;;; Since these modes are built on top of the general command-interpreter-in- ;;; a-buffer mode (comint mode), they share a common base functionality, ;;; and a common set of bindings, with all modes derived from comint mode. ;;; This makes them easier to use. ;;; For documentation on the functionality provided by comint mode, and ;;; the hooks available for customising it, see the file comint.el. ;;; For further information on the cmushell and cmulisp modes, see ;;; the comments below. ;;; Needs fixin: ;;; The load-file/compile-file default mechanism could be smarter -- it ;;; doesn't know about the relationship between filename extensions and ;;; whether the file is source or executable. If you compile foo.lisp ;;; with compile-file, then the next load-file should use foo.bin for ;;; the default, not foo.lisp. This is tricky to do right, particularly ;;; because the extension for executable files varies so much (.o, .bin, ;;; .lbin, .mo, .vo, .ao, ...). ;;; ;;; It would be nice if cmulisp (and inferior scheme, T, ...) modes ;;; had a verbose minor mode wherein sending or compiling defuns, etc. ;;; would be reflected in the transcript with suitable comments, e.g. ;;; ";;; redefining fact". Several ways to do this. Which is right? ;;; ;;; 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? (require 'comint) (provide 'cmushell) ;; YOUR .EMACS FILE ;;============================================================================= ;; Some suggestions for your .emacs file. ;; ;; ; If cmushell lives in some non-standard directory, you must tell emacs ;; ; where to get it. This may or may not be necessary. ;; (setq load-path (cons (expand-file-name "~jones/lib/emacs") load-path)) ;; ;; ; Autoload cmushell and cmulisp from file cmushell.el ;; (autoload 'cmushell "cmushell" ;; "Run an inferior shell process." ;; t) ;; (autoload 'cmulisp "cmushell" ;; "Run an inferior lisp process." ;; t) ;; ;; ; Define C-c C-t to run my favorite command in shell mode: ;; (setq cmushell-load-hook ;; '((lambda () ;; (define-key cmushell-mode-map "\C-c\C-t" 'favorite-cmd)))) ;;; Brief Command Documentation: ;;;============================================================================ ;;; Comint Mode Commands: (common to cmushell and cmulisp modes) ;;; ;;; m-p comint-previous-input Cycle backwards in input history ;;; m-n comint-next-input Cycle forwards ;;; c-c r comint-previous-input-matching Search backwards in input history ;;; return comint-send-input ;;; c-a comint-bol Beginning of line; skip prompt. ;;; c-d comint-delchar-or-maybe-eof Delete char unless at end of buff. ;;; c-c c-u comint-kill-input ^u ;;; c-c c-w backward-kill-word ^w ;;; c-c c-c comint-interrupt-subjob ^c ;;; c-c c-z comint-stop-subjob ^z ;;; c-c c-\ comint-quit-subjob ^\ ;;; c-c c-o comint-kill-output Delete last batch of process output ;;; c-c c-r comint-show-output Show last batch of process output ;;; send-invisible Read line w/o echo & send to proc ;;; comint-continue-subjob Useful if you accidentally suspend ;;; top-level job. ;;; comint-mode-hook is the comint mode hook. ;;; CMU Lisp Mode Commands: ;;; c-m-x lisp-send-defun This binding is a gnu convention. ;;; c-c l lisp-load-file Prompt for file name; tell Lisp to load it. ;;; c-c k lisp-compile-file Prompt for file name; tell Lisp to kompile it. ;;; Filename completion is available, of course. ;;; ;;; Additionally, these commands are added to the key bindings of Lisp mode: ;;; c-m-x lisp-eval-defun This binding is a gnu convention. ;;; c-c e lisp-eval-defun Send the current defun to Lisp process. ;;; c-c c-e lisp-eval-defun-and-go After sending the defun, switch-to-lisp. ;;; c-c r lisp-eval-region Send the current region to Lisp process. ;;; c-c c-r lisp-eval-region-and-go After sending the region, switch-to-lisp. ;;; c-c c lisp-compile-defun Compile the current defun in Lisp process. ;;; c-c c-c lisp-compile-defun-and-go After compiling defun, switch-to-lisp. ;;; c-c z switch-to-lisp Switch to the Lisp process buffer. ;;; c-c l lisp-load-file (See above. In a lisp file buffer, default ;;; c-c k lisp-compile-file is to load/compile the current file.) ;;; cmulisp Fires up the Lisp process. ;;; lisp-compile-region Compile all forms in the current region. ;;; lisp-compile-region-and-go After compiling region, switch-to-lisp. ;;; ;;; CMU Lisp Mode Variables: ;;; cmulisp-filter-regexp Match this => don't get saved on input hist ;;; inferior-lisp-program Name of Lisp program run-lisp executes ;;; inferior-lisp-load-command Customises lisp-load-file ;;; cmulisp-mode-hook ;;; inferior-lisp-prompt Initialises comint-prompt-regexp. ;;; Backwards compatibility. ;;; lisp-source-modes Anything loaded into a buffer that's in ;;; one of these modes is considered lisp ;;; source by lisp-load/compile-file. ;;; Shell Mode Commands: ;;; cmushell Fires up the shell process. ;;; m-tab comint-dynamic-complete Complete a partial file name ;;; m-? comint-dynamic-list-completions List completions in help buffer ;;; ;;; The cmushell mode hook is cmushell-mode-hook ;;; The cmushell-load-hook is run after this file is loaded. ;;; comint-prompt-regexp is initialised to shell-mode-pattern, for backwards ;;; compatibility. ;;; Read the rest of this file for more information. ;;; SHELL.EL COMPATIBILITY ;;;============================================================================ ;;; In brief: this package should have no trouble coexisting with shell.el. ;;; ;;; Most customising variables -- e.g., inferior-lisp-program, ;;; explicit-shell-file-name -- are the same, so the users shouldn't ;;; have much trouble. Hooks have different names, however, so you can ;;; customise shell mode differently from cmushell mode. You basically just ;;; have to remember to type M-x cmushell instead of M-x shell. ;;; ;;; It would be nice if this file was completely plug-compatible with the old ;;; shell package -- if you could just name this file shell.el, and have it ;;; transparently replace the old one. But you can't. Several other packages ;;; (tex-mode, background, dbx, gdb, kermit, monkey, prolog, telnet) are also ;;; clients of shell mode. These packages assume detailed knowledge of shell ;;; mode internals in ways that are incompatible with cmushell mode (mostly ;;; because of cmushell mode's greater functionality). So, unless we are ;;; willing to port all of these packages, we can't have this file be a ;;; complete replacement for shell.el -- that is, we can't name this file ;;; shell.el, and its main entry point (shell), because dbx.el will break ;;; when it loads it in and tries to use it. ;;; ;;; There are two ways to fix this. One: rewrite these other modes to use the ;;; new package. This is a win, but can't be assumed. The other, backwards ;;; compatible route, is to make this package non-conflict with shell.el, so ;;; both files can be loaded in at the same time. And *that* is why some ;;; functions and variables have different names: (cmushell), ;;; cmushell-mode-map, that sort of thing. All the names have been carefully ;;; chosen so that shell.el and cmushell.el won't tromp on each other. ;;; Lisp stuff ;;;============================================================================ ;;; (defvar cmulisp-filter-regexp "\\`\\s *\\(:\\(\\w\\|\\s_\\)\\)?\\s *\\'" "*What not to save on inferior Lisp's input history Input matching this regexp is not saved on the input history in cmulisp mode. Default is whitespace followed by 0 or 1 single-letter :keyword (as in :a, :c, etc.)") (defvar cmulisp-mode-map nil) (cond ((not cmulisp-mode-map) (setq cmulisp-mode-map (full-copy-sparse-keymap comint-mode-map)) (lisp-mode-commands cmulisp-mode-map) (define-key cmulisp-mode-map "\C-cl" 'lisp-load-file) (define-key cmulisp-mode-map "\C-ck" 'lisp-compile-file))) ;;; These commands augment lisp mode, so you can process lisp code in ;;; the source files. (define-key lisp-mode-map "\M-\C-x" 'lisp-eval-defun) ; Gnu convention (define-key lisp-mode-map "\C-ce" 'lisp-eval-defun) (define-key lisp-mode-map "\C-c\C-e" 'lisp-eval-defun-and-go) (define-key lisp-mode-map "\C-cr" 'lisp-eval-region) (define-key lisp-mode-map "\C-c\C-r" 'lisp-eval-region-and-go) (define-key lisp-mode-map "\C-cc" 'lisp-compile-defun) (define-key lisp-mode-map "\C-c\C-c" 'lisp-compile-defun-and-go) (define-key lisp-mode-map "\C-cz" 'switch-to-lisp) (define-key lisp-mode-map "\C-cl" 'lisp-load-file) (define-key lisp-mode-map "\C-ck" 'lisp-compile-file) ; "kompile" file (defvar inferior-lisp-program "lisp" "*Program name for invoking an inferior Lisp with `cmulisp'.") (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 recognise prompts in the inferior Lisp. Defaults to \"^[^> ]*>+:? *\", which works pretty good for Lucid, kcl, and franz. This variable is used to initialise comint-prompt-regexp in the cmulisp buffer. More precise choices: Lucid Common Lisp: \"^\\(>\\|\\(->\\)+\\) *\" franz: \"^\\(->\\|<[0-9]*>:\\) *\" kcl: \"^>+ *\" This is a fine thing to set in your .emacs file.") (defvar cmulisp-mode-hook '() "*Hook for customising cmulisp mode") (defun cmulisp-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, cmulisp-filter-regexp and inferior-lisp-load-command can customize this mode for different Lisp interpreters. \\{cmulisp-mode-map} Customisation: Entry to this mode runs the hooks on comint-mode-hook and cmulisp-mode-hook (in that order). You can send text to the inferior Lisp process from other buffers containing Lisp source. switch-to-lisp switches the current buffer to the Lisp process buffer. lisp-eval-defun sends the current defun to the Lisp process. lisp-compile-defun compiles the current defun. lisp-eval-region sends the current region to the Lisp process. lisp-compile-region compiles the current region. lisp-eval-defun-and-go, lisp-compile-defun-and-go, lisp-eval-region-and-go, and lisp-compile-region-and-go switch to the Lisp process buffer after sending their text. Commands: Return after the end of the process' output sends the text from the end of process to point. Return before the end of the process' output copies the sexp ending at point to the end of the process' output, and sends it. Delete converts tabs to spaces as it moves back. Tab indents for Lisp; with argument, shifts rest of expression rigidly with the current line. C-M-q does Tab on each line starting within following expression. Paragraphs are separated only by blank lines. Semicolons start comments. If you accidentally suspend your process, use \\[comint-continue-subjob] to continue it." (interactive) (comint-mode) (setq comint-prompt-regexp inferior-lisp-prompt) (setq major-mode 'cmulisp-mode) (setq mode-name "CMU Lisp") (setq mode-line-process '(": %s")) (if (string-match "^18.4" emacs-version) ; hack. (lisp-mode-variables) ; This is right for 18.49 (lisp-mode-variables t)) ; This is right for 18.50 (use-local-map cmulisp-mode-map) ;c-c k for "kompile" file (setq comint-get-old-input (function lisp-get-old-input)) (setq comint-input-filter (function lisp-input-filter)) (setq comint-input-sentinel 'ignore) (run-hooks 'cmulisp-mode-hook)) (defun lisp-get-old-input () "Snarf the sexp ending at point" (save-excursion (let ((end (point))) (backward-sexp) (buffer-substring (point) end)))) (defun lisp-input-filter (str) "Don't save anything matching cmulisp-filter-regexp" (not (string-match cmulisp-filter-regexp str))) (defun cmulisp () "Run an inferior Lisp process, input and output via buffer *cmulisp*. If there is a process already running in *cmulisp*, just switch to that buffer. Takes the program name from the variable inferior-lisp-program. \(Type \\[describe-mode] in the process buffer for a list of commands.)" (interactive) (cond ((not (comint-check-proc "*cmulisp*")) (set-buffer (make-comint "cmulisp" inferior-lisp-program)) (cmulisp-mode))) (switch-to-buffer "*cmulisp*")) (defun lisp-eval-region (start end) "Send the current region to the inferior Lisp process." (interactive "r") (send-region "cmulisp" start end) (send-string "cmulisp" "\n")) (defun lisp-eval-defun () "Send the current defun to the inferior Lisp process." (interactive) (save-excursion (end-of-defun) (let ((end (point))) (beginning-of-defun) (lisp-eval-region (point) end)))) ;;; CommonLisp COMPILE sux. (defun lisp-compile-region (start end) "Compile the current region in the inferior Lisp process." (interactive "r") (save-excursion (process-send-string "cmulisp" (format "(funcall (compile nil `(lambda () (progn 'compile %s))))\n" (buffer-substring start end))))) (defun lisp-compile-defun () "Compile the current defun in the inferior Lisp process." (interactive) (save-excursion (end-of-defun) (let ((e (point))) (beginning-of-defun) (lisp-compile-region (point) e)))) (defun switch-to-lisp (eob-p) "Switch to the inferior Lisp process buffer. With argument, positions cursor at end of buffer." (interactive "P") (pop-to-buffer "*cmulisp*") (cond (eob-p (push-mark) (goto-char (point-max))))) (defun lisp-eval-region-and-go (start end) "Send the current region to the inferior Lisp, and switch to the process buffer." (interactive "r") (lisp-eval-region start end) (switch-to-lisp t)) (defun lisp-eval-defun-and-go () "Send the current defun to the inferior Lisp, and switch to the process buffer." (interactive) (lisp-eval-defun) (switch-to-lisp t)) (defun lisp-compile-region-and-go (start end) "Compile the current region in the inferior Lisp, and switch to the process buffer." (interactive "r") (lisp-compile-region start end) (switch-to-lisp t)) (defun lisp-compile-defun-and-go () "Compile the current defun in the inferior Lisp, and switch to the process buffer." (interactive) (lisp-compile-defun) (switch-to-lisp t)) ;;; A version of the form in H. Shevis' soar-mode.el package. Less robust. ;(defun lisp-compile-sexp (start end) ; "Compile the s-expression bounded by START and END in the inferior lisp. ;If the sexp isn't a DEFUN form, it is evaluated instead." ; (cond ((looking-at "(defun\\s +") ; (goto-char (match-end 0)) ; (let ((name-start (point))) ; (forward-sexp 1) ; (process-send-string "cmulisp" (format "(compile '%s #'(lambda " ; (buffer-substring name-start ; (point))))) ; (let ((body-start (point))) ; (goto-char start) (forward-sexp 1) ; Can't use end-of-defun. ; (process-send-region "cmulisp" (buffer-substring body-start (point)))) ; (process-send-string "cmulisp" ")\n")) ; (t (lisp-eval-region start end))))) ; ;(defun lisp-compile-region (start end) ; "Each s-expression in the current region is compiled (if a DEFUN) ;or evaluated (if not) in the inferior lisp." ; (interactive "r") ; (save-excursion ; (goto-char start) (end-of-defun) (beginning-of-defun) ; error check ; (if (< (point) start) (error "region begins in middle of defun")) ; (goto-char start) ; (let ((s start)) ; (end-of-defun) ; (while (<= (point) end) ; Zip through ; (lisp-compile-sexp s (point)) ; compiling up defun-sized chunks. ; (setq s (point)) ; (end-of-defun)) ; (if (< s end) (lisp-compile-sexp s end))))) ;;; ;;; End of HS-style code (defvar lisp-prev-l/c-dir/file nil "Saves the (directory . file) pair used in the last lisp-load-file or lisp-compile-file command. Used for determining the default in the next one.") (defvar lisp-source-modes '(lisp-mode) "*Used to determine if a buffer contains Lisp source code. If it's loaded into a buffer that is in one of these major modes, it's considered a lisp source file by lisp-load-file and lisp-compile-file. Used by these commands to determine defaults.") (defun lisp-load-file (file-name) "Load a lisp file into the inferior Lisp process." (interactive (comint-get-source "Load Lisp file: " lisp-prev-l/c-dir/file lisp-source-modes nil)) ; NIL because LOAD ; doesn't need an exact name (comint-check-source file-name) ; Check to see if buffer needs saved. (setq lisp-prev-l/c-dir/file (cons (file-name-directory file-name) (file-name-nondirectory file-name))) (send-string "cmulisp" (format inferior-lisp-load-command file-name)) (switch-to-lisp t)) (defun lisp-compile-file (file-name) "Compile a Lisp file in the inferior Lisp process." (interactive (comint-get-source "Compile Lisp file: " lisp-prev-l/c-dir/file lisp-source-modes nil)) ; NIL = don't need ; suffix .lisp (comint-check-source file-name) ; Check to see if buffer needs saved. (setq lisp-prev-l/c-dir/file (cons (file-name-directory file-name) (file-name-nondirectory file-name))) (send-string "cmulisp" (concat "(compile-file \"" file-name "\"\)\n")) (switch-to-lisp t)) ;;; Shell stuff ;;;============================================================================ ;;; ;In loaddefs.el now. ;(defconst shell-prompt-pattern ; "^[^#$%>]*[#$%>] *" ; "*Regexp used by Newline command to match subshell prompts. ;;; Change the doc string for shell-prompt-pattern: (put 'shell-prompt-pattern 'variable-documentation "Regexp to match prompts in the inferior shell. Defaults to \"^[^#$%>]*[#$%>] *\", which works pretty well. This variable is used to initialise comint-prompt-regexp in the shell buffer. This is a fine thing to set in your .emacs file.") (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.") (defvar shell-pushd-regexp "pushd" "*Regexp to match subshell commands equivalent to pushd.") (defvar shell-cd-regexp "cd" "*Regexp to match subshell commands equivalent to cd.") (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 cmushell, if the shell is csh. Value is a list of strings, which may be nil.") (defvar cmushell-mode-map '()) (cond ((not cmushell-mode-map) (setq cmushell-mode-map (full-copy-sparse-keymap comint-mode-map)) (define-key cmushell-mode-map "\M-\t" 'comint-dynamic-complete) (define-key cmushell-mode-map "\M-?" 'comint-dynamic-list-completions))) (defvar cmushell-mode-hook '() "*Hook for customising cmushell mode") (defun cmushell-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 point. Return before end of process output copies rest of line to end (skipping the prompt) and sends it. send-invisible reads a line of text without echoing it, and sends it to the shell. \\{cmushell-mode-map} Customisation: Entry to this mode runs the hooks on comint-mode-hook and cmushell-mode-hook (in that order). 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. If you accidentally suspend your process, use \\[comint-continue-subjob] to continue it." (interactive) (comint-mode) (setq comint-prompt-regexp shell-prompt-pattern) (setq major-mode 'cmushell-mode) (setq mode-name "CMU shell") (use-local-map cmushell-mode-map) (make-local-variable 'shell-directory-stack) (setq shell-directory-stack nil) (setq comint-input-sentinel 'shell-directory-tracker) (run-hooks 'cmushell-mode-hook)) (defun cmushell () "Run an inferior shell, with I/O through buffer *cmushell*. If buffer exists but shell process is not running, make new shell. If buffer exists and shell process is running, just switch to buffer *cmushell*. 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 cmushell-mode, giving commands for sending input and controlling the subjobs of the shell. See cmushell-mode. See also variable shell-prompt-pattern. 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.)" (interactive) (cond ((not (comint-check-proc "*cmushell*")) (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 "cmushell" prog (if (file-exists-p startfile) startfile) (if (and xargs-name (boundp xargs-name)) (symbol-value xargs-name) '("-i")))) (cmushell-mode)))) (switch-to-buffer "*cmushell*")) ;;; Directory tracking ;;; =========================================================================== ;;; This code provides the cmushell 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 released version in shell.el. It has the following failings: ;;; 1. It doesn't know about the cdpath shell variable. ;;; 2. It doesn't implement the pushd and popd variants that take numerical ;;; arguments. E.g., "popd 3" means pop 3 directories off the directory ;;; stack. ;;; 3. It only spots the first command in a command sequence. E.g., it will ;;; miss the cd in "ls; cd foo" ;;; 4. 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. The right way would be to hack the shell so ;;; that when it changes its pwd, for any reason, it notifies emacs via some ;;; ipc channel or something. However, that requires access to shell sources, ;;; isn't so portable, etc., etc.. The solution implemented here works ;;; amazingly well in most circumstances, and, being essentially half-assed, ;;; is more in keeping with the spirit of Unix. Blech. ;;; Please let marick@gswd-vms.arpa know of any changes you ;;; make. OK. I OUGHTA DO IT. (defun shell-front-match (regexp str start) "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." (eq start (string-match regexp str start))) (defun shell-directory-tracker (str) (cond ((and (shell-front-match shell-popd-regexp str 0) ;; Optional whitespace followed by either ";" or end-of-string (shell-front-match "\\s *\\(\;\\|$\\)" str (match-end 0))) ;; popd (cond (shell-directory-stack (cd (car shell-directory-stack)) (setq shell-directory-stack (cdr shell-directory-stack))))) ;; pushd ((shell-front-match shell-pushd-regexp str 0) (shell-process-cd-or-pushd str nil)) ;; cd ((shell-front-match shell-cd-regexp str 0) (shell-process-cd-or-pushd str t)))) ;;; This function parses and executes cd or pushd commands. ;;; SHELL-DIRECTORY-TRACKER calls this function after it matches the ;;; initial cd or pushd command on the input string. STR is ;;; the entire input string; we assume that (MATCH-END 0) indexes ;;; the string immediately after the end of the command. CD-P ;;; is true if the the command matched was a cd; false if the ;;; command was a pushd. (defun shell-process-cd-or-pushd (str cd-p) ;; make sure the cd or pushd is followed by whitespace or a ";" (cond ((or (= (match-end 0) (length str)) (memq (aref str (match-end 0)) '(?\ ?\t ?\;))) (string-match "\\s *" str (match-end 0)) ; skip whitespace (let ((i (match-end 0))) ; i points at the directory or ";" (if (or (= i (length str)) (= '?\; (aref str i))) ;; The command was terminated by end-of-string or ";" ;; so there was no argument. If cd, go home. If pushd, ;; rotate the top of the directory stack. (if cd-p (cd (getenv "HOME")) (if shell-directory-stack (let ((old default-directory)) (cd (car shell-directory-stack)) (setq shell-directory-stack (cons old (cdr shell-directory-stack)))))) ;; pushd or cd ;; The command came with an argument. If pushd, push ;; the current directory (DEFAULT-DIRECTORY) on the stack. ;; In either case, cd to the argument. (let ((dir (progn (string-match "[^ \t\;]*" str i) ; match dir (expand-file-name (substitute-in-file-name (substring str i (match-end 0))))))) (cond ((file-directory-p dir) (if (not cd-p) ; push the current directory (setq shell-directory-stack (cons default-directory shell-directory-stack))) (cd dir))))))))) ;;; Interfacing to client packages (and converting them) ;;;============================================================================ ;;; Several gnu packages (tex-mode, background, dbx, gdb, kermit, prolog, ;;; telnet are some) use the shell package as clients. Most of them would ;;; be better off using the comint package directly, but they predate it. ;;; The catch is that most of these packages (dbx, gdb, prolog, telnet) ;;; assume total knowledge of all the local variables that shell mode ;;; functions depend on. So they (kill-all-local-variables), then create ;;; the few local variables that shell.el functions depend on. Alas, ;;; cmushell.el functions depend on a different set of vars (for example, ;;; the input history ring is a local variable in cmushell.el's shell mode, ;;; whereas there is no input history ring in shell.el's shell mode). ;;; So we have a situation where the greater functionality of cmushell.el ;;; is biting us -- you can't just replace shell will cmushell. ;;; ;;; Altering these packages to use comint mode directly should *greatly* ;;; improve their functionality, and is actually pretty easy. It's ;;; mostly a matter of renaming a few variable names. See comint.el for more. ;;; -Olin ;;; Do the user's customisation... ;;;=============================== (defvar cmushell-load-hook nil "This hook is run when cmushell is loaded in. This is a good place to put keybindings.") (run-hooks 'cmushell-load-hook) --