[comp.emacs] Problem in GNUemacs shell-mode

warsaw@cme.nist.gov (Barry A. Warsaw) (09/07/89)

[This didn't go out the first time, lets try again]

There seems to be a problem with shell-mode correctly handling the csh
builtin commands "cd", "popd", and "pushd", especially when using an
argument.  The following commands seem not to work:

% popd +2
% pushd +1
% pushd      ^M

In the last example, insert a bunch of spaces before hitting carriage
return (^M).  The function that handles capturing and tracking
directory commands is `shell-set-directory' in lisp/shell.el.  I've
looked at this file and I can see why it doesn't accurately track
these commands.  There may be other forms which are also not correctly
tracked.  I'm using

	GNU Emacs 18.55.13 of Sat Sep  2 1989 on norman (berkeley-unix)

Before I go ahead and hack a better shell-set-directory, I'm wondering
if anybody already has.  I seem to remember something being posted
quite a while back, but I can't dig it up.  Please email or post if
you have something that works.  Thanks.

-Barry

NAME: Barry A. Warsaw                USMAIL: National Institute of Standards
TELE: (301) 975-3460                         and Technology (formerly NBS)
UUCP: {...}!uunet!cme-durer!warsaw           Rm. B-124, Bldg. 220
ARPA: warsaw@cme.nist.gov                    Gaithersburg, MD 20899

ccs015@castor.ucdavis.edu (Kambiz Aghaiepour) (09/10/89)

In article <WARSAW.89Sep7114843@rtg.cme.nist.gov> warsaw@cme.nist.gov (Barry A. Warsaw) writes:
>
>[This didn't go out the first time, lets try again]
>
>There seems to be a problem with shell-mode correctly handling the csh
>builtin commands "cd", "popd", and "pushd", especially when using an
>argument.  The following commands seem not to work:
>
>% popd +2
>% pushd +1
>% pushd      ^M

Try using cmushell. 


;;; -*-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 <directory> or cd <directory>
	       ;; 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)


=============================================================================
Kambiz Aghaiepour @ UC Davis, Computing Services -*- Davis, CA (916) 752-3994
Internet: ccs015@bullwinkle.ucdavis.edu UUCP: ...!bullwinkle!ccs015.  -=*%*=-