home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / elisp / packages / tracker.el < prev    next >
Encoding:
Text File  |  1990-07-22  |  14.9 KB  |  436 lines

  1. ;From ark1!uakari.primate.wisc.edu!aplcen!uunet!cme!durer!warsaw Sat Mar  3 17:26:04 EST 1990
  2. ;Article 1528 of comp.emacs:
  3. ;Xref: ark1 gnu.emacs:1246 comp.emacs:1528
  4. ;Path: ark1!uakari.primate.wisc.edu!aplcen!uunet!cme!durer!warsaw
  5. ;From: warsaw@cme.nist.gov (Barry A. Warsaw)
  6. ;Newsgroups: gnu.emacs,comp.emacs
  7. ;Subject: Better Directory Tracking Engine for GNU emacs
  8. ;Message-ID: <WARSAW.90Mar1192018@rtg.cme.nist.gov>
  9. ;Date: 2 Mar 90 00:20:18 GMT
  10. ;Sender: news@cme.nist.gov
  11. ;Organization: National Institute of Standards and Technology
  12. ;Lines: 422
  13. ;
  14. ;
  15. ;I've been out of touch from these newsgroups for a while, but I just
  16. ;recently had someone ask me about the state of my replacement for
  17. ;shell-set-directory.  I've also seen some discussion of this while I
  18. ;was catching up the other day.  So I decided to post the latest
  19. ;version of my directory tracker.  Its undergone a major rewrite since
  20. ;my last posting but the basic mechanism is the same.  Now, however,
  21. ;there is a general tracking engine and front ends for shell-mode
  22. ;(shell-set-directory) and cmushell-mode (shell-directory-tracker).
  23. ;BTW, my latest interest in this was prompted by my move from
  24. ;shell-mode to cmushell-mode, which still had a somewhat faulty
  25. ;tracker.
  26. ;
  27. ;Caveats:
  28. ;1) I've only used it with /bin/csh on a Sun3 as the underlying shell
  29. ;process. Your mileage may differ.
  30. ;
  31. ;2) Its still not perfect. It seems to be quite tricky to track
  32. ;commands such as:
  33. ;
  34. ;    % cd `my-favorite-path`
  35. ;    % pushd $SHELL_VAR
  36. ;    % !cd
  37. ;    % alias frob 'cd ~/blah' ; frob
  38. ;
  39. ;And the like. Someone mentioned an interesting idea where the pwd
  40. ;command might be used to resync the tracking engine, but I opted for
  41. ;an explicit resync command (tk-resync) instead.
  42. ;
  43. ;I did include a mechanism whereby the tracker could be automatically
  44. ;or explicitly turned on and off.  This is useful if you go into
  45. ;something like ftp, you don't want tracking while you're cd'ing around
  46. ;in another environment.
  47. ;
  48. ;Enjoy, comments are of course welcome, and if you come up with ways to
  49. ;cleanly do some of #2 above, please share it with me.
  50. ;
  51. ;-Barry
  52. ;
  53. ;NAME: Barry A. Warsaw                USMAIL: National Institute of Standards
  54. ;TELE: (301) 975-3460                         and Technology (formerly NBS)
  55. ;UUCP: {...}!uunet!cme-durer!warsaw           Rm. B-124, Bldg. 220
  56. ;ARPA: warsaw@cme.nist.gov                    Gaithersburg, MD 20899
  57. ;
  58. ;===== cut here ==================================================
  59. ;; baw-tracker.el
  60. ;;
  61. ;; a better tracker of directory navigation
  62.  
  63. ;; Does a better, but still not perfect job of tracking csh builtin
  64. ;; commands that modify the directory and directory stack.  Correctly
  65. ;; tracks commands `cd', `pushd', `popd' and `dirs' with arguments.
  66. ;; Correctly expands paths containing environment variables, `~', `.'
  67. ;; and `..'.  Still can't track paths which contain shell variables,
  68. ;; execs, etc.  Currently only tested with /bin/csh, on Suns.
  69. ;; Compatible with both *shell* mode and *cmushell* mode.
  70.  
  71. ;; Builtins recognized:
  72. ;;
  73. ;; cd [path]
  74. ;; pushd [+n | path]
  75. ;; popd [+n]
  76. ;; dirs [-l]
  77. ;;
  78. ;; Other commands recognized:
  79. ;;
  80. ;; ftp  (this shuts off auto tracking, waits for "quit")
  81. ;;
  82. ;; path can be either absolute or relative, n must be > 0.
  83.  
  84. ;; Interactive commands available to you:
  85. ;; tk-on     : explicitly turn on directory tracking
  86. ;; tk-off    : explicitly turn off directory tracking
  87. ;; tk-resync : explicitly resync tracking engine
  88.  
  89. ;; To use cons this to your shell-mode-hook:
  90. ;; (load "baw-tracker")
  91.  
  92. ;; This file is distributed in the hope that it will be useful, but
  93. ;; WITHOUT ANY WARRANTY.  No author or distributor accepts
  94. ;; responsibility to anyone for the consequences of using it or for
  95. ;; whether it serves any particular purpose or works at all, unless
  96. ;; s/he says so in writing.
  97.  
  98. ;; This software was written as part of the author's official duty as
  99. ;; an employee of the United States Government and is thus in the
  100. ;; public domain.  You are free to use this software as you wish, but
  101. ;; WITHOUT ANY WARRANTY WHATSOEVER.  It would be nice, though if when
  102. ;; you use this code, you give due credit to the author.
  103.  
  104. ;; ======================================================================
  105. ;; Author:
  106. ;;
  107. ;; NAME: Barry A. Warsaw           USMAIL: National Institute of Standards
  108. ;; TELE: (301) 975-3460                      and Technology (formerly NBS)
  109. ;; UUCP: {...}!uunet!cme-durer!warsaw      Rm. B-124, Bldg. 220
  110. ;; ARPA: warsaw@cme.nist.gov               Gaithersburg, MD 20899
  111.  
  112. ;; ======================================================================
  113. ;; Modification history:
  114. ;;
  115. ;; posted  :  1-Mar-1990 baw (comp.emacs, gnu.emacs)
  116. ;; modified:  1-Mar-1990 baw (turn tracking on and off with ftp/quit)
  117. ;;                           (explicit resync)
  118. ;; modified: 26-Feb-1990 baw (fixed front end for *shell* mode)
  119. ;; modified: 22-Jan-1990 baw (for compatibility w/ cmushell)
  120. ;;                           (fix some algorithms, factor code)
  121. ;; modified: 16-Nov-1989 baw (buffer local variables)
  122. ;; posted  : 14-Sep-1989 baw (comp.emacs, gnu.emacs)
  123. ;; modified: 14-Sep-1989 baw (cleaned up for posting)
  124. ;; modified: 11-Sep-1989 baw (fixed regexps)
  125. ;; created :  8-Sep-1989 baw
  126.  
  127. ;; ======================================================================
  128. ;; Wish list:
  129. ;;
  130. ;; 1) Would like to be able to glob directory better to find the actual
  131. ;;    directory cd'd to in the case of shell variables, execs, etc.
  132. ;;
  133. ;; 2) Really would to be able to query the shell process for the current
  134. ;;    working directory.
  135. ;;
  136. ;; 3) Perhaps finagle "pwd" command for resyncing directory tracker when
  137. ;;    it gets off track.  This is a little more difficult since tracker
  138. ;;    has to watch for input that comes after its done processing the
  139. ;;    current line.  I'm currently opting for a manual resync of the
  140. ;;    the tracking engine via tk-resync
  141.  
  142.  
  143. (defvar tk-track-p t
  144.   "*Boolean flag which indicates whether tracking should or should not
  145. be done. This is used to turn off tracking when entering ftp mode and
  146. turning it back on when exiting ftp mode. Non-nil means to track
  147. directories, nil means don't track directories.")
  148.  
  149. (defvar tk-eos-regexp "\\s *\\([\n;]\\|$\\)"
  150.   "*Regular expression signifying the end of a shell builtin command,
  151. correctly locating either a newline terminated statement, or a \";\"
  152. delimited compound statement.")
  153.  
  154. (defvar tk-cd-regexp "cd"
  155.   "*Regular expression signifying builtin `cd' command.")
  156.  
  157. (defvar tk-popd-regexp "popd"
  158.   "*Regular expression signifying builtin `popd' command.")
  159.  
  160. (defvar tk-pushd-regexp "pushd"
  161.   "*Regular expression signifying builtin `pushd' command.")
  162.  
  163. (defvar tk-dirs-regexp "dirs"
  164.   "*Regular expression signifying builtin `dirs' command.")
  165.  
  166. (defvar tk-ftp-regexp "ftp"
  167.   "*Regular expression signifying builtin `ftp' commands.")
  168.  
  169. (defvar tk-tracking-error-hook 'ignore
  170.   "*Function called with no arguments when tracking in either
  171. shell-mode or cmushell-mode results in an error.")
  172.  
  173. (defvar tk-start-tracking-regexp "quit"
  174.   "*Regular expression which tells tracker to start tracking once
  175. turned off.")
  176.  
  177. (setq shell-set-directory-error-hook tk-tracking-error-hook)
  178. (setq tk-directory-stack nil)
  179. (make-variable-buffer-local 'default-directory)
  180. (make-variable-buffer-local 'tk-directory-stack)
  181.  
  182.  
  183. (defun tk-parse-statement (statement regexp)
  184.   "Parse STATEMENT to see if it contains the builtin command signified
  185. by REGEXP.  Returns nil if the statement is not the signified builtin
  186. command, otherwise returns a list specifying the arguments passed to
  187. command, in the form: (N (ARG1 ARG2 ...)) where N is the number of
  188. arguments."
  189.   (let ((n 0)
  190.     (args nil)
  191.     arg
  192.     (command (string-match (concat "^\\("
  193.                        regexp
  194.                        "\\)\\(\\s +\\|$\\)")
  195.                    statement))
  196.     (nextargi (match-end 0))
  197.     )
  198.     (if (not command) nil
  199.       (while (string-match "\\S +" statement nextargi)
  200.     (setq arg (substring statement (match-beginning 0) (match-end 0)))
  201.     (setq args (append args (list arg)))
  202.     (setq n (1+ n))
  203.     (string-match "\\S +\\s +" statement nextargi)
  204.     (setq nextargi (match-end 0))
  205.     )
  206.       (list n args)
  207.       )
  208.     ))
  209.  
  210.  
  211. (defun tk-wholenum-arg-p (string)
  212.   "Predicate which tests whether STRING is a whole number (i.e. an
  213. integer greater than zero).  It returns the whole number if STRING
  214. does not contain any non-numeric charcters, and is greater than zero,
  215. otherwise returns nil. Also note that since this is an argument to a
  216. builtin command, the first character of the string *must* be a plus
  217. sign."
  218.   (cond
  219.    ((not (string-match "^\\+[1-9]+[0-9]*$" string))
  220.     nil)
  221.    ((> (string-to-int string) 0)
  222.     (string-to-int string))
  223.    (t nil)
  224.    ))
  225.  
  226.  
  227. (defun tk-listify-compound-command (command)
  228.   "Convert COMMAND, which may be a compound statement (i.e. cd; ls)
  229. into a list of simple statements. Only separators I know of are
  230. \";\"."
  231.   (let ((statements nil)
  232.     (nextstatementi 0))
  233.     (while (< nextstatementi (length command))
  234.       (string-match tk-eos-regexp command nextstatementi)
  235.       (setq statements
  236.         (append statements (list (substring command
  237.                         nextstatementi
  238.                         (match-beginning 0)))))
  239.       (setq nextstatementi (1+ (match-end 0)))
  240.       )
  241.     statements
  242.     ))
  243.     
  244.  
  245. (defun tk-on ()
  246.   "Turn on directory tracker."
  247.   (interactive)
  248.   (message "Turning on directory tracker.")
  249.   (setq tk-track-p t)
  250.   )
  251.  
  252.  
  253. (defun tk-off ()
  254.   "Turn off directory tracker."
  255.   (interactive)
  256.   (message "Turning off directory tracker.")
  257.   (setq tk-track-p nil)
  258.   )
  259.  
  260.  
  261. (defun tk-resync (dir)
  262.   "Resync directory tracking engine."
  263.   (interactive "DResync to: ")
  264.   (cd dir)
  265.   )
  266.  
  267.  
  268. (defun tk-tracking-engine (statement)
  269.   "Parse the STATEMENT for one of the directory navigating builtin
  270. commands and modify the directory stack accordingly.  STATEMENT should
  271. be a single statement, not a compound statement."
  272.   (let ((dir0 default-directory)
  273.     args
  274.     numeric)
  275.  
  276.     ;; test for each builtin command
  277.     (cond
  278.      ((setq args (tk-parse-statement statement tk-start-tracking-regexp))
  279.       ;; we're looking at a command that restarts tracking, set by
  280.       ;; various tracked commands
  281.       (tk-on)
  282.       )
  283.  
  284.      ((not tk-track-p))
  285.      ;; is the tracking flag turned off?
  286.  
  287.      ((setq args (tk-parse-statement statement tk-ftp-regexp))
  288.       ;; we're looking at an ftp command which shouldn't track
  289.       (setq tk-start-tracking-regexp "quit")
  290.       (tk-off)
  291.       )
  292.  
  293.      ((setq args (tk-parse-statement statement tk-cd-regexp))
  294.       ;; we're looking at a cd command
  295.       (cond
  296.        ((= (car args) 0)
  297.     ;; looking at a no arg'd cd command, means cd to $HOME
  298.     (cd (expand-file-name (substitute-in-file-name "$HOME"))))
  299.        ((> (car args) 1)
  300.     (error "cd: Too many arguments."))
  301.        ((= (car args) 1)
  302.     (cd (expand-file-name
  303.          (substitute-in-file-name (car (car (cdr args)))))))
  304.        (t
  305.     (error "How did you get a negative number of arguments?"))
  306.        ))
  307.  
  308.      ((setq args (tk-parse-statement statement tk-popd-regexp))
  309.       ;; we're looking at a popd command
  310.       (cond
  311.        ((= (car args) 0)
  312.     ;; looking at a no arg'd popd, pop "top" directory from stack
  313.     (cd (or (car tk-directory-stack)
  314.         (error "popd: Directory stack empty.")))
  315.     (setq tk-directory-stack (cdr tk-directory-stack)))
  316.        ((> (car args) 1)
  317.     (error "popd: Too many arguments."))
  318.        ((< (car args) 0)
  319.     (error "How did you get a negative number of arguments?"))
  320.        ((not (setq numeric (tk-wholenum-arg-p (car (car (cdr args))))))
  321.     (error "popd: Invalid argument: %s" (car (car (cdr args)))))
  322.        ;; check to be sure there *is* an nth dir on the stack
  323.        ((not (nth (1- numeric) tk-directory-stack))
  324.     (error "popd: Directory stack not that deep."))
  325.        ;; first pecial case when only two dirs are on the stack
  326.        ((not (cdr tk-directory-stack))
  327.     (setq tk-directory-stack nil))
  328.        ;; second special case when popping the first dir on stack
  329.        ((= numeric 1)
  330.     (let ((tcdr (nthcdr numeric tk-directory-stack)))
  331.       (setcar tk-directory-stack (car tcdr))
  332.       (setcdr tk-directory-stack (cdr tcdr))
  333.       ))
  334.        (t
  335.     (setcdr (nthcdr (- numeric 2) tk-directory-stack)
  336.         (nthcdr numeric tk-directory-stack)))
  337.        ))
  338.  
  339.      ((setq args (tk-parse-statement statement tk-pushd-regexp))
  340.       ;; we're looking at a pushd command
  341.       (cond
  342.        ((= (car args) 0)
  343.     ;; looking at a no arg'd pushd, exchange top two directories
  344.     (cd (or (car tk-directory-stack)
  345.         (error "pushd: No other directory.")))
  346.     (setq tk-directory-stack (append (list dir0)
  347.                      (cdr tk-directory-stack))))
  348.        ;; looking at a numeric argument
  349.        ((setq numeric (tk-wholenum-arg-p (car (car (cdr args)))))
  350.     (cd (or (nth (1- numeric) tk-directory-stack)
  351.         (error "pushd: Directory stack not that deep.")))
  352.     (while (< 0 numeric)
  353.       (setq tk-directory-stack (append tk-directory-stack (list dir0))
  354.         dir0 (car tk-directory-stack)
  355.         tk-directory-stack (cdr tk-directory-stack)
  356.         numeric (1- numeric)
  357.         ))
  358.     )
  359.        (t
  360.     ;; must be looking at a directory pathname
  361.     (cd (expand-file-name
  362.          (substitute-in-file-name (car (car (cdr args))))))
  363.     (setq tk-directory-stack (append (list dir0) tk-directory-stack)))
  364.        ))
  365.  
  366.      ((setq args (tk-parse-statement statement tk-dirs-regexp))
  367.       ;; we're looking at a dirs command
  368.       (cond
  369.        ((= (car args) 0)
  370.     ;; looking at a no arg'd dirs command
  371.     (let ((dirs ""))
  372.       (mapcar
  373.        (function
  374.         (lambda (dir)
  375.           (and (string-match (concat "^" (substitute-in-file-name "$HOME"))
  376.                  dir)
  377.            (setq dir (concat "~" (substring dir (match-end 0))))
  378.            )
  379.           (setq dirs (concat dirs (if (string-match "^~/$" dir)
  380.                       "~"
  381.                     (directory-file-name dir))
  382.                  " "))
  383.           )) ;; function
  384.        (append (list default-directory) tk-directory-stack)) ;; mapcar
  385.       (message "%s" dirs)
  386.       ))
  387.        ((< (car args) 0)
  388.     (error "How did you get a negative number of arguments?"))
  389.        ((> (car args) 1)
  390.     (error "dirs: Too many arguments."))
  391.        ;; -l option is only one I know of for dirs
  392.        ((string-match "^-l$" (car (car (cdr args))))
  393.     (let ((dirs ""))
  394.       (mapcar
  395.        (function
  396.         (lambda (dir)
  397.           (setq dirs (concat dirs (directory-file-name dir) " "))
  398.           ))
  399.        (append (list default-directory) tk-directory-stack))
  400.       (message "%s" dirs)))
  401.        (t
  402.     (error "Usage: dirs [ -l ]."))
  403.        ))
  404.      )
  405.     ))
  406.  
  407.  
  408. (defun shell-set-directory ()
  409.   "Better directory navigation tracker for shell-mode."
  410.   (let* ((commandline (buffer-substring (point)
  411.                     (save-excursion
  412.                       (end-of-line)
  413.                       (point))))
  414.      (statements (tk-listify-compound-command commandline)))
  415.     (while statements
  416.       (tk-tracking-engine (car statements))
  417.       (setq statements (cdr statements))
  418.       )
  419.     ))
  420.  
  421.  
  422. (defun shell-directory-tracker (commandline)
  423.   "Better directory navigation tracker for cmushell-mode."
  424.   (let ((statements (tk-listify-compound-command commandline)))
  425.     (while statements
  426.       ;; note that we need to encase the call to the tracking engine
  427.       ;; in a condition case to trap any errors that are signaled in
  428.       ;; tk-tracking-engine.  Shell-mode automatically does this and we
  429.       ;; want the two to be compatible. Ripped this from shell.el
  430.       (condition-case ()
  431.       (tk-tracking-engine (car statements))
  432.     (error (funcall tk-tracking-error-hook)))
  433.       (setq statements (cdr statements))
  434.       )
  435.     ))
  436.