home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / elisp / misc / baw-tracker.el < prev    next >
Encoding:
Text File  |  1991-05-03  |  12.5 KB  |  378 lines

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