From 6f82d8ef7d3aea2d05677d2792f89b8e6084d66a Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Wed, 4 May 2016 00:42:43 +0300 Subject: Clear buffer-undo-list when showing xrefs * lisp/progmodes/xref.el (xref--show-xref-buffer): Clear buffer-undo-list and temporarily bind it to t while rendering the buffer contents. --- lisp/progmodes/xref.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lisp/progmodes') diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index b5632af13ab..540c7b0e925 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el @@ -687,7 +687,9 @@ Return an alist of the form ((FILENAME . (XREF ...)) ...)." (defun xref--show-xref-buffer (xrefs alist) (let ((xref-alist (xref--analyze xrefs))) (with-current-buffer (get-buffer-create xref-buffer-name) - (let ((inhibit-read-only t)) + (setq buffer-undo-list nil) + (let ((inhibit-read-only t) + (buffer-undo-list t)) (erase-buffer) (xref--insert-xrefs xref-alist) (xref--xref-buffer-mode) -- cgit v1.2.1 From 4d8fd9cf338cf9b5dfc613657adfeabff2d9c14e Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Wed, 4 May 2016 01:59:29 +0300 Subject: Handle "empty line" regexp in xref searches * lisp/progmodes/xref.el (xref--collect-matches-1): Stop after one match if re-search-forward doesn't move point (bug#23426). * test/automated/xref-tests.el (xref-collect-matches-finds-an-empty-line-regexp-match): Uncomment test. --- lisp/progmodes/xref.el | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lisp/progmodes') diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index 540c7b0e925..62cef235988 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el @@ -1016,7 +1016,11 @@ directory, used as the root of the ignore globs." (syntax-propertize line-end) ;; FIXME: This results in several lines with the same ;; summary. Solve with composite pattern? - (while (re-search-forward regexp line-end t) + (while (and + ;; REGEXP might match an empty string. Or line. + (or (null matches) + (> (point) line-beg)) + (re-search-forward regexp line-end t)) (let* ((beg-column (- (match-beginning 0) line-beg)) (end-column (- (match-end 0) line-beg)) (loc (xref-make-file-location file line beg-column)) -- cgit v1.2.1 From 922c7a3e48e649ad67bd12b1f83343b730dd1bc4 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Thu, 5 May 2016 02:52:34 +0300 Subject: 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. --- lisp/progmodes/xref.el | 131 ++++++++++++++++++++++++++++++------------------- 1 file changed, 81 insertions(+), 50 deletions(-) (limited to 'lisp/progmodes') 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." (let ((fr (read-regexp "Xref query-replace (regexp)" ".*"))) (list fr (read-regexp (format "Xref query-replace (regexp) %s with: " fr))))) - (let ((reporter (make-progress-reporter (format "Saving search results...") - 0 (line-number-at-pos (point-max)))) - (counter 0) - pairs item) + (let* (item xrefs iter) + (save-excursion + (while (setq item (xref--search-property 'xref-item)) + (when (xref-match-length item) + (push item xrefs)))) (unwind-protect (progn - (save-excursion - (goto-char (point-min)) - ;; TODO: This list should be computed on-demand instead. - ;; As long as the UI just iterates through matches one by - ;; one, there's no need to compute them all in advance. - ;; Then we can throw away the reporter. - (while (setq item (xref--search-property 'xref-item)) - (when (xref-match-length item) - (save-excursion - (let* ((loc (xref-item-location item)) - (beg (xref-location-marker loc)) - (end (move-marker (make-marker) - (+ beg (xref-match-length item)) - (marker-buffer beg)))) - ;; Perform sanity check first. - (xref--goto-location loc) - ;; FIXME: The check should probably be a generic - ;; function, instead of the assumption that all - ;; matches contain the full line as summary. - ;; TODO: Offer to re-scan otherwise. - (unless (equal (buffer-substring-no-properties - (line-beginning-position) - (line-end-position)) - (xref-item-summary item)) - (user-error "Search results out of date")) - (progress-reporter-update reporter (cl-incf counter)) - (push (cons beg end) pairs))))) - (setq pairs (nreverse pairs))) - (unless pairs (user-error "No suitable matches here")) - (progress-reporter-done reporter) - (xref--query-replace-1 from to pairs)) - (dolist (pair pairs) - (move-marker (car pair) nil) - (move-marker (cdr pair) nil))))) + (goto-char (point-min)) + (setq iter (xref--buf-pairs-iterator (nreverse xrefs))) + (xref--query-replace-1 from to iter)) + (funcall iter :cleanup)))) + +(defun xref--buf-pairs-iterator (xrefs) + (let (chunk-done item next-pair file-buf pairs all-pairs) + (lambda (action) + (pcase action + (:next + (when (or xrefs next-pair) + (setq chunk-done nil) + (when next-pair + (setq file-buf (marker-buffer (car next-pair)) + pairs (list next-pair) + next-pair nil)) + (while (and (not chunk-done) + (setq item (pop xrefs))) + (save-excursion + (let* ((loc (xref-item-location item)) + (beg (xref-location-marker loc)) + (end (move-marker (make-marker) + (+ beg (xref-match-length item)) + (marker-buffer beg)))) + (let ((pair (cons beg end))) + (push pair all-pairs) + ;; Perform sanity check first. + (xref--goto-location loc) + (if (xref--outdated-p item + (buffer-substring-no-properties + (line-beginning-position) + (line-end-position))) + (message "Search result out of date, skipping") + (cond + ((null file-buf) + (setq file-buf (marker-buffer beg)) + (push pair pairs)) + ((equal file-buf (marker-buffer beg)) + (push pair pairs)) + (t + (setq chunk-done t + next-pair pair)))))))) + (cons file-buf pairs))) + (:cleanup + (dolist (pair all-pairs) + (move-marker (car pair) nil) + (move-marker (cdr pair) nil))))))) + +(defun xref--outdated-p (item line-text) + ;; FIXME: The check should probably be a generic function instead of + ;; the assumption that all matches contain the full line as summary. + (let ((summary (xref-item-summary item)) + (strip (lambda (s) (if (string-match "\r\\'" s) + (substring-no-properties s 0 -1) + s)))) + (not + ;; Sometimes buffer contents include ^M, and sometimes Grep + ;; output includes it, and they don't always match. + (equal (funcall strip line-text) + (funcall strip summary))))) ;; FIXME: Write a nicer UI. -(defun xref--query-replace-1 (from to pairs) +(defun xref--query-replace-1 (from to iter) (let* ((query-replace-lazy-highlight nil) - current-beg current-end current-buf + (continue t) + did-it-once buf-pairs pairs + current-beg current-end ;; Counteract the "do the next match now" hack in ;; `perform-replace'. And still, it'll report that those ;; matches were "filtered out" at the end. (isearch-filter-predicate (lambda (beg end) (and current-beg - (eq (current-buffer) current-buf) (>= beg current-beg) (<= end current-end)))) (replace-re-search-function @@ -581,19 +609,22 @@ references displayed in the current *xref* buffer." (while (and (not found) pairs) (setq pair (pop pairs) current-beg (car pair) - current-end (cdr pair) - current-buf (marker-buffer current-beg)) - (xref--with-dedicated-window - (pop-to-buffer current-buf)) + current-end (cdr pair)) (goto-char current-beg) (when (re-search-forward from current-end noerror) (setq found t))) found)))) - ;; FIXME: Despite this being a multi-buffer replacement, `N' - ;; doesn't work, because we're not using - ;; `multi-query-replace-map', and it would expect the below - ;; function to be called once per buffer. - (perform-replace from to t t nil))) + (while (and continue (setq buf-pairs (funcall iter :next))) + (if did-it-once + ;; Reuse the same window for subsequent buffers. + (switch-to-buffer (car buf-pairs)) + (xref--with-dedicated-window + (pop-to-buffer (car buf-pairs))) + (setq did-it-once t)) + (setq pairs (cdr buf-pairs)) + (setq continue + (perform-replace from to t t nil nil multi-query-replace-map))) + (unless did-it-once (user-error "No suitable matches here")))) (defvar xref--xref-buffer-mode-map (let ((map (make-sparse-keymap))) -- cgit v1.2.1 From ab3ba912fc7b91b7b147ea36fabe461dc99a9fb8 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Thu, 5 May 2016 04:15:23 +0300 Subject: shell-quote-argument DIR when appropriate * lisp/progmodes/project.el (project-file-completion-table): `shell-quote-argument' DIR as well. * lisp/progmodes/xref.el (xref--rgrep-command): Pass DIR through `shell-quote-argument' (bug#23453). Thanks for Kaushal Modi for pointing out the problem. Assert that DIR doesn't start with `~'. --- lisp/progmodes/project.el | 3 ++- lisp/progmodes/xref.el | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'lisp/progmodes') diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index 9c8a88c80fc..a51c383b93b 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -172,7 +172,8 @@ to find the list of ignores for each directory." (let ((command (format "%s %s %s -type f -print0" find-program - dir + (shell-quote-argument + (expand-file-name dir)) (xref--find-ignores-arguments (project-ignores project dir) (expand-file-name dir))))) diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index 17bfdb69f8f..1c28390258c 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el @@ -941,6 +941,8 @@ IGNORES is a list of glob patterns." (require 'find-dired) ; for `find-name-arg' (defvar grep-find-template) (defvar find-name-arg) + ;; `shell-quote-argument' quotes the tilde as well. + (cl-assert (not (string-match-p "\\`~" dir))) (grep-expand-template grep-find-template regexp @@ -952,14 +954,13 @@ IGNORES is a list of glob patterns." (concat " -o " find-name-arg " ")) " " (shell-quote-argument ")")) - dir + (shell-quote-argument dir) (xref--find-ignores-arguments ignores dir))) (defun xref--find-ignores-arguments (ignores dir) "Convert IGNORES and DIR to a list of arguments for 'find'. IGNORES is a list of glob patterns. DIR is an absolute directory, used as the root of the ignore globs." - ;; `shell-quote-argument' quotes the tilde as well. (cl-assert (not (string-match-p "\\`~" dir))) (when ignores (concat -- cgit v1.2.1 From 1a4127dbd625ea64f535b3bd09844a99161290a6 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Thu, 5 May 2016 04:28:14 +0300 Subject: Use save-excursion in xref-location-marker more * lisp/progmodes/elisp-mode.el (xref-location-marker): Use save-excursion, in order not to alter the value of point if the buffer is currently open in the background (problem reported by Robert Weiner). * lisp/progmodes/etags.el (xref-location-marker): Same. --- lisp/progmodes/elisp-mode.el | 5 +++-- lisp/progmodes/etags.el | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'lisp/progmodes') diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el index fc4501d0cbe..6c6c3803f9e 100644 --- a/lisp/progmodes/elisp-mode.el +++ b/lisp/progmodes/elisp-mode.el @@ -826,8 +826,9 @@ non-nil result supercedes the xrefs produced by (pcase-let (((cl-struct xref-elisp-location symbol type file) l)) (let ((buffer-point (find-function-search-for-symbol symbol type file))) (with-current-buffer (car buffer-point) - (goto-char (or (cdr buffer-point) (point-min))) - (point-marker))))) + (save-excursion + (goto-char (or (cdr buffer-point) (point-min))) + (point-marker)))))) (cl-defmethod xref-location-group ((l xref-elisp-location)) (xref-elisp-location-file l)) diff --git a/lisp/progmodes/etags.el b/lisp/progmodes/etags.el index a2a0df2d6e1..890d55294cf 100644 --- a/lisp/progmodes/etags.el +++ b/lisp/progmodes/etags.el @@ -2146,8 +2146,9 @@ for \\[find-tag] (which see)." (with-slots (tag-info file) l (let ((buffer (find-file-noselect file))) (with-current-buffer buffer - (etags-goto-tag-location tag-info) - (point-marker))))) + (save-excursion + (etags-goto-tag-location tag-info) + (point-marker)))))) (cl-defmethod xref-location-line ((l xref-etags-location)) (with-slots (tag-info) l -- cgit v1.2.1 From 3347a733e0778dfefaeabe28ae73f4226236a881 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Thu, 5 May 2016 16:01:52 +0300 Subject: `nreverse' the marker pairs list * lisp/progmodes/xref.el (xref--buf-pairs-iterator): `nreverse' the marker pairs list for each buffer before returning. --- lisp/progmodes/xref.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lisp/progmodes') diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index 1c28390258c..f651dc9cd18 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el @@ -570,7 +570,7 @@ references displayed in the current *xref* buffer." (t (setq chunk-done t next-pair pair)))))))) - (cons file-buf pairs))) + (cons file-buf (nreverse pairs)))) (:cleanup (dolist (pair all-pairs) (move-marker (car pair) nil) -- cgit v1.2.1