-;;; grep.el --- run compiler as inferior of Emacs, parse error messages
+;;; grep.el --- run Grep as inferior of Emacs, parse match messages
-;; Copyright (C) 1985, 86, 87, 93, 94, 95, 96, 97, 98, 1999, 2001, 02, 2004
-;; Free Software Foundation, Inc.
+;; Copyright (C) 1985, 1986, 1987, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+;; 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,
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
;;; Commentary:
(require 'compile)
+
(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)
"*Number of lines in a grep window. If nil, use `compilation-window-height'."
:type '(choice (const :tag "Default" nil)
integer)
- :version "21.4"
+ :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 "21.4"
+(defcustom grep-highlight-matches 'auto-detect
+ "If t, use special markers to highlight grep matches.
+
+Some grep programs are able to surround matches with special
+markers in grep output. Such markers can be used to highlight
+matches in grep mode.
+
+This option sets the environment variable GREP_COLOR to specify
+markers for highlighting and GREP_OPTIONS to add the --color
+option in front of any explicit grep options before starting
+the grep.
+
+The default value of this variable is set up by `grep-compute-defaults';
+call that function before using this variable in your program."
+ :type '(choice (const :tag "Do not highlight matches with grep markers" nil)
+ (const :tag "Highlight matches with grep markers" t)
+ (other :tag "Not Set" auto-detect))
+ :version "22.1"
:group 'grep)
(defcustom grep-scroll-output nil
output window so that the end of the output is always visible rather
than the begining."
:type 'boolean
- :version "21.4"
+ :version "22.1"
:group 'grep)
+;;;###autoload
(defcustom grep-command nil
"The default grep command for \\[grep].
If the grep program used supports an option to always include file names
(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
The default value of this variable is set up by `grep-compute-defaults';
call that function before using this variable in your program."
- :type 'boolean
:type '(choice (const :tag "Do Not Append Null Device" nil)
(const :tag "Append Null Device" t)
(other :tag "Not Set" auto-detect))
:group 'grep)
+;;;###autoload
(defcustom grep-find-command nil
"The default find command for \\[grep-find].
The default value of this variable is set up by `grep-compute-defaults';
(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:
<R> - the regular expression searched for."
:type '(choice string
(const :tag "Not Set" nil))
- :version "21.4"
+ :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
+(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-tree-ignore-CVS-directories t
- "*If non-nil, `grep-tree' does no recurse into CVS directories."
- :type 'boolean
+(defcustom grep-error-screen-columns nil
+ "*If non-nil, column numbers in grep hits are screen columns.
+See `compilation-error-screen-columns'"
+ :type '(choice (const :tag "Default" nil)
+ integer)
+ :version "22.1"
:group 'grep)
;;;###autoload
(let ((map (cons 'keymap compilation-minor-mode-map)))
(define-key map " " 'scroll-up)
(define-key map "\^?" 'scroll-down)
-
- ;; This is intolerable -- rms
-;;; (define-key map [remap next-line] 'compilation-next-error)
-;;; (define-key map [remap previous-line] 'compilation-previous-error)
+ (define-key map "\C-c\C-f" 'next-error-follow-minor-mode)
(define-key map "\r" 'compile-goto-error) ;; ?
(define-key map "n" 'next-error-no-select)
(define-key map "p" 'previous-error-no-select)
(define-key map "{" 'compilation-previous-file)
(define-key map "}" 'compilation-next-file)
- (define-key map "\t" 'compilation-next-file)
+ (define-key map "\t" 'compilation-next-error)
+ (define-key map [backtab] 'compilation-previous-error)
;; Set up the menu-bar
(define-key map [menu-bar grep]
(define-key map [menu-bar grep compilation-compile]
'("Compile..." . compile))
(define-key map [menu-bar grep compilation-grep]
- '("Another grep" . 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]
"Keymap for grep buffers.
`compilation-minor-mode-map' is a cdr of this.")
+(defalias 'kill-grep 'kill-compilation)
+
;;;; TODO --- refine this!!
;;; (defcustom grep-use-compilation-buffer t
;; 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'.")
-;; Note: the character class after the optional drive letter does not
-;; include a space to support file names with blanks.
+;;;###autoload
(defvar grep-regexp-alist
- '(("\\([a-zA-Z]?:?.+?\\)[:( \t]+\\([0-9]+\\)[:) \t]" 1 2))
+ '(("^\\(.+?\\)\\(:[ \t]*\\)\\([0-9]+\\)\\2"
+ 1 3)
+ ;; Rule to match column numbers is commented out since no known grep
+ ;; produces them
+ ;; ("^\\(.+?\\)\\(:[ \t]*\\)\\([0-9]+\\)\\2\\(?:\\([0-9]+\\)\\(?:-\\([0-9]+\\)\\)?\\2\\)?"
+ ;; 1 3 (4 . 5))
+ ("^\\(\\(.+?\\):\\([0-9]+\\):\\).*?\
+\\(\033\\[01;31m\\(?:\033\\[K\\)?\\)\\(.*?\\)\\(\033\\[[0-9]*m\\)"
+ 2 3
+ ;; Calculate column positions (beg . end) of first grep match on a line
+ ((lambda ()
+ (setq compilation-error-screen-columns nil)
+ (- (match-beginning 4) (match-end 1)))
+ .
+ (lambda () (- (match-end 5) (match-end 1)
+ (- (match-end 4) (match-beginning 4)))))
+ nil 1)
+ ("^Binary file \\(.+\\) matches$" 1 nil nil 0 1))
"Regexp used to match grep hits. See `compilation-error-regexp-alist'.")
-(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-error "grep hit"
+ "Message to print when no matches are found.")
+
+;; Reverse the colors because grep hits are not errors (though we jump there
+;; with `next-error'), and unreadable files can't be gone to.
+(defvar grep-hit-face compilation-info-face
+ "Face name to use for grep hits.")
+
+(defvar grep-error-face 'compilation-error
+ "Face name to use for grep error messages.")
+
+(defvar grep-match-face 'match
+ "Face name to use for grep matches.")
+
+(defvar grep-context-face 'shadow
+ "Face name to use for grep context lines.")
+
+(defvar grep-mode-font-lock-keywords
+ '(;; Command output lines.
+ ("^\\([A-Za-z_0-9/\.+-]+\\)[ \t]*:" 1 font-lock-function-name-face)
+ (": \\(.+\\): \\(?:Permission denied\\|No such \\(?:file or directory\\|device or address\\)\\)$"
+ 1 grep-error-face)
+ ;; remove match from grep-regexp-alist before fontifying
+ ("^Grep[/a-zA-z]* started.*"
+ (0 '(face nil message nil help-echo nil mouse-face nil) t))
+ ("^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[/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))
+ ("^.+?-[0-9]+-.*\n" (0 grep-context-face))
+ ;; Highlight grep matches and delete markers
+ ("\\(\033\\[01;31m\\)\\(.*?\\)\\(\033\\[[0-9]*m\\)"
+ ;; Refontification does not work after the markers have been
+ ;; deleted. So we use the font-lock-face property here as Font
+ ;; Lock does not clear that.
+ (2 (list 'face nil 'font-lock-face grep-match-face))
+ ((lambda (bound))
+ (progn
+ ;; Delete markers with `replace-match' because it updates
+ ;; the match-data, whereas `delete-region' would render it obsolete.
+ (replace-match "" t t nil 3)
+ (replace-match "" t t nil 1))))
+ ("\\(\033\\[[0-9;]*[mK]\\)"
+ ;; Delete all remaining escape sequences
+ ((lambda (bound))
+ (replace-match "" t t nil 1))))
+ "Additional things to highlight in grep output.
+This gets tacked on the end of the generated expressions.")
+
+;;;###autoload
+(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 find-program "find"
"The default find program for `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.")
;; History of grep commands.
+;;;###autoload
(defvar grep-history nil)
+;;;###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'.
-Set up `compilation-exit-message-function' and `compilation-window-height'.
-Sets `grep-last-buffer' and runs `grep-setup-hook'."
- (setq grep-last-buffer (current-buffer))
+Set up `compilation-exit-message-function' and run `grep-setup-hook'."
+ (unless (or (not grep-highlight-matches) (eq grep-highlight-matches t))
+ (grep-compute-defaults))
+ (when (eq grep-highlight-matches t)
+ ;; Modify `process-environment' locally bound in `compilation-start'
+ (setenv "GREP_OPTIONS" (concat (getenv "GREP_OPTIONS") " --color=always"))
+ ;; for GNU grep 2.5.1
+ (setenv "GREP_COLOR" "01;31")
+ ;; for GNU grep 2.5.1-cvs
+ (setenv "GREP_COLORS" "mt=01;31:fn=:ln=:bn=:se=:ml=:cx=:ne"))
(set (make-local-variable 'compilation-exit-message-function)
(lambda (status code msg)
(if (eq status 'exit)
(t
(cons msg code)))
(cons msg code))))
- (if grep-window-height
- (set (make-local-variable 'compilation-window-height)
- grep-window-height))
- (set (make-local-variable 'compile-auto-highlight)
- grep-auto-highlight)
- (set (make-local-variable 'compilation-scroll-output)
- grep-scroll-output)
(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)))))))
+ (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
- (funcall (or find-tag-default-function
- (get major-mode 'find-tag-default-function)
- ;; We use grep-tag-default instead of
- ;; find-tag-default, to avoid loading etags.
- 'grep-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))))
- (replace-match (or tag-default "") t t grep-default 1))))
+ ;; 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-minor-mode-map>\\[compile-goto-error] in the grep \
+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
;; Setting process-setup-function makes exit-message-function work
;; even when async processes aren't supported.
- (let* ((compilation-process-setup-function 'grep-process-setup)
- (buf (compile-internal (if (and grep-use-null-device null-device)
- (concat command-args " " null-device)
- command-args)
- "No more grep hits" "grep"
- ;; Give it a simpler regexp to match.
- nil grep-regexp-alist
- nil nil nil nil nil nil
- highlight-regexp grep-mode-map)))))
-
-;; This is a copy of find-tag-default from etags.el.
-(defun grep-tag-default ()
- (save-excursion
- (while (looking-at "\\sw\\|\\s_")
- (forward-char 1))
- (when (or (re-search-backward "\\sw\\|\\s_"
- (save-excursion (beginning-of-line) (point))
- t)
- (re-search-forward "\\(\\sw\\|\\s_\\)+"
- (save-excursion (end-of-line) (point))
- t))
- (goto-char (match-end 0))
- (buffer-substring (point)
- (progn (forward-sexp -1)
- (while (looking-at "\\s'")
- (forward-char 1))
- (point))))))
+ (compilation-start (if (and grep-use-null-device null-device)
+ (concat command-args " " null-device)
+ command-args)
+ 'grep-mode))
+
;;;###autoload
(defun grep-find (command-args)
easily repeat a find command."
(interactive
(progn
- (unless grep-find-command
- (grep-compute-defaults))
+ (grep-compute-defaults)
(if grep-find-command
(list (read-from-minibuffer "Run find (like this): "
grep-find-command nil nil
(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))))
-(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)))
+;;;###autoload
+(defalias 'find-grep 'grep-find)
+
+
+;; 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 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)
-;;; arch-tag: 5a5b9169-a79d-4f38-9c38-f69615f39c4d
+;; arch-tag: 5a5b9169-a79d-4f38-9c38-f69615f39c4d
;;; grep.el ends here