]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/grep.el
Merge from origin/emacs-25
[gnu-emacs] / lisp / progmodes / grep.el
index 68852f7b55821d877deb828df862d58a94adbc70..fd55576e13513458d3957c5e1aceb749f144299a 100644 (file)
@@ -1,6 +1,6 @@
 ;;; 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>
@@ -130,7 +130,7 @@ Customize or call the function `grep-apply-setting'."
 (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.
@@ -177,7 +177,7 @@ The following place holders should be present in the string:
  <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
@@ -189,7 +189,7 @@ Customize or call the function `grep-apply-setting'."
   :group 'grep)
 
 (defcustom grep-files-aliases
-  '(("all" .   "* .*")
+  '(("all" .   "* .[!.]* ..?*") ;; Don't match `..'. See bug#22577
     ("el" .    "*.el")
     ("ch" .    "*.[ch]")
     ("c" .     "*.c")
@@ -227,6 +227,22 @@ to determine whether cdr should not be excluded."
                 (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'"
@@ -255,6 +271,8 @@ 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
@@ -293,6 +311,12 @@ See `compilation-error-screen-columns'"
     (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.")
@@ -448,9 +472,12 @@ This variable's value takes effect when `grep-compute-defaults' is called.")
 (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)
@@ -471,12 +498,14 @@ Set up `compilation-exit-message-function' and run `grep-setup-hook'."
             ;; 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))
 
@@ -572,20 +601,23 @@ This function is called from `compilation-filter-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)))
@@ -705,6 +737,10 @@ This function is called from `compilation-filter-hook'."
       ;; 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"
@@ -719,30 +755,80 @@ This function is called from `compilation-filter-hook'."
   ;; 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
@@ -752,7 +838,8 @@ list is empty)."
                                  (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)
@@ -791,7 +878,7 @@ easily repeat a find command."
 ;; 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)
@@ -804,7 +891,16 @@ 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)
-         (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)
@@ -937,6 +1033,7 @@ This command shares argument histories with \\[rgrep] and \\[grep]."
        (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)
@@ -999,6 +1096,7 @@ to specify a command to run."
                    (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.
@@ -1027,16 +1125,18 @@ to specify a command to run."
                  ;; 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 ")")
@@ -1094,9 +1194,9 @@ file name to `*.gz', and sets `grep-highlight-matches' to `always'."
                                            nil default-directory t))
                  (confirm (equal current-prefix-arg '(4))))
             (list regexp files dir confirm grep-find-template)))))))
-  ;; Set `grep-highlight-matches' to `always'
-  ;; since `zgrep' puts filters in the grep output.
   (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)))