;;; 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
;; 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,
("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)
;; 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'.")
(": \\(.+\\): \\(?: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))
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.")
;;;###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.")
(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 ()
(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 <C> %s <R> <F>" 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 <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)))
- (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 <C> %s <R>"
- grep-program grep-options)))
+ 'gnu)
+ (t
+ 'exec))))
+ (unless grep-find-command
+ (setq grep-find-command
(cond ((eq grep-find-use-xargs 'gnu)
- (format "%s . <X> -type f <F> -print0 | xargs -0 -e %s"
- find-program gcmd))
- (grep-find-use-xargs
- (format "%s . <X> -type f <F> -print | xargs %s"
- find-program gcmd))
- (t (format "%s . <X> -type f <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 <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))))
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.
(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))))
(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)
(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"
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 \\<grep-mode-map>\\[compile-goto-error]
(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
(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 \\<grep-mode-map>\\[compile-goto-error]
(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)