Perform xref searches without visiting unopened files

* lisp/progmodes/xref.el (xref-collect-references): Instead of
calling `semantic-symref-find-references-by-name', use
`semantic-symref-instantiate' and `semantic-symref-perform-search'
directly.  Ask for `line-and-text' results (bug#23223).
(xref-collect-matches): Include the line text in the "hit"
structure.
(xref--convert-hits): New function, split off from
`xref-collect-references' and `xref-collect-matches', to convert
"hits" to xref instance list.  Create a temporary buffer here, to
use it for post-processing all hit lines.
(xref--collect-matches): Use a different approach for non-visited
files.  Insert the line text into the temp buffer, apply the
file's major mode the best we can without reading its whole
contents, syntax-propertize, and search in the result.
(xref--collect-matches-1): Extract, to handle the common logic
between two cases.
(xref--find-buffer-visiting): New function, a wrapper around
`find-buffer-visiting' to amortize its cost.

* lisp/cedet/semantic/symref/idutils.el
(semantic-symref-idutils--line-re): New constant.
(semantic-symref-parse-tool-output-one-line): Support result type
`line-and-text'.

* lisp/cedet/semantic/symref/grep.el
(semantic-symref-grep--line-re)
(semantic-symref-parse-tool-output-one-line): Same.

* lisp/cedet/semantic/symref/cscope.el
(semantic-symref-cscope--line-re)
(semantic-symref-parse-tool-output-one-line): Same.

* lisp/cedet/semantic/symref/global.el
(semantic-symref-global--line-re)
(semantic-symref-parse-tool-output-one-line): Same.
This commit is contained in:
Dmitry Gutov 2016-04-12 21:08:22 +03:00
parent 50455754b5
commit cc0b713210
5 changed files with 121 additions and 63 deletions

View file

@ -60,6 +60,9 @@ See the function `cedet-cscope-search' for more details.")
(semantic-symref-parse-tool-output tool b)
))
(defconst semantic-symref-cscope--line-re
"^\\([^ ]+\\) [^ ]+ \\([0-9]+\\) ")
(cl-defmethod semantic-symref-parse-tool-output-one-line ((tool semantic-symref-tool-cscope))
"Parse one line of grep output, and return it as a match list.
Moves cursor to end of the match."
@ -78,8 +81,13 @@ Moves cursor to end of the match."
;; We have to return something at this point.
subtxt)))
)
(t
(when (re-search-forward "^\\([^ ]+\\) [^ ]+ \\([0-9]+\\) " nil t)
((eq (oref tool :resulttype) 'line-and-text)
(when (re-search-forward semantic-symref-cscope--line-re nil t)
(list (string-to-number (match-string 2))
(expand-file-name (match-string 1))
(buffer-substring-no-properties (point) (line-end-position)))))
(t ; :resulttype is 'line
(when (re-search-forward semantic-symref-cscope--line-re nil t)
(cons (string-to-number (match-string 2))
(expand-file-name (match-string 1)))
))))

View file

@ -49,6 +49,9 @@ See the function `cedet-gnu-global-search' for more details.")
(semantic-symref-parse-tool-output tool b)
))
(defconst semantic-symref-global--line-re
"^\\([^ ]+\\) +\\([0-9]+\\) \\([^ ]+\\) ")
(cl-defmethod semantic-symref-parse-tool-output-one-line ((tool semantic-symref-tool-global))
"Parse one line of grep output, and return it as a match list.
Moves cursor to end of the match."
@ -57,8 +60,13 @@ Moves cursor to end of the match."
;; Search for files
(when (re-search-forward "^\\([^\n]+\\)$" nil t)
(match-string 1)))
((eq (oref tool :resulttype) 'line-and-text)
(when (re-search-forward semantic-symref-global--line-re nil t)
(list (string-to-number (match-string 2))
(match-string 3)
(buffer-substring-no-properties (point) (line-end-position)))))
(t
(when (re-search-forward "^\\([^ ]+\\) +\\([0-9]+\\) \\([^ ]+\\) " nil t)
(when (re-search-forward semantic-symref-global--line-re nil t)
(cons (string-to-number (match-string 2))
(match-string 3))
))))

View file

@ -188,6 +188,9 @@ This shell should support pipe redirect syntax."
;; Return the answer
ans))
(defconst semantic-symref-grep--line-re
"^\\(\\(?:[a-zA-Z]:\\)?[^:\n]+\\):\\([0-9]+\\):")
(cl-defmethod semantic-symref-parse-tool-output-one-line ((tool semantic-symref-tool-grep))
"Parse one line of grep output, and return it as a match list.
Moves cursor to end of the match."
@ -195,8 +198,13 @@ Moves cursor to end of the match."
;; Search for files
(when (re-search-forward "^\\([^\n]+\\)$" nil t)
(match-string 1)))
((eq (oref tool :resulttype) 'line-and-text)
(when (re-search-forward semantic-symref-grep--line-re nil t)
(list (string-to-number (match-string 2))
(match-string 1)
(buffer-substring-no-properties (point) (line-end-position)))))
(t
(when (re-search-forward "^\\(\\(?:[a-zA-Z]:\\)?[^:\n]+\\):\\([0-9]+\\):" nil t)
(when (re-search-forward semantic-symref-grep--line-re nil t)
(cons (string-to-number (match-string 2))
(match-string 1))
))))

View file

@ -49,6 +49,9 @@ See the function `cedet-idutils-search' for more details.")
(semantic-symref-parse-tool-output tool b)
))
(defconst semantic-symref-idutils--line-re
"^\\(\\(?:[a-zA-Z]:\\)?[^:\n]+\\):\\([0-9]+\\):")
(cl-defmethod semantic-symref-parse-tool-output-one-line ((tool semantic-symref-tool-idutils))
"Parse one line of grep output, and return it as a match list.
Moves cursor to end of the match."
@ -59,8 +62,13 @@ Moves cursor to end of the match."
((eq (oref tool :searchtype) 'tagcompletions)
(when (re-search-forward "^\\([^ ]+\\) " nil t)
(match-string 1)))
(t
(when (re-search-forward "^\\(\\(?:[a-zA-Z]:\\)?[^:\n]+\\):\\([0-9]+\\):" nil t)
((eq (oref tool :resulttype) 'line-and-text)
(when (re-search-forward semantic-symref-idutils--line-re nil t)
(list (string-to-number (match-string 2))
(expand-file-name (match-string 1) default-directory)
(buffer-substring-no-properties (point) (line-end-position)))))
(t ; resulttype is line
(when (re-search-forward semantic-symref-idutils--line-re nil t)
(cons (string-to-number (match-string 2))
(expand-file-name (match-string 1) default-directory))
))))

View file

@ -839,16 +839,16 @@ and just use etags."
(kill-local-variable 'xref-backend-functions))
(setq-local xref-backend-functions xref-etags-mode--saved)))
(declare-function semantic-symref-find-references-by-name "semantic/symref")
(declare-function semantic-find-file-noselect "semantic/fw")
(declare-function semantic-symref-instantiate "semantic/symref")
(declare-function semantic-symref-perform-search "semantic/symref")
(declare-function grep-expand-template "grep")
(defvar ede-minor-mode) ;; ede.el
(defun xref-collect-references (symbol dir)
"Collect references to SYMBOL inside DIR.
This function uses the Semantic Symbol Reference API, see
`semantic-symref-find-references-by-name' for details on which
tools are used, and when."
`semantic-symref-tool-alist' for details on which tools are used,
and when."
(cl-assert (directory-name-p dir))
(require 'semantic/symref)
(defvar semantic-symref-tool)
@ -859,19 +859,19 @@ tools are used, and when."
;; to force the backend to use `default-directory'.
(let* ((ede-minor-mode nil)
(default-directory dir)
;; FIXME: Remove CScope and Global from the recognized tools?
;; The current implementations interpret the symbol search as
;; "find all calls to the given function", but not function
;; definition. And they return nothing when passed a variable
;; name, even a global one.
(semantic-symref-tool 'detect)
(case-fold-search nil)
(res (semantic-symref-find-references-by-name symbol 'subdirs))
(hits (and res (oref res hit-lines)))
(orig-buffers (buffer-list)))
(unwind-protect
(cl-mapcan (lambda (hit) (xref--collect-matches
hit (format "\\_<%s\\_>" (regexp-quote symbol))))
hits)
;; TODO: Implement "lightweight" buffer visiting, so that we
;; don't have to kill them.
(mapc #'kill-buffer
(cl-set-difference (buffer-list) orig-buffers)))))
(inst (semantic-symref-instantiate :searchfor symbol
:searchtype 'symbol
:searchscope 'subdirs
:resulttype 'line-and-text)))
(xref--convert-hits (semantic-symref-perform-search inst)
(format "\\_<%s\\_>" (regexp-quote symbol)))))
;;;###autoload
(defun xref-collect-matches (regexp files dir ignores)
@ -890,34 +890,19 @@ IGNORES is a list of glob patterns."
files
(expand-file-name dir)
ignores))
(orig-buffers (buffer-list))
(buf (get-buffer-create " *xref-grep*"))
(grep-re (caar grep-regexp-alist))
(counter 0)
reporter
hits)
(with-current-buffer buf
(erase-buffer)
(call-process-shell-command command nil t)
(goto-char (point-min))
(while (re-search-forward grep-re nil t)
(push (cons (string-to-number (match-string 2))
(match-string 1))
(push (list (string-to-number (match-string 2))
(match-string 1)
(buffer-substring-no-properties (point) (line-end-position)))
hits)))
(setq reporter (make-progress-reporter
(format "Collecting search results...")
0 (length hits)))
(unwind-protect
(cl-mapcan (lambda (hit)
(prog1
(progress-reporter-update reporter counter)
(cl-incf counter))
(xref--collect-matches hit regexp))
(nreverse hits))
(progress-reporter-done reporter)
;; TODO: Same as above.
(mapc #'kill-buffer
(cl-set-difference (buffer-list) orig-buffers)))))
(xref--convert-hits hits regexp)))
(defun xref--rgrep-command (regexp files dir ignores)
(require 'find-dired) ; for `find-name-arg'
@ -980,30 +965,71 @@ directory, used as the root of the ignore globs."
(match-string 1 str)))))
str t t))
(defun xref--collect-matches (hit regexp)
(pcase-let* ((`(,line . ,file) hit)
(buf (or (find-buffer-visiting file)
(semantic-find-file-noselect file))))
(with-current-buffer buf
(save-excursion
(defvar xref--last-visiting-buffer nil)
(defvar xref--temp-buffer-file-name nil)
(defun xref--convert-hits (hits regexp)
(let (xref--last-visiting-buffer
(tmp-buffer (generate-new-buffer " *xref-temp*")))
(unwind-protect
(cl-mapcan (lambda (hit) (xref--collect-matches hit regexp tmp-buffer))
hits)
(kill-buffer tmp-buffer))))
(defun xref--collect-matches (hit regexp tmp-buffer)
(pcase-let* ((`(,line ,file ,text) hit)
(buf (xref--find-buffer-visiting file)))
(if buf
(with-current-buffer buf
(save-excursion
(goto-char (point-min))
(forward-line (1- line))
(xref--collect-matches-1 regexp file line
(line-beginning-position)
(line-end-position))))
;; Using the temporary buffer is both a performance and a buffer
;; management optimization.
(with-current-buffer tmp-buffer
(erase-buffer)
(unless (equal file xref--temp-buffer-file-name)
(insert-file-contents file nil 0 200)
;; Can't (setq-local delay-mode-hooks t) because of
;; bug#23272, but the performance penalty seems minimal.
(let ((buffer-file-name file)
(inhibit-message t)
message-log-max)
(ignore-errors
(set-auto-mode t)))
(setq-local xref--temp-buffer-file-name file)
(setq-local inhibit-read-only t)
(erase-buffer))
(insert text)
(goto-char (point-min))
(forward-line (1- line))
(let ((line-end (line-end-position))
(line-beg (line-beginning-position))
matches)
(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)
(let* ((beg-column (- (match-beginning 0) line-beg))
(end-column (- (match-end 0) line-beg))
(loc (xref-make-file-location file line beg-column))
(summary (buffer-substring line-beg line-end)))
(add-face-text-property beg-column end-column 'highlight
t summary)
(push (xref-make-match summary loc (- end-column beg-column))
matches)))
(nreverse matches))))))
(xref--collect-matches-1 regexp file line
(point)
(point-max))))))
(defun xref--collect-matches-1 (regexp file line line-beg line-end)
(let (matches)
(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)
(let* ((beg-column (- (match-beginning 0) line-beg))
(end-column (- (match-end 0) line-beg))
(loc (xref-make-file-location file line beg-column))
(summary (buffer-substring line-beg line-end)))
(add-face-text-property beg-column end-column 'highlight
t summary)
(push (xref-make-match summary loc (- end-column beg-column))
matches)))
(nreverse matches)))
(defun xref--find-buffer-visiting (file)
(unless (equal (car xref--last-visiting-buffer) file)
(setq xref--last-visiting-buffer
(cons file (find-buffer-visiting file))))
(cdr xref--last-visiting-buffer))
(provide 'xref)