Subject: bug#29321: Isearch hit count

From: Juri Linkov <[email-masked]>
Date: Sat, 27 Oct 2018 23:55:56 +0300

> Isearch could show in the mode line how many matches follow or precede 
> the currently highlighted one (or we could write "3 of 4 matches", as, 
> e.g., Firefox does).  For big files, it could be helpful to calculate 
> this information either lazily or in another thread.

Thanks to Drew, now we have the full-buffer lazy-highlighting
loop that can be reused for Isearch hit count.

Here is a minimal patch that implements this feature:

diff --git a/lisp/isearch.el b/lisp/isearch.el
index 580b3ac40a..8c264ab3ed 100644
--- a/lisp/isearch.el
+++ b/lisp/isearch.el
@@ -316,6 +316,13 @@ isearch-lazy-highlight
   :group 'lazy-highlight
   :group 'isearch)
 
+(defcustom isearch-lazy-count nil
+  "Counts matches in the full buffer.
+When non-nil, all matches in the full buffer are counted."
+  :type 'boolean
+  :group 'isearch
+  :version "27.1")
+
 ;;; Lazy highlight customization.
 
 (defgroup lazy-highlight nil
@@ -2794,7 +2801,15 @@ isearch-message-prefix
 			(concat " [" current-input-method-title "]: "))
 		     ": ")
 		   )))
-    (propertize (concat (upcase (substring m 0 1)) (substring m 1))
+    (propertize (concat (if (and isearch-lazy-count isearch-lazy-count-current)
+                            (format "%d/%d " (if isearch-forward
+                                                 isearch-lazy-count-current
+                                               (- isearch-lazy-count-total
+                                                  isearch-lazy-count-current
+                                                  -1))
+                                    (or isearch-lazy-count-total "?"))
+                          "")
+                        (upcase (substring m 0 1)) (substring m 1))
 		'face 'minibuffer-prompt)))
 
 (defun isearch-message-suffix (&optional c-q-hack)
@@ -3212,6 +3227,10 @@ 'isearch-lazy-highlight-word
 (defvar isearch-lazy-highlight-regexp-function nil)
 (defvar isearch-lazy-highlight-forward nil)
 (defvar isearch-lazy-highlight-error nil)
+(defvar isearch-lazy-count-current nil)
+(defvar isearch-lazy-count-total nil)
+(defvar isearch-lazy-count-start nil)
+(defvar isearch-lazy-count-hash (make-hash-table))
 
 (defun lazy-highlight-cleanup (&optional force procrastinate)
   "Stop lazy highlighting and remove extra highlighting from current buffer.
@@ -3235,8 +3254,7 @@ isearch-lazy-highlight-new-loop
 This is called when `isearch-update' is invoked (which can cause the
 search string to change or the window to scroll).  It is also used
 by other Emacs features."
-  (when (and (null executing-kbd-macro)
-             (sit-for 0)         ;make sure (window-start) is credible
+  (setq isearch-lazy-count-start
         (or (not (equal isearch-string
                         isearch-lazy-highlight-last-string))
             (not (memq (selected-window)
@@ -3251,17 +3269,20 @@ isearch-lazy-highlight-new-loop
 		     isearch-lax-whitespace))
 	    (not (eq isearch-lazy-highlight-regexp-lax-whitespace
 		     isearch-regexp-lax-whitespace))
-		 (not (or lazy-highlight-buffer
-			  (= (window-group-start)
-			     isearch-lazy-highlight-window-start)))
-		 (not (or lazy-highlight-buffer
-			  (= (window-group-end) ; Window may have been split/joined.
-			     isearch-lazy-highlight-window-end)))
 	    (not (eq isearch-forward
 		     isearch-lazy-highlight-forward))
 	    ;; In case we are recovering from an error.
 	    (not (equal isearch-error
 			isearch-lazy-highlight-error))))
+  (when (and (null executing-kbd-macro)
+             (sit-for 0)         ;make sure (window-start) is credible
+             (or isearch-lazy-count-start
+                 (not (or lazy-highlight-buffer
+		          (= (window-group-start)
+			     isearch-lazy-highlight-window-start)))
+	         (not (or lazy-highlight-buffer
+		          (= (window-group-end) ; Window may have been split/joined.
+			     isearch-lazy-highlight-window-end)))))
     ;; something important did indeed change
     (lazy-highlight-cleanup t (not (equal isearch-string ""))) ;stop old timer
     (setq isearch-lazy-highlight-error isearch-error)
@@ -3303,9 +3324,18 @@ isearch-lazy-highlight-new-loop
 		    (1- (length isearch-lazy-highlight-last-string)))
 		 (point-min))))
     (unless (equal isearch-string "")
+      (when (and isearch-lazy-count isearch-mode)
+        (when isearch-lazy-count-start
+          (clrhash isearch-lazy-count-hash)
+          (setq isearch-lazy-count-current nil
+                isearch-lazy-count-total nil)))
       (setq isearch-lazy-highlight-timer
             (run-with-idle-timer lazy-highlight-initial-delay nil
-                                 'isearch-lazy-highlight-start)))))
+                                 'isearch-lazy-highlight-start))))
+  (when (and isearch-lazy-count isearch-mode)
+    (setq isearch-lazy-count-current
+          (gethash isearch-other-end isearch-lazy-count-hash))
+    (funcall (or isearch-message-function #'isearch-message) nil t)))
 
 (defun isearch-lazy-highlight-search (string bound)
   "Search ahead for the next or previous match, for lazy highlighting.
@@ -3426,7 +3456,8 @@ isearch-lazy-highlight-update
 			(goto-char (min (or isearch-lazy-highlight-end-limit (point-max))
 					window-end)))))))
 	    (if nomore
-		(when isearch-lazy-highlight-buffer
+                (when (or isearch-lazy-highlight-buffer
+                          isearch-lazy-count-start)
 		  (if isearch-lazy-highlight-forward
 		      (setq isearch-lazy-highlight-end (point-min))
 		    (setq isearch-lazy-highlight-start (point-max)))
@@ -3475,8 +3506,13 @@ isearch-lazy-highlight-buffer-update
 			    (if (= mb (point-min))
 				(setq found nil)
 			      (forward-char -1)))
+                        (setq isearch-lazy-count-total (1+ (or isearch-lazy-count-total 0)))
+                        (puthash (if isearch-lazy-highlight-forward mb me)
+                                 isearch-lazy-count-total
+                                 isearch-lazy-count-hash)
 			;; Already highlighted by isearch-lazy-highlight-update
-			(unless (and (>= mb window-start) (<= me window-end))
+                        (unless (or (and (>= mb window-start) (<= me window-end))
+                                    (not isearch-lazy-highlight-buffer))
 			  ;; non-zero-length match
 			  (isearch-lazy-highlight-match mb me)))
 		      ;; Remember the current position of point for
@@ -3491,6 +3527,10 @@ isearch-lazy-highlight-buffer-update
 		    (setq looping nil
 			  nomore  t))))
 	    (unless nomore
+	      (when (and isearch-lazy-count isearch-mode)
+                (setq isearch-lazy-count-current
+                      (gethash isearch-other-end isearch-lazy-count-hash))
+                (funcall (or isearch-message-function #'isearch-message) nil t))
 	      (setq isearch-lazy-highlight-timer
 		    (run-at-time lazy-highlight-interval nil
 				 'isearch-lazy-highlight-buffer-update)))))))))