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.el238
1 files changed, 166 insertions, 72 deletions
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el
index 18fdd963fb1..d3780d571fc 100644
--- a/lisp/progmodes/xref.el
+++ b/lisp/progmodes/xref.el
@@ -1,7 +1,7 @@
;;; xref.el --- Cross-referencing commands -*-lexical-binding:t-*-
;; Copyright (C) 2014-2021 Free Software Foundation, Inc.
-;; Version: 1.0.4
+;; Version: 1.1.0
;; Package-Requires: ((emacs "26.1"))
;; This is a GNU ELPA :core package. Avoid functionality that is not
@@ -24,11 +24,6 @@
;;; Commentary:
-;; NOTE: The xref API is still experimental and can change in major,
-;; backward-incompatible ways. Everyone is encouraged to try it, and
-;; report to us any problems or use cases we hadn't anticipated, by
-;; sending an email to emacs-devel, or `M-x report-emacs-bug'.
-;;
;; This file provides a somewhat generic infrastructure for cross
;; referencing commands, in particular "find-definition".
;;
@@ -97,17 +92,13 @@ This is typically the filename.")
"Return the line number corresponding to the location."
nil)
-(cl-defgeneric xref-location-column (_location)
- "Return the exact column corresponding to the location."
- nil)
-
(cl-defgeneric xref-match-length (_item)
"Return the length of the match."
nil)
;;;; Commonly needed location classes are defined here:
-(defcustom xref-file-name-display 'abs
+(defcustom xref-file-name-display 'project-relative
"Style of file name display in *xref* buffers.
If the value is the symbol `abs', the default, show the file names
@@ -130,7 +121,7 @@ in its full absolute form."
(defclass xref-file-location (xref-location)
((file :type string :initarg :file)
(line :type fixnum :initarg :line :reader xref-location-line)
- (column :type fixnum :initarg :column :reader xref-location-column))
+ (column :type fixnum :initarg :column :reader xref-file-location-column))
:documentation "A file location is a file/line/column triple.
Line numbers start from 1 and columns from 0.")
@@ -415,6 +406,12 @@ elements is negated: these commands will NOT prompt."
"Functions called after returning to a pre-jump location."
:type 'hook)
+(defcustom xref-after-update-hook nil
+ "Functions called after the xref buffer is updated."
+ :type 'hook
+ :version "28.1"
+ :package-version '(xref . "1.0.4"))
+
(defvar xref--marker-ring (make-ring xref-marker-ring-length)
"Ring of markers to implement the marker stack.")
@@ -519,7 +516,7 @@ If SELECT is non-nil, select the target window."
"Face for displaying line numbers in the xref buffer."
:version "27.1")
-(defface xref-match '((t :inherit highlight))
+(defface xref-match '((t :inherit match))
"Face used to highlight matches in the xref buffer."
:version "27.1")
@@ -607,16 +604,26 @@ SELECT is `quit', also quit the *xref* window."
(when xref
(xref--show-location (xref-item-location xref)))))
+(defun xref-next-line-no-show ()
+ "Move to the next xref but don't display its source."
+ (interactive)
+ (xref--search-property 'xref-item))
+
(defun xref-next-line ()
"Move to the next xref and display its source in the appropriate window."
(interactive)
- (xref--search-property 'xref-item)
+ (xref-next-line-no-show)
(xref-show-location-at-point))
+(defun xref-prev-line-no-show ()
+ "Move to the previous xref but don't display its source."
+ (interactive)
+ (xref--search-property 'xref-item t))
+
(defun xref-prev-line ()
"Move to the previous xref and display its source in the appropriate window."
(interactive)
- (xref--search-property 'xref-item t)
+ (xref-prev-line-no-show)
(xref-show-location-at-point))
(defun xref-next-group ()
@@ -645,12 +652,12 @@ SELECT is `quit', also quit the *xref* window."
(defun xref-goto-xref (&optional quit)
"Jump to the xref on the current line and select its window.
-Non-interactively, non-nil QUIT, or interactively, with prefix argument
-means to first quit the *xref* buffer."
+If QUIT is non-nil (interactively, with prefix argument), also
+quit the *xref* buffer."
(interactive "P")
(let* ((buffer (current-buffer))
(xref (or (xref--item-at-point)
- (user-error "No reference at point")))
+ (user-error "Choose a reference to visit")))
(xref--current-item xref))
(xref--show-location (xref-item-location xref) (if quit 'quit t))
(if (fboundp 'next-error-found)
@@ -713,10 +720,7 @@ references displayed in the current *xref* buffer."
(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)))
+ (if (xref--outdated-p item)
(message "Search result out of date, skipping")
(cond
((null file-buf)
@@ -733,18 +737,38 @@ references displayed in the current *xref* buffer."
(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))))
+(defun xref--outdated-p (item)
+ "Check that the match location at current position is up-to-date.
+ITEMS is an xref item which "
+ ;; FIXME: The check should most likely be a generic function instead
+ ;; of the assumption that all matches' summaries relate to the
+ ;; buffer text in a particular way.
+ (let* ((summary (xref-item-summary item))
+ ;; Sometimes buffer contents include ^M, and sometimes Grep
+ ;; output includes it, and they don't always match.
+ (strip (lambda (s) (if (string-match "\r\\'" s)
+ (substring-no-properties s 0 -1)
+ s)))
+ (stripped-summary (funcall strip summary))
+ (lendpos (line-end-position))
+ (check (lambda ()
+ (let ((comparison-end
+ (+ (point) (length stripped-summary))))
+ (and (>= lendpos comparison-end)
+ (equal stripped-summary
+ (buffer-substring-no-properties
+ (point) comparison-end)))))))
(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)))))
+ (or
+ ;; Either summary contains match text and after
+ ;; (2nd+ match on the line)...
+ (funcall check)
+ ;; ...or it starts at bol, includes the match and after.
+ (and (< (point) (+ (line-beginning-position)
+ (length stripped-summary)))
+ (save-excursion
+ (forward-line 0)
+ (funcall check)))))))
;; FIXME: Write a nicer UI.
(defun xref--query-replace-1 (from to iter)
@@ -872,6 +896,44 @@ beginning of the line."
(xref--search-property 'xref-item))
(xref-show-location-at-point))
+(defcustom xref-truncation-width 400
+ "The column to visually \"truncate\" each Xref buffer line to."
+ :type '(choice
+ (integer :tag "Number of columns")
+ (const :tag "Disable truncation" nil)))
+
+(defun xref--apply-truncation ()
+ (let ((bol (line-beginning-position))
+ (eol (line-end-position))
+ (inhibit-read-only t)
+ pos adjusted-bol)
+ (when (and xref-truncation-width
+ (> (- eol bol) xref-truncation-width)
+ ;; Either truncation not applied yet, or it hides the current
+ ;; position: need to refresh.
+ (or (and (null (get-text-property (1- eol) 'invisible))
+ (null (get-text-property bol 'invisible)))
+ (get-text-property (point) 'invisible)))
+ (setq adjusted-bol
+ (cond
+ ((eq (get-text-property bol 'face) 'xref-line-number)
+ (next-single-char-property-change bol 'face))
+ (t bol)))
+ (cond
+ ((< (- (point) bol) xref-truncation-width)
+ (setq pos (+ bol xref-truncation-width))
+ (remove-text-properties bol pos '(invisible))
+ (put-text-property pos eol 'invisible 'ellipsis))
+ ((< (- eol (point)) xref-truncation-width)
+ (setq pos (- eol xref-truncation-width))
+ (remove-text-properties pos eol '(invisible))
+ (put-text-property adjusted-bol pos 'invisible 'ellipsis))
+ (t
+ (setq pos (- (point) (/ xref-truncation-width 2)))
+ (put-text-property adjusted-bol pos 'invisible 'ellipsis)
+ (remove-text-properties pos (+ pos xref-truncation-width) '(invisible))
+ (put-text-property (+ pos xref-truncation-width) eol 'invisible 'ellipsis))))))
+
(defun xref--insert-xrefs (xref-alist)
"Insert XREF-ALIST in the current-buffer.
XREF-ALIST is of the form ((GROUP . (XREF ...)) ...), where
@@ -886,30 +948,27 @@ GROUP is a string for decoration purposes and XREF is an
(length (and line (format "%d" line)))))
for line-format = (and max-line-width
(format "%%%dd: " max-line-width))
- with prev-line-key = nil
+ with prev-group = nil
+ with prev-line = nil
do
(xref--insert-propertized '(face xref-file-header xref-group t)
group "\n")
(cl-loop for (xref . more2) on xrefs do
(with-slots (summary location) xref
(let* ((line (xref-location-line location))
- (new-summary summary)
- (line-key (list (xref-location-group location) line))
(prefix
- (if line
- (propertize (format line-format line)
- 'face 'xref-line-number)
- " ")))
+ (cond
+ ((not line) " ")
+ ((and (equal line prev-line)
+ (equal prev-group group))
+ "")
+ (t (propertize (format line-format line)
+ 'face 'xref-line-number)))))
;; Render multiple matches on the same line, together.
- (when (and line (equal prev-line-key line-key))
- (when-let ((column (xref-location-column location)))
- (delete-region
- (save-excursion
- (forward-line -1)
- (move-to-column (+ (length prefix) column))
- (point))
- (point))
- (setq new-summary (substring summary column) prefix "")))
+ (when (and (equal prev-group group)
+ (or (null line)
+ (not (equal prev-line line))))
+ (insert "\n"))
(xref--insert-propertized
(list 'xref-item xref
'mouse-face 'highlight
@@ -917,9 +976,16 @@ GROUP is a string for decoration purposes and XREF is an
'help-echo
(concat "mouse-2: display in another window, "
"RET or mouse-1: follow reference"))
- prefix new-summary)
- (setq prev-line-key line-key)))
- (insert "\n"))))
+ prefix summary)
+ (setq prev-line line
+ prev-group group))))
+ (insert "\n"))
+ (add-to-invisibility-spec '(ellipsis . t))
+ (save-excursion
+ (goto-char (point-min))
+ (while (= 0 (forward-line 1))
+ (xref--apply-truncation)))
+ (run-hooks 'xref-after-update-hook))
(defun xref--analyze (xrefs)
"Find common filenames in XREFS.
@@ -956,6 +1022,7 @@ Return an alist of the form ((FILENAME . (XREF ...)) ...)."
(buffer-undo-list t))
(erase-buffer)
(xref--insert-xrefs xref-alist)
+ (add-hook 'post-command-hook 'xref--apply-truncation nil t)
(goto-char (point-min))
(setq xref--original-window (assoc-default 'window alist)
xref--original-window-intent (assoc-default 'display-action alist))
@@ -1024,6 +1091,12 @@ local keymap that binds `RET' to `xref-quit-and-goto-xref'."
(define-obsolete-function-alias 'xref--show-defs-buffer-at-bottom
#'xref-show-definitions-buffer-at-bottom "28.1")
+(defun xref--completing-read-group (cand transform)
+ "Return group title of candidate CAND or TRANSFORM the candidate."
+ (if transform
+ (substring cand (1+ (next-single-property-change 0 'xref--group cand)))
+ (get-text-property 0 'xref--group cand)))
+
(defun xref-show-definitions-completing-read (fetcher alist)
"Let the user choose the target definition with completion.
@@ -1052,10 +1125,12 @@ between them by typing in the minibuffer with completion."
(format #("%d:" 0 2 (face xref-line-number))
line)
""))
+ (group-prefix
+ (substring group group-prefix-length))
(group-fmt
- (propertize
- (substring group group-prefix-length)
- 'face 'xref-file-header))
+ (propertize group-prefix
+ 'face 'xref-file-header
+ 'xref--group group-prefix))
(candidate
(format "%s:%s%s" group-fmt line-fmt summary)))
(push (cons candidate xref) xref-alist-with-line-info)))))
@@ -1067,7 +1142,9 @@ between them by typing in the minibuffer with completion."
(lambda (string pred action)
(cond
((eq action 'metadata)
- '(metadata . ((category . xref-location))))
+ `(metadata
+ . ((category . xref-location)
+ (group-function . ,#'xref--completing-read-group))))
(t
(complete-with-action action collection string pred)))))
(def (caar collection)))
@@ -1279,7 +1356,9 @@ This command is intended to be bound to a mouse event."
The argument has the same meaning as in `apropos'."
(interactive (list (read-string
"Search for pattern (word list or regexp): "
- nil 'xref--read-pattern-history)))
+ nil 'xref--read-pattern-history
+ (xref-backend-identifier-at-point
+ (xref-find-backend)))))
(require 'apropos)
(let* ((newpat
(if (and (version< emacs-version "28.0.50")
@@ -1390,8 +1469,9 @@ IGNORES is a list of glob patterns for files to ignore."
;; do that reliably enough, without creating false negatives?
(command (xref--rgrep-command (xref--regexp-to-extended regexp)
files
- (file-name-as-directory
- (file-local-name (expand-file-name dir)))
+ (directory-file-name
+ (file-name-unquote
+ (file-local-name (expand-file-name dir))))
ignores))
(def default-directory)
(buf (get-buffer-create " *xref-grep*"))
@@ -1508,6 +1588,8 @@ FILES must be a list of absolute file names."
#'tramp-file-local-name
#'file-local-name)
files)))
+ (when (file-name-quoted-p (car files))
+ (setq files (mapcar #'file-name-unquote files)))
(with-current-buffer output
(erase-buffer)
(with-temp-buffer
@@ -1647,12 +1729,14 @@ Such as the current syntax table and the applied syntax properties."
(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)
- syntax-needed)))
+ (save-restriction
+ (widen)
+ (goto-char (point-min))
+ (forward-line (1- line))
+ (xref--collect-matches-1 regexp file line
+ (line-beginning-position)
+ (line-end-position)
+ syntax-needed))))
;; Using the temporary buffer is both a performance and a buffer
;; management optimization.
(with-current-buffer tmp-buffer
@@ -1678,20 +1762,30 @@ Such as the current syntax table and the applied syntax properties."
syntax-needed)))))
(defun xref--collect-matches-1 (regexp file line line-beg line-end syntax-needed)
- (let (matches)
+ (let (match-pairs matches)
(when syntax-needed
(syntax-propertize line-end))
- ;; FIXME: This results in several lines with the same
- ;; summary. Solve with composite pattern?
(while (and
;; REGEXP might match an empty string. Or line.
- (or (null matches)
+ (or (null match-pairs)
(> (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))
+ (push (cons (match-beginning 0)
+ (match-end 0))
+ match-pairs))
+ (setq match-pairs (nreverse match-pairs))
+ (while match-pairs
+ (let* ((beg-end (pop match-pairs))
+ (beg-column (- (car beg-end) line-beg))
+ (end-column (- (cdr beg-end) line-beg))
(loc (xref-make-file-location file line beg-column))
- (summary (buffer-substring line-beg line-end)))
+ (summary (buffer-substring (if matches (car beg-end) line-beg)
+ (if match-pairs
+ (caar match-pairs)
+ line-end))))
+ (when matches
+ (cl-decf beg-column (- (car beg-end) line-beg))
+ (cl-decf end-column (- (car beg-end) line-beg)))
(add-face-text-property beg-column end-column 'xref-match
t summary)
(push (xref-make-match summary loc (- end-column beg-column))