;;; grep.el --- run `grep' and display the results -*- lexical-binding:t -*-
-;; Copyright (C) 1985-1987, 1993-1999, 2001-2015 Free Software
+;; Copyright (C) 1985-1987, 1993-1999, 2001-2016 Free Software
;; Foundation, Inc.
;; Author: Roland McGrath <roland@gnu.org>
(defcustom grep-template nil
"The default command to run for \\[lgrep].
The following place holders should be present in the string:
- <C> - place to put -i if case insensitive grep.
+ <C> - place to put the options like -i and --color.
<F> - file names and wildcards to search.
<X> - file names and wildcards to exclude.
<R> - the regular expression searched for.
<D> - base directory for find
<X> - find options to restrict or expand the directory list
<F> - find options to limit the files matched
- <C> - place to put -i if case insensitive grep
+ <C> - place to put the grep options like -i and --color
<R> - the regular expression searched for.
In interactive usage, the actual value of this variable is set up
by `grep-compute-defaults'; to change the default value, use
:group 'grep)
(defcustom grep-files-aliases
- '(("all" . "* .*")
+ '(("all" . "* .[!.]* ..?*") ;; Don't match `..'. See bug#22577
("el" . "*.el")
("ch" . "*.[ch]")
("c" . "*.c")
(const :tag "No ignored files" nil))
:group 'grep)
+(defcustom grep-save-buffers 'ask
+ "If non-nil, save buffers before running the grep commands.
+If `ask', ask before saving. If a function, call it with no arguments
+with each buffer current, as a predicate to determine whether that
+buffer should be saved or not. E.g., one can set this to
+ (lambda ()
+ (string-prefix-p my-grep-root (file-truename (buffer-file-name))))
+to limit saving to files located under `my-grep-root'."
+ :version "25.2"
+ :type '(choice
+ (const :tag "Ask before saving" ask)
+ (const :tag "Don't save buffers" nil)
+ function
+ (other :tag "Save all buffers" t))
+ :group 'grep)
+
(defcustom grep-error-screen-columns nil
"If non-nil, column numbers in grep hits are screen columns.
See `compilation-error-screen-columns'"
(define-key map "{" 'compilation-previous-file)
(define-key map "}" 'compilation-next-file)
(define-key map "\t" 'compilation-next-error)
+ (define-key map "r" 'grep-forward-history)
+ (define-key map "l" 'grep-backward-history)
(define-key map [backtab] 'compilation-previous-error)
;; Set up the menu-bar
(define-key map [menu-bar grep compilation-next-error]
'(menu-item "Next Match" next-error
:help "Visit the next match and corresponding location"))
+ (define-key map [menu-bar grep grep-backward-history]
+ '(menu-item "Previous Command" grep-backward-history
+ :help "Run the previous grep command from the command history"))
+ (define-key map [menu-bar grep grep-forward-history]
+ '(menu-item "Next Command" grep-forward-history
+ :help "Run the next grep command from the command history"))
map)
"Keymap for grep buffers.
`compilation-minor-mode-map' is a cdr of this.")
(defvar grep-files-history nil)
;;;###autoload
-(defun grep-process-setup ()
+(defun grep-process-setup (&optional point)
"Setup compilation variables and buffer for `grep'.
-Set up `compilation-exit-message-function' and run `grep-setup-hook'."
+Set up `compilation-exit-message-function' and run
+`grep-setup-hook'. If the optional parameter POINT is given,
+point will be moved to this vicinity when the grep command has
+finished."
(when (eq grep-highlight-matches 'auto-detect)
(grep-compute-defaults))
(unless (or (eq grep-highlight-matches 'auto-detect)
;; This relies on the fact that `compilation-start'
;; sets buffer-modified to nil before running the command,
;; so the buffer is still unmodified if there is no output.
- (cond ((and (zerop code) (buffer-modified-p))
- '("finished (matches found)\n" . "matched"))
- ((not (buffer-modified-p))
- '("finished with no matches found\n" . "no match"))
- (t
- (cons msg code)))
+ (progn
+ (goto-char (min point (point-max)))
+ (cond ((and (zerop code) (buffer-modified-p))
+ '("finished (matches found)\n" . "matched"))
+ ((not (buffer-modified-p))
+ '("finished with no matches found\n" . "no match"))
+ (t
+ (cons msg code))))
(cons msg code))))
(run-hooks 'grep-setup-hook))
(unless (and grep-command grep-find-command
grep-template grep-find-template)
(let ((grep-options
- (concat (and grep-highlight-matches
- (grep-probe grep-program
- `(nil nil nil "--color" "x" ,null-device)
- nil 1)
- (if (eq grep-highlight-matches 'always)
- "--color=always " "--color "))
- (if grep-use-null-device "-n" "-nH")
+ (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)))
+ (format "%s %s %s " grep-program
+ (or
+ (and grep-highlight-matches
+ (grep-probe grep-program
+ `(nil nil nil "--color" "x" ,null-device)
+ nil 1)
+ (if (eq grep-highlight-matches 'always)
+ "--color=always" "--color"))
+ "")
+ grep-options)))
(unless grep-template
(setq grep-template
(format "%s <X> <C> %s <R> <F>" grep-program grep-options)))
;; Now replace the pattern with the default tag.
(replace-match tag-default t t grep-default 1))))
+(defvar grep--command-history nil)
+(defvar grep--history-inhibit nil)
+(defvar grep--history-place 0)
+(defvar grep--history-point 0)
;;;###autoload
(define-compilation-mode grep-mode "Grep"
;; can never match.
(set (make-local-variable 'compilation-directory-matcher) '("\\`a\\`"))
(set (make-local-variable 'compilation-process-setup-function)
- 'grep-process-setup)
+ (lambda ()
+ (grep-process-setup grep--history-point)))
(set (make-local-variable 'compilation-disable-input) t)
(set (make-local-variable 'compilation-error-screen-columns)
grep-error-screen-columns)
(add-hook 'compilation-filter-hook 'grep-filter nil t))
+(defun grep--save-history (command)
+ (unless grep--history-inhibit
+ (when grep--command-history
+ (setcar (cdr (car grep--command-history)) (point)))
+ (push (list default-directory 0 command)
+ grep--command-history)
+ (setq grep--history-place 0)
+ ;; Don't let the history grow without bounds.
+ (when (> (length grep--command-history) 100)
+ (setcdr (nthcdr 100 grep--command-history) nil))))
+
+(defun grep-forward-history ()
+ "Go to the next result in the grep command history.
+Also see `grep-backward-history'."
+ (interactive)
+ (let ((elem (and (> grep--history-place 0)
+ (nth (1- grep--history-place) grep--command-history)))
+ (grep--history-inhibit t))
+ (unless elem
+ (error "Nothing further in the command history"))
+ (setcar (cdr (nth grep--history-place grep--command-history)) (point))
+ (cl-decf grep--history-place)
+ (let ((default-directory (car elem))
+ (grep--history-point (nth 1 elem)))
+ (grep (nth 2 elem)))))
+
+(defun grep-backward-history ()
+ "Go to the previous result in the grep command history.
+Also see `grep-forward-history'."
+ (interactive)
+ (let ((elem (nth (1+ grep--history-place) grep--command-history))
+ (grep--history-inhibit t))
+ (unless elem
+ (error "Nothing further in the command history"))
+ (setcar (cdr (nth grep--history-place grep--command-history)) (point))
+ (cl-incf grep--history-place)
+ (let ((default-directory (car elem))
+ (grep--history-point (nth 1 elem)))
+ (grep (nth 2 elem)))))
+
+(defun grep--save-buffers ()
+ (when grep-save-buffers
+ (save-some-buffers (and (not (eq grep-save-buffers 'ask))
+ (not (functionp grep-save-buffers)))
+ (and (functionp grep-save-buffers)
+ grep-save-buffers))))
;;;###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),
+ "Run Grep with user-specified COMMAND-ARGS, 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* \
-buffer, to go to the lines where grep found
-matches. To kill the grep job before it finishes, type \\[kill-compilation].
+buffer, to go to the lines where Grep found
+matches. To kill the Grep job before it finishes, type \\[kill-compilation].
+
+Noninteractively, COMMAND-ARGS should specify the Grep command-line
+arguments.
For doing a recursive `grep', see the `rgrep' command. For running
-`grep' in a specific directory, see `lgrep'.
+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
+A prefix argument says to default the COMMAND-ARGS based on 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)."
(interactive
(progn
(if current-prefix-arg default grep-command)
'grep-history
(if current-prefix-arg nil default))))))
-
+ (grep--save-history command-args)
+ (grep--save-buffers)
;; 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)
;; User-friendly interactive API.
(defconst grep-expand-keywords
- '(("<C>" . (and cf (isearch-no-upper-case-p regexp t) "-i"))
+ '(("<C>" . (mapconcat #'identity opts " "))
("<D>" . (or dir "."))
("<F>" . files)
("<N>" . null-device)
(defun grep-expand-template (template &optional regexp files dir excl)
"Patch grep COMMAND string replacing <C>, <D>, <F>, <R>, and <X>."
(let* ((command template)
- (env `((cf . ,case-fold-search)
+ (env `((opts . ,(let (opts)
+ (when (and case-fold-search
+ (isearch-no-upper-case-p regexp t))
+ (push "-i" opts))
+ (cond
+ ((eq grep-highlight-matches 'always)
+ (push "--color=always" opts))
+ ((eq grep-highlight-matches 'auto)
+ (push "--color" opts)))
+ opts))
(excl . ,excl)
(dir . ,dir)
(files . ,files)
(let ((default-directory dir))
;; Setting process-setup-function makes exit-message-function work
;; even when async processes aren't supported.
+ (grep--save-buffers)
(compilation-start (if (and grep-use-null-device null-device)
(concat command " " null-device)
command)
(read-from-minibuffer "Confirm: "
command nil nil 'grep-find-history))
(add-to-history 'grep-find-history command))
+ (grep--save-buffers)
(let ((default-directory dir))
(compilation-start command 'grep-mode))
;; Set default-directory if we started rgrep in the *grep* buffer.
;; we should use shell-quote-argument here
" -path "
(mapconcat
- #'(lambda (ignore)
- (cond ((stringp ignore)
- (shell-quote-argument
- (concat "*/" ignore)))
- ((consp ignore)
- (and (funcall (car ignore) dir)
- (shell-quote-argument
- (concat "*/"
- (cdr ignore)))))))
- grep-find-ignored-directories
+ 'identity
+ (delq nil (mapcar
+ #'(lambda (ignore)
+ (cond ((stringp ignore)
+ (shell-quote-argument
+ (concat "*/" ignore)))
+ ((consp ignore)
+ (and (funcall (car ignore) dir)
+ (shell-quote-argument
+ (concat "*/"
+ (cdr ignore)))))))
+ grep-find-ignored-directories))
" -o -path ")
" "
(shell-quote-argument ")")
(grep-find-template nil)
(grep-find-command nil)
(grep-host-defaults-alist nil)
- ;; Set `grep-highlight-matches' to `always'
- ;; since `zgrep' puts filters in the grep output.
- (grep-highlight-matches 'always)
;; Use for `grep-read-files'
(grep-files-aliases '(("all" . "* .*")
("gz" . "*.gz"))))
nil default-directory t))
(confirm (equal current-prefix-arg '(4))))
(list regexp files dir confirm grep-find-template)))))))
- (let ((grep-find-template template))
+ (let ((grep-find-template template)
+ ;; Set `grep-highlight-matches' to `always'
+ ;; since `zgrep' puts filters in the grep output.
+ (grep-highlight-matches 'always))
(rgrep regexp files dir confirm)))
;;;###autoload