Subject: Re: better cd tracking for ntemacs

From: Stan Lanning <[email-masked]>
Date: Sat, 9 Nov 1996 14:20:04 -0800 (PST)

I use the following terrible hack.  It "works" by examining all output
from the shell, looking for a standard prompt.  It works pretty well for
me, but as usual YMMV.

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

;; Tracking of current directory

(defvar nt-shell-dirtrackp
  (cond ((not (eq system-type 'windows-nt)) nil)
	((boundp 'shell-dirtrackp) shell-dirtrackp)
	(t t))
  "*Enable tracking of the current directory in NT shells.")

(defun nt-shell-note-prompt (str)
  ;; Make directory tracking work in NT, by tracking the command prommpt
  ;; instead of parsing commands that change the cwd.
  (if (string-match "^\n" str)
      (setq str (substring str 1)))
  (if (string-match "\n$" str)
      (setq str (substring str 0 -1)))
  (if (and nt-shell-dirtrackp
	   ;; looks like a pathaname
	   (string-match "^[a-zA-Z]:\\\\[^:]*>$" str)
	   ;; not an ange-ftp pathname
	   (not (string-match canonicalize-pathname-ignore-regexp str))
	   ;; not the same as the previous dir
	   (not (string-equal (substring str 0 -1) shell-last-dir))
	   ;; but is a directory
	   (file-directory-p (substring str 0 -1)))
      ;; Probably a prompt, and different than last time
      (shell-process-cd (substring str 0 -1))))

(if (eq system-type 'windows-nt)
    ;; Install hook for tracking current directory
    (add-hook 'shell-mode-hook
	      (lambda ()
		(if nt-shell-dirtrackp
		    (setq shell-dirtrackp nil))
		(setq comint-output-filter-functions
		      (cons 'nt-shell-note-prompt comint-output-filter-functions)))))

---

From: Peter Breton <[email-masked]>
Date: Sun, 10 Nov 96 09:24:06 EST

David> The directory tracking for NT shells does not work well when
 David> there are drive changes, etc.  Is anyone working on this?

I got directory synchronization to work (eg M-x dirs) by:

1) Turning off command echoing, using the "/q" switch to CMD.EXE
2) Setting the variable 'shell-dirstack-query' to "pwd" (I got this
from MKS toolkit, is there a free one around?)

I use this when the shell gets confused about where it is. You can also
set 'shell-cd-regexp' to include the /D switch.

 David> I'd trying to implement a small hack which tracks better, and
 David> also allows forward slashes (I'm an old Unix bigot) and
 David> defaults to CMD.EXE's /D option on CHDIR, which allows you to
 David> specify the drive and the directory in one command, i.e.

 David>   cd d:/gnu/emacs-19.34

It seems to me that you could do this in lisp, by adding a hook function
which looks for a CD command, and then replaces the forward-slashes with
backwards ones. 

You could also use an abbrev to turn "cd" into "chdir /D". I find that
expand.el (an enhanced abbrev facility available as an add-on) works quite 
well with shell-mode, because it only expands abbrevs at the end of a line, 
which is where commands occur.

Since completion works the right way for CMD.EXE, I don't find the slashes
to be an issue.

 David> I have this partially working, and using gnudoit from my
 David> cd.exe, I tell Emacs to do a (cd "current dir") for the shell
 David> buffer, but my cd.exe program does not change the current
 David> directory for the parent process, i.e. the CMD shell from
 David> which it is called.

I wrote something like this from inside emacs, which basically does
the following:

1) Sets the default-directory for *shell* to the new directory (is this
the piece you're missing?)
2) Sends a command "cd /d NEW-DIRECTORY" to the shell process.

It works OK, but is essentially a hack.

 David> If someone knows the trick to compile this program so that it
 David> can change the shells default drive/directory, I can make my
 David> cd.exe and the Emacs Lisp hooks available.

My personal preference is to avoid extra little magic native exes if 
possible. I'll admit that there are times when they're very useful, though 
(eg, the "pwd" program above).

				Peter

---

From: Sarir Khamsi <[email-masked]>
Date: Sun, 10 Nov 1996 14:31:50 -0800

>>>>> "peter" == Peter Breton <[email-masked]> writes:

peter> I got directory synchronization to work (eg M-x dirs) by:

peter> 1) Turning off command echoing, using the "/q" switch to
peter> CMD.EXE 2) Setting the variable 'shell-dirstack-query' to "pwd"
peter> (I got this from MKS toolkit, is there a free one around?)

How about this for a free pwd.exe:

#include <iostream.h>
#include <direct.h>
#include <stdlib.h>

void main (void )
{
  int curdrive;
  char path[_MAX_PATH];

  curdrive = _getdrive();
  cout << _getdcwd (curdrive, path, _MAX_PATH) << endl;
}

Sarir

-- 
Sarir (Riri) Khamsi
[email-masked]

---

From: Peter Breton <[email-masked]>
Date: Mon, 11 Nov 1996 10:26:31 -0500

Actually I had another brainstorm, which I credit to your post. :)
It's so simple I'm thinking someone must have either have thought
of it already, or that there's a good reason against it.

Put simply, directory tracking is very easy, provided that we make
the strong assumption that the prompt can be customized to contain
the current directory. In this way, we let the shell worry about
figuring out the correct directory; all we do is parse the prompt.
For customizing, we let the user provide a regexp which describes
where the path is in the prompt.

I did a quickie implementation of this idea too (had some free time :)
This did work for me, but is frankly pretty raw. My prompt is set to
"$p$g". You also need to add 'pb-directory-track' to 
'comint-output-filter-functions'. If I can get this to work on Unix
(assuming more free time :) I may spin this up into a full-fledged
package.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Directory tracking
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defvar pb-directory-track-list (list "\\(.*\\)>" 1)
  "List for directory tracking.
First item is a regexp that describes where to find the path in a prompt.
Second is which regexp group to match")

(defvar pb-directory-track-directory-function 
  (if (memq system-type (list 'ms-dos 'windows-nt))
      (function (lambda (dir)
		  (concat (downcase (replace-slash dir t)) "/")))
    'identity)
  "Function to apply to the prompt directory for comparison purposes.")

(defvar pb-directory-track-canonicalize-function  
  (if (memq system-type (list 'ms-dos 'windows-nt))
      'downcase 'identity)
  "Function to apply to the default directory for comparison purposes")

;; Makes the strong assumption that the prompt contains the path!
(defun pb-directory-track (input)
  (let ((prompt-path)
	(current-dir default-directory)
	(pb-directory-track-regexp (nth 0 pb-directory-track-list))
	(match-num		   (nth 1 pb-directory-track-list))
	)
    (save-excursion
      (beginning-of-line)
      (if (not (looking-at pb-directory-track-regexp))
	  nil
	(setq prompt-path 
	      (buffer-substring-no-properties
	       (match-beginning match-num) (match-end match-num)))
	;; For DOS/Windows, turn backslashes into forward ones
	(setq prompt-path (funcall pb-directory-track-directory-function
				   prompt-path))
	(setq current-dir (funcall pb-directory-track-canonicalize-function
				   current-dir))
	(if (not (string= current-dir prompt-path))
	    (shell-process-cd prompt-path))
    ))))

(defvar forward-slash  (regexp-quote "/"))
(defvar backward-slash (regexp-quote "\\"))

(defun replace-slash (string &optional opposite)
  "Replace forward slashes with backwards ones.
If additional argument is non-nil, replace backwards slashes with 
forward ones"
  (let ((orig    (if opposite backward-slash forward-slash))
	(replace (if opposite forward-slash backward-slash))
	)
    (while (string-match orig string)
      (setq string (replace-match replace nil t string))))
  string)