X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/4ffc92c2d8cffb5a8ed984bd2556ff7b124ccb0a..bdaf8a62d53cf8d5a0dc4f0dc530ecd6fc1f44fe:/lisp/progmodes/grep.el diff --git a/lisp/progmodes/grep.el b/lisp/progmodes/grep.el index 6afa3f2934..9151864193 100644 --- a/lisp/progmodes/grep.el +++ b/lisp/progmodes/grep.el @@ -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 ;; 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) @@ -48,23 +48,6 @@ :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: + - place to put -i if case insensitive grep. + - file names and wildcards to search. + - the regular expression searched for. + - 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 " " (substring grep-command glen)))) - (cond ((eq grep-find-use-xargs 'gnu) - (format "%s -type f -print0 | xargs -0 -e %s " - find-program gcmd)) - (grep-find-use-xargs - (format "%s -type f -print | xargs %s " - find-program gcmd)) - (t (format "%s -type f -exec %s {} %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 %s " 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 %s " + grep-program grep-options))) + (cond ((eq grep-find-use-xargs 'gnu) + (format "%s . -type f -print0 | xargs -0 -e %s" + find-program gcmd)) + ((eq grep-find-use-xargs 'exec) + (format "%s . -type f -exec %s {} %s %s" + find-program gcmd null-device + (shell-quote-argument ";"))) + (t + (format "%s . -type 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 \\\\[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 , etc." - (setq command - (replace-regexp-in-string "" - (or dir ".") command t t)) - (setq command - (replace-regexp-in-string "" - (or excl "") command t t)) - (setq command - (replace-regexp-in-string "" - (or files "") command t t)) - (setq command - (replace-regexp-in-string "" - (if case-fold "-i" "") command t t)) - (setq command - (replace-regexp-in-string "" - (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 + '(("" . (and cf (isearch-no-upper-case-p regexp t) "-i")) + ("" . dir) + ("" . files) + ("" . null-device) + ("" . excl) + ("" . (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 , , , , and ." + (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 \\\\[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 \\\\[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)