7

When I use incremental search in emacs I can't know where I am in whole matches. In Chrome browser it says location using "2 of 30". How can I do that in Emacs?

Nordlöw
  • 11,838
  • 10
  • 52
  • 99
user150497
  • 490
  • 4
  • 14

4 Answers4

7

Here's my first attempt to implement this.

It uses the lazy highlighting that isearch implements, and forces the highlighting to cover the entire buffer (not just the visible portions) - which can slow down the search on large buffers. It then updates the display to include a current position (of total) relative to the highlighted search results.

This has the drawback that it is dependent on the entire buffer being searched and highlighted. And, sometimes when you C-s to the next match, the display changes to (0 of 1) even though the highlights are clearly still present.

But, it seems to be a reasonable first cut.

Prepare for big cut/paste:

(require 'isearch)
(defun lazy-highlight-cleanup (&optional force)
  "Stop lazy highlighting and remove extra highlighting from current buffer.
FORCE non-nil means do it whether or not `lazy-highlight-cleanup'
is nil.  This function is called when exiting an incremental search if
`lazy-highlight-cleanup' is non-nil."
  (interactive '(t))
  (if (or force lazy-highlight-cleanup)
      (while isearch-lazy-highlight-overlays
        (delete-overlay (car isearch-lazy-highlight-overlays))
        (setq isearch-lazy-highlight-overlays
              (cdr isearch-lazy-highlight-overlays))))
  (when isearch-lazy-highlight-timer
    (cancel-timer isearch-lazy-highlight-timer)
    (setq isearch-message-suffix-add "")
    (setq isearch-lazy-highlight-timer nil)))

(defun isearch-lazy-highlight-search ()
  "Search ahead for the next or previous match, for lazy highlighting.
Attempt to do the search exactly the way the pending Isearch would."
  (condition-case nil
      (let ((case-fold-search isearch-lazy-highlight-case-fold-search)
            (isearch-regexp isearch-lazy-highlight-regexp)
            (search-spaces-regexp isearch-lazy-highlight-space-regexp)
            (isearch-word isearch-lazy-highlight-word)
            (search-invisible nil)  ; don't match invisible text
            (retry t)
            (success nil)
            (isearch-forward isearch-lazy-highlight-forward)
            (bound (if isearch-lazy-highlight-forward
                       (min (or isearch-lazy-highlight-end-limit (point-max))
                            (if isearch-lazy-highlight-wrapped
                                isearch-lazy-highlight-start
                              (isearch-window-end)))
                     (max (or isearch-lazy-highlight-start-limit (point-min))
                          (if isearch-lazy-highlight-wrapped
                              isearch-lazy-highlight-end
                            (isearch-window-start))))))
        ;; Use a loop like in `isearch-search'.
        (while retry
          (setq success (isearch-search-string
                         isearch-lazy-highlight-last-string bound t))

          ;; Clear RETRY unless the search predicate says
          ;; to skip this search hit.
          (if (or (not success)
                  (= (point) bound) ; like (bobp) (eobp) in `isearch-search'.
                  (= (match-beginning 0) (match-end 0))
                  (funcall isearch-filter-predicate
                           (match-beginning 0) (match-end 0)))
              (setq retry nil)))
        success)
    (error nil)))

(defun isearch-find-current-overlay ()
  (let ((total 0)
        (count 1)
        (olist isearch-lazy-highlight-overlays))
    (while olist
      (setq total (1+ total))
      (if (< (overlay-end (car olist)) (point))
          (setq count (1+ count)))
      (setq olist
            (cdr olist)))
    (cons count total)))

(add-hook 'isearch-update-post-hook 'isearch-count-message)

(defun isearch-count-message ()
  (let ((counts (isearch-find-current-overlay)))
    (setq isearch-message-suffix-add (format " (%d of %d)" (car counts) (cdr counts)))))

(defun isearch-window-start ()
  "force highlight entire buffer"
  (point-min))

(defun isearch-window-end ()
  "force highlight entire buffer"
  (point-max))

(defun isearch-lazy-highlight-update ()
  "Update highlighting of other matches for current search."
  (let ((max lazy-highlight-max-at-a-time)
        (looping t)
        nomore)
    (with-local-quit
      (save-selected-window
        (if (and (window-live-p isearch-lazy-highlight-window)
                 (not (eq (selected-window) isearch-lazy-highlight-window)))
            (select-window isearch-lazy-highlight-window))
        (save-excursion
          (save-match-data
            (goto-char (if isearch-lazy-highlight-forward
                           isearch-lazy-highlight-end
                         isearch-lazy-highlight-start))
            (while looping
              (let ((found (isearch-lazy-highlight-search)))
                (when max
                  (setq max (1- max))
                  (if (<= max 0)
                      (setq looping nil)))
                (if found
                    (let ((mb (match-beginning 0))
                          (me (match-end 0)))
                      (if (= mb me) ;zero-length match
                          (if isearch-lazy-highlight-forward
                              (if (= mb (if isearch-lazy-highlight-wrapped
                                            isearch-lazy-highlight-start
                                          (isearch-window-end)))
                                  (setq found nil)
                                (forward-char 1))
                            (if (= mb (if isearch-lazy-highlight-wrapped
                                          isearch-lazy-highlight-end
                                        (isearch-window-start)))
                                (setq found nil)
                              (forward-char -1)))

                        ;; non-zero-length match
                        (let ((ov (make-overlay mb me)))
                          (push ov isearch-lazy-highlight-overlays)
                          ;; 1000 is higher than ediff's 100+,
                          ;; but lower than isearch main overlay's 1001
                          (overlay-put ov 'priority 1000)
                          (overlay-put ov 'face lazy-highlight-face)
                          (overlay-put ov 'window (selected-window))))
                      (if isearch-lazy-highlight-forward
                          (setq isearch-lazy-highlight-end (point))
                        (setq isearch-lazy-highlight-start (point)))))

                ;; not found or zero-length match at the search bound
                (if (not found)
                    (if isearch-lazy-highlight-wrapped
                        (setq looping nil
                              nomore  t)
                      (setq isearch-lazy-highlight-wrapped t)
                      (if isearch-lazy-highlight-forward
                          (progn
                            (setq isearch-lazy-highlight-end (isearch-window-start))
                            (goto-char (max (or isearch-lazy-highlight-start-limit (point-min))
                                            (isearch-window-start))))
                        (setq isearch-lazy-highlight-start (isearch-window-end))
                        (goto-char (min (or isearch-lazy-highlight-end-limit (point-max))
                                        (isearch-window-end))))))))
            (unless nomore
              (setq isearch-lazy-highlight-timer
                    (run-at-time lazy-highlight-interval nil
                                 'isearch-lazy-highlight-update)))))))))
Trey Jackson
  • 73,529
  • 11
  • 197
  • 229
  • Why not call `[re]-search-[forward|backward]` in `isearch-count-message()`? I can't see the need to scan overlays for this. – Nordlöw Aug 23 '14 at 15:52
  • At the point where 'isearch-count-message' is called, the search has already been done throughout the buffer, so adding yet another call to search could slow things down dramatically - whereas the overlays were present and had the necessary information to determine the count. Think of the case where there are many occurrences of the searched term, if you jump to the next occurrence 50 times, you would have had to scan the buffer from the beginning to the Nth term, 50 times. '1+2+3+...+50 = 1275' - that many searches just to determine you're at the 50th occurrence. – Trey Jackson Aug 25 '14 at 15:00
7

I saw this the other day:

https://github.com/syohex/emacs-anzu

anzu.el is an Emacs port of anzu.vim. anzu.el provides a minor mode which displays current match and total matches information in the mode-line in various search modes.

The screenshot on the github page is a gif animation of its functionality.

phils
  • 71,335
  • 11
  • 153
  • 198
2

Out of the box, you can use M-so while isearching to at least see the total number of matches (even if it unfortunately isn't smart enough to track the match that you're on in the original buffer).

phils
  • 71,335
  • 11
  • 153
  • 198
1

Add the following to your emacs init file. Despite the use of count-matches, it works very fast on my laptop. I have not tried it with very large files.

(defun my-isearch-update-post-hook()
  (let (suffix num-before num-after num-total)
    (setq num-before (count-matches isearch-string (point-min) (point)))
    (setq num-after (count-matches isearch-string (point) (point-max)))
    (setq num-total (+ num-before num-after))
    (setq suffix (if (= num-total 0)
                     ""
                   (format " [%d of %d]" num-before num-total)))
    (setq isearch-message-suffix-add suffix)
    (isearch-message)))

(add-hook 'isearch-update-post-hook 'my-isearch-update-post-hook)
  • This is nice! If you add `(case-fold-search isearch-case-fold-search)` to the `let` binding it can even update the count of matches after typing `M-c`. Anzu doesn't do that. – Arch Stanton Feb 16 '20 at 07:37