dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

xref.el (84291B)


      1 ;;; xref.el --- Cross-referencing commands              -*-lexical-binding:t-*-
      2 
      3 ;; Copyright (C) 2014-2023 Free Software Foundation, Inc.
      4 ;; Version: 1.6.1
      5 ;; Package-Requires: ((emacs "26.1"))
      6 
      7 ;; This is a GNU ELPA :core package.  Avoid functionality that is not
      8 ;; compatible with the version of Emacs recorded above.
      9 
     10 ;; This file is part of GNU Emacs.
     11 
     12 ;; GNU Emacs is free software: you can redistribute it and/or modify
     13 ;; it under the terms of the GNU General Public License as published by
     14 ;; the Free Software Foundation, either version 3 of the License, or
     15 ;; (at your option) any later version.
     16 
     17 ;; GNU Emacs is distributed in the hope that it will be useful,
     18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     20 ;; GNU General Public License for more details.
     21 
     22 ;; You should have received a copy of the GNU General Public License
     23 ;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
     24 
     25 ;;; Commentary:
     26 
     27 ;; This file provides a somewhat generic infrastructure for cross
     28 ;; referencing commands, in particular "find-definition".
     29 ;;
     30 ;; Some part of the functionality must be implemented in a language
     31 ;; dependent way and that's done by defining an xref backend.
     32 ;;
     33 ;; That consists of a constructor function, which should return a
     34 ;; backend value, and a set of implementations for the generic
     35 ;; functions:
     36 ;;
     37 ;; `xref-backend-identifier-at-point',
     38 ;; `xref-backend-identifier-completion-table',
     39 ;; `xref-backend-definitions', `xref-backend-references',
     40 ;; `xref-backend-apropos', which see.
     41 ;;
     42 ;; A major mode would normally use `add-hook' to add the backend
     43 ;; constructor to `xref-backend-functions'.
     44 ;;
     45 ;; The last three methods operate with "xref" and "location" values.
     46 ;;
     47 ;; One would usually call `xref-make' and `xref-make-file-location',
     48 ;; `xref-make-buffer-location' or `xref-make-bogus-location' to create
     49 ;; them.  More generally, a location must be an instance of a type for
     50 ;; which methods `xref-location-group' and `xref-location-marker' are
     51 ;; implemented.
     52 ;;
     53 ;; There's a special kind of xrefs we call "match xrefs", which
     54 ;; correspond to search results.  For these values,
     55 ;; `xref-match-length' must be defined, and `xref-location-marker'
     56 ;; must return the beginning of the match.
     57 ;;
     58 ;; Each identifier must be represented as a string.  Implementers can
     59 ;; use string properties to store additional information about the
     60 ;; identifier, but they should keep in mind that values returned from
     61 ;; `xref-backend-identifier-completion-table' should still be
     62 ;; distinct, because the user can't see the properties when making the
     63 ;; choice.
     64 ;;
     65 ;; Older versions of Xref used EIEIO for implementation of the
     66 ;; built-in types, and included a class called `xref-location' which
     67 ;; was supposed to be inherited from.  Neither is true anymore.
     68 ;;
     69 ;; See the etags and elisp-mode implementations for full examples.
     70 
     71 ;;; Code:
     72 
     73 (require 'cl-lib)
     74 (require 'ring)
     75 (require 'project)
     76 
     77 (eval-and-compile
     78   (when (version< emacs-version "28.0.60")
     79     ;; etags.el in Emacs 26 and 27 uses EIEIO, and its location type
     80     ;; inherits from `xref-location'.
     81     (require 'eieio)
     82 
     83     ;; Suppressing byte-compilation warnings (in Emacs 28+) about
     84     ;; `defclass' not being defined, which happens because the
     85     ;; `require' statement above is not evaluated either.
     86     ;; FIXME: Use `with-suppressed-warnings' when we stop supporting Emacs 26.
     87     (with-no-warnings
     88       (defclass xref-location () ()
     89         :documentation "(Obsolete) location represents a position in a file or buffer."))))
     90 
     91 (defgroup xref nil "Cross-referencing commands."
     92   :version "25.1"
     93   :group 'tools)
     94 
     95 
     96 ;;; Locations
     97 
     98 (cl-defgeneric xref-location-marker (location)
     99   "Return the marker for LOCATION.")
    100 
    101 (cl-defgeneric xref-location-group (location)
    102   "Return a string used to group a set of locations.
    103 This is typically a file name, but can also be a package name, or
    104 some other label.
    105 
    106 When it is a file name, it should be the \"expanded\" version.")
    107 
    108 (cl-defgeneric xref-location-line (_location)
    109   "Return the line number corresponding to the location."
    110   nil)
    111 
    112 (cl-defgeneric xref-match-length (_item)
    113   "Return the length of the match."
    114   nil)
    115 
    116 ;;;; Commonly needed location types are defined here:
    117 
    118 (defcustom xref-file-name-display 'project-relative
    119   "Style of file name display in *xref* buffers.
    120 
    121 If the value is the symbol `abs', show the file names in their
    122 full absolute form.
    123 
    124 If `nondirectory', show only the nondirectory (a.k.a. \"base name\")
    125 part of the file name.
    126 
    127 If `project-relative', the default, show only the file name
    128 relative to the current project root.  If there is no current
    129 project, or if the file resides outside of its root, show that
    130 particular file name in its full absolute form."
    131   :type '(choice (const :tag "absolute file name" abs)
    132                  (const :tag "nondirectory file name" nondirectory)
    133                  (const :tag "relative to project root" project-relative))
    134   :version "27.1")
    135 
    136 ;; FIXME: might be useful to have an optional "hint" i.e. a string to
    137 ;; search for in case the line number is slightly out of date.
    138 (cl-defstruct (xref-file-location
    139                (:constructor xref-make-file-location (file line column)))
    140   "A file location is a file/line/column triple.
    141 Line numbers start from 1 and columns from 0."
    142   file line column)
    143 
    144 (cl-defmethod xref-location-group ((l xref-file-location))
    145   (xref-file-location-file l))
    146 
    147 (cl-defmethod xref-location-line ((l xref-file-location))
    148   (xref-file-location-line l))
    149 
    150 (cl-defmethod xref-location-marker ((l xref-file-location))
    151   (pcase-let (((cl-struct xref-file-location file line column) l))
    152     (with-current-buffer
    153         (or (get-file-buffer file)
    154             (let ((find-file-suppress-same-file-warnings t))
    155               (find-file-noselect file)))
    156       (save-restriction
    157         (widen)
    158         (save-excursion
    159           (goto-char (point-min))
    160           (ignore-errors
    161             ;; xref location may be out of date; it may be past the
    162             ;; end of the current file, or the file may have been
    163             ;; deleted. Return a reasonable location; the user will
    164             ;; figure it out.
    165             (beginning-of-line line)
    166             (forward-char column))
    167           (point-marker))))))
    168 
    169 (cl-defstruct (xref-buffer-location
    170                (:constructor xref-make-buffer-location (buffer position)))
    171   buffer position)
    172 
    173 (cl-defmethod xref-location-marker ((l xref-buffer-location))
    174   (pcase-let (((cl-struct xref-buffer-location buffer position) l))
    175     (let ((m (make-marker)))
    176       (move-marker m position buffer))))
    177 
    178 (cl-defmethod xref-location-group ((l xref-buffer-location))
    179   (pcase-let (((cl-struct xref-buffer-location buffer) l))
    180     (or (buffer-file-name buffer)
    181         (format "(buffer %s)" (buffer-name buffer)))))
    182 
    183 (cl-defstruct (xref-bogus-location
    184                (:constructor xref-make-bogus-location (message)))
    185   "Bogus locations are sometimes useful to indicate errors,
    186 e.g. when we know that a function exists but the actual location
    187 is not known."
    188   message)
    189 
    190 (cl-defmethod xref-location-marker ((l xref-bogus-location))
    191   (user-error "%s" (xref-bogus-location-message l)))
    192 
    193 (cl-defmethod xref-location-group ((_ xref-bogus-location)) "(No location)")
    194 
    195 
    196 ;;; Cross-reference
    197 
    198 (defmacro xref--defstruct (name &rest fields)
    199   (declare (indent 1))
    200   `(cl-defstruct ,(if (>= emacs-major-version 27)
    201                       name
    202                     (remq (assq :noinline name) name))
    203      ,@fields))
    204 
    205 (xref--defstruct (xref-item
    206                   (:constructor xref-make (summary location))
    207                   (:noinline t))
    208   "An xref item describes a reference to a location somewhere."
    209   (summary nil :documentation "String which describes the location.
    210 
    211 When `xref-location-line' returns non-nil (a number), the summary
    212 is implied to be the contents of a file or buffer line containing
    213 the location.  When multiple locations in a row report the same
    214 line, in the same group (corresponding to the case of multiple
    215 locations on one line), the summaries are concatenated in the
    216 Xref output buffer.  Consequently, any code that creates xref
    217 values should take care to slice the summary values when several
    218 locations point to the same line.
    219 
    220 This behavior is new in Emacs 28.")
    221   location)
    222 
    223 (xref--defstruct (xref-match-item
    224                   (:include xref-item)
    225                   (:constructor xref-make-match (summary location length))
    226                   (:noinline t))
    227   "A match xref item describes a search result."
    228   length)
    229 
    230 (cl-defmethod xref-match-length ((item xref-match-item))
    231   "Return the length of the match."
    232   (xref-match-item-length item))
    233 
    234 
    235 ;;; API
    236 
    237 (defvar xref-backend-functions nil
    238   "Special hook to find the xref backend for the current context.
    239 Each function on this hook is called in turn with no arguments,
    240 and should return either nil to mean that it is not applicable,
    241 or an xref backend, which is a value to be used to dispatch the
    242 generic functions.")
    243 
    244 ;; We make the etags backend the default for now, until something
    245 ;; better comes along.  Use APPEND so that any `add-hook' calls made
    246 ;; before this package is loaded put new items before this one.
    247 (add-hook 'xref-backend-functions #'etags--xref-backend t)
    248 
    249 ;;;###autoload
    250 (defun xref-find-backend ()
    251   (run-hook-with-args-until-success 'xref-backend-functions))
    252 
    253 (cl-defgeneric xref-backend-definitions (backend identifier)
    254   "Find definitions of IDENTIFIER.
    255 
    256 The result must be a list of xref objects.  If IDENTIFIER
    257 contains sufficient information to determine a unique definition,
    258 return only that definition.  If there are multiple possible
    259 definitions, return all of them.  If no definitions can be found,
    260 return nil.
    261 
    262 IDENTIFIER can be any string returned by
    263 `xref-backend-identifier-at-point', or from the table returned by
    264 `xref-backend-identifier-completion-table'.
    265 
    266 To create an xref object, call `xref-make'.")
    267 
    268 (cl-defgeneric xref-backend-references (_backend identifier)
    269   "Find references of IDENTIFIER.
    270 The result must be a list of xref objects.  If no references can
    271 be found, return nil.
    272 
    273 The default implementation uses `semantic-symref-tool-alist' to
    274 find a search tool; by default, this uses \"find | grep\" in the
    275 current project's main and external roots."
    276   (mapcan
    277    (lambda (dir)
    278      (message "Searching %s..." dir)
    279      (redisplay)
    280      (prog1
    281          (xref-references-in-directory identifier dir)
    282        (message "Searching %s... done" dir)))
    283    (let ((pr (project-current t)))
    284      (cons
    285       (xref--project-root pr)
    286       (project-external-roots pr)))))
    287 
    288 (cl-defgeneric xref-backend-apropos (backend pattern)
    289   "Find all symbols that match PATTERN string.
    290 The second argument has the same meaning as in `apropos'.
    291 
    292 If BACKEND is implemented in Lisp, it can use
    293 `xref-apropos-regexp' to convert the pattern to regexp.")
    294 
    295 (cl-defgeneric xref-backend-identifier-at-point (_backend)
    296   "Return the relevant identifier at point.
    297 
    298 The return value must be a string, or nil meaning no identifier
    299 at point found.
    300 
    301 If it's hard to determine the identifier precisely (e.g., because
    302 it's a method call on unknown type), the implementation can
    303 return a simple string (such as symbol at point) marked with a
    304 special text property which e.g. `xref-backend-definitions' would
    305 recognize and then delegate the work to an external process."
    306   (let ((thing (thing-at-point 'symbol)))
    307     (and thing (substring-no-properties thing))))
    308 
    309 (cl-defgeneric xref-backend-identifier-completion-table (backend)
    310   "Return the completion table for identifiers.")
    311 
    312 (cl-defgeneric xref-backend-identifier-completion-ignore-case (_backend)
    313   "Return t if case is not significant in identifier completion."
    314   completion-ignore-case)
    315 
    316 
    317 ;;; misc utilities
    318 (defun xref--alistify (list key)
    319   "Partition the elements of LIST into an alist.
    320 KEY extracts the key from an element."
    321   (let ((table (make-hash-table :test #'equal)))
    322     (dolist (e list)
    323       (let* ((k (funcall key e))
    324              (probe (gethash k table)))
    325         (if probe
    326             (puthash k (cons e probe) table)
    327           (puthash k (list e) table))))
    328     ;; Put them back in order.
    329     (cl-loop for key being hash-keys of table using (hash-values value)
    330              collect (cons key (nreverse value)))))
    331 
    332 (defun xref--insert-propertized (props &rest strings)
    333   "Insert STRINGS with text properties PROPS."
    334   (let ((start (point)))
    335     (apply #'insert strings)
    336     (add-text-properties start (point) props)))
    337 
    338 (defun xref--search-property (property &optional backward)
    339     "Search the next text range where text property PROPERTY is non-nil.
    340 Return the value of PROPERTY.  If BACKWARD is non-nil, search
    341 backward."
    342   (let ((next (if backward
    343                   #'previous-single-char-property-change
    344                 #'next-single-char-property-change))
    345         (start (point))
    346         (value nil))
    347     (while (progn
    348              (goto-char (funcall next (point) property))
    349              (not (or (and
    350                        (memq (get-char-property (point) 'invisible) '(ellipsis nil))
    351                        (setq value (get-text-property (point) property)))
    352                       (eobp)
    353                       (bobp)))))
    354     (cond (value)
    355           (t (goto-char start) nil))))
    356 
    357 
    358 ;; Dummy variable retained for compatibility.
    359 (defvar xref-marker-ring-length 16)
    360 (make-obsolete-variable 'xref-marker-ring-length nil "29.1")
    361 
    362 (defcustom xref-prompt-for-identifier '(not xref-find-definitions
    363                                             xref-find-definitions-other-window
    364                                             xref-find-definitions-other-frame)
    365   "If non-nil, prompt for the identifier to find.
    366 
    367 When t, always prompt for the identifier name.
    368 
    369 When nil, prompt only when there's no value at point we can use,
    370 or when the command has been called with the prefix argument.
    371 
    372 Otherwise, it's a list of xref commands which will always prompt,
    373 with the identifier at point, if any, used as the default.
    374 If the list starts with `not', the meaning of the rest of the
    375 elements is negated: these commands will NOT prompt."
    376   :type '(choice (const :tag "Always prompt for identifier" t)
    377                  (const :tag "Prompt if no identifier at point" nil)
    378                  (set :menu-tag "Prompt according to command"
    379                       :tag "Prompt according to command"
    380 		      :value (not)
    381 		      (const :tag "Except for commands listed below" not)
    382 		      (repeat :inline t (symbol :tag "command")))))
    383 
    384 (defcustom xref-after-jump-hook '(recenter
    385                                   xref-pulse-momentarily)
    386   "Functions called after jumping to an xref.
    387 Also see `xref-current-item'."
    388   :type 'hook)
    389 
    390 (defcustom xref-after-return-hook '(xref-pulse-momentarily)
    391   "Functions called after returning to a pre-jump location."
    392   :type 'hook)
    393 
    394 (defcustom xref-after-update-hook nil
    395   "Functions called after the xref buffer is updated."
    396   :type 'hook
    397   :version "28.1"
    398   :package-version '(xref . "1.0.4"))
    399 
    400 (defcustom xref-auto-jump-to-first-definition nil
    401   "If t, `xref-find-definitions' always jumps to the first result.
    402 `show' means to show the first result's location, but keep the
    403 focus on the Xref buffer's window.
    404 `move' means to only move point to the first result.
    405 This variable also affects the variants of `xref-find-definitions',
    406 such as `xref-find-definitions-other-window'."
    407   :type '(choice (const :tag "Jump" t)
    408                  (const :tag "Show" show)
    409                  (const :tag "Move point only" move)
    410                  (const :tag "No auto-jump" nil))
    411   :version "28.1"
    412   :package-version '(xref . "1.2.0"))
    413 
    414 (defcustom xref-auto-jump-to-first-xref nil
    415   "If t, `xref-find-references' always jumps to the first result.
    416 `show' means to show the first result's location, but keep the
    417 focus on the Xref buffer's window.
    418 `move' means to only move point to the first result.
    419 This variable also affects commands similar to `xref-find-references',
    420 such as `xref-find-references-at-mouse', `xref-find-apropos',
    421 and `project-find-regexp'.
    422 
    423 Please be careful when changing the value if you are using Emacs 27
    424 or earlier: it can break `dired-do-find-regexp-and-replace'."
    425   :type '(choice (const :tag "Jump" t)
    426                  (const :tag "Show" show)
    427                  (const :tag "Move point only" move)
    428                  (const :tag "No auto-jump" nil))
    429   :version "28.1"
    430   :package-version '(xref . "1.2.0"))
    431 
    432 (defcustom xref-history-storage #'xref-global-history
    433   "Function that returns xref history.
    434 
    435 The following functions that can be used as this variable's value
    436 are predefined:
    437 
    438 - `xref-global-history'
    439     Return a single, global history used across the entire Emacs
    440     session.  This is the default.
    441 - `xref-window-local-history'
    442     Return separate xref histories, one per window.  Allows
    443     independent navigation of code in each window.  A new
    444     xref history is created for every new window."
    445   :type '(radio
    446           (function-item :tag "Per-window history" xref-window-local-history)
    447           (function-item :tag "Global history for Emacs session"
    448                          xref-global-history)
    449           (function :tag "Other"))
    450   :version "29.1"
    451   :package-version '(xref . "1.6.0"))
    452 
    453 (make-obsolete-variable 'xref--marker-ring 'xref--history "29.1")
    454 
    455 (defun xref-set-marker-ring-length (_var _val)
    456   (declare (obsolete nil "29.1"))
    457   nil)
    458 
    459 (defun xref--make-xref-history ()
    460   "Return a new xref history."
    461   (cons nil nil))
    462 
    463 (defvar xref--history (xref--make-xref-history)
    464   "(BACKWARD-STACK . FORWARD-STACK) of markers to visited Xref locations.")
    465 
    466 (defun xref-global-history (&optional new-value)
    467   "Return the xref history that is global for the current Emacs session.
    468 
    469 Override existing value with NEW-VALUE if NEW-VALUE is set."
    470   (if new-value
    471       (setq xref--history new-value)
    472     xref--history))
    473 
    474 (defun xref-window-local-history (&optional new-value)
    475   "Return window-local xref history for the selected window.
    476 
    477 Override existing value with NEW-VALUE if NEW-VALUE is set."
    478   (let ((w (selected-window)))
    479     (if new-value
    480         (set-window-parameter w 'xref--history new-value)
    481       (or (window-parameter w 'xref--history)
    482           (set-window-parameter w 'xref--history (xref--make-xref-history))))))
    483 
    484 (defun xref--get-history ()
    485   "Return xref history using xref-history-storage."
    486   (funcall xref-history-storage))
    487 
    488 (defun xref--push-backward (m)
    489   "Push marker M onto the backward history stack."
    490   (let ((history (xref--get-history)))
    491     (unless (equal m (caar history))
    492       (push m (car history)))))
    493 
    494 (defun xref--push-forward (m)
    495   "Push marker M onto the forward history stack."
    496   (let ((history (xref--get-history)))
    497     (unless (equal m (cadr history))
    498       (push m (cdr history)))))
    499 
    500 (defun xref-push-marker-stack (&optional m)
    501   "Add point M (defaults to `point-marker') to the marker stack.
    502 The future stack is erased."
    503   (xref--push-backward (or m (point-marker)))
    504   (let ((history (xref--get-history)))
    505     (dolist (mk (cdr history))
    506       (set-marker mk nil nil))
    507     (setcdr history nil)))
    508 
    509 ;;;###autoload
    510 (define-obsolete-function-alias 'xref-pop-marker-stack #'xref-go-back "29.1")
    511 
    512 ;;;###autoload
    513 (defun xref-go-back ()
    514   "Go back to the previous position in xref history.
    515 To undo, use \\[xref-go-forward]."
    516   (interactive)
    517   (let ((history (xref--get-history)))
    518     (if (null (car history))
    519         (user-error "At start of xref history")
    520       (let ((marker (pop (car history))))
    521         (xref--push-forward (point-marker))
    522         (switch-to-buffer (or (marker-buffer marker)
    523                               (user-error "The marked buffer has been deleted")))
    524         (goto-char (marker-position marker))
    525         (set-marker marker nil nil)
    526         (run-hooks 'xref-after-return-hook)))))
    527 
    528 ;;;###autoload
    529 (defun xref-go-forward ()
    530   "Got to the point where a previous \\[xref-go-back] was invoked."
    531   (interactive)
    532   (let ((history (xref--get-history)))
    533     (if (null (cdr history))
    534         (user-error "At end of xref history")
    535       (let ((marker (pop (cdr history))))
    536         (xref--push-backward (point-marker))
    537         (switch-to-buffer (or (marker-buffer marker)
    538                               (user-error "The marked buffer has been deleted")))
    539         (goto-char (marker-position marker))
    540         (set-marker marker nil nil)
    541         (run-hooks 'xref-after-return-hook)))))
    542 
    543 (define-obsolete-variable-alias
    544   'xref--current-item
    545   'xref-current-item
    546   "29.1")
    547 
    548 (defvar xref-current-item nil
    549   "Dynamically bound to the current item being processed.
    550 This can be used from `xref-after-jump-hook', for instance.")
    551 
    552 (defun xref-pulse-momentarily ()
    553   (pcase-let ((`(,beg . ,end)
    554                (save-excursion
    555                  (or
    556                   (let ((length (xref-match-length xref-current-item)))
    557                     (and length (cons (point) (+ (point) length))))
    558                   (back-to-indentation)
    559                   (if (eolp)
    560                       (cons (line-beginning-position) (1+ (point)))
    561                     (cons (point) (line-end-position)))))))
    562     (pulse-momentary-highlight-region beg end 'next-error)))
    563 
    564 ;; etags.el needs this
    565 (defun xref-clear-marker-stack ()
    566   "Discard all markers from the xref history."
    567   (let ((history (xref--get-history)))
    568     (dolist (l (list (car history) (cdr history)))
    569       (dolist (m l)
    570         (set-marker m nil nil)))
    571     (setq history (cons nil nil)))
    572   nil)
    573 
    574 ;;;###autoload
    575 (defun xref-marker-stack-empty-p ()
    576   "Whether the xref back-history is empty."
    577   (null (car (xref--get-history))))
    578 ;; FIXME: rename this to `xref-back-history-empty-p'.
    579 
    580 ;;;###autoload
    581 (defun xref-forward-history-empty-p ()
    582   "Whether the xref forward-history is empty."
    583   (null (cdr (xref--get-history))))
    584 
    585 
    586 (defun xref--goto-char (pos)
    587   (cond
    588    ((and (<= (point-min) pos) (<= pos (point-max))))
    589    (widen-automatically (widen))
    590    (t (user-error "Position is outside accessible part of buffer")))
    591   (goto-char pos))
    592 
    593 (defun xref--goto-location (location)
    594   "Set buffer and point according to `xref-location' LOCATION."
    595   (let ((marker (xref-location-marker location)))
    596     (set-buffer (marker-buffer marker))
    597     (xref--goto-char marker)))
    598 
    599 (defun xref-pop-to-location (item &optional action)
    600   "Go to the location of ITEM and display the buffer.
    601 ACTION controls how the buffer is displayed:
    602   nil      -- `switch-to-buffer'
    603   `window' -- `pop-to-buffer' (other window)
    604   `frame'  -- `pop-to-buffer' (other frame)
    605 If SELECT is non-nil, select the target window."
    606   (let* ((marker (save-excursion
    607                    (xref-location-marker (xref-item-location item))))
    608          (buf (marker-buffer marker)))
    609     (cl-ecase action
    610       ((nil)  (switch-to-buffer buf))
    611       (window (pop-to-buffer buf t))
    612       (frame  (let ((pop-up-frames t)) (pop-to-buffer buf t))))
    613     (xref--goto-char marker))
    614   (let ((xref-current-item item))
    615     (run-hooks 'xref-after-jump-hook)))
    616 
    617 
    618 ;;; XREF buffer (part of the UI)
    619 
    620 ;; The xref buffer is used to display a set of xrefs.
    621 (defconst xref-buffer-name "*xref*"
    622   "The name of the buffer to show xrefs.")
    623 
    624 (defface xref-file-header '((t :inherit compilation-info))
    625   "Face used to highlight file header in the xref buffer."
    626   :version "27.1")
    627 
    628 (defface xref-line-number '((t :inherit compilation-line-number))
    629   "Face for displaying line numbers in the xref buffer."
    630   :version "27.1")
    631 
    632 (defface xref-match '((t :inherit match))
    633   "Face used to highlight matches in the xref buffer."
    634   :version "27.1")
    635 
    636 (defmacro xref--with-dedicated-window (&rest body)
    637   `(let* ((xref-w (get-buffer-window xref-buffer-name))
    638           (xref-w-dedicated (window-dedicated-p xref-w)))
    639      (unwind-protect
    640          (progn
    641            (when xref-w
    642              (set-window-dedicated-p xref-w 'soft))
    643            ,@body)
    644        (when xref-w
    645          (set-window-dedicated-p xref-w xref-w-dedicated)))))
    646 
    647 (defvar-local xref--original-window-intent nil
    648   "Original window-switching intent before xref buffer creation.")
    649 
    650 (defvar-local xref--original-window nil
    651   "The original window this xref buffer was created from.")
    652 
    653 (defvar-local xref--fetcher nil
    654   "The original function to call to fetch the list of xrefs.")
    655 
    656 (defun xref--show-pos-in-buf (pos buf)
    657   "Goto and display position POS of buffer BUF in a window.
    658 Honor `xref--original-window-intent', run `xref-after-jump-hook'
    659 and finally return the window."
    660   (let* ((pop-up-frames
    661           (or (eq xref--original-window-intent 'frame)
    662               pop-up-frames))
    663          (action
    664           (cond ((eq xref--original-window-intent 'frame)
    665                  t)
    666                 ((eq xref--original-window-intent 'window)
    667                  `((xref--display-buffer-in-other-window)
    668                    (window . ,xref--original-window)))
    669                 ((and
    670                   (window-live-p xref--original-window)
    671                   (or (not (window-dedicated-p xref--original-window))
    672                       (eq (window-buffer xref--original-window) buf)))
    673                  `((xref--display-buffer-in-window)
    674                    (window . ,xref--original-window))))))
    675     (with-selected-window (display-buffer buf action)
    676       (xref--goto-char pos)
    677       (run-hooks 'xref-after-jump-hook)
    678       (selected-window))))
    679 
    680 (defun xref--display-buffer-in-other-window (buffer alist)
    681   (let ((window (assoc-default 'window alist)))
    682     (cl-assert window)
    683     (xref--with-dedicated-window
    684      (with-selected-window window
    685        (display-buffer buffer t)))))
    686 
    687 (defun xref--display-buffer-in-window (buffer alist)
    688   (let ((window (assoc-default 'window alist)))
    689     (cl-assert window)
    690     (with-selected-window window
    691       (display-buffer buffer '(display-buffer-same-window)))))
    692 
    693 (defun xref--show-location (location &optional select)
    694   "Help `xref-show-xref' and `xref-goto-xref' do their job.
    695 Go to LOCATION and if SELECT is non-nil select its window.
    696 If SELECT is `quit', also quit the *xref* window."
    697   (condition-case err
    698       (let* ((marker (xref-location-marker location))
    699              (buf (marker-buffer marker))
    700              (xref-buffer (current-buffer)))
    701         (cond (select
    702                (if (eq select 'quit) (quit-window nil nil))
    703                (let* ((old-frame (selected-frame))
    704                       (window (with-current-buffer xref-buffer
    705                                 (xref--show-pos-in-buf marker buf)))
    706                       (frame (window-frame window)))
    707                  ;; If we chose another frame, make sure it gets input
    708                  ;; focus.
    709                  (unless (eq frame old-frame)
    710                    (select-frame-set-input-focus frame))
    711                  (select-window window)))
    712               (t
    713                (save-selected-window
    714                  (xref--with-dedicated-window
    715                   (xref--show-pos-in-buf marker buf))))))
    716     (user-error (message (error-message-string err)))))
    717 
    718 (defun xref--set-arrow ()
    719   "Set the overlay arrow at the line at point."
    720   (setq overlay-arrow-position
    721         (set-marker (or overlay-arrow-position (make-marker))
    722                     (line-beginning-position))))
    723 
    724 (defun xref-show-location-at-point ()
    725   "Display the source of xref at point in the appropriate window, if any."
    726   (interactive)
    727   (let* ((xref (xref--item-at-point))
    728          (xref-current-item xref))
    729     (when xref
    730       (xref--set-arrow)
    731       (xref--show-location (xref-item-location xref)))))
    732 
    733 (defun xref-next-line-no-show ()
    734   "Move to the next xref but don't display its source."
    735   (interactive)
    736   (xref--search-property 'xref-item))
    737 
    738 (defun xref-next-line ()
    739   "Move to the next xref and display its source in the appropriate window."
    740   (interactive)
    741   (xref-next-line-no-show)
    742   (xref-show-location-at-point))
    743 
    744 (defun xref-prev-line-no-show ()
    745   "Move to the previous xref but don't display its source."
    746   (interactive)
    747   (xref--search-property 'xref-item t))
    748 
    749 (defun xref-prev-line ()
    750   "Move to the previous xref and display its source in the appropriate window."
    751   (interactive)
    752   (xref-prev-line-no-show)
    753   (xref-show-location-at-point))
    754 
    755 (defun xref-next-group ()
    756   "Move to the first item of the next xref group and display its source."
    757   (interactive)
    758   (xref--search-property 'xref-group)
    759   (xref--search-property 'xref-item)
    760   (xref-show-location-at-point))
    761 
    762 (defun xref-prev-group ()
    763   "Move to the first item of the previous xref group and display its source."
    764   (interactive)
    765   ;; Search for the xref group of the current item, provided that the
    766   ;; point is not already in an xref group.
    767   (unless (plist-member (text-properties-at (point)) 'xref-group)
    768     (xref--search-property 'xref-group t))
    769   ;; Search for the previous xref group.
    770   (xref--search-property 'xref-group t)
    771   (xref--search-property 'xref-item)
    772   (xref-show-location-at-point))
    773 
    774 (defun xref--item-at-point ()
    775   (get-text-property
    776    (if (eolp) (1- (point)) (point))
    777    'xref-item))
    778 
    779 (defun xref-goto-xref (&optional quit)
    780   "Jump to the xref on the current line and select its window.
    781 If QUIT is non-nil (interactively, with prefix argument), also
    782 quit the *xref* buffer."
    783   (interactive "P")
    784   (let* ((buffer (current-buffer))
    785          (xref (or (xref--item-at-point)
    786                    (user-error "Choose a reference to visit")))
    787          (xref-current-item xref))
    788     (xref--set-arrow)
    789     (xref--show-location (xref-item-location xref) (if quit 'quit t))
    790     (if (fboundp 'next-error-found)
    791         (next-error-found buffer (current-buffer))
    792       ;; Emacs < 27
    793       (setq next-error-last-buffer buffer))))
    794 
    795 (defun xref-quit-and-goto-xref ()
    796   "Quit *xref* buffer, then jump to xref on current line."
    797   (interactive)
    798   (xref-goto-xref t))
    799 
    800 (defun xref-quit-and-pop-marker-stack ()
    801   "Quit *xref* buffer, then pop the xref marker stack."
    802   (interactive)
    803   (quit-window)
    804   (xref-go-back))
    805 
    806 (defun xref-query-replace-in-results (from to)
    807   "Perform interactive replacement of FROM with TO in all displayed xrefs.
    808 
    809 This function interactively replaces FROM with TO in the names of the
    810 references displayed in the current *xref* buffer.
    811 
    812 When called interactively, it uses '.*' as FROM, which means replace
    813 the whole name, and prompts the user for TO.
    814 If invoked with prefix argument, it prompts the user for both FROM and TO.
    815 
    816 As each match is found, the user must type a character saying
    817 what to do with it.  Type SPC or `y' to replace the match,
    818 DEL or `n' to skip and go to the next match.  For more directions,
    819 type \\[help-command] at that time.
    820 
    821 Note that this function cannot be used in *xref* buffers that show
    822 a partial list of all references, such as the *xref* buffer created
    823 by \\[xref-find-definitions] and its variants, since those list only
    824 some of the references to the identifiers."
    825   (interactive
    826    (let* ((fr
    827            (if current-prefix-arg
    828                (read-regexp "Query-replace (regexp)" ".*")
    829              ".*"))
    830           (prompt (if current-prefix-arg
    831                       (format "Query-replace (regexp) %s with: " fr)
    832                     "Query-replace all matches with: ")))
    833      (list fr (read-regexp prompt))))
    834   (let* (item xrefs iter)
    835     (save-excursion
    836       (while (setq item (xref--search-property 'xref-item))
    837         (when (xref-match-length item)
    838           (push item xrefs))))
    839     (unwind-protect
    840         (progn
    841           (goto-char (point-min))
    842           (setq iter (xref--buf-pairs-iterator (nreverse xrefs)))
    843           (xref--query-replace-1 from to iter))
    844       (funcall iter :cleanup))))
    845 
    846 (defun xref--buf-pairs-iterator (xrefs)
    847   (let (chunk-done item next-pair file-buf pairs all-pairs)
    848     (lambda (action)
    849       (pcase action
    850         (:next
    851          (when (or xrefs next-pair)
    852            (setq chunk-done nil)
    853            (when next-pair
    854              (setq file-buf (marker-buffer (car next-pair))
    855                    pairs (list next-pair)
    856                    next-pair nil))
    857            (while (and (not chunk-done)
    858                        (setq item (pop xrefs)))
    859              (save-excursion
    860                (let* ((loc (xref-item-location item))
    861                       (beg (xref-location-marker loc))
    862                       (end (move-marker (make-marker)
    863                                         (+ beg (xref-match-length item))
    864                                         (marker-buffer beg))))
    865                  (let ((pair (cons beg end)))
    866                    (push pair all-pairs)
    867                    ;; Perform sanity check first.
    868                    (xref--goto-location loc)
    869                    (if (xref--outdated-p item)
    870                        (message "Search result out of date, skipping")
    871                      (cond
    872                       ((null file-buf)
    873                        (setq file-buf (marker-buffer beg))
    874                        (push pair pairs))
    875                       ((equal file-buf (marker-buffer beg))
    876                        (push pair pairs))
    877                       (t
    878                        (setq chunk-done t
    879                              next-pair pair))))))))
    880            (cons file-buf (nreverse pairs))))
    881         (:cleanup
    882          (dolist (pair all-pairs)
    883            (move-marker (car pair) nil)
    884            (move-marker (cdr pair) nil)))))))
    885 
    886 (defun xref--outdated-p (item)
    887   "Check that the match location at current position is up-to-date.
    888 
    889 ITEM is an xref item which is expected to be produced by a search
    890 command and have summary that matches buffer contents near point.
    891 Depending on whether it's the first of the matches on the line,
    892 the summary should either start from bol, or only match after
    893 point."
    894   ;; FIXME: The check should most likely be a generic function instead
    895   ;; of the assumption that all matches' summaries relate to the
    896   ;; buffer text in a particular way.
    897   (let* ((summary (xref-item-summary item))
    898          ;; Sometimes buffer contents include ^M, and sometimes Grep
    899          ;; output includes it, and they don't always match.
    900          (strip (lambda (s) (if (string-match "\r\\'" s)
    901                            (substring-no-properties s 0 -1)
    902                          s)))
    903          (stripped-summary (funcall strip summary))
    904          (lendpos (line-end-position))
    905          (check (lambda ()
    906                   (let ((comparison-end
    907                          (+ (point) (length stripped-summary))))
    908                     (and (>= lendpos comparison-end)
    909                          (equal stripped-summary
    910                                 (buffer-substring-no-properties
    911                                  (point) comparison-end)))))))
    912     (not
    913      (or
    914       ;; Either summary contains match text and after
    915       ;; (2nd+ match on the line)...
    916       (funcall check)
    917       ;; ...or it starts at bol, includes the match and after.
    918       (and (< (point) (+ (line-beginning-position)
    919                          (length stripped-summary)))
    920            (save-excursion
    921              (forward-line 0)
    922              (funcall check)))))))
    923 
    924 ;; FIXME: Write a nicer UI.
    925 (defun xref--query-replace-1 (from to iter)
    926   (let* ((query-replace-lazy-highlight nil)
    927          (continue t)
    928          did-it-once buf-pairs pairs
    929          current-beg current-end
    930          ;; Counteract the "do the next match now" hack in
    931          ;; `perform-replace'.  And still, it'll report that those
    932          ;; matches were "filtered out" at the end.
    933          (isearch-filter-predicate
    934           (lambda (beg end)
    935             (and current-beg
    936                  (>= beg current-beg)
    937                  (<= end current-end))))
    938          (replace-re-search-function
    939           (lambda (from &optional _bound noerror)
    940             (let (found pair)
    941               (while (and (not found) pairs)
    942                 (setq pair (pop pairs)
    943                       current-beg (car pair)
    944                       current-end (cdr pair))
    945                 (goto-char current-beg)
    946                 (when (re-search-forward from current-end noerror)
    947                   (setq found t)))
    948               found))))
    949     (while (and continue (setq buf-pairs (funcall iter :next)))
    950       (if did-it-once
    951           ;; Reuse the same window for subsequent buffers.
    952           (switch-to-buffer (car buf-pairs))
    953         (xref--with-dedicated-window
    954          (pop-to-buffer (car buf-pairs)))
    955         (setq did-it-once t))
    956       (setq pairs (cdr buf-pairs))
    957       (setq continue
    958             (perform-replace from to t t nil nil multi-query-replace-map)))
    959     (unless did-it-once
    960       (user-error
    961        "Cannot perform global renaming of symbols using find-definition results"))
    962     (when (and continue (not buf-pairs))
    963       (message "All results processed"))))
    964 
    965 (defvar xref--xref-buffer-mode-map
    966   (let ((map (make-sparse-keymap)))
    967     (define-key map (kbd "n") #'xref-next-line)
    968     (define-key map (kbd "p") #'xref-prev-line)
    969     (define-key map (kbd "N") #'xref-next-group)
    970     (define-key map (kbd "P") #'xref-prev-group)
    971     (define-key map (kbd "r") #'xref-query-replace-in-results)
    972     (define-key map (kbd "RET") #'xref-goto-xref)
    973     (define-key map (kbd "TAB")  #'xref-quit-and-goto-xref)
    974     (define-key map (kbd "C-o") #'xref-show-location-at-point)
    975     ;; suggested by Johan Claesson "to further reduce finger movement":
    976     (define-key map (kbd ".") #'xref-next-line)
    977     (define-key map (kbd ",") #'xref-prev-line)
    978     (define-key map (kbd "g") #'xref-revert-buffer)
    979     (define-key map (kbd "M-,") #'xref-quit-and-pop-marker-stack)
    980     map))
    981 
    982 (declare-function outline-search-text-property "outline"
    983 		  (property &optional value bound move backward looking-at))
    984 
    985 (define-derived-mode xref--xref-buffer-mode special-mode "XREF"
    986   "Mode for displaying cross-references."
    987   (setq buffer-read-only t)
    988   (setq next-error-function #'xref--next-error-function)
    989   (setq next-error-last-buffer (current-buffer))
    990   (setq imenu-prev-index-position-function
    991         #'xref--imenu-prev-index-position)
    992   (setq imenu-extract-index-name-function
    993         #'xref--imenu-extract-index-name)
    994   (setq-local add-log-current-defun-function
    995 	      #'xref--add-log-current-defun)
    996   (setq-local outline-minor-mode-cycle t
    997               outline-minor-mode-use-buttons 'insert
    998               outline-search-function
    999               (lambda (&optional bound move backward looking-at)
   1000                 (outline-search-text-property
   1001                  'xref-group nil bound move backward looking-at))
   1002               outline-level (lambda () 1)))
   1003 
   1004 (defvar xref--transient-buffer-mode-map
   1005   (let ((map (make-sparse-keymap)))
   1006     (define-key map (kbd "RET") #'xref-quit-and-goto-xref)
   1007     (set-keymap-parent map xref--xref-buffer-mode-map)
   1008     map))
   1009 
   1010 (define-derived-mode xref--transient-buffer-mode
   1011   xref--xref-buffer-mode
   1012   "XREF Transient.")
   1013 
   1014 (defun xref--imenu-prev-index-position ()
   1015   "Move point to previous line in `xref' buffer.
   1016 This function is used as a value for
   1017 `imenu-prev-index-position-function'."
   1018   (if (bobp)
   1019       nil
   1020     (xref--search-property 'xref-group t)))
   1021 
   1022 (defun xref--imenu-extract-index-name ()
   1023   "Return imenu name for line at point.
   1024 This function is used as a value for
   1025 `imenu-extract-index-name-function'.  Point should be at the
   1026 beginning of the line."
   1027   (buffer-substring-no-properties (line-beginning-position)
   1028                                   (line-end-position)))
   1029 
   1030 (defun xref--add-log-current-defun ()
   1031   "Return the string used to group a set of locations.
   1032 This function is used as a value for `add-log-current-defun-function'."
   1033   (xref--group-name-for-display
   1034    (if-let (item (xref--item-at-point))
   1035        (xref-location-group (xref-match-item-location item))
   1036      (xref--imenu-extract-index-name))
   1037    (xref--project-root (project-current))))
   1038 
   1039 (defun xref--next-error-function (n reset?)
   1040   (when reset?
   1041     (goto-char (point-min)))
   1042   (let ((backward (< n 0))
   1043         (n (abs n))
   1044         (xref nil))
   1045     (if (= n 0)
   1046         (setq xref (get-text-property (point) 'xref-item))
   1047       (dotimes (_ n)
   1048         (setq xref (xref--search-property 'xref-item backward))))
   1049     (cond (xref
   1050            ;; Save the current position (when the buffer is visible,
   1051            ;; it gets reset to that window's point from time to time).
   1052            (let ((win (get-buffer-window (current-buffer))))
   1053              (and win (set-window-point win (point))))
   1054            (xref--set-arrow)
   1055            (let ((xref-current-item xref))
   1056              (xref--show-location (xref-item-location xref) t)))
   1057           (t
   1058            (error "No %s xref" (if backward "previous" "next"))))))
   1059 
   1060 (defvar xref--button-map
   1061   (let ((map (make-sparse-keymap)))
   1062     (define-key map [follow-link] 'mouse-face)
   1063     (define-key map [mouse-2] #'xref-goto-xref)
   1064     map))
   1065 
   1066 (defun xref-select-and-show-xref (event)
   1067   "Move point to the button and show the xref definition.
   1068 The window showing the xref buffer will be selected."
   1069   (interactive "e")
   1070   (mouse-set-point event)
   1071   (forward-line 0)
   1072   (or (get-text-property (point) 'xref-item)
   1073       (xref--search-property 'xref-item))
   1074   (xref-show-location-at-point))
   1075 
   1076 (define-obsolete-function-alias
   1077   'xref--mouse-2 #'xref-select-and-show-xref "28.1")
   1078 
   1079 (defcustom xref-truncation-width 400
   1080   "The column to visually \"truncate\" each Xref buffer line to."
   1081   :type '(choice
   1082           (integer :tag "Number of columns")
   1083           (const :tag "Disable truncation" nil)))
   1084 
   1085 (defun xref--apply-truncation ()
   1086   (let ((bol (line-beginning-position))
   1087         (eol (line-end-position))
   1088         (inhibit-read-only t)
   1089         pos adjusted-bol)
   1090     (when (and xref-truncation-width
   1091                (> (- eol bol) xref-truncation-width)
   1092                ;; Either truncation not applied yet, or it hides the current
   1093                ;; position: need to refresh.
   1094                (or (and (null (get-text-property (1- eol) 'invisible))
   1095                         (null (get-text-property bol 'invisible)))
   1096                    (get-text-property (point) 'invisible)))
   1097       (setq adjusted-bol
   1098             (cond
   1099              ((eq (get-text-property bol 'face) 'xref-line-number)
   1100               (next-single-char-property-change bol 'face))
   1101              (t bol)))
   1102       (cond
   1103        ((< (- (point) bol) xref-truncation-width)
   1104         (setq pos (+ bol xref-truncation-width))
   1105         (remove-text-properties bol pos '(invisible))
   1106         (put-text-property pos eol 'invisible 'ellipsis))
   1107        ((< (- eol (point)) xref-truncation-width)
   1108         (setq pos (- eol xref-truncation-width))
   1109         (remove-text-properties pos eol '(invisible))
   1110         (put-text-property adjusted-bol pos 'invisible 'ellipsis))
   1111        (t
   1112         (setq pos (- (point) (/ xref-truncation-width 2)))
   1113         (put-text-property adjusted-bol pos 'invisible 'ellipsis)
   1114         (remove-text-properties pos (+ pos xref-truncation-width) '(invisible))
   1115         (put-text-property (+ pos xref-truncation-width) eol 'invisible 'ellipsis))))))
   1116 
   1117 (defun xref--insert-xrefs (xref-alist)
   1118   "Insert XREF-ALIST in the current buffer.
   1119 XREF-ALIST is of the form ((GROUP . (XREF ...)) ...), where
   1120 GROUP is a string for decoration purposes and XREF is an
   1121 `xref-item' object."
   1122   (require 'compile) ; For the compilation faces.
   1123   (cl-loop for (group . xrefs) in xref-alist
   1124            for max-line = (cl-loop for xref in xrefs
   1125                                    maximize (xref-location-line
   1126                                              (xref-item-location xref)))
   1127            for line-format = (and max-line
   1128                                   (format "%%%dd: " (1+ (floor (log max-line 10)))))
   1129            with item-text-props = (list 'mouse-face 'highlight
   1130                                         'keymap xref--button-map
   1131                                         'help-echo
   1132                                         (concat "mouse-2: display in another window, "
   1133                                                 "RET or mouse-1: follow reference"))
   1134            with prev-group = nil
   1135            with prev-line = nil
   1136            do
   1137            (xref--insert-propertized '(face xref-file-header xref-group t)
   1138                                      group "\n")
   1139            (dolist (xref xrefs)
   1140              (pcase-let (((cl-struct xref-item summary location) xref))
   1141                (let* ((line (xref-location-line location))
   1142                       (prefix
   1143                        (cond
   1144                         ((not line) "  ")
   1145                         ((and (equal line prev-line)
   1146                               (equal prev-group group))
   1147                          "")
   1148                         (t (propertize (format line-format line)
   1149                                        'face 'xref-line-number)))))
   1150                  ;; Render multiple matches on the same line, together.
   1151                  (when (and (equal prev-group group)
   1152                             (or (null line)
   1153                                 (not (equal prev-line line))))
   1154                    (insert "\n"))
   1155                  (xref--insert-propertized (nconc (list 'xref-item xref)
   1156                                                   item-text-props)
   1157                                            prefix summary)
   1158                  (setq prev-line line
   1159                        prev-group group))))
   1160            (insert "\n"))
   1161   (add-to-invisibility-spec '(ellipsis . t))
   1162   (save-excursion
   1163     (goto-char (point-min))
   1164     (while (= 0 (forward-line 1))
   1165       (xref--apply-truncation)))
   1166   (run-hooks 'xref-after-update-hook))
   1167 
   1168 (defun xref--group-name-for-display (group project-root)
   1169   "Return GROUP formatted in the preferred style.
   1170 
   1171 The style is determined by the value of `xref-file-name-display'.
   1172 If GROUP looks like a file name, its value is formatted according
   1173 to that style.  Otherwise it is returned unchanged."
   1174   ;; XXX: The way we verify that it's indeed a file name and not some
   1175   ;; other kind of string, e.g. Java package name or TITLE from
   1176   ;; `tags-apropos-additional-actions', is pretty lax.  But we don't
   1177   ;; want to use `file-exists-p' for performance reasons.  If this
   1178   ;; ever turns out to be a problem, some other alternatives are to
   1179   ;; either have every location type which uses file names format the
   1180   ;; values themselves (e.g. by piping through some public function),
   1181   ;; or adding a new accessor to locations, like GROUP-TYPE.
   1182   (cl-ecase xref-file-name-display
   1183     (abs group)
   1184     (nondirectory
   1185      (if (file-name-absolute-p group)
   1186          (file-name-nondirectory group)
   1187        group))
   1188     (project-relative
   1189      (if (and project-root
   1190               (string-prefix-p project-root group))
   1191          (substring group (length project-root))
   1192        group))))
   1193 
   1194 (defun xref--analyze (xrefs)
   1195   "Find common groups in XREFS and format group names.
   1196 Return an alist of the form ((GROUP . (XREF ...)) ...)."
   1197   (let* ((alist
   1198           (xref--alistify xrefs
   1199                           (lambda (x)
   1200                             (xref-location-group (xref-item-location x)))))
   1201          (project (and
   1202                    (eq xref-file-name-display 'project-relative)
   1203                    (project-current)))
   1204          (project-root (and project
   1205                             (expand-file-name (xref--project-root project)))))
   1206     (mapcar
   1207      (lambda (pair)
   1208        (cons (xref--group-name-for-display (car pair) project-root)
   1209              (cdr pair)))
   1210      alist)))
   1211 
   1212 (defun xref--ensure-default-directory (dd buffer)
   1213   ;; We might be in a let-binding which will restore the current value
   1214   ;; to a previous one (bug#53626).  So do this later.
   1215   (run-with-timer
   1216    0 nil
   1217    (lambda () (with-current-buffer buffer (setq default-directory dd)))))
   1218 
   1219 (defun xref--show-xref-buffer (fetcher alist)
   1220   (cl-assert (functionp fetcher))
   1221   (let* ((xrefs
   1222           (or
   1223            (assoc-default 'fetched-xrefs alist)
   1224            (funcall fetcher)))
   1225          (xref-alist (xref--analyze xrefs))
   1226          (dd default-directory)
   1227          buf)
   1228     (with-current-buffer (get-buffer-create xref-buffer-name)
   1229       (xref--ensure-default-directory dd (current-buffer))
   1230       (xref--xref-buffer-mode)
   1231       (xref--show-common-initialize xref-alist fetcher alist)
   1232       (pop-to-buffer (current-buffer))
   1233       (setq buf (current-buffer)))
   1234     (xref--auto-jump-first buf (assoc-default 'auto-jump alist))
   1235     buf))
   1236 
   1237 (defun xref--project-root (project)
   1238   (if (fboundp 'project-root)
   1239       (project-root project)
   1240     (with-no-warnings
   1241       (car (project-roots project)))))
   1242 
   1243 (defun xref--show-common-initialize (xref-alist fetcher alist)
   1244   (setq buffer-undo-list nil)
   1245   (let ((inhibit-read-only t)
   1246         (buffer-undo-list t)
   1247         (inhibit-modification-hooks t))
   1248     (erase-buffer)
   1249     (setq overlay-arrow-position nil)
   1250     (xref--insert-xrefs xref-alist)
   1251     (add-hook 'post-command-hook 'xref--apply-truncation nil t)
   1252     (goto-char (point-min))
   1253     (setq xref--original-window (assoc-default 'window alist)
   1254           xref--original-window-intent (assoc-default 'display-action alist))
   1255     (setq xref--fetcher fetcher)))
   1256 
   1257 (defun xref-revert-buffer ()
   1258   "Refresh the search results in the current buffer."
   1259   (interactive)
   1260   (let ((inhibit-read-only t)
   1261         (buffer-undo-list t)
   1262         (inhibit-modification-hooks t))
   1263     (save-excursion
   1264       (condition-case err
   1265           (let ((alist (xref--analyze (funcall xref--fetcher))))
   1266             (erase-buffer)
   1267             (xref--insert-xrefs alist))
   1268         (user-error
   1269          (erase-buffer)
   1270          (insert
   1271           (propertize
   1272            (error-message-string err)
   1273            'face 'error)))))))
   1274 
   1275 (defun xref--auto-jump-first (buf value)
   1276   (when value
   1277     (select-window (get-buffer-window buf))
   1278     (goto-char (point-min)))
   1279   (cond
   1280    ((eq value t)
   1281     (xref-next-line-no-show)
   1282     (xref-goto-xref))
   1283    ((eq value 'show)
   1284     (xref-next-line))
   1285    ((eq value 'move)
   1286     (forward-line 1))))
   1287 
   1288 (defun xref-show-definitions-buffer (fetcher alist)
   1289   "Show the definitions list in a regular window.
   1290 
   1291 When only one definition found, jump to it right away instead."
   1292   (let ((xrefs (funcall fetcher))
   1293         buf)
   1294     (cond
   1295      ((not (cdr xrefs))
   1296       (xref-pop-to-location (car xrefs)
   1297                             (assoc-default 'display-action alist)))
   1298      (t
   1299       (setq buf
   1300             (xref--show-xref-buffer fetcher
   1301                                     (cons (cons 'fetched-xrefs xrefs)
   1302                                           alist)))
   1303       (xref--auto-jump-first buf (assoc-default 'auto-jump alist))
   1304       buf))))
   1305 
   1306 (define-obsolete-function-alias
   1307   'xref--show-defs-buffer #'xref-show-definitions-buffer "28.1")
   1308 
   1309 (defun xref-show-definitions-buffer-at-bottom (fetcher alist)
   1310   "Show the definitions list in a window at the bottom.
   1311 
   1312 When there is more than one definition, split the selected window
   1313 and show the list in a small window at the bottom.  And use a
   1314 local keymap that binds `RET' to `xref-quit-and-goto-xref'."
   1315   (let* ((xrefs (funcall fetcher))
   1316          (dd default-directory)
   1317          ;; XXX: Make percentage customizable maybe?
   1318          (max-height (/ (window-height) 2))
   1319          (size-fun (lambda (window)
   1320                      (fit-window-to-buffer window max-height)))
   1321          xref-alist
   1322          buf)
   1323     (cond
   1324      ((not (cdr xrefs))
   1325       (xref-pop-to-location (car xrefs)
   1326                             (assoc-default 'display-action alist)))
   1327      (t
   1328       ;; Call it here because it can call (project-current), and that
   1329       ;; might depend on individual buffer, not just directory.
   1330       (setq xref-alist (xref--analyze xrefs))
   1331 
   1332       (with-current-buffer (get-buffer-create xref-buffer-name)
   1333         (xref--ensure-default-directory dd (current-buffer))
   1334         (xref--transient-buffer-mode)
   1335         (xref--show-common-initialize xref-alist fetcher alist)
   1336         (pop-to-buffer (current-buffer)
   1337                        `(display-buffer-in-direction . ((direction . below)
   1338                                                         (window-height . ,size-fun))))
   1339         (setq buf (current-buffer)))
   1340       (xref--auto-jump-first buf (assoc-default 'auto-jump alist))
   1341       buf))))
   1342 
   1343 (define-obsolete-function-alias 'xref--show-defs-buffer-at-bottom
   1344   #'xref-show-definitions-buffer-at-bottom "28.1")
   1345 
   1346 (defun xref--completing-read-group (cand transform)
   1347   "Return group title of candidate CAND or TRANSFORM the candidate."
   1348   (if transform
   1349       (substring cand (1+ (next-single-property-change 0 'xref--group cand)))
   1350     (get-text-property 0 'xref--group cand)))
   1351 
   1352 (defun xref-show-definitions-completing-read (fetcher alist)
   1353   "Let the user choose the target definition with completion.
   1354 
   1355 When there is more than one definition, let the user choose
   1356 between them by typing in the minibuffer with completion."
   1357   (let* ((xrefs (funcall fetcher))
   1358          (xref-alist (xref--analyze xrefs))
   1359          xref-alist-with-line-info
   1360          xref
   1361          (group-prefix-length
   1362           ;; FIXME: Groups are not always file names, but they often
   1363           ;; are.  At least this shouldn't make the other kinds of
   1364           ;; groups look worse.
   1365           (let ((common-prefix (try-completion "" xref-alist)))
   1366             (if (> (length common-prefix) 0)
   1367                 (length (file-name-directory common-prefix))
   1368               0))))
   1369 
   1370     (cl-loop for ((group . xrefs) . more1) on xref-alist
   1371              do
   1372              (cl-loop for (xref . more2) on xrefs do
   1373                       (let* ((summary (xref-item-summary xref))
   1374                              (location (xref-item-location xref))
   1375                              (line (xref-location-line location))
   1376                              (line-fmt
   1377                               (if line
   1378                                   (format #("%d:" 0 2 (face xref-line-number))
   1379                                           line)
   1380                                 ""))
   1381                              (group-prefix
   1382                               (substring group group-prefix-length))
   1383                              (group-fmt
   1384                               (propertize group-prefix
   1385                                           'face 'xref-file-header
   1386                                           'xref--group group-prefix))
   1387                              (candidate
   1388                               (format "%s:%s%s" group-fmt line-fmt summary)))
   1389                         (push (cons candidate xref) xref-alist-with-line-info))))
   1390 
   1391     (setq xref (if (not (cdr xrefs))
   1392                    (car xrefs)
   1393                  (let* ((collection (reverse xref-alist-with-line-info))
   1394                         (ctable
   1395                          (lambda (string pred action)
   1396                            (cond
   1397                             ((eq action 'metadata)
   1398                              `(metadata
   1399                                . ((category . xref-location)
   1400                                   (group-function . ,#'xref--completing-read-group))))
   1401                             (t
   1402                              (complete-with-action action collection string pred)))))
   1403                         (def (caar collection)))
   1404                    (cdr (assoc (completing-read "Choose definition: "
   1405                                                 ctable nil t
   1406                                                 nil nil
   1407                                                 def)
   1408                                collection)))))
   1409 
   1410     (xref-pop-to-location xref (assoc-default 'display-action alist))))
   1411 
   1412 ;; TODO: Can delete this alias before Emacs 28's release.
   1413 (define-obsolete-function-alias
   1414   'xref--show-defs-minibuffer #'xref-show-definitions-completing-read "28.1")
   1415 
   1416 
   1417 (defcustom xref-show-xrefs-function 'xref--show-xref-buffer
   1418   "Function to display a list of search results.
   1419 
   1420 It should accept two arguments: FETCHER and ALIST.
   1421 
   1422 FETCHER is a function of no arguments that returns a list of xref
   1423 values.  It must not depend on the current buffer or selected
   1424 window.
   1425 
   1426 ALIST can include, but is not limited to, the following keys:
   1427 
   1428 WINDOW for the window that was selected before the current
   1429 command was called.
   1430 
   1431 DISPLAY-ACTION indicates where the target location should be
   1432 displayed.  The possible values are nil, `window' meaning the
   1433 other window, or `frame' meaning the other frame."
   1434   :type 'function)
   1435 
   1436 (defcustom xref-show-definitions-function 'xref-show-definitions-buffer
   1437   "Function to handle the definition search results.
   1438 
   1439 Accepts the same arguments as `xref-show-xrefs-function'.
   1440 
   1441 Generally, it is expected to jump to the definition if there's
   1442 only one, and otherwise provide some way to choose among the
   1443 definitions."
   1444   :type '(choice
   1445           (const :tag "Show a regular list of locations"
   1446                  xref-show-definitions-buffer)
   1447           (const :tag "Show a \"transient\" list at the bottom of the window"
   1448                  xref-show-definitions-buffer-at-bottom)
   1449           (const :tag "Choose the definition with completion"
   1450                  xref-show-definitions-completing-read)
   1451           (function :tag "Custom function")))
   1452 
   1453 (defvar xref--read-identifier-history nil)
   1454 
   1455 (defvar xref--read-pattern-history nil)
   1456 
   1457 ;;;###autoload
   1458 (defun xref-show-xrefs (fetcher display-action)
   1459   "Display some Xref values produced by FETCHER using DISPLAY-ACTION.
   1460 The meanings of both arguments are the same as documented in
   1461 `xref-show-xrefs-function'."
   1462   (xref--show-xrefs fetcher display-action))
   1463 
   1464 (defun xref--show-xrefs (fetcher display-action &optional _always-show-list)
   1465   (xref--push-markers)
   1466   (unless (functionp fetcher)
   1467     ;; Old convention.
   1468     (let ((xrefs fetcher))
   1469       (setq fetcher
   1470             (lambda ()
   1471               (if (eq xrefs 'called-already)
   1472                   (user-error "Refresh is not supported")
   1473                 (prog1
   1474                     xrefs
   1475                   (setq xrefs 'called-already)))))))
   1476   (funcall xref-show-xrefs-function fetcher
   1477            `((window . ,(selected-window))
   1478              (display-action . ,display-action)
   1479              (auto-jump . ,xref-auto-jump-to-first-xref))))
   1480 
   1481 (defun xref--show-defs (xrefs display-action)
   1482   (xref--push-markers)
   1483   (funcall xref-show-definitions-function xrefs
   1484            `((window . ,(selected-window))
   1485              (display-action . ,display-action)
   1486              (auto-jump . ,xref-auto-jump-to-first-definition))))
   1487 
   1488 (defun xref--push-markers ()
   1489   (unless (region-active-p) (push-mark nil t))
   1490   (xref-push-marker-stack))
   1491 
   1492 (defun xref--prompt-p (command)
   1493   (or (eq xref-prompt-for-identifier t)
   1494       (if (eq (car xref-prompt-for-identifier) 'not)
   1495           (not (memq command (cdr xref-prompt-for-identifier)))
   1496         (memq command xref-prompt-for-identifier))))
   1497 
   1498 (defun xref--read-identifier (prompt)
   1499   "Return the identifier at point or read it from the minibuffer."
   1500   (let* ((backend (xref-find-backend))
   1501          (def (xref-backend-identifier-at-point backend))
   1502          (completion-ignore-case
   1503           (xref-backend-identifier-completion-ignore-case backend)))
   1504     (cond ((or current-prefix-arg
   1505                (not def)
   1506                (xref--prompt-p this-command))
   1507            (let ((id
   1508                   (completing-read
   1509                    ;; `format-prompt' is new in Emacs 28.1
   1510                    (if (fboundp 'format-prompt)
   1511                        (format-prompt (substring prompt 0 (string-match
   1512                                                            "[ :]+\\'" prompt))
   1513                                       def)
   1514                      (if def
   1515                          (format "%s (default %s): "
   1516                                  (substring prompt 0 (string-match
   1517                                                       "[ :]+\\'" prompt))
   1518                                  def)
   1519                        prompt))
   1520                    (xref-backend-identifier-completion-table backend)
   1521                    nil nil nil
   1522                    'xref--read-identifier-history def)))
   1523              (if (equal id "")
   1524                  (or def (user-error "There is no default identifier"))
   1525                id)))
   1526           (t def))))
   1527 
   1528 
   1529 ;;; Commands
   1530 
   1531 (defun xref--find-xrefs (input kind arg display-action)
   1532   (xref--show-xrefs
   1533    (xref--create-fetcher input kind arg)
   1534    display-action))
   1535 
   1536 (defun xref--find-definitions (id display-action)
   1537   (xref--show-defs
   1538    (xref--create-fetcher id 'definitions id)
   1539    display-action))
   1540 
   1541 (defun xref--create-fetcher (input kind arg)
   1542   "Return an xref list fetcher function.
   1543 
   1544 It revisits the saved position and delegates the finding logic to
   1545 the xref backend method indicated by KIND and passes ARG to it."
   1546   (let* ((orig-buffer (current-buffer))
   1547          (orig-position (point))
   1548          (backend (xref-find-backend))
   1549          (method (intern (format "xref-backend-%s" kind))))
   1550     (lambda ()
   1551       (save-excursion
   1552         ;; Xref methods are generally allowed to depend on the text
   1553         ;; around point, not just on their explicit arguments.
   1554         ;;
   1555         ;; There is only so much we can do, however, to recreate that
   1556         ;; context, given that the user is free to change the buffer
   1557         ;; contents freely in the meantime.
   1558         (when (buffer-live-p orig-buffer)
   1559           (set-buffer orig-buffer)
   1560           (ignore-errors (goto-char orig-position)))
   1561         (let ((xrefs (funcall method backend arg)))
   1562           (unless xrefs
   1563             (xref--not-found-error kind input))
   1564           xrefs)))))
   1565 
   1566 (defun xref--not-found-error (kind input)
   1567   (user-error "No %s found for: %s" (symbol-name kind) input))
   1568 
   1569 ;;;###autoload
   1570 (defun xref-find-definitions (identifier)
   1571   "Find the definition of the identifier at point.
   1572 With prefix argument or when there's no identifier at point,
   1573 prompt for it.
   1574 
   1575 If sufficient information is available to determine a unique
   1576 definition for IDENTIFIER, display it in the selected window.
   1577 Otherwise, display the list of the possible definitions in a
   1578 buffer where the user can select from the list.
   1579 
   1580 Use \\[xref-go-back] to return back to where you invoked this command."
   1581   (interactive (list (xref--read-identifier "Find definitions of: ")))
   1582   (xref--find-definitions identifier nil))
   1583 
   1584 ;;;###autoload
   1585 (defun xref-find-definitions-other-window (identifier)
   1586   "Like `xref-find-definitions' but switch to the other window."
   1587   (interactive (list (xref--read-identifier "Find definitions of: ")))
   1588   (xref--find-definitions identifier 'window))
   1589 
   1590 ;;;###autoload
   1591 (defun xref-find-definitions-other-frame (identifier)
   1592   "Like `xref-find-definitions' but switch to the other frame."
   1593   (interactive (list (xref--read-identifier "Find definitions of: ")))
   1594   (xref--find-definitions identifier 'frame))
   1595 
   1596 ;;;###autoload
   1597 (defun xref-find-references (identifier)
   1598   "Find references to the identifier at point.
   1599 This command might prompt for the identifier as needed, perhaps
   1600 offering the symbol at point as the default.
   1601 With prefix argument, or if `xref-prompt-for-identifier' is t,
   1602 always prompt for the identifier.  If `xref-prompt-for-identifier'
   1603 is nil, prompt only if there's no usable symbol at point."
   1604   (interactive (list (xref--read-identifier "Find references of: ")))
   1605   (xref--find-xrefs identifier 'references identifier nil))
   1606 
   1607 (defun xref-find-references-and-replace (from to)
   1608   "Replace all references to identifier FROM with TO."
   1609   (interactive
   1610    (let* ((query-replace-read-from-default 'find-tag-default)
   1611           (common
   1612            (query-replace-read-args "Query replace identifier" nil)))
   1613      (list (nth 0 common) (nth 1 common))))
   1614   (require 'xref)
   1615   (with-current-buffer
   1616       (let ((xref-show-xrefs-function
   1617              ;; Some future-proofing (bug#44905).
   1618              (custom--standard-value 'xref-show-xrefs-function))
   1619             ;; Disable auto-jumping, it will mess up replacement logic.
   1620             xref-auto-jump-to-first-xref)
   1621         (xref-find-references from))
   1622     (xref-query-replace-in-results ".*" to)))
   1623 
   1624 ;;;###autoload
   1625 (defun xref-find-definitions-at-mouse (event)
   1626   "Find the definition of identifier at or around mouse click.
   1627 This command is intended to be bound to a mouse event."
   1628   (interactive "e")
   1629   (let ((identifier
   1630          (save-excursion
   1631            (mouse-set-point event)
   1632            (xref-backend-identifier-at-point (xref-find-backend)))))
   1633     (if identifier
   1634         (xref-find-definitions identifier)
   1635       (user-error "No identifier here"))))
   1636 
   1637 ;;;###autoload
   1638 (defun xref-find-references-at-mouse (event)
   1639   "Find references to the identifier at or around mouse click.
   1640 This command is intended to be bound to a mouse event."
   1641   (interactive "e")
   1642   (let ((identifier
   1643          (save-excursion
   1644            (mouse-set-point event)
   1645            (xref-backend-identifier-at-point (xref-find-backend)))))
   1646     (if identifier
   1647         (let ((xref-prompt-for-identifier nil))
   1648           (xref-find-references identifier))
   1649       (user-error "No identifier here"))))
   1650 
   1651 (declare-function apropos-parse-pattern "apropos" (pattern &optional do-all))
   1652 
   1653 ;;;###autoload
   1654 (defun xref-find-apropos (pattern)
   1655   "Find all meaningful symbols that match PATTERN.
   1656 The argument has the same meaning as in `apropos'.
   1657 See `tags-apropos-additional-actions' for how to augment the
   1658 output of this command when the backend is etags."
   1659   (interactive (list (read-string
   1660                       "Search for pattern (word list or regexp): "
   1661                       nil 'xref--read-pattern-history
   1662                       (xref-backend-identifier-at-point
   1663                        (xref-find-backend)))))
   1664   (require 'apropos)
   1665   (let* ((newpat
   1666           (if (and (version< emacs-version "28.0.50")
   1667                    (memq (xref-find-backend) '(elisp etags)))
   1668               ;; Handle backends in older Emacs.
   1669               (xref-apropos-regexp pattern)
   1670             ;; Delegate pattern handling to the backend fully.
   1671             ;; The old way didn't work for "external" backends.
   1672             pattern)))
   1673     (xref--find-xrefs pattern 'apropos newpat nil)))
   1674 
   1675 (defun xref-apropos-regexp (pattern)
   1676   "Return an Emacs regexp from PATTERN similar to `apropos'."
   1677   (apropos-parse-pattern
   1678    (if (string-equal (regexp-quote pattern) pattern)
   1679        ;; Split into words
   1680        (or (split-string pattern "[ \t]+" t)
   1681            (user-error "No word list given"))
   1682      pattern)))
   1683 
   1684 
   1685 ;;; Key bindings
   1686 
   1687 ;;;###autoload (define-key esc-map "." #'xref-find-definitions)
   1688 ;;;###autoload (define-key esc-map "," #'xref-go-back)
   1689 ;;;###autoload (define-key esc-map [?\C-,] #'xref-go-forward)
   1690 ;;;###autoload (define-key esc-map "?" #'xref-find-references)
   1691 ;;;###autoload (define-key esc-map [?\C-.] #'xref-find-apropos)
   1692 ;;;###autoload (define-key ctl-x-4-map "." #'xref-find-definitions-other-window)
   1693 ;;;###autoload (define-key ctl-x-5-map "." #'xref-find-definitions-other-frame)
   1694 
   1695 
   1696 ;;; Helper functions
   1697 
   1698 (defvar xref-etags-mode--saved nil)
   1699 
   1700 (define-minor-mode xref-etags-mode
   1701   "Minor mode to make xref use etags again.
   1702 
   1703 Certain major modes install their own mechanisms for listing
   1704 identifiers and navigation.  Turn this on to undo those settings
   1705 and just use etags."
   1706   :lighter ""
   1707   (if xref-etags-mode
   1708       (progn
   1709         (setq xref-etags-mode--saved xref-backend-functions)
   1710         (kill-local-variable 'xref-backend-functions))
   1711     (setq-local xref-backend-functions xref-etags-mode--saved)))
   1712 
   1713 (declare-function semantic-symref-instantiate "semantic/symref")
   1714 (declare-function semantic-symref-perform-search "semantic/symref")
   1715 (declare-function grep-expand-template "grep")
   1716 (defvar ede-minor-mode) ;; ede.el
   1717 
   1718 ;;;###autoload
   1719 (defun xref-references-in-directory (symbol dir)
   1720   "Find all references to SYMBOL in directory DIR.
   1721 Return a list of xref values.
   1722 
   1723 This function uses the Semantic Symbol Reference API, see
   1724 `semantic-symref-tool-alist' for details on which tools are used,
   1725 and when."
   1726   (cl-assert (directory-name-p dir))
   1727   (require 'semantic/symref)
   1728   (defvar semantic-symref-tool)
   1729 
   1730   ;; Some symref backends use `ede-project-root-directory' as the root
   1731   ;; directory for the search, rather than `default-directory'. Since
   1732   ;; the caller has specified `dir', we bind `ede-minor-mode' to nil
   1733   ;; to force the backend to use `default-directory'.
   1734   (let* ((ede-minor-mode nil)
   1735          (default-directory dir)
   1736          ;; FIXME: Remove CScope and Global from the recognized tools?
   1737          ;; The current implementations interpret the symbol search as
   1738          ;; "find all calls to the given function", but not function
   1739          ;; definition. And they return nothing when passed a variable
   1740          ;; name, even a global one.
   1741          (semantic-symref-tool 'detect)
   1742          (case-fold-search nil)
   1743          (inst (semantic-symref-instantiate :searchfor symbol
   1744                                             :searchtype 'symbol
   1745                                             :searchscope 'subdirs
   1746                                             :resulttype 'line-and-text)))
   1747     (xref--convert-hits (semantic-symref-perform-search inst)
   1748                         (format "\\_<%s\\_>" (regexp-quote symbol)))))
   1749 
   1750 (define-obsolete-function-alias
   1751   'xref-collect-references
   1752   #'xref-references-in-directory
   1753   "27.1")
   1754 
   1755 ;;;###autoload
   1756 (defun xref-matches-in-directory (regexp files dir ignores)
   1757   "Find all matches for REGEXP in directory DIR.
   1758 Return a list of xref values.
   1759 Only files matching some of FILES and none of IGNORES are searched.
   1760 FILES is a string with glob patterns separated by spaces.
   1761 IGNORES is a list of glob patterns for files to ignore."
   1762   ;; DIR can also be a regular file for now; let's not advertise that.
   1763   (grep-compute-defaults)
   1764   (defvar grep-find-template)
   1765   (defvar grep-highlight-matches)
   1766   (pcase-let*
   1767       ((grep-find-template (replace-regexp-in-string "<C>" "<C> -E"
   1768                                                      grep-find-template t t))
   1769        (grep-highlight-matches nil)
   1770        ;; TODO: Sanitize the regexp to remove Emacs-specific terms,
   1771        ;; so that Grep can search for the "relaxed" version.  Can we
   1772        ;; do that reliably enough, without creating false negatives?
   1773        (command (xref--rgrep-command (xref--regexp-to-extended regexp)
   1774                                      files
   1775                                      "."
   1776                                      ignores))
   1777        (local-dir (directory-file-name
   1778                    (file-name-unquote
   1779                     (file-local-name (expand-file-name dir)))))
   1780        (buf (get-buffer-create " *xref-grep*"))
   1781        (`(,grep-re ,file-group ,line-group . ,_) (car grep-regexp-alist))
   1782        (status nil)
   1783        (hits nil))
   1784     (with-current-buffer buf
   1785       (erase-buffer)
   1786       (setq default-directory dir)
   1787       (setq status
   1788             (process-file-shell-command command nil t))
   1789       (goto-char (point-min))
   1790       ;; Can't use the exit status: Grep exits with 1 to mean "no
   1791       ;; matches found".  Find exits with 1 if any of the invocations
   1792       ;; exit with non-zero. "No matches" and "Grep program not found"
   1793       ;; are all the same to it.
   1794       (when (and (/= (point-min) (point-max))
   1795                  (not (looking-at grep-re)))
   1796         (user-error "Search failed with status %d: %s" status (buffer-string)))
   1797       (while (re-search-forward grep-re nil t)
   1798         (push (list (string-to-number (match-string line-group))
   1799                     (concat local-dir (substring (match-string file-group) 1))
   1800                     (buffer-substring-no-properties (point) (line-end-position)))
   1801               hits)))
   1802     (xref--convert-hits (nreverse hits) regexp)))
   1803 
   1804 (define-obsolete-function-alias
   1805   'xref-collect-matches
   1806   #'xref-matches-in-directory
   1807   "27.1")
   1808 
   1809 (declare-function tramp-tramp-file-p "tramp")
   1810 (declare-function tramp-file-local-name "tramp")
   1811 
   1812 ;; TODO: Experiment with 'xargs -P4' (or any other number).
   1813 ;; This speeds up either command, even more than rg's '-j4' does.
   1814 ;; Ripgrep gets jumbled output, though, even with --line-buffered.
   1815 ;; But Grep seems to be stable. Even without --line-buffered.
   1816 (defcustom xref-search-program-alist
   1817   '((grep
   1818      .
   1819      ;; '-s' because 'git ls-files' can output broken symlinks.
   1820      "xargs -0 grep <C> --null -snHE -e <R>")
   1821     (ripgrep
   1822      .
   1823      ;; '!*/' is there to filter out dirs (e.g. submodules).
   1824      "xargs -0 rg <C> --null -nH --no-heading --no-messages -g '!*/' -e <R>"
   1825      )
   1826     (ugrep . "xargs -0 ugrep <C> --null -ns -e <R>"))
   1827   "Association list mapping program identifiers to command templates.
   1828 
   1829 Program identifier should be a symbol, named after the search program.
   1830 
   1831 The command template must be a shell command (or usually a
   1832 pipeline) that will search the files based on the list of file
   1833 names that is piped from stdin, separated by null characters.
   1834 The template should have the following fields:
   1835 
   1836   <C> for extra arguments such as -i and --color
   1837   <R> for the regexp itself (in Extended format)"
   1838   :type '(repeat
   1839           (cons (symbol :tag "Program identifier")
   1840                 (string :tag "Command template")))
   1841   :version "28.1"
   1842   :package-version '(xref . "1.0.4"))
   1843 
   1844 (defcustom xref-search-program 'grep
   1845   "The program to use for regexp search inside files.
   1846 
   1847 This must reference a corresponding entry in `xref-search-program-alist'.
   1848 
   1849 This variable is used in `xref-matches-in-files', which is the
   1850 utility function used by commands like `dired-do-find-regexp' and
   1851 `project-find-regexp'."
   1852   :type '(choice
   1853           (const :tag "Use Grep" grep)
   1854           (const :tag "Use ripgrep" ripgrep)
   1855           (const :tag "Use ugrep" ugrep)
   1856           (symbol :tag "User defined"))
   1857   :version "28.1"
   1858   :package-version '(xref . "1.0.4"))
   1859 
   1860 (defmacro xref--with-connection-local-variables (&rest body)
   1861   (declare (debug t))
   1862   (if (>= emacs-major-version 27)
   1863       `(with-connection-local-variables ,@body)
   1864     `(progn ,@body)))
   1865 
   1866 ;;;###autoload
   1867 (defun xref-matches-in-files (regexp files)
   1868   "Find all matches for REGEXP in FILES.
   1869 Return a list of xref values.
   1870 FILES must be a list of absolute file names.
   1871 
   1872 See `xref-search-program' and `xref-search-program-alist' for how
   1873 to control which program to use when looking for matches."
   1874   (cl-assert (consp files))
   1875   (require 'grep)
   1876   (defvar grep-highlight-matches)
   1877   (pcase-let*
   1878       ((output (get-buffer-create " *project grep output*"))
   1879        (`(,grep-re ,file-group ,line-group . ,_) (car grep-regexp-alist))
   1880        (status nil)
   1881        (hits nil)
   1882        ;; Support for remote files.  The assumption is that, if the
   1883        ;; first file is remote, they all are, and on the same host.
   1884        (dir (file-name-directory (car files)))
   1885        (remote-id (file-remote-p dir))
   1886        ;; The 'auto' default would be fine too, but ripgrep can't handle
   1887        ;; the options we pass in that case.
   1888        (grep-highlight-matches nil)
   1889        (command (grep-expand-template (cdr
   1890                                        (or
   1891                                         (assoc
   1892                                          xref-search-program
   1893                                          xref-search-program-alist)
   1894                                         (user-error "Unknown search program `%s'"
   1895                                                     xref-search-program)))
   1896                                       (xref--regexp-to-extended regexp))))
   1897     (when remote-id
   1898       (require 'tramp)
   1899       (setq files (mapcar
   1900                    (if (tramp-tramp-file-p dir)
   1901                        #'tramp-file-local-name
   1902                        #'file-local-name)
   1903                    files)))
   1904     (when (file-name-quoted-p (car files))
   1905       (setq files (mapcar #'file-name-unquote files)))
   1906     (with-current-buffer output
   1907       (erase-buffer)
   1908       (with-temp-buffer
   1909         (insert (mapconcat #'identity files "\0"))
   1910         (setq default-directory dir)
   1911         (setq status
   1912               (xref--with-connection-local-variables
   1913                (xref--process-file-region (point-min)
   1914                                           (point-max)
   1915                                           shell-file-name
   1916                                           output
   1917                                           nil
   1918                                           shell-command-switch
   1919                                           command))))
   1920       (goto-char (point-min))
   1921       (when (and (/= (point-min) (point-max))
   1922                  (not (looking-at grep-re))
   1923                  ;; TODO: Show these matches as well somehow?
   1924                  ;; Matching both Grep's and Ripgrep 13's messages.
   1925                  (not (looking-at ".*[bB]inary file.* matches")))
   1926         (user-error "Search failed with status %d: %s" status
   1927                     (buffer-substring (point-min) (line-end-position))))
   1928       (while (re-search-forward grep-re nil t)
   1929         (push (list (string-to-number (match-string line-group))
   1930                     (match-string file-group)
   1931                     (buffer-substring-no-properties (point) (line-end-position)))
   1932               hits)))
   1933     ;; By default, ripgrep's output order is non-deterministic
   1934     ;; (https://github.com/BurntSushi/ripgrep/issues/152)
   1935     ;; because it does the search in parallel.
   1936     ;; Grep's output also comes out in seemingly arbitrary order,
   1937     ;; though stable one. Let's sort both for better UI.
   1938     (setq hits
   1939           (sort (nreverse hits)
   1940                 (lambda (h1 h2)
   1941                   (string< (cadr h1) (cadr h2)))))
   1942     (xref--convert-hits hits regexp)))
   1943 
   1944 (defun xref--process-file-region ( start end program
   1945                                    &optional buffer display
   1946                                    &rest args)
   1947   ;; FIXME: This branching shouldn't be necessary, but
   1948   ;; call-process-region *is* measurably faster, even for a program
   1949   ;; doing some actual work (for a period of time). Even though
   1950   ;; call-process-region also creates a temp file internally
   1951   ;; (https://lists.gnu.org/archive/html/emacs-devel/2019-01/msg00211.html).
   1952   (if (not (file-remote-p default-directory))
   1953       (apply #'call-process-region
   1954              start end program nil buffer display args)
   1955     (let ((infile (make-temp-file "ppfr")))
   1956       (unwind-protect
   1957           (progn
   1958             (write-region start end infile nil 'silent)
   1959             (apply #'process-file program infile buffer display args))
   1960         (delete-file infile)))))
   1961 
   1962 (defun xref--rgrep-command (regexp files dir ignores)
   1963   (require 'find-dired)      ; for `find-name-arg'
   1964   (defvar grep-find-template)
   1965   (defvar find-name-arg)
   1966   ;; `shell-quote-argument' quotes the tilde as well.
   1967   (cl-assert (not (string-match-p "\\`~" dir)))
   1968   (grep-expand-template
   1969    grep-find-template
   1970    regexp
   1971    (concat (shell-quote-argument "(")
   1972            " " find-name-arg " "
   1973            (mapconcat
   1974             #'shell-quote-argument
   1975             (split-string files)
   1976             (concat " -o " find-name-arg " "))
   1977            " "
   1978            (shell-quote-argument ")"))
   1979    (shell-quote-argument dir)
   1980    (xref--find-ignores-arguments ignores dir)))
   1981 
   1982 (defun xref--find-ignores-arguments (ignores dir)
   1983   "Convert IGNORES and DIR to a list of arguments for `find'.
   1984 IGNORES is a list of glob patterns.  DIR is an absolute
   1985 directory, used as the root of the ignore globs."
   1986   (cl-assert (not (string-match-p "\\`~" dir)))
   1987   (if (not ignores)
   1988       ""
   1989     ;; TODO: All in-tree callers are passing in just "." or "./".
   1990     ;; We can simplify.
   1991     ;; And, if we ever end up deleting xref-matches-in-directory, move
   1992     ;; this function to the project package.
   1993     (setq dir (file-name-as-directory dir))
   1994     (concat
   1995      (shell-quote-argument "(")
   1996      " -path "
   1997      (mapconcat
   1998       (lambda (ignore)
   1999         (when (string-match-p "/\\'" ignore)
   2000           (setq ignore (concat ignore "*")))
   2001         (shell-quote-argument (if (string-match "\\`\\./" ignore)
   2002                                   (replace-match dir t t ignore)
   2003                                 (if (string-prefix-p "*" ignore)
   2004                                     ignore
   2005                                   (concat "*/" ignore)))))
   2006       ignores
   2007       " -o -path ")
   2008      " "
   2009      (shell-quote-argument ")")
   2010      " -prune -o ")))
   2011 
   2012 (defun xref--regexp-to-extended (str)
   2013   (replace-regexp-in-string
   2014    ;; FIXME: Add tests.  Move to subr.el, make a public function.
   2015    ;; Maybe error on Emacs-only constructs.
   2016    "\\(?:\\\\\\\\\\)*\\(?:\\\\[][]\\)?\\(?:\\[.+?\\]\\|\\(\\\\?[(){}|]\\)\\)"
   2017    (lambda (str)
   2018      (cond
   2019       ((not (match-beginning 1))
   2020        str)
   2021       ((eq (length (match-string 1 str)) 2)
   2022        (concat (substring str 0 (match-beginning 1))
   2023                (substring (match-string 1 str) 1 2)))
   2024       (t
   2025        (concat (substring str 0 (match-beginning 1))
   2026                "\\"
   2027                (match-string 1 str)))))
   2028    str t t))
   2029 
   2030 (defun xref--regexp-syntax-dependent-p (str)
   2031   "Return non-nil when STR depends on the buffer's syntax.
   2032 Such as the current syntax table and the applied syntax properties."
   2033   (let ((case-fold-search nil))
   2034     (string-match-p (rx
   2035                      (or string-start (not (in ?\\)))
   2036                      (0+ (= 2 ?\\))
   2037                      ?\\
   2038                      (in ?b ?B ?< ?> ?w ?W ?_ ?s ?S))
   2039                     str)))
   2040 
   2041 (defvar xref--last-file-buffer nil)
   2042 (defvar xref--temp-buffer-file-name nil)
   2043 (defvar xref--hits-remote-id nil)
   2044 
   2045 (defun xref--convert-hits (hits regexp)
   2046   (let (xref--last-file-buffer
   2047         (tmp-buffer (generate-new-buffer " *xref-temp*"))
   2048         (xref--hits-remote-id (file-remote-p default-directory))
   2049         (syntax-needed (xref--regexp-syntax-dependent-p regexp)))
   2050     (unwind-protect
   2051         (mapcan (lambda (hit)
   2052                   (xref--collect-matches hit regexp tmp-buffer syntax-needed))
   2053                 hits)
   2054       (kill-buffer tmp-buffer))))
   2055 
   2056 (defun xref--collect-matches (hit regexp tmp-buffer syntax-needed)
   2057   (pcase-let* ((`(,line ,file ,text) hit)
   2058                (file (and file (concat xref--hits-remote-id file)))
   2059                (buf (xref--find-file-buffer file))
   2060                (inhibit-modification-hooks t))
   2061     (if buf
   2062         (with-current-buffer buf
   2063           (save-excursion
   2064             (save-restriction
   2065               (widen)
   2066               (goto-char (point-min))
   2067               (forward-line (1- line))
   2068               (xref--collect-matches-1 regexp file line
   2069                                        (line-beginning-position)
   2070                                        (line-end-position)
   2071                                        syntax-needed))))
   2072       ;; Using the temporary buffer is both a performance and a buffer
   2073       ;; management optimization.
   2074       (with-current-buffer tmp-buffer
   2075         (erase-buffer)
   2076         (when (and syntax-needed
   2077                    (not (equal file xref--temp-buffer-file-name)))
   2078           (insert-file-contents file nil 0 200)
   2079           ;; Can't (setq-local delay-mode-hooks t) because of
   2080           ;; bug#23272, but the performance penalty seems minimal.
   2081           (let ((buffer-file-name file)
   2082                 (inhibit-message t)
   2083                 message-log-max)
   2084             (ignore-errors
   2085               (set-auto-mode t)))
   2086           (setq-local xref--temp-buffer-file-name file)
   2087           (setq-local inhibit-read-only t)
   2088           (erase-buffer))
   2089         (insert text)
   2090         (goto-char (point-min))
   2091         (xref--collect-matches-1 regexp file line
   2092                                  (point)
   2093                                  (point-max)
   2094                                  syntax-needed)))))
   2095 
   2096 (defun xref--collect-matches-1 (regexp file line line-beg line-end syntax-needed)
   2097   (let (matches
   2098         stop beg end
   2099         last-beg last-end
   2100         summary-end)
   2101     (when syntax-needed
   2102       (syntax-propertize line-end))
   2103     (while (not stop)
   2104       (if (and
   2105            ;; REGEXP might match an empty string.  Or line.
   2106            (not (and last-beg (eql end line-beg)))
   2107            (re-search-forward regexp line-end t))
   2108           (setq beg (match-beginning 0)
   2109                 end (match-end 0)
   2110                 summary-end beg)
   2111         (setq stop t
   2112               summary-end line-end))
   2113       (when last-beg
   2114         (let* ((beg-column (- last-beg line-beg))
   2115                (end-column (- last-end line-beg))
   2116                (summary-start (if matches last-beg line-beg))
   2117                (summary (buffer-substring summary-start
   2118                                           summary-end))
   2119                (loc (xref-make-file-location file line beg-column)))
   2120           (add-face-text-property (- last-beg summary-start)
   2121                                   (- last-end summary-start)
   2122                                   'xref-match t summary)
   2123           (push (xref-make-match summary loc (- end-column beg-column))
   2124                 matches)))
   2125       (setq last-beg beg
   2126             last-end end))
   2127     (nreverse matches)))
   2128 
   2129 (defun xref--find-file-buffer (file)
   2130   (unless (equal (car xref--last-file-buffer) file)
   2131     ;; `find-buffer-visiting' is considerably slower,
   2132     ;; especially on remote files.
   2133     (let ((buf (get-file-buffer file)))
   2134       (when (and buf
   2135                  (or
   2136                   (buffer-modified-p buf)
   2137                   (unless xref--hits-remote-id
   2138                     (not (verify-visited-file-modtime (current-buffer))))))
   2139         ;; We can't use buffers whose contents diverge from disk (bug#54025).
   2140         (setq buf nil))
   2141       (setq xref--last-file-buffer (cons file buf))))
   2142   (cdr xref--last-file-buffer))
   2143 
   2144 (provide 'xref)
   2145 
   2146 ;;; xref.el ends here