]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/grep.el
Merge from emacs--rel--22
[gnu-emacs] / lisp / progmodes / grep.el
index 6afa3f293489b3313a985bb5cdc10168b7cd6270..91518641938b65a1aa54c31dfb2ee71b9b8c7d9e 100644 (file)
@@ -1,7 +1,7 @@
 ;;; grep.el --- run Grep as inferior of Emacs, parse match messages
 
 ;; Copyright (C) 1985, 1986, 1987, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
-;;   2001, 2002, 2003, 2004, 2005, 2006  Free Software Foundation, Inc.
+;;   2001, 2002, 2003, 2004, 2005, 2006, 2007  Free Software Foundation, Inc.
 
 ;; Author: Roland McGrath <roland@gnu.org>
 ;; Maintainer: FSF
@@ -11,7 +11,7 @@
 
 ;; GNU Emacs is free software; you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
+;; the Free Software Foundation; either version 3, or (at your option)
 ;; any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
@@ -35,7 +35,7 @@
 
 
 (defgroup grep nil
-  "Run compiler as inferior of Emacs, parse error messages."
+  "Run grep as inferior of Emacs, parse error messages."
   :group 'tools
   :group 'processes)
 
   :version "22.1"
   :group 'grep)
 
-(defcustom grep-auto-highlight t
-  "*Specify how many grep matches to highlight (and parse) initially.
-\(Highlighting applies to an grep match when the mouse is over it.)
-If this is a number N, all grep matches in the first N lines
-are highlighted and parsed as soon as they arrive in Emacs.
-If t, highlight and parse the whole grep output as soon as it arrives.
-If nil, don't highlight or parse any of the grep buffer until you try to
-move to the error messages.
-
-Those grep matches which are not parsed and highlighted initially
-will be parsed and highlighted as soon as you try to move to them."
-  :type '(choice (const :tag "All" t)
-                (const :tag "None" nil)
-                (integer :tag "First N lines"))
-  :version "22.1"
-  :group 'grep)
-
 (defcustom grep-highlight-matches 'auto-detect
   "If t, use special markers to highlight grep matches.
 
@@ -108,6 +91,20 @@ call that function before using this variable in your program."
                 (const :tag "Not Set" nil))
   :group 'grep)
 
+(defcustom grep-template nil
+  "The default command to run for \\[lgrep].
+The default value of this variable is set up by `grep-compute-defaults';
+call that function before using this variable in your program.
+The following place holders should be present in the string:
+ <C> - place to put -i if case insensitive grep.
+ <F> - file names and wildcards to search.
+ <R> - the regular expression searched for.
+ <N> - place to insert null-device."
+  :type '(choice string
+                (const :tag "Not Set" nil))
+  :version "22.1"
+  :group 'grep)
+
 (defcustom grep-use-null-device 'auto-detect
   "If t, append the value of `null-device' to `grep' commands.
 This is done to ensure that the output of grep includes the filename of
@@ -130,8 +127,8 @@ call that function before using this variable in your program."
                 (const :tag "Not Set" nil))
   :group 'grep)
 
-(defcustom grep-tree-command nil
-  "The default find command for \\[grep-tree].
+(defcustom grep-find-template nil
+  "The default command to run for \\[rgrep].
 The default value of this variable is set up by `grep-compute-defaults';
 call that function before using this variable in your program.
 The following place holders should be present in the string:
@@ -145,27 +142,25 @@ The following place holders should be present in the string:
   :version "22.1"
   :group 'grep)
 
-(defcustom grep-tree-files-aliases '(
+(defcustom grep-files-aliases '(
+       ("el" . "*.el")
        ("ch" . "*.[ch]")
        ("c" .  "*.c")
        ("h" .  "*.h")
-       ("m" .  "[Mm]akefile*")
        ("asm" . "*.[sS]")
-       ("all" . "*")
-       ("el" . "*.el")
+       ("m" .  "[Mm]akefile*")
+       ("l" . "[Cc]hange[Ll]og*")
+       ("tex" . "*.tex")
+       ("texi" . "*.texi")
        )
-  "*Alist of aliases for the FILES argument to `grep-tree'."
+  "*Alist of aliases for the FILES argument to `lgrep' and `rgrep'."
   :type 'alist
   :group 'grep)
 
-(defcustom grep-tree-ignore-case t
-  "*If non-nil, `grep-tree' ignores case in matches."
-  :type 'boolean
-  :group 'grep)
-
-(defcustom grep-tree-ignore-CVS-directories t
-  "*If non-nil, `grep-tree' does no recurse into CVS directories."
-  :type 'boolean
+(defcustom grep-find-ignored-directories '("CVS" ".svn" "{arch}" ".hg" "_darcs"
+                                          ".git" ".bzr")
+  "*List of names of sub-directories which `rgrep' shall not recurse into."
+  :type '(repeat string)
   :group 'grep)
 
 (defcustom grep-error-screen-columns nil
@@ -208,6 +203,8 @@ See `compilation-error-screen-columns'"
       '("Compile..." . compile))
     (define-key map [menu-bar grep compilation-grep]
       '("Another grep..." . grep))
+    (define-key map [menu-bar grep compilation-grep-find]
+      '("Recursive grep..." . grep-find))
     (define-key map [menu-bar grep compilation-recompile]
       '("Repeat grep" . recompile))
     (define-key map [menu-bar grep compilation-separator2]
@@ -238,8 +235,7 @@ See `compilation-error-screen-columns'"
 ;; override compilation-last-buffer
 (defvar grep-last-buffer nil
   "The most recent grep buffer.
-A grep buffer becomes most recent when its process is started
-or when it is used with \\[grep-next-match].
+A grep buffer becomes most recent when you select Grep mode in it.
 Notice that using \\[next-error] or \\[compile-goto-error] modifies
 `complation-last-buffer' rather than `grep-last-buffer'.")
 
@@ -288,13 +284,13 @@ Notice that using \\[next-error] or \\[compile-goto-error] modifies
      (": \\(.+\\): \\(?:Permission denied\\|No such \\(?:file or directory\\|device or address\\)\\)$"
       1 grep-error-face)
      ;; remove match from grep-regexp-alist before fontifying
-     ("^Grep started.*"
+     ("^Grep[/a-zA-z]* started.*"
       (0 '(face nil message nil help-echo nil mouse-face nil) t))
-     ("^Grep finished \\(?:(\\(matches found\\))\\|with \\(no matches found\\)\\).*"
+     ("^Grep[/a-zA-z]* finished \\(?:(\\(matches found\\))\\|with \\(no matches found\\)\\).*"
       (0 '(face nil message nil help-echo nil mouse-face nil) t)
       (1 compilation-info-face nil t)
       (2 compilation-warning-face nil t))
-     ("^Grep \\(exited abnormally\\|interrupt\\|killed\\|terminated\\)\\(?:.*with code \\([0-9]+\\)\\)?.*"
+     ("^Grep[/a-zA-z]* \\(exited abnormally\\|interrupt\\|killed\\|terminated\\)\\(?:.*with code \\([0-9]+\\)\\)?.*"
       (0 '(face nil message nil help-echo nil mouse-face nil) t)
       (1 grep-error-face)
       (2 grep-error-face nil t))
@@ -319,17 +315,7 @@ Notice that using \\[next-error] or \\[compile-goto-error] modifies
 This gets tacked on the end of the generated expressions.")
 
 ;;;###autoload
-(defvar grep-program
-  ;; Currently zgrep has trouble.  It runs egrep instead of grep,
-  ;; and it doesn't pass along long options right.
-  "grep"
-  ;; (if (equal (condition-case nil    ; in case "zgrep" isn't in exec-path
-  ;;            (call-process "zgrep" nil nil nil
-  ;;                          "foo" null-device)
-  ;;          (error nil))
-  ;;        1)
-  ;;     "zgrep"
-  ;;   "grep")
+(defvar grep-program "grep"
   "The default grep program for `grep-command' and `grep-find-command'.
 This variable's value takes effect when `grep-compute-defaults' is called.")
 
@@ -340,10 +326,10 @@ This variable's value takes effect when `grep-compute-defaults' is called.")
 
 ;;;###autoload
 (defvar grep-find-use-xargs nil
-  "Whether \\[grep-find] uses the `xargs' utility by default.
-
-If nil, it uses `find -exec'; if `gnu', it uses `find -print0' and `xargs -0';
-if not nil and not `gnu', it uses `find -print' and `xargs'.
+  "Non-nil means that `grep-find' uses the `xargs' utility by default.
+If `exec', use `find -exec'.
+If `gnu', use `find -print0' and `xargs -0'.
+Any other non-nil value means to use `find -print' and `xargs'.
 
 This variable's value takes effect when `grep-compute-defaults' is called.")
 
@@ -353,6 +339,17 @@ This variable's value takes effect when `grep-compute-defaults' is called.")
 ;;;###autoload
 (defvar grep-find-history nil)
 
+;; History of lgrep and rgrep regexp and files args.
+(defvar grep-regexp-history nil)
+(defvar grep-files-history '("ch" "el"))
+
+(defvar grep-host-defaults-alist nil
+  "Default values depending on target host.
+`grep-compute-defaults' returns default values for every local or
+remote host `grep' runs.  These values can differ from host to
+host.  Once computed, the default values are kept here in order
+to avoid computing them again.")
+
 ;;;###autoload
 (defun grep-process-setup ()
   "Setup compilation variables and buffer for `grep'.
@@ -378,138 +375,235 @@ Set up `compilation-exit-message-function' and run `grep-setup-hook'."
           (cons msg code))))
   (run-hooks 'grep-setup-hook))
 
+(defun grep-probe (command args &optional func result)
+  (equal (condition-case nil
+            (apply (or func 'process-file) command args)
+          (error nil))
+        (or result 0)))
+
 ;;;###autoload
 (defun grep-compute-defaults ()
-  (unless (or (not grep-use-null-device) (eq grep-use-null-device t))
-    (setq grep-use-null-device
-         (with-temp-buffer
-           (let ((hello-file (expand-file-name "HELLO" data-directory)))
-             (not
-              (and (equal (condition-case nil
-                              (if grep-command
-                                  ;; `grep-command' is already set, so
-                                  ;; use that for testing.
-                                  (call-process-shell-command
-                                   grep-command nil t nil
-                                   "^English" hello-file)
-                                ;; otherwise use `grep-program'
-                                (call-process grep-program nil t nil
-                                              "-nH" "^English" hello-file))
-                            (error nil))
-                          0)
-                   (progn
-                     (goto-char (point-min))
-                     (looking-at
-                      (concat (regexp-quote hello-file)
-                              ":[0-9]+:English")))))))))
-  (unless grep-command
+  ;; Keep default values.
+  (unless grep-host-defaults-alist
+    (add-to-list
+     'grep-host-defaults-alist
+     (cons nil
+          `((grep-command ,grep-command)
+            (grep-template ,grep-template)
+            (grep-use-null-device ,grep-use-null-device)
+            (grep-find-command ,grep-find-command)
+            (grep-find-template ,grep-find-template)
+            (grep-find-use-xargs ,grep-find-use-xargs)
+            (grep-highlight-matches ,grep-highlight-matches)))))
+  (let* ((host-id
+         (intern (or (file-remote-p default-directory 'host) "localhost")))
+        (host-defaults (assq host-id grep-host-defaults-alist))
+        (defaults (assq nil grep-host-defaults-alist)))
+    ;; There are different defaults on different hosts.  They must be
+    ;; computed for every host once.
     (setq grep-command
-         (let ((required-options (if grep-use-null-device "-n" "-nH")))
-           (if (equal (condition-case nil ; in case "grep" isn't in exec-path
-                          (call-process grep-program nil nil nil
-                                        "-e" "foo" null-device)
-                        (error nil))
-                      1)
-               (format "%s %s -e " grep-program required-options)
-             (format "%s %s " grep-program required-options)))))
-  (unless grep-find-use-xargs
-    (setq grep-find-use-xargs
-         (if (and
-               (equal (call-process "find" nil nil nil
-                                    null-device "-print0")
-                      0)
-               (equal (call-process "xargs" nil nil nil
-                                    "-0" "-e" "echo")
-                     0))
-             'gnu)))
-  (unless grep-find-command
-    (setq grep-find-command
-          (cond ((eq grep-find-use-xargs 'gnu)
-                (format "%s . -type f -print0 | xargs -0 -e %s"
-                        find-program grep-command))
-               (grep-find-use-xargs
-                (format "%s . -type f -print | xargs %s"
-                         find-program grep-command))
-               (t (cons (format "%s . -type f -exec %s {} %s \\;"
-                                find-program grep-command null-device)
-                        (+ 22 (length grep-command)))))))
-  (unless grep-tree-command
-    (setq grep-tree-command
-         (let* ((glen (length grep-program))
-                (gcmd (concat grep-program " <C>" (substring grep-command glen))))
-           (cond ((eq grep-find-use-xargs 'gnu)
-                  (format "%s <D> <X> -type f <F> -print0 | xargs -0 -e %s <R>"
-                          find-program gcmd))
-                 (grep-find-use-xargs
-                  (format "%s <D> <X> -type f <F> -print | xargs %s <R>"
-                          find-program gcmd))
-                 (t (format "%s <D> <X> -type f <F> -exec %s <R> {} %s \\;"
-                            find-program gcmd null-device))))))
-  (unless (or (not grep-highlight-matches) (eq grep-highlight-matches t))
-    (setq grep-highlight-matches
-         (with-temp-buffer
-           (and (equal (condition-case nil
-                           (call-process grep-program nil t nil "--help")
-                         (error nil))
-                       0)
-                (progn
-                  (goto-char (point-min))
-                  (search-forward "--color" nil t))
-                t)))))
+         (or (cadr (assq 'grep-command host-defaults))
+             (cadr (assq 'grep-command defaults)))
+
+         grep-template
+          (or (cadr (assq 'grep-template host-defaults))
+             (cadr (assq 'grep-template defaults)))
+
+         grep-use-null-device
+         (or (cadr (assq 'grep-use-null-device host-defaults))
+             (cadr (assq 'grep-use-null-device defaults)))
+
+         grep-find-command
+         (or (cadr (assq 'grep-find-command host-defaults))
+             (cadr (assq 'grep-find-command defaults)))
+
+         grep-find-template
+         (or (cadr (assq 'grep-find-template host-defaults))
+             (cadr (assq 'grep-find-template defaults)))
+
+         grep-find-use-xargs
+         (or (cadr (assq 'grep-find-use-xargs host-defaults))
+             (cadr (assq 'grep-find-use-xargs defaults)))
+
+         grep-highlight-matches
+         (or (cadr (assq 'grep-highlight-matches host-defaults))
+             (cadr (assq 'grep-highlight-matches defaults))))
+
+    (unless (or (not grep-use-null-device) (eq grep-use-null-device t))
+      (setq grep-use-null-device
+           (with-temp-buffer
+             (let ((hello-file (expand-file-name "HELLO" data-directory)))
+               (not
+                (and (if grep-command
+                         ;; `grep-command' is already set, so
+                         ;; use that for testing.
+                         (grep-probe grep-command
+                                     `(nil t nil "^English" ,hello-file)
+                                     #'call-process-shell-command)
+                       ;; otherwise use `grep-program'
+                       (grep-probe grep-program
+                                   `(nil t nil "-nH" "^English" ,hello-file)))
+                     (progn
+                       (goto-char (point-min))
+                       (looking-at
+                        (concat (regexp-quote hello-file)
+                                ":[0-9]+:English")))))))))
+    (unless (and grep-command grep-find-command
+                grep-template grep-find-template)
+      (let ((grep-options
+            (concat (if grep-use-null-device "-n" "-nH")
+                    (if (grep-probe grep-program
+                                    `(nil nil nil "-e" "foo" ,null-device)
+                                    nil 1)
+                        " -e"))))
+       (unless grep-command
+         (setq grep-command
+               (format "%s %s " grep-program grep-options)))
+       (unless grep-template
+         (setq grep-template
+               (format "%s <C> %s <R> <F>" grep-program grep-options)))
+       (unless grep-find-use-xargs
+         (setq grep-find-use-xargs
+               (cond
+                ((and
+                  (grep-probe find-program `(nil nil nil ,null-device "-print0"))
+                  (grep-probe "xargs" `(nil nil nil "-0" "-e" "echo")))
+                 'gnu)
+                (t
+                 'exec))))
+       (unless grep-find-command
+         (setq grep-find-command
+               (cond ((eq grep-find-use-xargs 'gnu)
+                      (format "%s . -type f -print0 | xargs -0 -e %s"
+                              find-program grep-command))
+                     ((eq grep-find-use-xargs 'exec)
+                      (let ((cmd0 (format "%s . -type f -exec %s"
+                                          find-program grep-command)))
+                        (cons
+                         (format "%s {} %s %s"
+                                 cmd0 null-device
+                                 (shell-quote-argument ";"))
+                         (1+ (length cmd0)))))
+                     (t
+                      (format "%s . -type f -print | xargs %s"
+                              find-program grep-command)))))
+       (unless grep-find-template
+         (setq grep-find-template
+               (let ((gcmd (format "%s <C> %s <R>"
+                                   grep-program grep-options)))
+                 (cond ((eq grep-find-use-xargs 'gnu)
+                        (format "%s . <X> -type f <F> -print0 | xargs -0 -e %s"
+                                find-program gcmd))
+                       ((eq grep-find-use-xargs 'exec)
+                        (format "%s . <X> -type f <F> -exec %s {} %s %s"
+                                find-program gcmd null-device
+                                (shell-quote-argument ";")))
+                       (t
+                        (format "%s . <X> -type f <F> -print | xargs %s"
+                                find-program gcmd))))))))
+    (unless (or (not grep-highlight-matches) (eq grep-highlight-matches t))
+      (setq grep-highlight-matches
+           (with-temp-buffer
+             (and (grep-probe grep-program '(nil t nil "--help"))
+                  (progn
+                    (goto-char (point-min))
+                    (search-forward "--color" nil t))
+                  t))))
+
+    ;; Save defaults for this host.
+    (setq grep-host-defaults-alist
+         (delete (assq host-id grep-host-defaults-alist)
+                 grep-host-defaults-alist))
+    (add-to-list
+     'grep-host-defaults-alist
+     (cons host-id
+          `((grep-command ,grep-command)
+            (grep-template ,grep-template)
+            (grep-use-null-device ,grep-use-null-device)
+            (grep-find-command ,grep-find-command)
+            (grep-find-template ,grep-find-template)
+            (grep-find-use-xargs ,grep-find-use-xargs)
+            (grep-highlight-matches ,grep-highlight-matches))))))
+
+(defun grep-tag-default ()
+  (or (and transient-mark-mode mark-active
+          (/= (point) (mark))
+          (buffer-substring-no-properties (point) (mark)))
+      (funcall (or find-tag-default-function
+                  (get major-mode 'find-tag-default-function)
+                  'find-tag-default))
+      ""))
 
 (defun grep-default-command ()
-  (let ((tag-default
-         (shell-quote-argument
-          (or (funcall (or find-tag-default-function
-                           (get major-mode 'find-tag-default-function)
-                           'find-tag-default))
-              "")))
+  "Compute the default grep command for C-u M-x grep to offer."
+  (let ((tag-default (shell-quote-argument (grep-tag-default)))
+       ;; This a regexp to match single shell arguments.
+       ;; Could someone please add comments explaining it?
        (sh-arg-re "\\(\\(?:\"\\(?:[^\"]\\|\\\\\"\\)+\"\\|'[^']+'\\|[^\"' \t\n]\\)+\\)")
        (grep-default (or (car grep-history) grep-command)))
-    ;; Replace the thing matching for with that around cursor.
+    ;; In the default command, find the arg that specifies the pattern.
     (when (or (string-match
               (concat "[^ ]+\\s +\\(?:-[^ ]+\\s +\\)*"
                       sh-arg-re "\\(\\s +\\(\\S +\\)\\)?")
               grep-default)
              ;; If the string is not yet complete.
              (string-match "\\(\\)\\'" grep-default))
-      (unless (or (not (stringp buffer-file-name))
-                 (when (match-beginning 2)
-                   (save-match-data
-                     (string-match
-                      (wildcard-to-regexp
-                       (file-name-nondirectory
-                        (match-string 3 grep-default)))
-                      (file-name-nondirectory buffer-file-name)))))
-       (setq grep-default (concat (substring grep-default
-                                             0 (match-beginning 2))
-                                  " *."
-                                  (file-name-extension buffer-file-name))))
+      ;; Maybe we will replace the pattern with the default tag.
+      ;; But first, maybe replace the file name pattern.
+      (condition-case nil
+         (unless (or (not (stringp buffer-file-name))
+                     (when (match-beginning 2)
+                       (save-match-data
+                         (string-match
+                          (wildcard-to-regexp
+                           (file-name-nondirectory
+                            (match-string 3 grep-default)))
+                          (file-name-nondirectory buffer-file-name)))))
+           (setq grep-default (concat (substring grep-default
+                                                 0 (match-beginning 2))
+                                      " *."
+                                      (file-name-extension buffer-file-name))))
+       ;; In case wildcard-to-regexp gets an error
+       ;; from invalid data.
+       (error nil))
+      ;; Now replace the pattern with the default tag.
       (replace-match tag-default t t grep-default 1))))
 
+
 ;;;###autoload
-(defun grep (command-args &optional highlight-regexp)
+(define-compilation-mode grep-mode "Grep"
+  "Sets `grep-last-buffer' and `compilation-window-height'."
+  (setq grep-last-buffer (current-buffer))
+  (set (make-local-variable 'compilation-error-face)
+       grep-hit-face)
+  (set (make-local-variable 'compilation-error-regexp-alist)
+       grep-regexp-alist)
+  (set (make-local-variable 'compilation-process-setup-function)
+       'grep-process-setup)
+  (set (make-local-variable 'compilation-disable-input) t))
+
+
+;;;###autoload
+(defun grep (command-args)
   "Run grep, with user-specified args, and collect output in a buffer.
 While grep runs asynchronously, you can use \\[next-error] (M-x next-error),
 or \\<grep-mode-map>\\[compile-goto-error] in the grep \
 output buffer, to go to the lines
 where grep found matches.
 
+For doing a recursive `grep', see the `rgrep' command.  For running
+`grep' in a specific directory, see `lgrep'.
+
 This command uses a special history list for its COMMAND-ARGS, so you can
 easily repeat a grep command.
 
 A prefix argument says to default the argument based upon the current
 tag the cursor is over, substituting it into the last grep command
 in the grep command history (or into `grep-command'
-if that history list is empty).
-
-If specified, optional second arg HIGHLIGHT-REGEXP is the regexp to
-temporarily highlight in visited source lines."
+if that history list is empty)."
   (interactive
    (progn
-     (unless (and grep-command
-                 (or (not grep-use-null-device) (eq grep-use-null-device t)))
-       (grep-compute-defaults))
+     (grep-compute-defaults)
      (let ((default (grep-default-command)))
        (list (read-from-minibuffer "Run grep (like this): "
                                   (if current-prefix-arg
@@ -522,19 +616,8 @@ temporarily highlight in visited source lines."
   (compilation-start (if (and grep-use-null-device null-device)
                         (concat command-args " " null-device)
                       command-args)
-                    'grep-mode nil highlight-regexp))
+                    'grep-mode))
 
-;;;###autoload
-(define-compilation-mode grep-mode "Grep"
-  "Sets `grep-last-buffer' and `compilation-window-height'."
-  (setq grep-last-buffer (current-buffer))
-  (set (make-local-variable 'compilation-error-face)
-       grep-hit-face)
-  (set (make-local-variable 'compilation-error-regexp-alist)
-       grep-regexp-alist)
-  (set (make-local-variable 'compilation-process-setup-function)
-       'grep-process-setup)
-  (set (make-local-variable 'compilation-disable-input) t))
 
 ;;;###autoload
 (defun grep-find (command-args)
@@ -547,9 +630,7 @@ This command uses a special history list for its arguments, so you can
 easily repeat a find command."
   (interactive
    (progn
-     (unless (and grep-command
-                 (or (not grep-use-null-device) (eq grep-use-null-device t)))
-       (grep-compute-defaults))
+     (grep-compute-defaults)
      (if grep-find-command
         (list (read-from-minibuffer "Run find (like this): "
                                     grep-find-command nil nil
@@ -558,91 +639,215 @@ easily repeat a find command."
        (read-string
         "compile.el: No `grep-find-command' command available. Press RET.")
        (list nil))))
-  (when (and grep-find-command command-args)
+  (when command-args
     (let ((null-device nil))           ; see grep
       (grep command-args))))
 
 ;;;###autoload
 (defalias 'find-grep 'grep-find)
 
-(defun grep-expand-command-macros (command &optional regexp files dir excl case-fold)
-  "Patch grep COMMAND replacing <D>, etc."
-  (setq command
-       (replace-regexp-in-string "<D>"
-                                 (or dir ".") command t t))
-  (setq command
-       (replace-regexp-in-string "<X>"
-                                 (or excl "") command t t))
-  (setq command
-       (replace-regexp-in-string "<F>"
-                                 (or files "") command t t))
-  (setq command
-       (replace-regexp-in-string "<C>"
-                                 (if case-fold "-i" "") command t t))
-  (setq command
-       (replace-regexp-in-string "<R>"
-                                 (or regexp "") command t t))
-  command)
-
-(defvar grep-tree-last-regexp "")
-(defvar grep-tree-last-files (car (car grep-tree-files-aliases)))
+
+;; User-friendly interactive API.
+
+(defconst grep-expand-keywords
+  '(("<C>" . (and cf (isearch-no-upper-case-p regexp t) "-i"))
+    ("<D>" . dir)
+    ("<F>" . files)
+    ("<N>" . null-device)
+    ("<X>" . excl)
+    ("<R>" . (shell-quote-argument (or regexp ""))))
+  "List of substitutions performed by `grep-expand-template'.
+If car of an element matches, the cdr is evalled in to get the
+substitution string.  Note dynamic scoping of variables.")
+
+(defun grep-expand-template (template &optional regexp files dir excl)
+  "Patch grep COMMAND string replacing <C>, <D>, <F>, <R>, and <X>."
+  (let ((command template)
+       (cf case-fold-search)
+       (case-fold-search nil))
+    (dolist (kw grep-expand-keywords command)
+      (if (string-match (car kw) command)
+         (setq command
+               (replace-match
+                (or (if (symbolp (cdr kw))
+                        (symbol-value (cdr kw))
+                      (save-match-data (eval (cdr kw))))
+                    "")
+                t t command))))))
+
+(defun grep-read-regexp ()
+  "Read regexp arg for interactive grep."
+  (let ((default (grep-tag-default)))
+    (read-string
+     (concat "Search for"
+            (if (and default (> (length default) 0))
+                (format " (default \"%s\"): " default) ": "))
+     nil 'grep-regexp-history default)))
+
+(defun grep-read-files (regexp)
+  "Read files arg for interactive grep."
+  (let* ((bn (or (buffer-file-name) (buffer-name)))
+        (fn (and bn
+                 (stringp bn)
+                 (file-name-nondirectory bn)))
+        (default
+          (or (and fn
+                   (let ((aliases grep-files-aliases)
+                         alias)
+                     (while aliases
+                       (setq alias (car aliases)
+                             aliases (cdr aliases))
+                       (if (string-match (wildcard-to-regexp (cdr alias)) fn)
+                           (setq aliases nil)
+                         (setq alias nil)))
+                     (cdr alias)))
+              (and fn
+                   (let ((ext (file-name-extension fn)))
+                     (and ext (concat "*." ext))))
+              (car grep-files-history)
+              (car (car grep-files-aliases))))
+        (files (read-string
+                (concat "Search for \"" regexp
+                        "\" in files"
+                        (if default (concat " (default " default ")"))
+                        ": ")
+                nil 'grep-files-history default)))
+    (and files
+        (or (cdr (assoc files grep-files-aliases))
+            files))))
 
 ;;;###autoload
-(defun grep-tree (regexp files dir &optional subdirs)
-  "Grep for REGEXP in FILES in directory tree rooted at DIR.
-Collect output in a buffer.
-Interactively, prompt separately for each search parameter.
-With prefix arg, reuse previous REGEXP.
+(defun lgrep (regexp &optional files dir)
+  "Run grep, searching for REGEXP in FILES in directory DIR.
 The search is limited to file names matching shell pattern FILES.
-FILES may use abbreviations defined in `grep-tree-files-aliases', e.g.
+FILES may use abbreviations defined in `grep-files-aliases', e.g.
 entering `ch' is equivalent to `*.[ch]'.
 
-While find runs asynchronously, you can use the \\[next-error] command
-to find the text that grep hits refer to.
+With \\[universal-argument] prefix, you can edit the constructed shell command line
+before it is executed.
+With two \\[universal-argument] prefixes, directly edit and run `grep-command'.
 
-This command uses a special history list for its arguments, so you can
-easily repeat a find command.
+Collect output in a buffer.  While grep runs asynchronously, you
+can use \\[next-error] (M-x next-error), or \\<grep-mode-map>\\[compile-goto-error]
+in the grep output buffer, to go to the lines where grep found matches.
 
-When used non-interactively, optional arg SUBDIRS limits the search to
-those sub directories of DIR."
+This command shares argument histories with \\[rgrep] and \\[grep]."
   (interactive
-   (let* ((regexp
-          (if current-prefix-arg
-              grep-tree-last-regexp
-            (let* ((default (current-word))
-                   (spec (read-string
-                          (concat "Search for"
-                                  (if (and default (> (length default) 0))
-                                      (format " (default %s): " default) ": ")))))
-              (if (equal spec "") default spec))))
-         (files
-          (read-string (concat "Search for \"" regexp "\" in files (default "   grep-tree-last-files  "): ")))
-         (dir
-          (read-directory-name "Base directory: " nil default-directory t)))
-     (list regexp files dir)))
-  (unless grep-tree-command
-    (grep-compute-defaults))
-  (unless (and (stringp files) (> (length files) 0))
-    (setq files grep-tree-last-files))
-  (when files
-    (setq grep-tree-last-files files)
-    (let ((mf (assoc files grep-tree-files-aliases)))
-      (if mf
-         (setq files (cdr mf)))))
-  (let ((command-args (grep-expand-command-macros
-                      grep-tree-command
-                      (setq grep-tree-last-regexp regexp)
-                      (and files (concat "-name '" files "'"))
-                      (if subdirs
-                          (if (stringp subdirs)
-                              subdirs
-                            (mapconcat 'identity subdirs " "))
-                        nil)  ;; we change default-directory to dir
-                      (and grep-tree-ignore-CVS-directories "-path '*/CVS' -prune -o ")
-                      grep-tree-ignore-case))
-       (default-directory (file-name-as-directory (expand-file-name dir)))
-       (null-device nil))              ; see grep
-    (grep command-args regexp)))
+   (progn
+     (grep-compute-defaults)
+     (cond
+      ((and grep-command (equal current-prefix-arg '(16)))
+       (list (read-from-minibuffer "Run: " grep-command
+                                  nil nil 'grep-history)
+            nil))
+      ((not grep-template)
+       (list nil
+            (read-string "grep.el: No `grep-template' available. Press RET.")))
+      (t (let* ((regexp (grep-read-regexp))
+               (files (grep-read-files regexp))
+               (dir (read-directory-name "In directory: "
+                                         nil default-directory t)))
+          (list regexp files dir))))))
+  (when (and (stringp regexp) (> (length regexp) 0))
+    (let ((command regexp))
+      (if (null files)
+         (if (string= command grep-command)
+             (setq command nil))
+       (setq dir (file-name-as-directory (expand-file-name dir)))
+       (setq command (grep-expand-template
+                      grep-template
+                      regexp
+                      files))
+       (when command
+         (if (equal current-prefix-arg '(4))
+             (setq command
+                   (read-from-minibuffer "Confirm: "
+                                         command nil nil 'grep-history))
+           (add-to-history 'grep-history command))))
+      (when command
+       (let ((default-directory dir))
+         ;; Setting process-setup-function makes exit-message-function work
+         ;; even when async processes aren't supported.
+         (compilation-start (if (and grep-use-null-device null-device)
+                                (concat command " " null-device)
+                              command)
+                            'grep-mode))
+       (if (eq next-error-last-buffer (current-buffer))
+           (setq default-directory dir))))))
+
+
+
+;;;###autoload
+(defun rgrep (regexp &optional files dir)
+  "Recursively grep for REGEXP in FILES in directory tree rooted at DIR.
+The search is limited to file names matching shell pattern FILES.
+FILES may use abbreviations defined in `grep-files-aliases', e.g.
+entering `ch' is equivalent to `*.[ch]'.
+
+With \\[universal-argument] prefix, you can edit the constructed shell command line
+before it is executed.
+With two \\[universal-argument] prefixes, directly edit and run `grep-find-command'.
+
+Collect output in a buffer.  While find runs asynchronously, you
+can use \\[next-error] (M-x next-error), or \\<grep-mode-map>\\[compile-goto-error]
+in the grep output buffer, to go to the lines where grep found matches.
+
+This command shares argument histories with \\[lgrep] and \\[grep-find]."
+  (interactive
+   (progn
+     (grep-compute-defaults)
+     (cond
+      ((and grep-find-command (equal current-prefix-arg '(16)))
+       (list (read-from-minibuffer "Run: " grep-find-command
+                                  nil nil 'grep-find-history)
+            nil))
+      ((not grep-find-template)
+       (list nil nil
+            (read-string "grep.el: No `grep-find-template' available. Press RET.")))
+      (t (let* ((regexp (grep-read-regexp))
+               (files (grep-read-files regexp))
+               (dir (read-directory-name "Base directory: "
+                                         nil default-directory t)))
+          (list regexp files dir))))))
+  (when (and (stringp regexp) (> (length regexp) 0))
+    (if (null files)
+       (if (not (string= regexp grep-find-command))
+           (compilation-start regexp 'grep-mode))
+      (setq dir (file-name-as-directory (expand-file-name dir)))
+      (let ((command (grep-expand-template
+                     grep-find-template
+                     regexp
+                     (concat (shell-quote-argument "(")
+                             " -name "
+                             (mapconcat #'shell-quote-argument
+                                        (split-string files)
+                                        " -o -name ")
+                             " "
+                             (shell-quote-argument ")"))
+                      dir
+                      (and grep-find-ignored-directories
+                           (concat (shell-quote-argument "(")
+                                   ;; we should use shell-quote-argument here
+                                   " -path "
+                                   (mapconcat #'(lambda (dir)
+                                                  (shell-quote-argument
+                                                   (concat "*/" dir)))
+                                              grep-find-ignored-directories
+                                              " -o -path ")
+                                   " "
+                                   (shell-quote-argument ")")
+                                   " -prune -o ")))))
+       (when command
+         (if current-prefix-arg
+             (setq command
+                   (read-from-minibuffer "Confirm: "
+                                         command nil nil 'grep-find-history))
+           (add-to-history 'grep-find-history command))
+         (let ((default-directory dir))
+           (compilation-start command 'grep-mode))
+         ;; Set default-directory if we started rgrep in the *grep* buffer.
+         (if (eq next-error-last-buffer (current-buffer))
+             (setq default-directory dir)))))))
 
 
 (provide 'grep)