Subject: bug#30397: Random numbers in grep mode-line

From: Juri Linkov <[email-masked]>
Date: Sun, 11 Feb 2018 23:40:05 +0200

>> Yes, some adjustment is needed for grep.  That reminded me
>> about two unclosed feature requests: bug#13417 and bug#14017
>> that proposed to display these numbers also at the bottom of
>> output buffers.  But the showstopper was to decide on the
>> final format of such messages.  Although this looks good:
>> 
>>   Grep finished with 42 matches in 5 lines at Thu Jul 21 15:02:15
>> 
>> Than the mode-line will display two numbers: the number of matches
>> and the number of matching lines (in green).
>
> Is the total number of lines (in the search space) also
> available?  If so, would that be useful?  Maybe something
> like this?
>
> Grep finished at Thu Jul 21 15:02:15 - 42 matches in 5/113 lines 
>                                                       ^^^^

Unfortunately the total number of lines is not available.
There is even problems with getting the right number of matches.
When grep doesn't highlight matches, we can't count them.

Another problem is that grep matches to be printed at the end of the
grep buffer can't be counted in grep-regexp-alist because it is
based on font-lock which is invoked at unpredictable times
when grep process is already finished.  This leaves only one way
to count matches in grep-filter in this patch.

For the same reason, printing the number of compilation errors at
the end of the compilation buffer can't be implemented for bug#13417.

diff --git a/lisp/progmodes/grep.el b/lisp/progmodes/grep.el
index 9ce4ff8..23de8aa 100644
--- a/lisp/progmodes/grep.el
+++ b/lisp/progmodes/grep.el
@@ -425,6 +425,14 @@ grep-match-face
 (defvar grep-context-face 'shadow
   "Face name to use for grep context lines.")
 
+(defvar grep-num-matches-found 0)
+
+(defconst grep-mode-line-matches
+  `(" [" (:propertize (:eval (int-to-string grep-num-matches-found))
+                      face ,grep-hit-face
+                      help-echo "Number of matches so far")
+    "]"))
+
 (defvar grep-mode-font-lock-keywords
    '(;; Command output lines.
      (": \\(.+\\): \\(?:Permission denied\\|No such \\(?:file or directory\\|device or address\\)\\)$"
@@ -432,7 +440,7 @@ grep-mode-font-lock-keywords
      ;; remove match from grep-regexp-alist before fontifying
      ("^Grep[/a-zA-z]* started.*"
       (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t))
-     ("^Grep[/a-zA-z]* finished \\(?:(\\(matches found\\))\\|with \\(no matches found\\)\\).*"
+     ("^Grep[/a-zA-z]* finished \\(?:with \\(\\(?:[0-9]+ \\)?matches found\\)\\|with \\(no matches found\\)\\).*"
       (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t)
       (1 compilation-info-face nil t)
       (2 compilation-warning-face nil t))
@@ -503,21 +511,28 @@ grep-process-setup
     (setenv "GREP_COLOR" "01;31")
     ;; GREP_COLORS is used in GNU grep 2.5.2 and later versions
     (setenv "GREP_COLORS" "mt=01;31:fn=:ln=:bn=:se=:sl=:cx=:ne"))
+  (setq-local grep-num-matches-found 0)
   (set (make-local-variable 'compilation-exit-message-function)
-       (lambda (status code msg)
-	 (if (eq status 'exit)
-	     ;; 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)))
-	   (cons msg code))))
+       'grep-exit-message)
   (run-hooks 'grep-setup-hook))
 
+(defun grep-exit-message (status code msg)
+  "Return a status message for grep results."
+  (if (eq status 'exit)
+      ;; 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))
+	     (if (> grep-num-matches-found 0)
+                 (cons (format "finished with %d matches found\n" grep-num-matches-found)
+                       "matched")
+               '("finished with matches found\n" . "matched")))
+	    ((not (buffer-modified-p))
+	     '("finished with no matches found\n" . "no match"))
+	    (t
+	     (cons msg code)))
+    (cons msg code)))
+
 (defun grep-filter ()
   "Handle match highlighting escape sequences inserted by the grep process.
 This function is called from `compilation-filter-hook'."
@@ -535,7 +550,8 @@ grep-filter
         (while (re-search-forward "\033\\[0?1;31m\\(.*?\\)\033\\[[0-9]*m" end 1)
           (replace-match (propertize (match-string 1)
                                      'face nil 'font-lock-face grep-match-face)
-                         t t))
+                         t t)
+          (cl-incf grep-num-matches-found))
         ;; Delete all remaining escape sequences
         (goto-char beg)
         (while (re-search-forward "\033\\[[0-9;]*[mK]" end 1)
@@ -775,6 +791,8 @@ grep-mode
        grep-hit-face)
   (set (make-local-variable 'compilation-error-regexp-alist)
        grep-regexp-alist)
+  (set (make-local-variable 'compilation-mode-line-errors)
+       grep-mode-line-matches)
   ;; compilation-directory-matcher can't be nil, so we set it to a regexp that
   ;; can never match.
   (set (make-local-variable 'compilation-directory-matcher) '("\\`a\\`"))