summaryrefslogtreecommitdiff
path: root/lisp/progmodes/xref.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/progmodes/xref.el')
-rw-r--r--lisp/progmodes/xref.el343
1 files changed, 254 insertions, 89 deletions
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el
index e59bfdd36d2..44934d44ebd 100644
--- a/lisp/progmodes/xref.el
+++ b/lisp/progmodes/xref.el
@@ -71,9 +71,6 @@
(require 'ring)
(require 'project)
-(eval-when-compile
- (require 'semantic/symref)) ;; for hit-lines slot
-
(defgroup xref nil "Cross-referencing commands"
:version "25.1"
:group 'tools)
@@ -317,8 +314,12 @@ backward."
;;; Marker stack (M-. pushes, M-, pops)
(defcustom xref-marker-ring-length 16
- "Length of the xref marker ring."
- :type 'integer)
+ "Length of the xref marker ring.
+If this variable is not set through Customize, you must call
+`xref-set-marker-ring-length' for changes to take effect."
+ :type 'integer
+ :initialize #'custom-initialize-default
+ :set #'xref-set-marker-ring-length)
(defcustom xref-prompt-for-identifier '(not xref-find-definitions
xref-find-definitions-other-window
@@ -354,6 +355,14 @@ elements is negated: these commands will NOT prompt."
(defvar xref--marker-ring (make-ring xref-marker-ring-length)
"Ring of markers to implement the marker stack.")
+(defun xref-set-marker-ring-length (var val)
+ "Set `xref-marker-ring-length'.
+VAR is the symbol `xref-marker-ring-length' and VAL is the new
+value."
+ (set-default var val)
+ (if (ring-p xref--marker-ring)
+ (ring-resize xref--marker-ring val)))
+
(defun xref-push-marker-stack (&optional m)
"Add point M (defaults to `point-marker') to the marker stack."
(ring-insert xref--marker-ring (or m (point-marker))))
@@ -414,7 +423,7 @@ elements is negated: these commands will NOT prompt."
(set-buffer (marker-buffer marker))
(xref--goto-char marker)))
-(defun xref--pop-to-location (item &optional action)
+(defun xref-pop-to-location (item &optional action)
"Go to the location of ITEM and display the buffer.
ACTION controls how the buffer is displayed:
nil -- switch-to-buffer
@@ -439,6 +448,18 @@ If SELECT is non-nil, select the target window."
(defconst xref-buffer-name "*xref*"
"The name of the buffer to show xrefs.")
+(defface xref-file-header '((t :inherit compilation-info))
+ "Face used to highlight file header in the xref buffer."
+ :version "27.1")
+
+(defface xref-line-number '((t :inherit compilation-line-number))
+ "Face for displaying line numbers in the xref buffer."
+ :version "27.1")
+
+(defface xref-match '((t :inherit highlight))
+ "Face used to highlight matches in the xref buffer."
+ :version "27.1")
+
(defmacro xref--with-dedicated-window (&rest body)
`(let* ((xref-w (get-buffer-window xref-buffer-name))
(xref-w-dedicated (window-dedicated-p xref-w)))
@@ -456,6 +477,9 @@ If SELECT is non-nil, select the target window."
(defvar-local xref--original-window nil
"The original window this xref buffer was created from.")
+(defvar-local xref--fetcher nil
+ "The original function to call to fetch the list of xrefs.")
+
(defun xref--show-pos-in-buf (pos buf)
"Goto and display position POS of buffer BUF in a window.
Honor `xref--original-window-intent', run `xref-after-jump-hook'
@@ -465,27 +489,18 @@ and finally return the window."
(or (eq xref--original-window-intent 'frame)
pop-up-frames))
(action
- (cond ((memq
- xref--original-window-intent
- '(window frame))
+ (cond ((eq xref--original-window-intent 'frame)
t)
+ ((eq xref--original-window-intent 'window)
+ `((xref--display-buffer-in-other-window)
+ (window . ,xref--original-window)))
((and
(window-live-p xref--original-window)
(or (not (window-dedicated-p xref--original-window))
(eq (window-buffer xref--original-window) buf)))
- `(,(lambda (buf _alist)
- (set-window-buffer xref--original-window buf)
- xref--original-window))))))
- (with-selected-window
- (with-selected-window
- ;; Just before `display-buffer', place ourselves in the
- ;; original window to suggest preserving it. Of course, if
- ;; user has deleted the original window, all bets are off,
- ;; just use the selected one.
- (or (and (window-live-p xref--original-window)
- xref--original-window)
- (selected-window))
- (display-buffer buf action))
+ `((xref--display-buffer-in-window)
+ (window . ,xref--original-window))))))
+ (with-selected-window (display-buffer buf action)
(xref--goto-char pos)
(run-hooks 'xref-after-jump-hook)
(let ((buf (current-buffer)))
@@ -493,6 +508,19 @@ and finally return the window."
(setq-local other-window-scroll-buffer buf)))
(selected-window))))
+(defun xref--display-buffer-in-other-window (buffer alist)
+ (let ((window (assoc-default 'window alist)))
+ (cl-assert window)
+ (xref--with-dedicated-window
+ (with-selected-window window
+ (display-buffer buffer t)))))
+
+(defun xref--display-buffer-in-window (buffer alist)
+ (let ((window (assoc-default 'window alist)))
+ (cl-assert window)
+ (with-selected-window window
+ (display-buffer buffer '(display-buffer-same-window)))))
+
(defun xref--show-location (location &optional select)
"Help `xref-show-xref' and `xref-goto-xref' do their job.
Go to LOCATION and if SELECT is non-nil select its window. If
@@ -503,8 +531,9 @@ SELECT is `quit', also quit the *xref* window."
(xref-buffer (current-buffer)))
(cond (select
(if (eq select 'quit) (quit-window nil nil))
- (with-current-buffer xref-buffer
- (select-window (xref--show-pos-in-buf marker buf))))
+ (select-window
+ (with-current-buffer xref-buffer
+ (xref--show-pos-in-buf marker buf))))
(t
(save-selected-window
(xref--with-dedicated-window
@@ -541,9 +570,12 @@ SELECT is `quit', also quit the *xref* window."
Non-interactively, non-nil QUIT means to first quit the *xref*
buffer."
(interactive)
- (let ((xref (or (xref--item-at-point)
- (user-error "No reference at point"))))
- (xref--show-location (xref-item-location xref) (if quit 'quit t))))
+ (let* ((buffer (current-buffer))
+ (xref (or (xref--item-at-point)
+ (user-error "No reference at point")))
+ (xref--current-item xref))
+ (xref--show-location (xref-item-location xref) (if quit 'quit t))
+ (next-error-found buffer (current-buffer))))
(defun xref-quit-and-goto-xref ()
"Quit *xref* buffer, then jump to xref on current line."
@@ -677,6 +709,7 @@ references displayed in the current *xref* buffer."
;; suggested by Johan Claesson "to further reduce finger movement":
(define-key map (kbd ".") #'xref-next-line)
(define-key map (kbd ",") #'xref-prev-line)
+ (define-key map (kbd "g") #'xref-revert-buffer)
map))
(define-derived-mode xref--xref-buffer-mode special-mode "XREF"
@@ -685,14 +718,26 @@ references displayed in the current *xref* buffer."
(setq next-error-function #'xref--next-error-function)
(setq next-error-last-buffer (current-buffer)))
+(defvar xref--transient-buffer-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "RET") #'xref-quit-and-goto-xref)
+ (set-keymap-parent map xref--xref-buffer-mode-map)
+ map))
+
+(define-derived-mode xref--transient-buffer-mode
+ xref--xref-buffer-mode
+ "XREF Transient")
+
(defun xref--next-error-function (n reset?)
(when reset?
(goto-char (point-min)))
(let ((backward (< n 0))
(n (abs n))
(xref nil))
- (dotimes (_ n)
- (setq xref (xref--search-property 'xref-item backward)))
+ (if (= n 0)
+ (setq xref (get-text-property (point) 'xref-item))
+ (dotimes (_ n)
+ (setq xref (xref--search-property 'xref-item backward))))
(cond (xref
;; Save the current position (when the buffer is visible,
;; it gets reset to that window's point from time to time).
@@ -704,7 +749,6 @@ references displayed in the current *xref* buffer."
(defvar xref--button-map
(let ((map (make-sparse-keymap)))
- (define-key map [(control ?m)] #'xref-goto-xref)
(define-key map [mouse-1] #'xref-goto-xref)
(define-key map [mouse-2] #'xref--mouse-2)
map))
@@ -714,7 +758,8 @@ references displayed in the current *xref* buffer."
(interactive "e")
(mouse-set-point event)
(forward-line 0)
- (xref--search-property 'xref-item)
+ (or (get-text-property (point) 'xref-item)
+ (xref--search-property 'xref-item))
(xref-show-location-at-point))
(defun xref--insert-xrefs (xref-alist)
@@ -732,18 +777,17 @@ GROUP is a string for decoration purposes and XREF is an
for line-format = (and max-line-width
(format "%%%dd: " max-line-width))
do
- (xref--insert-propertized '(face compilation-info) group "\n")
+ (xref--insert-propertized '(face xref-file-header) group "\n")
(cl-loop for (xref . more2) on xrefs do
(with-slots (summary location) xref
(let* ((line (xref-location-line location))
(prefix
(if line
(propertize (format line-format line)
- 'face 'compilation-line-number)
+ 'face 'xref-line-number)
" ")))
(xref--insert-propertized
(list 'xref-item xref
- ;; 'face 'font-lock-keyword-face
'mouse-face 'highlight
'keymap xref--button-map
'help-echo
@@ -760,47 +804,121 @@ Return an alist of the form ((FILENAME . (XREF ...)) ...)."
(xref-location-group (xref-item-location x)))
#'equal))
-(defun xref--show-xref-buffer (xrefs alist)
- (let ((xref-alist (xref--analyze xrefs)))
+(defun xref--show-xref-buffer (fetcher alist)
+ (cl-assert (functionp fetcher))
+ (let* ((xrefs
+ (or
+ (assoc-default 'fetched-xrefs alist)
+ (funcall fetcher)))
+ (xref-alist (xref--analyze xrefs)))
(with-current-buffer (get-buffer-create xref-buffer-name)
- (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)
- (pop-to-buffer (current-buffer))
- (goto-char (point-min))
- (setq xref--original-window (assoc-default 'window alist)
- xref--original-window-intent (assoc-default 'display-action alist))
- (current-buffer)))))
+ (xref--xref-buffer-mode)
+ (xref--show-common-initialize xref-alist fetcher alist)
+ (pop-to-buffer (current-buffer))
+ (current-buffer))))
+
+(defun xref--show-common-initialize (xref-alist fetcher alist)
+ (setq buffer-undo-list nil)
+ (let ((inhibit-read-only t)
+ (buffer-undo-list t))
+ (erase-buffer)
+ (xref--insert-xrefs xref-alist)
+ (goto-char (point-min))
+ (setq xref--original-window (assoc-default 'window alist)
+ xref--original-window-intent (assoc-default 'display-action alist))
+ (setq xref--fetcher fetcher)))
+
+(defun xref-revert-buffer ()
+ "Refresh the search results in the current buffer."
+ (interactive)
+ (let ((inhibit-read-only t)
+ (buffer-undo-list t))
+ (save-excursion
+ (erase-buffer)
+ (condition-case err
+ (xref--insert-xrefs
+ (xref--analyze (funcall xref--fetcher)))
+ (user-error
+ (insert
+ (propertize
+ (error-message-string err)
+ 'face 'error))))
+ (goto-char (point-min)))))
+
+(defun xref--show-defs-buffer (fetcher alist)
+ (let ((xrefs (funcall fetcher)))
+ (cond
+ ((not (cdr xrefs))
+ (xref-pop-to-location (car xrefs)
+ (assoc-default 'display-action alist)))
+ (t
+ (xref--show-xref-buffer fetcher
+ (cons (cons 'fetched-xrefs xrefs)
+ alist))))))
+
+(defun xref--show-defs-buffer-at-bottom (fetcher alist)
+ "Show definitions list in a window at the bottom.
+When there is more than one definition, split the selected window
+and show the list in a small window at the bottom. And use a
+local keymap that binds `RET' to `xref-quit-and-goto-xref'."
+ (let ((xrefs (funcall fetcher)))
+ (cond
+ ((not (cdr xrefs))
+ (xref-pop-to-location (car xrefs)
+ (assoc-default 'display-action alist)))
+ (t
+ (with-current-buffer (get-buffer-create xref-buffer-name)
+ (xref--transient-buffer-mode)
+ (xref--show-common-initialize (xref--analyze xrefs) fetcher alist)
+ (pop-to-buffer (current-buffer)
+ '(display-buffer-in-direction . ((direction . below))))
+ (current-buffer))))))
-;; This part of the UI seems fairly uncontroversial: it reads the
-;; identifier and deals with the single definition case.
-;; (FIXME: do we really want this case to be handled like that in
-;; "find references" and "find regexp searches"?)
-;;
-;; The controversial multiple definitions case is handed off to
-;; xref-show-xrefs-function.
+(defcustom xref-show-xrefs-function 'xref--show-xref-buffer
+ "Function to display a list of search results.
+
+It should accept two arguments: FETCHER and ALIST.
+
+FETCHER is a function of no arguments that returns a list of xref
+values. It must not depend on the current buffer or selected
+window.
+
+ALIST can include, but limited to, the following keys:
+
+WINDOW for the window that was selected before the current
+command was called.
-(defvar xref-show-xrefs-function 'xref--show-xref-buffer
- "Function to display a list of xrefs.")
+DISPLAY-ACTION indicates where the target location should be
+displayed. The possible values are nil, `window' meaning the
+other window, or `frame' meaning the other frame."
+ :type 'function)
+
+(defcustom xref-show-definitions-function 'xref--show-defs-buffer
+ "Function to display a list of definitions.
+
+Accepts the same arguments as `xref-show-xrefs-function'."
+ :type 'function)
(defvar xref--read-identifier-history nil)
(defvar xref--read-pattern-history nil)
-(defun xref--show-xrefs (xrefs display-action &optional always-show-list)
- (cond
- ((and (not (cdr xrefs)) (not always-show-list))
- (xref-push-marker-stack)
- (xref--pop-to-location (car xrefs) display-action))
- (t
- (xref-push-marker-stack)
- (funcall xref-show-xrefs-function xrefs
- `((window . ,(selected-window))
- (display-action . ,display-action))))))
+(defun xref--show-xrefs (fetcher display-action)
+ (xref--push-markers)
+ (funcall xref-show-xrefs-function fetcher
+ `((window . ,(selected-window))
+ (display-action . ,display-action))))
+
+(defun xref--show-defs (xrefs display-action)
+ (xref--push-markers)
+ (funcall xref-show-definitions-function xrefs
+ `((window . ,(selected-window))
+ (display-action . ,display-action))))
+
+(defun xref--push-markers ()
+ (unless (region-active-p) (push-mark nil t))
+ (xref-push-marker-stack))
(defun xref--prompt-p (command)
(or (eq xref-prompt-for-identifier t)
@@ -811,34 +929,66 @@ Return an alist of the form ((FILENAME . (XREF ...)) ...)."
(defun xref--read-identifier (prompt)
"Return the identifier at point or read it from the minibuffer."
(let* ((backend (xref-find-backend))
- (id (xref-backend-identifier-at-point backend)))
+ (def (xref-backend-identifier-at-point backend)))
(cond ((or current-prefix-arg
- (not id)
+ (not def)
(xref--prompt-p this-command))
- (completing-read (if id
- (format "%s (default %s): "
- (substring prompt 0 (string-match
- "[ :]+\\'" prompt))
- id)
- prompt)
- (xref-backend-identifier-completion-table backend)
- nil nil nil
- 'xref--read-identifier-history id))
- (t id))))
+ (let ((id
+ (completing-read
+ (if def
+ (format "%s (default %s): "
+ (substring prompt 0 (string-match
+ "[ :]+\\'" prompt))
+ def)
+ prompt)
+ (xref-backend-identifier-completion-table backend)
+ nil nil nil
+ 'xref--read-identifier-history def)))
+ (if (equal id "")
+ (or def (user-error "There is no defailt identifier"))
+ id)))
+ (t def))))
;;; Commands
(defun xref--find-xrefs (input kind arg display-action)
- (let ((xrefs (funcall (intern (format "xref-backend-%s" kind))
- (xref-find-backend)
- arg)))
- (unless xrefs
- (user-error "No %s found for: %s" (symbol-name kind) input))
- (xref--show-xrefs xrefs display-action)))
+ (xref--show-xrefs
+ (xref--create-fetcher input kind arg)
+ display-action))
(defun xref--find-definitions (id display-action)
- (xref--find-xrefs id 'definitions id display-action))
+ (xref--show-defs
+ (xref--create-fetcher id 'definitions id)
+ display-action))
+
+(defun xref--create-fetcher (input kind arg)
+ "Return an xref list fetcher function.
+
+It revisits the saved position and delegates the finding logic to
+the xref backend method indicated by KIND and passes ARG to it."
+ (let* ((orig-buffer (current-buffer))
+ (orig-position (point))
+ (backend (xref-find-backend))
+ (method (intern (format "xref-backend-%s" kind))))
+ (lambda ()
+ (save-excursion
+ ;; Xref methods are generally allowed to depend on the text
+ ;; around point, not just on their explicit arguments.
+ ;;
+ ;; There is only so much we can do, however, to recreate that
+ ;; context, given that the user is free to change the buffer
+ ;; contents freely in the meantime.
+ (when (buffer-live-p orig-buffer)
+ (set-buffer orig-buffer)
+ (ignore-errors (goto-char orig-position)))
+ (let ((xrefs (funcall method backend arg)))
+ (unless xrefs
+ (xref--not-found-error kind input))
+ xrefs)))))
+
+(defun xref--not-found-error (kind input)
+ (user-error "No %s found for: %s" (symbol-name kind) input))
;;;###autoload
(defun xref-find-definitions (identifier)
@@ -876,6 +1026,19 @@ is nil, prompt only if there's no usable symbol at point."
(interactive (list (xref--read-identifier "Find references of: ")))
(xref--find-xrefs identifier 'references identifier nil))
+;;;###autoload
+(defun xref-find-definitions-at-mouse (event)
+ "Find the definition of identifier at or around mouse click.
+This command is intended to be bound to a mouse event."
+ (interactive "e")
+ (let ((identifier
+ (save-excursion
+ (mouse-set-point event)
+ (xref-backend-identifier-at-point (xref-find-backend)))))
+ (if identifier
+ (xref-find-definitions identifier)
+ (user-error "No identifier here"))))
+
(declare-function apropos-parse-pattern "apropos" (pattern))
;;;###autoload
@@ -976,7 +1139,7 @@ IGNORES is a list of glob patterns."
;; do that reliably enough, without creating false negatives?
(command (xref--rgrep-command (xref--regexp-to-extended regexp)
files
- (expand-file-name dir)
+ (file-local-name (expand-file-name dir))
ignores))
(def default-directory)
(buf (get-buffer-create " *xref-grep*"))
@@ -987,7 +1150,7 @@ IGNORES is a list of glob patterns."
(erase-buffer)
(setq default-directory def)
(setq status
- (call-process-shell-command command nil t))
+ (process-file-shell-command command nil t))
(goto-char (point-min))
;; Can't use the exit status: Grep exits with 1 to mean "no
;; matches found". Find exits with 1 if any of the invocations
@@ -1028,7 +1191,8 @@ IGNORES is a list of glob patterns."
IGNORES is a list of glob patterns. DIR is an absolute
directory, used as the root of the ignore globs."
(cl-assert (not (string-match-p "\\`~" dir)))
- (when ignores
+ (if (not ignores)
+ ""
(concat
(shell-quote-argument "(")
" -path "
@@ -1089,6 +1253,7 @@ Such as the current syntax table and the applied syntax properties."
(defun xref--collect-matches (hit regexp tmp-buffer)
(pcase-let* ((`(,line ,file ,text) hit)
+ (file (and file (concat (file-remote-p default-directory) file)))
(buf (xref--find-buffer-visiting file))
(syntax-needed (xref--regexp-syntax-dependent-p regexp)))
(if buf
@@ -1139,7 +1304,7 @@ Such as the current syntax table and the applied syntax properties."
(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
+ (add-face-text-property beg-column end-column 'xref-match
t summary)
(push (xref-make-match summary loc (- end-column beg-column))
matches)))