diff options
Diffstat (limited to 'lisp/progmodes/xref.el')
| -rw-r--r-- | lisp/progmodes/xref.el | 146 |
1 files changed, 92 insertions, 54 deletions
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index b5632af13ab..f651dc9cd18 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el | |||
| @@ -521,58 +521,86 @@ references displayed in the current *xref* buffer." | |||
| 521 | (let ((fr (read-regexp "Xref query-replace (regexp)" ".*"))) | 521 | (let ((fr (read-regexp "Xref query-replace (regexp)" ".*"))) |
| 522 | (list fr | 522 | (list fr |
| 523 | (read-regexp (format "Xref query-replace (regexp) %s with: " fr))))) | 523 | (read-regexp (format "Xref query-replace (regexp) %s with: " fr))))) |
| 524 | (let ((reporter (make-progress-reporter (format "Saving search results...") | 524 | (let* (item xrefs iter) |
| 525 | 0 (line-number-at-pos (point-max)))) | 525 | (save-excursion |
| 526 | (counter 0) | 526 | (while (setq item (xref--search-property 'xref-item)) |
| 527 | pairs item) | 527 | (when (xref-match-length item) |
| 528 | (push item xrefs)))) | ||
| 528 | (unwind-protect | 529 | (unwind-protect |
| 529 | (progn | 530 | (progn |
| 530 | (save-excursion | 531 | (goto-char (point-min)) |
| 531 | (goto-char (point-min)) | 532 | (setq iter (xref--buf-pairs-iterator (nreverse xrefs))) |
| 532 | ;; TODO: This list should be computed on-demand instead. | 533 | (xref--query-replace-1 from to iter)) |
| 533 | ;; As long as the UI just iterates through matches one by | 534 | (funcall iter :cleanup)))) |
| 534 | ;; one, there's no need to compute them all in advance. | 535 | |
| 535 | ;; Then we can throw away the reporter. | 536 | (defun xref--buf-pairs-iterator (xrefs) |
| 536 | (while (setq item (xref--search-property 'xref-item)) | 537 | (let (chunk-done item next-pair file-buf pairs all-pairs) |
| 537 | (when (xref-match-length item) | 538 | (lambda (action) |
| 538 | (save-excursion | 539 | (pcase action |
| 539 | (let* ((loc (xref-item-location item)) | 540 | (:next |
| 540 | (beg (xref-location-marker loc)) | 541 | (when (or xrefs next-pair) |
| 541 | (end (move-marker (make-marker) | 542 | (setq chunk-done nil) |
| 542 | (+ beg (xref-match-length item)) | 543 | (when next-pair |
| 543 | (marker-buffer beg)))) | 544 | (setq file-buf (marker-buffer (car next-pair)) |
| 544 | ;; Perform sanity check first. | 545 | pairs (list next-pair) |
| 545 | (xref--goto-location loc) | 546 | next-pair nil)) |
| 546 | ;; FIXME: The check should probably be a generic | 547 | (while (and (not chunk-done) |
| 547 | ;; function, instead of the assumption that all | 548 | (setq item (pop xrefs))) |
| 548 | ;; matches contain the full line as summary. | 549 | (save-excursion |
| 549 | ;; TODO: Offer to re-scan otherwise. | 550 | (let* ((loc (xref-item-location item)) |
| 550 | (unless (equal (buffer-substring-no-properties | 551 | (beg (xref-location-marker loc)) |
| 551 | (line-beginning-position) | 552 | (end (move-marker (make-marker) |
| 552 | (line-end-position)) | 553 | (+ beg (xref-match-length item)) |
| 553 | (xref-item-summary item)) | 554 | (marker-buffer beg)))) |
| 554 | (user-error "Search results out of date")) | 555 | (let ((pair (cons beg end))) |
| 555 | (progress-reporter-update reporter (cl-incf counter)) | 556 | (push pair all-pairs) |
| 556 | (push (cons beg end) pairs))))) | 557 | ;; Perform sanity check first. |
| 557 | (setq pairs (nreverse pairs))) | 558 | (xref--goto-location loc) |
| 558 | (unless pairs (user-error "No suitable matches here")) | 559 | (if (xref--outdated-p item |
| 559 | (progress-reporter-done reporter) | 560 | (buffer-substring-no-properties |
| 560 | (xref--query-replace-1 from to pairs)) | 561 | (line-beginning-position) |
| 561 | (dolist (pair pairs) | 562 | (line-end-position))) |
| 562 | (move-marker (car pair) nil) | 563 | (message "Search result out of date, skipping") |
| 563 | (move-marker (cdr pair) nil))))) | 564 | (cond |
| 565 | ((null file-buf) | ||
| 566 | (setq file-buf (marker-buffer beg)) | ||
| 567 | (push pair pairs)) | ||
| 568 | ((equal file-buf (marker-buffer beg)) | ||
| 569 | (push pair pairs)) | ||
| 570 | (t | ||
| 571 | (setq chunk-done t | ||
| 572 | next-pair pair)))))))) | ||
| 573 | (cons file-buf (nreverse pairs)))) | ||
| 574 | (:cleanup | ||
| 575 | (dolist (pair all-pairs) | ||
| 576 | (move-marker (car pair) nil) | ||
| 577 | (move-marker (cdr pair) nil))))))) | ||
| 578 | |||
| 579 | (defun xref--outdated-p (item line-text) | ||
| 580 | ;; FIXME: The check should probably be a generic function instead of | ||
| 581 | ;; the assumption that all matches contain the full line as summary. | ||
| 582 | (let ((summary (xref-item-summary item)) | ||
| 583 | (strip (lambda (s) (if (string-match "\r\\'" s) | ||
| 584 | (substring-no-properties s 0 -1) | ||
| 585 | s)))) | ||
| 586 | (not | ||
| 587 | ;; Sometimes buffer contents include ^M, and sometimes Grep | ||
| 588 | ;; output includes it, and they don't always match. | ||
| 589 | (equal (funcall strip line-text) | ||
| 590 | (funcall strip summary))))) | ||
| 564 | 591 | ||
| 565 | ;; FIXME: Write a nicer UI. | 592 | ;; FIXME: Write a nicer UI. |
| 566 | (defun xref--query-replace-1 (from to pairs) | 593 | (defun xref--query-replace-1 (from to iter) |
| 567 | (let* ((query-replace-lazy-highlight nil) | 594 | (let* ((query-replace-lazy-highlight nil) |
| 568 | current-beg current-end current-buf | 595 | (continue t) |
| 596 | did-it-once buf-pairs pairs | ||
| 597 | current-beg current-end | ||
| 569 | ;; Counteract the "do the next match now" hack in | 598 | ;; Counteract the "do the next match now" hack in |
| 570 | ;; `perform-replace'. And still, it'll report that those | 599 | ;; `perform-replace'. And still, it'll report that those |
| 571 | ;; matches were "filtered out" at the end. | 600 | ;; matches were "filtered out" at the end. |
| 572 | (isearch-filter-predicate | 601 | (isearch-filter-predicate |
| 573 | (lambda (beg end) | 602 | (lambda (beg end) |
| 574 | (and current-beg | 603 | (and current-beg |
| 575 | (eq (current-buffer) current-buf) | ||
| 576 | (>= beg current-beg) | 604 | (>= beg current-beg) |
| 577 | (<= end current-end)))) | 605 | (<= end current-end)))) |
| 578 | (replace-re-search-function | 606 | (replace-re-search-function |
| @@ -581,19 +609,22 @@ references displayed in the current *xref* buffer." | |||
| 581 | (while (and (not found) pairs) | 609 | (while (and (not found) pairs) |
| 582 | (setq pair (pop pairs) | 610 | (setq pair (pop pairs) |
| 583 | current-beg (car pair) | 611 | current-beg (car pair) |
| 584 | current-end (cdr pair) | 612 | current-end (cdr pair)) |
| 585 | current-buf (marker-buffer current-beg)) | ||
| 586 | (xref--with-dedicated-window | ||
| 587 | (pop-to-buffer current-buf)) | ||
| 588 | (goto-char current-beg) | 613 | (goto-char current-beg) |
| 589 | (when (re-search-forward from current-end noerror) | 614 | (when (re-search-forward from current-end noerror) |
| 590 | (setq found t))) | 615 | (setq found t))) |
| 591 | found)))) | 616 | found)))) |
| 592 | ;; FIXME: Despite this being a multi-buffer replacement, `N' | 617 | (while (and continue (setq buf-pairs (funcall iter :next))) |
| 593 | ;; doesn't work, because we're not using | 618 | (if did-it-once |
| 594 | ;; `multi-query-replace-map', and it would expect the below | 619 | ;; Reuse the same window for subsequent buffers. |
| 595 | ;; function to be called once per buffer. | 620 | (switch-to-buffer (car buf-pairs)) |
| 596 | (perform-replace from to t t nil))) | 621 | (xref--with-dedicated-window |
| 622 | (pop-to-buffer (car buf-pairs))) | ||
| 623 | (setq did-it-once t)) | ||
| 624 | (setq pairs (cdr buf-pairs)) | ||
| 625 | (setq continue | ||
| 626 | (perform-replace from to t t nil nil multi-query-replace-map))) | ||
| 627 | (unless did-it-once (user-error "No suitable matches here")))) | ||
| 597 | 628 | ||
| 598 | (defvar xref--xref-buffer-mode-map | 629 | (defvar xref--xref-buffer-mode-map |
| 599 | (let ((map (make-sparse-keymap))) | 630 | (let ((map (make-sparse-keymap))) |
| @@ -687,7 +718,9 @@ Return an alist of the form ((FILENAME . (XREF ...)) ...)." | |||
| 687 | (defun xref--show-xref-buffer (xrefs alist) | 718 | (defun xref--show-xref-buffer (xrefs alist) |
| 688 | (let ((xref-alist (xref--analyze xrefs))) | 719 | (let ((xref-alist (xref--analyze xrefs))) |
| 689 | (with-current-buffer (get-buffer-create xref-buffer-name) | 720 | (with-current-buffer (get-buffer-create xref-buffer-name) |
| 690 | (let ((inhibit-read-only t)) | 721 | (setq buffer-undo-list nil) |
| 722 | (let ((inhibit-read-only t) | ||
| 723 | (buffer-undo-list t)) | ||
| 691 | (erase-buffer) | 724 | (erase-buffer) |
| 692 | (xref--insert-xrefs xref-alist) | 725 | (xref--insert-xrefs xref-alist) |
| 693 | (xref--xref-buffer-mode) | 726 | (xref--xref-buffer-mode) |
| @@ -908,6 +941,8 @@ IGNORES is a list of glob patterns." | |||
| 908 | (require 'find-dired) ; for `find-name-arg' | 941 | (require 'find-dired) ; for `find-name-arg' |
| 909 | (defvar grep-find-template) | 942 | (defvar grep-find-template) |
| 910 | (defvar find-name-arg) | 943 | (defvar find-name-arg) |
| 944 | ;; `shell-quote-argument' quotes the tilde as well. | ||
| 945 | (cl-assert (not (string-match-p "\\`~" dir))) | ||
| 911 | (grep-expand-template | 946 | (grep-expand-template |
| 912 | grep-find-template | 947 | grep-find-template |
| 913 | regexp | 948 | regexp |
| @@ -919,14 +954,13 @@ IGNORES is a list of glob patterns." | |||
| 919 | (concat " -o " find-name-arg " ")) | 954 | (concat " -o " find-name-arg " ")) |
| 920 | " " | 955 | " " |
| 921 | (shell-quote-argument ")")) | 956 | (shell-quote-argument ")")) |
| 922 | dir | 957 | (shell-quote-argument dir) |
| 923 | (xref--find-ignores-arguments ignores dir))) | 958 | (xref--find-ignores-arguments ignores dir))) |
| 924 | 959 | ||
| 925 | (defun xref--find-ignores-arguments (ignores dir) | 960 | (defun xref--find-ignores-arguments (ignores dir) |
| 926 | "Convert IGNORES and DIR to a list of arguments for 'find'. | 961 | "Convert IGNORES and DIR to a list of arguments for 'find'. |
| 927 | IGNORES is a list of glob patterns. DIR is an absolute | 962 | IGNORES is a list of glob patterns. DIR is an absolute |
| 928 | directory, used as the root of the ignore globs." | 963 | directory, used as the root of the ignore globs." |
| 929 | ;; `shell-quote-argument' quotes the tilde as well. | ||
| 930 | (cl-assert (not (string-match-p "\\`~" dir))) | 964 | (cl-assert (not (string-match-p "\\`~" dir))) |
| 931 | (when ignores | 965 | (when ignores |
| 932 | (concat | 966 | (concat |
| @@ -1014,7 +1048,11 @@ directory, used as the root of the ignore globs." | |||
| 1014 | (syntax-propertize line-end) | 1048 | (syntax-propertize line-end) |
| 1015 | ;; FIXME: This results in several lines with the same | 1049 | ;; FIXME: This results in several lines with the same |
| 1016 | ;; summary. Solve with composite pattern? | 1050 | ;; summary. Solve with composite pattern? |
| 1017 | (while (re-search-forward regexp line-end t) | 1051 | (while (and |
| 1052 | ;; REGEXP might match an empty string. Or line. | ||
| 1053 | (or (null matches) | ||
| 1054 | (> (point) line-beg)) | ||
| 1055 | (re-search-forward regexp line-end t)) | ||
| 1018 | (let* ((beg-column (- (match-beginning 0) line-beg)) | 1056 | (let* ((beg-column (- (match-beginning 0) line-beg)) |
| 1019 | (end-column (- (match-end 0) line-beg)) | 1057 | (end-column (- (match-end 0) line-beg)) |
| 1020 | (loc (xref-make-file-location file line beg-column)) | 1058 | (loc (xref-make-file-location file line beg-column)) |