[comp.emacs] cdpath in shell mode

david@ametek.COM (David Lim) (10/29/87)

In the Inferior Interactive shell, `cd', `pushd', and `popd' are watched by
Emacs so it can keep the `*shell*' buffer's default directory the same as
the shell's working directory. 

I would like to enhance the way shell-set-directory works to recognize
cdpath, and switches to popd and pushd (as in pushd +2).

Has anyone done any work in this area?

wolfgang@mgm.mit.edu.UUCP (10/30/87)

In article <8710291857.AA09540@ametek.COM> david@ametek.COM (David Lim) writes:
>I would like to enhance the way shell-set-directory works to recognize
>cdpath, and switches to popd and pushd (as in pushd +2).

The shell directory watching stuff is relatively easy to fool. Its 
basically impossible for emacs to really know where the shell is at
without doing much of the work of the shell. For example:

cd $foo

What is the poor shell directory watcher to do? You can have emacs
evaluate $foo with (getenv "foo"), but this is the wrong variable
"foo". It is the foo in emacs's own environment. Someone could have 
done a "foo=/usr/tmp" in the shell.

Watch all setting of variables, and record their current values?
It becomes involved when people do "eval `tset ... `" or ". foo".

Watching the shell is probably not worth the trouble. Why not just
have the emacs shell stuff emit a 'pwd' down the connection and 
note the result? This works well, if you note the prompt when the
shell starts up, and only emit the "pwd" when you see the prompt.

Wolfgang Rupprecht	UUCP: mirror!mit-mgm!wolfgang
			ARPA: wolfgang@mgm.mit.edu (IP addr 18.82.0.114)

aglew@ccvaxa.UUCP (10/31/87)

Somebody here just changed shell-mode to recognize a dirs or pwd after
a cd command; everybody who uses shell mode has an alias for cd.

Andy "Krazy" Glew. Gould CSD-Urbana.    USEnet:  ihnp4!uiucdcs!ccvaxa!aglew
1101 E. University, Urbana, IL 61801    ARPAnet: aglew@gswd-vms.arpa

I always felt that disclaimers were silly and affected, but there are people
who let themselves be affected by silly things, so: my opinions are my own,
and not the opinions of my employer, or any other organisation with which I am
affiliated. I indicate my employer only so that other people may account for
any possible bias I may have towards my employer's products or systems.

wombat@ccvaxa.UUCP (11/02/87)

And, in fact, here's how he did it. To use this, 'cd' needs to be
aliased to 'cd \!*; dirs', and any directory-changing aliases need to
have 'dirs' tacked on their ends and be added to shell-pushd-regexp.
This allows shells to recognize directory changes of the form

% cd $foo		# where $foo is set in the user's environment

and

% cd development	# where csh's $cdpath variable is used

and

% devel			# where 'devel' is aliased to 'cd /x/y/devel;dirs'

There is one bug in that it won't recognize things of the form '% cd !$'
because csh preempts the first line of output to show you its expansion
of !$.  I just type 'cd .' whenever I run into that. This code also
includes support for multiple shells, either named or numbered, and
putting the current working directory into the mode line.
----------------------------------------
;; Run subshell under Emacs -- Changes to distributed version.  (See
;; log below).  This file contains a little code from shell.el, so the
;; header from that file is included below.


;; Copyright (C) 1985 Richard M. Stallman.

;; This file is part of GNU Emacs.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY.  No author or distributor
;; accepts responsibility to anyone for the consequences of using it
;; or for whether it serves any particular purpose or works at all,
;; unless he says so in writing.  Refer to the GNU Emacs General Public
;; License for full details.

;; Everyone is granted permission to copy, modify and redistribute
;; GNU Emacs, but only under the conditions described in the
;; GNU Emacs General Public License.   A copy of this license is
;; supposed to have been given to you along with GNU Emacs so you
;; can know your rights and responsibilities.  It should be in a
;; file named COPYING.  Among other things, the copyright notice
;; and this notice must be preserved on all copies.

;;; $Header: shell.el,v 1.10 87/08/20 20:43:26 dlaliber Exp $
;;;
;;; $Log:	shell.el,v $
;;; Revision 1.10  87/08/20  20:43:26  dlaliber
;;; Ask user for shell buffer name if desired.
;;; 
;;; Revision 1.9  87/07/23  12:25:42  dlaliber
;;; change gould-test back to gould-custom.
;;; 
;;; Revision 1.8  87/07/23  12:18:48  dlaliber
;;; Use modern mode-line format
;;; 
;;; Revision 1.7  87/06/29  21:49:33  marick
;;; Looking-at-command skips whitespace.
;;; 
;;; Revision 1.6  86/08/14  23:06:46  marick
;;; No longer confuses a command with a shell-*-regexp prefix with a 
;;; genuine shell-*-regexp command.
;;; 
;;; Revision 1.5  86/08/11  00:25:02  marick
;;; Slight modification to last change:  Leave the shell buffer 1 named *shell*.
;;; This is for backward compatibility; you now get the same thing from 
;;; M-x shell as you always did.
;;; 
;;; Revision 1.4  86/08/11  00:01:10  marick
;;; Now shadows the distributed shell.el.
;;; The shell function now takes a prefix argument that is a number of a 
;;; shell to create.  M-x shell creates *shell-1*, but C-u 2 M-x shell
;;; creates *shell-2*.  Provided by Andy Glew.
;;; 
;;; Revision 1.3  86/08/10  20:29:01  marick
;;; Added mode-line-with-working-directory.  It produces a mode line with the
;;; working directory in it.  Shell-send-input will run this whenever it runs
;;; a directory-change command.  It should also be called in the shell-mode-hook.
;;; 
;;; Revision 1.2  86/08/10  18:12:07  marick
;;; This version of shell-send-input handles directory-changing commands 
;;; differently.  When it sees such a command, it parses the command's output
;;; to see where the new working directory is.  The standard method fails 
;;; when used by people with a set cdpath variable or directory-changing 
;;; aliases.
;;; 



;;; Functions in this file inherit from the default shell.el, so we remove
;;; ourselves from the load-path, reload, then continue this shadowing
;;; load.

(require 'custom-shadowing)
(load-super "shell" "gould-custom")

(defvar prompt-for-shell-buffer-name nil
  "*If non-nil, shell-mode prompts for the shell buffer name rather than
use the generic shell-#.")

;;; MODE-LINE-WITH-WORKING-DIRECTORY should be called on startup (in
;;; the shell-mode-hook function) and whenever the shell's working
;;; directory changes.  It modifies the mode-line so that it contains
;;; the current working directory.

(defun shell-mode-line-with-working-directory ()
  (message "shell-mode-line-with-working-directory not used anymore")
  )


;;; PARSE-AND-EXPAND-DIRECTORIES -- helper function for SHELL-SEND-INPUT.
;;; This code expects a string of directories.  Each one (including
;;; the last) should be terminated by a single space.  That given, it's
;;; straightforward to iterate down the string, extracting substrings
;;; at each space and pushing them on a list.  That list (which is now 
;;; in reverse order) should have substitute-in-file-name and 
;;; expand-file-name applied to each element, to get a list of 
;;; absolute pathnames without metacharacters.
(defun parse-and-expand-directories (string-of-directories)
  (let ((result nil))
    (let ((first-char 0)		; first character of directory.
	  (pos 0)			; current position rover.
	  (len (length string-of-directories)))	; Stop here.
    
      (while (< pos len)
	(cond ((eql (elt string-of-directories pos) ?\ )
	       (setq result
		     (cons (substring string-of-directories first-char pos)
			   result))
	       (setq first-char (+ pos 1))))
	(setq pos (+ 1 pos))))
    (mapcar (function (lambda (directory)
			(expand-file-name
			 (substitute-in-file-name directory))))
	    (nreverse result))))

;;; Looking-At-Command is like Looking-At, except that it makes sure 
;;; the regexp is followed by a character that will terminate a shell
;;; command.  (We don't bother with all of them, just the most common.)
(defun looking-at-command (regexp)
  (if (looking-at "[ \t]*")		; Skip whitespace.
      (goto-char (match-end 0)))
  (and (looking-at regexp)
       (memq (char-after (match-end 0)) '(?\n ?\; ?\ ?\t ?|))))

;;; New version of shell-send-input.  The old version could get
;;; confused about the current working directory.  This version
;;; expects directory-changing-commands to print a "dirs(1)"-style
;;; message after finishing.  This function then reads that line,
;;; parses it, and installs it as the value of shell-directory-stack.
;;; Things to know:
;;; 1.  We detect errors in the directory-changing command by looking
;;; for a missing space at the end of the output line.  Dirs puts one
;;; there, error messages don't generally have one.  This is shaky.
;;; 2.  There's really no point to having a shell-directory-stack any
;;; more, nor to having three different directory-changing regexps.
;;; These are retained for compatibility, also in case there's a use
;;; for them someday.


(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.
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)
  (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)
    (re-search-forward shell-prompt-pattern nil 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))))
  (let ((directory-change-possible
	   (save-excursion
	     (progn
	       (goto-char last-input-start)
	       (or (looking-at-command shell-popd-regexp)
		   (looking-at-command shell-pushd-regexp)
		   (looking-at-command shell-cd-regexp))))))
      ;; Now send the command to the shell.  If a directory change is
      ;; possible, wait for a reply.
      (let ((process (get-buffer-process (current-buffer))))
	(send-region process last-input-start last-input-end)
	(set-marker (process-mark process) (point))
	(if directory-change-possible
	    (accept-process-output process)))

      ;; If we might have changed the directory, parse the shell's answer.
      (if directory-change-possible
	  (let ((shell-answer

		 (save-excursion
		   (buffer-substring last-input-end
				     (progn
				       (goto-char last-input-end)
				       (skip-chars-forward "^\n")
				       (point))))))
	    ;; Test to see whether we've been given a directory list or
	    ;; an error message.  Determined by whether there's a trailing
	    ;; blank on the line.  If no error, set up the
	    ;; shell-directory-stack.
	    (cond ((eql (elt shell-answer (- (length shell-answer) 1))
			?\ )
		   (setq shell-directory-stack
			 (parse-and-expand-directories shell-answer))
		   (cd (car shell-directory-stack))
;		   (shell-mode-line-with-working-directory)
		   (setq shell-directory-stack
			 (cdr shell-directory-stack))))))))
	

(defun shell (number)
  "Run an inferior shell, with I/O through buffer *shell*.
Multiple shells can be created using a prefix argument, in which case
the shell buffer is named *shell-<number>*.  (*shell* is shell number 1.)
If buffer exists but shell process is not running, make new shell.
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.  See shell-mode.
See also variable shell-prompt-pattern.

Note that many people's .cshrc files unconditionally clear the prompt.
If yours does, you will probably want to change it."
  (interactive "p")
  (let* ((prog (or explicit-shell-file-name
		   (getenv "ESHELL")
		   (if (eq system-type 'hpux) "sh"
		     ;; On hpux people normally use csh,
		     ;; but the csh in hpux has stty sanity checking
		     ;; so it does not work under emacs.
		     (getenv "SHELL"))
		   "/bin/sh"))		     
	 (name (file-name-nondirectory prog)))
    (switch-to-buffer
     (make-shell (let ((name (if (= number 1)
				 "shell"  ;  for backward compatibility.
			       (concat "shell-" number))))
		   (if prompt-for-shell-buffer-name
		       (let ((newname (read-string
		       (format "Shell buffer name (default %s): " name))))
			 (if (string-equal newname "")
			     name
			   newname))
		     name))
		 prog
		 (if (file-exists-p (concat "~/.emacs_" name))
		     (concat "~/.emacs_" name))
		 "-i"))
    ))

-------------------------------------------------
this version of shell.el needs this file, too:
-------------------------------------------------
;; This code supports shadowing of standard files by
;; custom files.  The general scheme is this:
;;
;; 1.  The directory with customizations (the "shadowing directory") is placed
;; at the head of the search path.
;; 2.  The shadowing file is loaded.  At some point, it calls
;; Load-Super with its own file name (a deficiency) and its home
;; directory (a grave deficiency, that has no effect if the user does
;; what I consider the right thing -- see below).  Note that the home
;; directory can be a partial name -- it's usually just the containing
;; directory's name (no slashes).
;; 3.  Run-Super rebinds the load path to a different ("popped")
;; version and reloads the file.

;; There are two ways that the user controls how the load-path is
;; rebound.
;; 1.  If Load-Path-Stack is bound, the top value is popped off and
;; used as Load-Path.  This is the cleaner method.
;; 2.  If Load-Path-Stack is unbound, elements of the Load-Path are
;; popped until one matches (with a regexp search) the directory name.
;; This method is less robust and clean, but it keeps library pathnames out
;; of .emacs files -- something that's unimportant at sites where all
;; machines use the same pathname to get to Emacs libraries.
;;
;; Note:  During Run-Super, the new Load-Path is not allowed to have
;; leading NILs, as that makes it difficult to test a new package in
;; its home directory.

;;;
;;; $Header: custom-shadowing.el,v 1.2 86/08/14 23:08:42 marick Exp $
;;; 
;;; $Log:	custom-shadowing.el,v $
;;; Revision 1.2  86/08/14  23:08:42  marick
;;; Initial version supports two ways of shadowing same-named lisp files.
;;; 
;;; 
(provide 'custom-shadowing)

;; Pop-Until-Match (stack regexp) pops strings off the stack until one
;; of the popped strings matches the regexp.

(defun pop-until-match (stack regexp)
    (while (or (null (car stack))
	       (not (string-match regexp (car stack))))
      (setq stack (cdr stack)))
    (cdr stack))
    

(defun load-super (self self-directory)
  (if (boundp 'load-path-stack)		; clean method.
      (let ((load-path (car load-path-stack))
	    (load-path-stack (cdr load-path-stack)))
	(while (null (car load-path))
	    (setq load-path (cdr load-path)))
	(load self))
    (let ((load-path (pop-until-match load-path self-directory)))
      (while (null (car load-path))
	(setq load-path (cdr load-path)))
      (load self))))

----------------------------------------