aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias EngdegÄrd2021-07-24 16:32:11 +0200
committerMattias EngdegÄrd2021-07-25 10:29:09 +0200
commitc52e26df30d5679dc2b9b34853a3c2db062524ac (patch)
tree1609b2b6b34afb5228fe2ddbe65c99f165b6af14
parentd3415724a686107236f78d745700221a397ffb4f (diff)
downloademacs-c52e26df30d5679dc2b9b34853a3c2db062524ac.tar.gz
emacs-c52e26df30d5679dc2b9b34853a3c2db062524ac.zip
Keep track of match extents in occur-mode (bug#39121)
Use the `occur-target` text property to keep track of the extents of all matches on each line instead of just the start of the first match. Doing so allows us to highlight all matches when jumping to a matching line instead of just the first one, and it works in a more principled way. It also removes compatibility problems that were introduced with occur-highlight-regexp. For compatibility with code that populate their own occur-mode buffers, we still accept `occur-target` properties with a single marker as value. * lisp/replace.el (occur-highlight-regexp, occur-highlight-overlay): Remove. (occur-highlight-overlays): New. (occur--targets-start): New. * lisp/replace.el (occur-after-change-function): (occur-mode-find-occurrence): Replace with... (occur-mode--find-occurrences): ...this function that returns the whole `occur-target` property value. (occur-mode-goto-occurrence, occur-mode-goto-occurrence-other-window) (occur-goto-locus-delete-o, occur-mode-display-occurrence) (occur-engine): Adjust to new property format. (occur--highlight-occurrence): Replace with... (occur--highlight-occurrences): ...this function that takes the `occur-target` property value as argument. (occur-1): Don't use `occur-highlight-regexp`. * test/lisp/replace-tests.el (occur-highlight-occurrence): Adapt to new property format.
-rw-r--r--lisp/replace.el177
-rw-r--r--test/lisp/replace-tests.el2
2 files changed, 91 insertions, 88 deletions
diff --git a/lisp/replace.el b/lisp/replace.el
index 7e30f1fc553..24befed2412 100644
--- a/lisp/replace.el
+++ b/lisp/replace.el
@@ -792,12 +792,8 @@ which will run faster and will not set the mark or print anything."
792Maximum length of the history list is determined by the value 792Maximum length of the history list is determined by the value
793of `history-length', which see.") 793of `history-length', which see.")
794 794
795(defvar occur-highlight-regexp t 795(defvar occur-highlight-overlays nil
796 "Regexp matching part of visited source lines to highlight temporarily. 796 "Overlays used to temporarily highlight occur matches.")
797Highlight entire line if t; don't highlight source lines if nil.")
798
799(defvar occur-highlight-overlay nil
800 "Overlay used to temporarily highlight occur matches.")
801 797
802(defvar occur-collect-regexp-history '("\\1") 798(defvar occur-collect-regexp-history '("\\1")
803 "History of regexp for occur's collect operation") 799 "History of regexp for occur's collect operation")
@@ -1357,18 +1353,27 @@ To return to ordinary Occur mode, use \\[occur-cease-edit]."
1357 (occur-mode) 1353 (occur-mode)
1358 (message "Switching to Occur mode."))) 1354 (message "Switching to Occur mode.")))
1359 1355
1356(defun occur--targets-start (targets)
1357 "First marker of the `occur-target' property value TARGETS."
1358 (if (consp targets)
1359 (caar targets)
1360 ;; Tolerate an `occur-target' value that is a single marker for
1361 ;; compatibility.
1362 targets))
1363
1360(defun occur-after-change-function (beg end length) 1364(defun occur-after-change-function (beg end length)
1361 (save-excursion 1365 (save-excursion
1362 (goto-char beg) 1366 (goto-char beg)
1363 (let* ((line-beg (line-beginning-position)) 1367 (let* ((line-beg (line-beginning-position))
1364 (m (get-text-property line-beg 'occur-target)) 1368 (targets (get-text-property line-beg 'occur-target))
1369 (m (occur--targets-start targets))
1365 (buf (marker-buffer m)) 1370 (buf (marker-buffer m))
1366 col) 1371 col)
1367 (when (and (get-text-property line-beg 'occur-prefix) 1372 (when (and (get-text-property line-beg 'occur-prefix)
1368 (not (get-text-property end 'occur-prefix))) 1373 (not (get-text-property end 'occur-prefix)))
1369 (when (= length 0) 1374 (when (= length 0)
1370 ;; Apply occur-target property to inserted (e.g. yanked) text. 1375 ;; Apply occur-target property to inserted (e.g. yanked) text.
1371 (put-text-property beg end 'occur-target m) 1376 (put-text-property beg end 'occur-target targets)
1372 ;; Did we insert a newline? Occur Edit mode can't create new 1377 ;; Did we insert a newline? Occur Edit mode can't create new
1373 ;; Occur entries; just discard everything after the newline. 1378 ;; Occur entries; just discard everything after the newline.
1374 (save-excursion 1379 (save-excursion
@@ -1402,35 +1407,38 @@ To return to ordinary Occur mode, use \\[occur-cease-edit]."
1402 "Handle `revert-buffer' for Occur mode buffers." 1407 "Handle `revert-buffer' for Occur mode buffers."
1403 (apply #'occur-1 (append occur-revert-arguments (list (buffer-name))))) 1408 (apply #'occur-1 (append occur-revert-arguments (list (buffer-name)))))
1404 1409
1405(defun occur-mode-find-occurrence () 1410(defun occur-mode--find-occurrences ()
1406 (let ((pos (get-text-property (point) 'occur-target))) 1411 ;; The `occur-target' property value is a list of (BEG . END) for each
1407 (unless pos 1412 ;; match on the line, or (for compatibility) a single marker to the start
1413 ;; of the first match.
1414 (let* ((targets (get-text-property (point) 'occur-target))
1415 (start (occur--targets-start targets)))
1416 (unless targets
1408 (error "No occurrence on this line")) 1417 (error "No occurrence on this line"))
1409 (unless (buffer-live-p (marker-buffer pos)) 1418 (unless (buffer-live-p (marker-buffer start))
1410 (error "Buffer for this occurrence was killed")) 1419 (error "Buffer for this occurrence was killed"))
1411 pos)) 1420 targets))
1412 1421
1413(defalias 'occur-mode-mouse-goto 'occur-mode-goto-occurrence) 1422(defalias 'occur-mode-mouse-goto 'occur-mode-goto-occurrence)
1414(defun occur-mode-goto-occurrence (&optional event) 1423(defun occur-mode-goto-occurrence (&optional event)
1415 "Go to the occurrence specified by EVENT, a mouse click. 1424 "Go to the occurrence specified by EVENT, a mouse click.
1416If not invoked by a mouse click, go to occurrence on the current line." 1425If not invoked by a mouse click, go to occurrence on the current line."
1417 (interactive (list last-nonmenu-event)) 1426 (interactive (list last-nonmenu-event))
1418 (let ((buffer (when event (current-buffer))) 1427 (let* ((buffer (when event (current-buffer)))
1419 (pos 1428 (targets
1420 (if (null event) 1429 (if (null event)
1421 ;; Actually `event-end' works correctly with a nil argument as 1430 ;; Actually `event-end' works correctly with a nil argument as
1422 ;; well, so we could dispense with this test, but let's not 1431 ;; well, so we could dispense with this test, but let's not
1423 ;; rely on this undocumented behavior. 1432 ;; rely on this undocumented behavior.
1424 (occur-mode-find-occurrence) 1433 (occur-mode--find-occurrences)
1425 (with-current-buffer (window-buffer (posn-window (event-end event))) 1434 (with-current-buffer (window-buffer (posn-window (event-end event)))
1426 (save-excursion 1435 (save-excursion
1427 (goto-char (posn-point (event-end event))) 1436 (goto-char (posn-point (event-end event)))
1428 (occur-mode-find-occurrence))))) 1437 (occur-mode--find-occurrences)))))
1429 (regexp occur-highlight-regexp)) 1438 (pos (occur--targets-start targets)))
1430 (pop-to-buffer (marker-buffer pos)) 1439 (pop-to-buffer (marker-buffer pos))
1431 (goto-char pos) 1440 (goto-char pos)
1432 (let ((end-mk (save-excursion (re-search-forward regexp nil t)))) 1441 (occur--highlight-occurrences targets)
1433 (occur--highlight-occurrence pos end-mk))
1434 (when buffer (next-error-found buffer (current-buffer))) 1442 (when buffer (next-error-found buffer (current-buffer)))
1435 (run-hooks 'occur-mode-find-occurrence-hook))) 1443 (run-hooks 'occur-mode-find-occurrence-hook)))
1436 1444
@@ -1438,15 +1446,15 @@ If not invoked by a mouse click, go to occurrence on the current line."
1438 "Go to the occurrence the current line describes, in another window." 1446 "Go to the occurrence the current line describes, in another window."
1439 (interactive) 1447 (interactive)
1440 (let ((buffer (current-buffer)) 1448 (let ((buffer (current-buffer))
1441 (pos (occur-mode-find-occurrence))) 1449 (pos (occur--targets-start (occur-mode--find-occurrences))))
1442 (switch-to-buffer-other-window (marker-buffer pos)) 1450 (switch-to-buffer-other-window (marker-buffer pos))
1443 (goto-char pos) 1451 (goto-char pos)
1444 (next-error-found buffer (current-buffer)) 1452 (next-error-found buffer (current-buffer))
1445 (run-hooks 'occur-mode-find-occurrence-hook))) 1453 (run-hooks 'occur-mode-find-occurrence-hook)))
1446 1454
1447;; Stolen from compile.el
1448(defun occur-goto-locus-delete-o () 1455(defun occur-goto-locus-delete-o ()
1449 (delete-overlay occur-highlight-overlay) 1456 (mapc #'delete-overlay occur-highlight-overlays)
1457 (setq occur-highlight-overlays nil)
1450 ;; Get rid of timer and hook that would try to do this again. 1458 ;; Get rid of timer and hook that would try to do this again.
1451 (if (timerp next-error-highlight-timer) 1459 (if (timerp next-error-highlight-timer)
1452 (cancel-timer next-error-highlight-timer)) 1460 (cancel-timer next-error-highlight-timer))
@@ -1454,64 +1462,55 @@ If not invoked by a mouse click, go to occurrence on the current line."
1454 #'occur-goto-locus-delete-o)) 1462 #'occur-goto-locus-delete-o))
1455 1463
1456;; Highlight the current visited occurrence. 1464;; Highlight the current visited occurrence.
1457;; Adapted from `compilation-goto-locus'. 1465(defun occur--highlight-occurrences (targets)
1458(defun occur--highlight-occurrence (mk end-mk) 1466 (let ((start-marker (occur--targets-start targets)))
1459 (let ((highlight-regexp occur-highlight-regexp)) 1467 (occur-goto-locus-delete-o)
1460 (if (timerp next-error-highlight-timer) 1468 (with-current-buffer (marker-buffer start-marker)
1461 (cancel-timer next-error-highlight-timer)) 1469 (when (or (eq next-error-highlight t)
1462 (unless occur-highlight-overlay 1470 (numberp next-error-highlight))
1463 (setq occur-highlight-overlay 1471 (setq occur-highlight-overlays
1464 (make-overlay (point-min) (point-min))) 1472 (mapcar (lambda (target)
1465 (overlay-put occur-highlight-overlay 'face 'next-error)) 1473 (let ((o (make-overlay (car target) (cdr target))))
1466 (with-current-buffer (marker-buffer mk) 1474 (overlay-put o 'face 'next-error)
1467 (save-excursion 1475 o))
1468 (if end-mk (goto-char end-mk) (end-of-line)) 1476 (if (listp targets)
1469 (let ((end (point))) 1477 targets
1470 (if mk (goto-char mk) (beginning-of-line)) 1478 ;; `occur-target' compatibility: when we only
1471 (if (and (stringp highlight-regexp) 1479 ;; have a single starting point, highlight the
1472 (re-search-forward highlight-regexp end t)) 1480 ;; rest of the line.
1473 (progn 1481 (let ((end-pos (save-excursion
1474 (goto-char (match-beginning 0)) 1482 (goto-char start-marker)
1475 (move-overlay occur-highlight-overlay 1483 (line-end-position))))
1476 (match-beginning 0) (match-end 0) 1484 (list (cons start-marker end-pos))))))
1477 (current-buffer))) 1485 (add-hook 'pre-command-hook #'occur-goto-locus-delete-o)
1478 (move-overlay occur-highlight-overlay 1486 (when (numberp next-error-highlight)
1479 (point) end (current-buffer))) 1487 ;; We want highlighting for a limited time:
1480 (if (or (eq next-error-highlight t) 1488 ;; set up a timer to delete it.
1481 (numberp next-error-highlight)) 1489 (setq next-error-highlight-timer
1482 ;; We want highlighting: delete overlay on next input. 1490 (run-at-time next-error-highlight nil
1483 (add-hook 'pre-command-hook 1491 'occur-goto-locus-delete-o))))
1484 #'occur-goto-locus-delete-o) 1492
1485 ;; We don't want highlighting: delete overlay now. 1493 (when (eq next-error-highlight 'fringe-arrow)
1486 (delete-overlay occur-highlight-overlay)) 1494 ;; We want a fringe arrow (instead of highlighting).
1487 ;; We want highlighting for a limited time: 1495 (setq next-error-overlay-arrow-position
1488 ;; set up a timer to delete it. 1496 (copy-marker (line-beginning-position)))))))
1489 (when (numberp next-error-highlight)
1490 (setq next-error-highlight-timer
1491 (run-at-time next-error-highlight nil
1492 'occur-goto-locus-delete-o))))))
1493 (when (eq next-error-highlight 'fringe-arrow)
1494 ;; We want a fringe arrow (instead of highlighting).
1495 (setq next-error-overlay-arrow-position
1496 (copy-marker (line-beginning-position))))))
1497 1497
1498(defun occur-mode-display-occurrence () 1498(defun occur-mode-display-occurrence ()
1499 "Display in another window the occurrence the current line describes." 1499 "Display in another window the occurrence the current line describes."
1500 (interactive) 1500 (interactive)
1501 (let ((buffer (current-buffer)) 1501 (let* ((buffer (current-buffer))
1502 (pos (occur-mode-find-occurrence)) 1502 (targets (occur-mode--find-occurrences))
1503 (regexp occur-highlight-regexp) 1503 (pos (occur--targets-start targets))
1504 (next-error-highlight next-error-highlight-no-select) 1504 (next-error-highlight next-error-highlight-no-select)
1505 (display-buffer-overriding-action 1505 (display-buffer-overriding-action
1506 '(nil (inhibit-same-window . t))) 1506 '(nil (inhibit-same-window . t)))
1507 window) 1507 window)
1508 (setq window (display-buffer (marker-buffer pos) t)) 1508 (setq window (display-buffer (marker-buffer pos) t))
1509 ;; This is the way to set point in the proper window. 1509 ;; This is the way to set point in the proper window.
1510 (save-selected-window 1510 (save-selected-window
1511 (select-window window) 1511 (select-window window)
1512 (goto-char pos) 1512 (goto-char pos)
1513 (let ((end-mk (save-excursion (re-search-forward regexp nil t)))) 1513 (occur--highlight-occurrences targets)
1514 (occur--highlight-occurrence pos end-mk))
1515 (next-error-found buffer (current-buffer)) 1514 (next-error-found buffer (current-buffer))
1516 (run-hooks 'occur-mode-find-occurrence-hook)))) 1515 (run-hooks 'occur-mode-find-occurrence-hook))))
1517 1516
@@ -1868,7 +1867,6 @@ See also `multi-occur'."
1868 (buffer-undo-list t) 1867 (buffer-undo-list t)
1869 (occur--final-pos nil)) 1868 (occur--final-pos nil))
1870 (erase-buffer) 1869 (erase-buffer)
1871 (setq-local occur-highlight-regexp regexp)
1872 (let ((count 1870 (let ((count
1873 (if (stringp nlines) 1871 (if (stringp nlines)
1874 ;; Treat nlines as a regexp to collect. 1872 ;; Treat nlines as a regexp to collect.
@@ -1968,7 +1966,7 @@ See also `multi-occur'."
1968 (origpt nil) 1966 (origpt nil)
1969 (begpt nil) 1967 (begpt nil)
1970 (endpt nil) 1968 (endpt nil)
1971 (marker nil) 1969 markers ; list of (BEG-MARKER . END-MARKER)
1972 (curstring "") 1970 (curstring "")
1973 (ret nil) 1971 (ret nil)
1974 ;; The following binding is for when case-fold-search 1972 ;; The following binding is for when case-fold-search
@@ -1994,8 +1992,7 @@ See also `multi-occur'."
1994 (setq endpt (line-end-position))) 1992 (setq endpt (line-end-position)))
1995 ;; Sum line numbers up to the first match line. 1993 ;; Sum line numbers up to the first match line.
1996 (setq curr-line (+ curr-line (count-lines origpt begpt))) 1994 (setq curr-line (+ curr-line (count-lines origpt begpt)))
1997 (setq marker (make-marker)) 1995 (setq markers nil)
1998 (set-marker marker matchbeg)
1999 (setq curstring (occur-engine-line begpt endpt keep-props)) 1996 (setq curstring (occur-engine-line begpt endpt keep-props))
2000 ;; Highlight the matches 1997 ;; Highlight the matches
2001 (let ((len (length curstring)) 1998 (let ((len (length curstring))
@@ -2017,6 +2014,11 @@ See also `multi-occur'."
2017 (setq orig-line-shown-p t))) 2014 (setq orig-line-shown-p t)))
2018 (while (and (< start len) 2015 (while (and (< start len)
2019 (string-match regexp curstring start)) 2016 (string-match regexp curstring start))
2017 (push (cons (set-marker (make-marker)
2018 (+ begpt (match-beginning 0)))
2019 (set-marker (make-marker)
2020 (+ begpt (match-end 0))))
2021 markers)
2020 (setq matches (1+ matches)) 2022 (setq matches (1+ matches))
2021 (add-text-properties 2023 (add-text-properties
2022 (match-beginning 0) (match-end 0) 2024 (match-beginning 0) (match-end 0)
@@ -2029,6 +2031,7 @@ See also `multi-occur'."
2029 ;; Avoid infloop (Bug#7593). 2031 ;; Avoid infloop (Bug#7593).
2030 (let ((end (match-end 0))) 2032 (let ((end (match-end 0)))
2031 (setq start (if (= start end) (1+ start) end))))) 2033 (setq start (if (= start end) (1+ start) end)))))
2034 (setq markers (nreverse markers))
2032 ;; Generate the string to insert for this match 2035 ;; Generate the string to insert for this match
2033 (let* ((match-prefix 2036 (let* ((match-prefix
2034 ;; Using 7 digits aligns tabs properly. 2037 ;; Using 7 digits aligns tabs properly.
@@ -2042,7 +2045,7 @@ See also `multi-occur'."
2042 ;; (for Occur Edit mode). 2045 ;; (for Occur Edit mode).
2043 front-sticky t 2046 front-sticky t
2044 rear-nonsticky t 2047 rear-nonsticky t
2045 occur-target ,marker 2048 occur-target ,markers
2046 follow-link t 2049 follow-link t
2047 help-echo "mouse-2: go to this occurrence")))) 2050 help-echo "mouse-2: go to this occurrence"))))
2048 (match-str 2051 (match-str
@@ -2050,7 +2053,7 @@ See also `multi-occur'."
2050 ;; because that loses. And don't put it 2053 ;; because that loses. And don't put it
2051 ;; on context lines to reduce flicker. 2054 ;; on context lines to reduce flicker.
2052 (propertize curstring 2055 (propertize curstring
2053 'occur-target marker 2056 'occur-target markers
2054 'follow-link t 2057 'follow-link t
2055 'help-echo 2058 'help-echo
2056 "mouse-2: go to this occurrence")) 2059 "mouse-2: go to this occurrence"))
@@ -2069,8 +2072,8 @@ See also `multi-occur'."
2069 ;; get a contiguous highlight. 2072 ;; get a contiguous highlight.
2070 (propertize (concat match-prefix match-str) 2073 (propertize (concat match-prefix match-str)
2071 'mouse-face 'highlight)) 2074 'mouse-face 'highlight))
2072 ;; Add marker at eol, but no mouse props. 2075 ;; Add markers at eol, but no mouse props.
2073 (propertize "\n" 'occur-target marker))) 2076 (propertize "\n" 'occur-target markers)))
2074 (data 2077 (data
2075 (if (= nlines 0) 2078 (if (= nlines 0)
2076 ;; The simple display style 2079 ;; The simple display style
diff --git a/test/lisp/replace-tests.el b/test/lisp/replace-tests.el
index 417946c35fe..7f62a417a02 100644
--- a/test/lisp/replace-tests.el
+++ b/test/lisp/replace-tests.el
@@ -589,7 +589,7 @@ bound to HIGHLIGHT-LOCUS."
589 (replace-tests-with-highlighted-occurrence highlight-locus 589 (replace-tests-with-highlighted-occurrence highlight-locus
590 (occur-mode-display-occurrence) 590 (occur-mode-display-occurrence)
591 (with-current-buffer (marker-buffer 591 (with-current-buffer (marker-buffer
592 (get-text-property (point) 'occur-target)) 592 (caar (get-text-property (point) 'occur-target)))
593 (should (funcall check-overlays has-overlay))))))) 593 (should (funcall check-overlays has-overlay)))))))
594 594
595(ert-deftest replace-regexp-bug45973 () 595(ert-deftest replace-regexp-bug45973 ()