aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Gutov2016-05-05 02:52:34 +0300
committerDmitry Gutov2016-05-05 03:26:04 +0300
commit922c7a3e48e649ad67bd12b1f83343b730dd1bc4 (patch)
tree551beacca9963c49e4d3a8e14be27b80e4728b83
parent3fe351072841becbb1902c19f784890949f41c1d (diff)
downloademacs-922c7a3e48e649ad67bd12b1f83343b730dd1bc4.tar.gz
emacs-922c7a3e48e649ad67bd12b1f83343b730dd1bc4.zip
Rework xref-query-replace-in-results
* lisp/progmodes/xref.el (xref-query-replace-in-results): Collect all xrefs from the buffer first, then delegate most of the processing to the value returned by xref--buf-pairs-iterator. (xref--buf-pairs-iterator): New function. Return an "iterator" which partitions returned markers into buffers, and only processes markers from one buffer at a time. When an xref is out of date, skip it with a message instead of signaling error (bug#23284). (xref--outdated-p): Extract from xref--buf-pairs-iterator. Trim CR from both strings before comparing. (xref--query-replace-1): Remove the variable current-buf, no need to track it anymore. Simplify the filter-predicate and search functions accordingly. Iterate over buffer-markers pairs returned by the iterator, and call `perform-replace' for each of them. Use multi-query-replace-map (bug#23284). Use `switch-to-buffer' every time after the first, in order not to jump between windows. * test/automated/xref-tests.el (xref--buf-pairs-iterator-groups-markers-by-buffers-1) (xref--buf-pairs-iterator-groups-markers-by-buffers-2) (xref--buf-pairs-iterator-cleans-up-markers): New tests.
-rw-r--r--lisp/progmodes/xref.el131
-rw-r--r--test/automated/xref-tests.el29
2 files changed, 110 insertions, 50 deletions
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el
index 62cef235988..17bfdb69f8f 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 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)))
diff --git a/test/automated/xref-tests.el b/test/automated/xref-tests.el
index b288e2d7584..079b196aa8b 100644
--- a/test/automated/xref-tests.el
+++ b/test/automated/xref-tests.el
@@ -60,3 +60,32 @@
60 (should (string-match-p "file2\\.txt\\'" (xref-location-group (nth 0 locs)))) 60 (should (string-match-p "file2\\.txt\\'" (xref-location-group (nth 0 locs))))
61 (should (equal 1 (xref-location-line (nth 0 locs)))) 61 (should (equal 1 (xref-location-line (nth 0 locs))))
62 (should (equal 0 (xref-file-location-column (nth 0 locs)))))) 62 (should (equal 0 (xref-file-location-column (nth 0 locs))))))
63
64(ert-deftest xref--buf-pairs-iterator-groups-markers-by-buffers-1 ()
65 (let* ((xrefs (xref-collect-matches "foo" "*" xref-tests-data-dir nil))
66 (iter (xref--buf-pairs-iterator xrefs))
67 (cons (funcall iter :next)))
68 (should (null (funcall iter :next)))
69 (should (string-match "file1\\.txt\\'" (buffer-file-name (car cons))))
70 (should (= 2 (length (cdr cons))))))
71
72(ert-deftest xref--buf-pairs-iterator-groups-markers-by-buffers-2 ()
73 (let* ((xrefs (xref-collect-matches "bar" "*" xref-tests-data-dir nil))
74 (iter (xref--buf-pairs-iterator xrefs))
75 (cons1 (funcall iter :next))
76 (cons2 (funcall iter :next)))
77 (should (null (funcall iter :next)))
78 (should-not (equal (car cons1) (car cons2)))
79 (should (= 1 (length (cdr cons1))))
80 (should (= 1 (length (cdr cons2))))))
81
82(ert-deftest xref--buf-pairs-iterator-cleans-up-markers ()
83 (let* ((xrefs (xref-collect-matches "bar" "*" xref-tests-data-dir nil))
84 (iter (xref--buf-pairs-iterator xrefs))
85 (cons1 (funcall iter :next))
86 (cons2 (funcall iter :next)))
87 (funcall iter :cleanup)
88 (should (null (marker-position (car (nth 0 (cdr cons1))))))
89 (should (null (marker-position (cdr (nth 0 (cdr cons1))))))
90 (should (null (marker-position (car (nth 0 (cdr cons2))))))
91 (should (null (marker-position (cdr (nth 0 (cdr cons2))))))))