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) (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)) (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. "Parse one line of grep output, and return it as a match list.
Moves cursor to end of the match." 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. ;; We have to return something at this point.
subtxt))) subtxt)))
) )
(t ((eq (oref tool :resulttype) 'line-and-text)
(when (re-search-forward "^\\([^ ]+\\) [^ ]+ \\([0-9]+\\) " nil t) (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)) (cons (string-to-number (match-string 2))
(expand-file-name (match-string 1))) (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) (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)) (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. "Parse one line of grep output, and return it as a match list.
Moves cursor to end of the match." Moves cursor to end of the match."
@ -57,8 +60,13 @@ Moves cursor to end of the match."
;; Search for files ;; Search for files
(when (re-search-forward "^\\([^\n]+\\)$" nil t) (when (re-search-forward "^\\([^\n]+\\)$" nil t)
(match-string 1))) (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 (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)) (cons (string-to-number (match-string 2))
(match-string 3)) (match-string 3))
)))) ))))

View file

@ -188,6 +188,9 @@ This shell should support pipe redirect syntax."
;; Return the answer ;; Return the answer
ans)) 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)) (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. "Parse one line of grep output, and return it as a match list.
Moves cursor to end of the match." Moves cursor to end of the match."
@ -195,8 +198,13 @@ Moves cursor to end of the match."
;; Search for files ;; Search for files
(when (re-search-forward "^\\([^\n]+\\)$" nil t) (when (re-search-forward "^\\([^\n]+\\)$" nil t)
(match-string 1))) (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 (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)) (cons (string-to-number (match-string 2))
(match-string 1)) (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) (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)) (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. "Parse one line of grep output, and return it as a match list.
Moves cursor to end of the match." Moves cursor to end of the match."
@ -59,8 +62,13 @@ Moves cursor to end of the match."
((eq (oref tool :searchtype) 'tagcompletions) ((eq (oref tool :searchtype) 'tagcompletions)
(when (re-search-forward "^\\([^ ]+\\) " nil t) (when (re-search-forward "^\\([^ ]+\\) " nil t)
(match-string 1))) (match-string 1)))
(t ((eq (oref tool :resulttype) 'line-and-text)
(when (re-search-forward "^\\(\\(?:[a-zA-Z]:\\)?[^:\n]+\\):\\([0-9]+\\):" nil t) (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)) (cons (string-to-number (match-string 2))
(expand-file-name (match-string 1) default-directory)) (expand-file-name (match-string 1) default-directory))
)))) ))))

View file

@ -839,16 +839,16 @@ and just use etags."
(kill-local-variable 'xref-backend-functions)) (kill-local-variable 'xref-backend-functions))
(setq-local xref-backend-functions xref-etags-mode--saved))) (setq-local xref-backend-functions xref-etags-mode--saved)))
(declare-function semantic-symref-find-references-by-name "semantic/symref") (declare-function semantic-symref-instantiate "semantic/symref")
(declare-function semantic-find-file-noselect "semantic/fw") (declare-function semantic-symref-perform-search "semantic/symref")
(declare-function grep-expand-template "grep") (declare-function grep-expand-template "grep")
(defvar ede-minor-mode) ;; ede.el (defvar ede-minor-mode) ;; ede.el
(defun xref-collect-references (symbol dir) (defun xref-collect-references (symbol dir)
"Collect references to SYMBOL inside DIR. "Collect references to SYMBOL inside DIR.
This function uses the Semantic Symbol Reference API, see This function uses the Semantic Symbol Reference API, see
`semantic-symref-find-references-by-name' for details on which `semantic-symref-tool-alist' for details on which tools are used,
tools are used, and when." and when."
(cl-assert (directory-name-p dir)) (cl-assert (directory-name-p dir))
(require 'semantic/symref) (require 'semantic/symref)
(defvar semantic-symref-tool) (defvar semantic-symref-tool)
@ -859,19 +859,19 @@ tools are used, and when."
;; to force the backend to use `default-directory'. ;; to force the backend to use `default-directory'.
(let* ((ede-minor-mode nil) (let* ((ede-minor-mode nil)
(default-directory dir) (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) (semantic-symref-tool 'detect)
(case-fold-search nil) (case-fold-search nil)
(res (semantic-symref-find-references-by-name symbol 'subdirs)) (inst (semantic-symref-instantiate :searchfor symbol
(hits (and res (oref res hit-lines))) :searchtype 'symbol
(orig-buffers (buffer-list))) :searchscope 'subdirs
(unwind-protect :resulttype 'line-and-text)))
(cl-mapcan (lambda (hit) (xref--collect-matches (xref--convert-hits (semantic-symref-perform-search inst)
hit (format "\\_<%s\\_>" (regexp-quote symbol)))) (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)))))
;;;###autoload ;;;###autoload
(defun xref-collect-matches (regexp files dir ignores) (defun xref-collect-matches (regexp files dir ignores)
@ -890,34 +890,19 @@ IGNORES is a list of glob patterns."
files files
(expand-file-name dir) (expand-file-name dir)
ignores)) ignores))
(orig-buffers (buffer-list))
(buf (get-buffer-create " *xref-grep*")) (buf (get-buffer-create " *xref-grep*"))
(grep-re (caar grep-regexp-alist)) (grep-re (caar grep-regexp-alist))
(counter 0)
reporter
hits) hits)
(with-current-buffer buf (with-current-buffer buf
(erase-buffer) (erase-buffer)
(call-process-shell-command command nil t) (call-process-shell-command command nil t)
(goto-char (point-min)) (goto-char (point-min))
(while (re-search-forward grep-re nil t) (while (re-search-forward grep-re nil t)
(push (cons (string-to-number (match-string 2)) (push (list (string-to-number (match-string 2))
(match-string 1)) (match-string 1)
(buffer-substring-no-properties (point) (line-end-position)))
hits))) hits)))
(setq reporter (make-progress-reporter (xref--convert-hits hits regexp)))
(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)))))
(defun xref--rgrep-command (regexp files dir ignores) (defun xref--rgrep-command (regexp files dir ignores)
(require 'find-dired) ; for `find-name-arg' (require 'find-dired) ; for `find-name-arg'
@ -980,30 +965,71 @@ directory, used as the root of the ignore globs."
(match-string 1 str))))) (match-string 1 str)))))
str t t)) str t t))
(defun xref--collect-matches (hit regexp) (defvar xref--last-visiting-buffer nil)
(pcase-let* ((`(,line . ,file) hit) (defvar xref--temp-buffer-file-name nil)
(buf (or (find-buffer-visiting file)
(semantic-find-file-noselect file)))) (defun xref--convert-hits (hits regexp)
(with-current-buffer buf (let (xref--last-visiting-buffer
(save-excursion (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)) (goto-char (point-min))
(forward-line (1- line)) (xref--collect-matches-1 regexp file line
(let ((line-end (line-end-position)) (point)
(line-beg (line-beginning-position)) (point-max))))))
matches)
(syntax-propertize line-end) (defun xref--collect-matches-1 (regexp file line line-beg line-end)
;; FIXME: This results in several lines with the same (let (matches)
;; summary. Solve with composite pattern? (syntax-propertize line-end)
(while (re-search-forward regexp line-end t) ;; FIXME: This results in several lines with the same
(let* ((beg-column (- (match-beginning 0) line-beg)) ;; summary. Solve with composite pattern?
(end-column (- (match-end 0) line-beg)) (while (re-search-forward regexp line-end t)
(loc (xref-make-file-location file line beg-column)) (let* ((beg-column (- (match-beginning 0) line-beg))
(summary (buffer-substring line-beg line-end))) (end-column (- (match-end 0) line-beg))
(add-face-text-property beg-column end-column 'highlight (loc (xref-make-file-location file line beg-column))
t summary) (summary (buffer-substring line-beg line-end)))
(push (xref-make-match summary loc (- end-column beg-column)) (add-face-text-property beg-column end-column 'highlight
matches))) t summary)
(nreverse matches)))))) (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) (provide 'xref)