X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/d0afff349efb346e3a4339f4a831af53f1c6878b..bdaf8a62d53cf8d5a0dc4f0dc530ecd6fc1f44fe:/lisp/progmodes/grep.el diff --git a/lisp/progmodes/grep.el b/lisp/progmodes/grep.el index 0aba9d42b8..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, @@ -150,12 +150,15 @@ The following place holders should be present in the string: ("asm" . "*.[sS]") ("m" . "[Mm]akefile*") ("l" . "[Cc]hange[Ll]og*") + ("tex" . "*.tex") + ("texi" . "*.texi") ) "*Alist of aliases for the FILES argument to `lgrep' and `rgrep'." :type 'alist :group 'grep) -(defcustom grep-find-ignored-directories '("CVS" ".hg" "{arch}") +(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) @@ -232,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'.") @@ -282,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)) @@ -313,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.") @@ -334,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.") @@ -351,6 +343,12 @@ This variable's value takes effect when `grep-compute-defaults' is called.") (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 () @@ -379,111 +377,196 @@ Set up `compilation-exit-message-function' and run `grep-setup-hook'." (defun grep-probe (command args &optional func result) (equal (condition-case nil - (apply (or func 'call-process) command args) + (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 (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 - (if (and + ;; 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 + (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))) - (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-find-template - (setq grep-find-template - (let ((gcmd (format "%s %s " - grep-program grep-options))) + '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 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 (grep-probe grep-program '(nil t nil "--help")) - (progn - (goto-char (point-min)) - (search-forward "--color" nil t)) - t))))) + (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)))) @@ -508,6 +591,9 @@ 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. @@ -553,7 +639,7 @@ 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)))) @@ -584,22 +670,18 @@ substitution string. Note dynamic scoping of variables.") (setq command (replace-match (or (if (symbolp (cdr kw)) - (eval (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 - (or (funcall (or find-tag-default-function - (get major-mode 'find-tag-default-function) - 'find-tag-default)) - ""))) + (let ((default (grep-tag-default))) (read-string (concat "Search for" (if (and default (> (length default) 0)) - (format " (default %s): " default) ": ")) + (format " (default \"%s\"): " default) ": ")) nil 'grep-regexp-history default))) (defun grep-read-files (regexp) @@ -621,7 +703,9 @@ substitution string. Note dynamic scoping of variables.") (cdr alias))) (and fn (let ((ext (file-name-extension fn))) - (and ext (concat "*." ext)))))) + (and ext (concat "*." ext)))) + (car grep-files-history) + (car (car grep-files-aliases)))) (files (read-string (concat "Search for \"" regexp "\" in files" @@ -633,15 +717,15 @@ substitution string. Note dynamic scoping of variables.") files)))) ;;;###autoload -(defun lgrep (regexp &optional files) - "Run grep, searching for REGEXP in FILES in current directory. +(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-files-aliases', e.g. entering `ch' is equivalent to `*.[ch]'. -With \\[universal-argument] prefix, allow user to edit the constructed -shell command line before it is executed. -With two \\[universal-argument] prefixes, edit and run grep shell command. +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'. Collect output in a buffer. While grep runs asynchronously, you can use \\[next-error] (M-x next-error), or \\\\[compile-goto-error] @@ -660,13 +744,16 @@ This command shares argument histories with \\[rgrep] and \\[grep]." (list nil (read-string "grep.el: No `grep-template' available. Press RET."))) (t (let* ((regexp (grep-read-regexp)) - (files (grep-read-files regexp))) - (list regexp files)))))) + (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 @@ -676,25 +763,30 @@ This command shares argument histories with \\[rgrep] and \\[grep]." (setq command (read-from-minibuffer "Confirm: " command nil nil 'grep-history)) - (push command grep-history)))) + (add-to-history 'grep-history command)))) (when command - ;; 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))))) + (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) - "Recusively grep for REGEXP in FILES in directory tree rooted at 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, allow user to edit the constructed -shell command line before it is executed. -With two \\[universal-argument] prefixes, edit and run grep-find shell command. +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] @@ -721,29 +813,41 @@ This command shares argument histories with \\[lgrep] and \\[grep-find]." (if (null files) (if (not (string= regexp grep-find-command)) (compilation-start regexp 'grep-mode)) - (let* ((default-directory (file-name-as-directory (expand-file-name dir))) - (command (grep-expand-template - grep-find-template - regexp - (concat "\\( -name " - (mapconcat #'shell-quote-argument - (split-string files) - " -o -name ") - " \\)") - default-directory + (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 "\\( -path '*/" - (mapconcat #'identity + (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 '*/") - "' \\) -prune -o "))))) + " -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)) - (push command grep-find-history)) - (compilation-start command 'grep-mode)))))) + (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)