2014-12-25 22:08:19 +02:00
|
|
|
|
;; xref.el --- Cross-referencing commands -*-lexical-binding:t-*-
|
|
|
|
|
|
2016-01-01 01:16:19 -08:00
|
|
|
|
;; Copyright (C) 2014-2016 Free Software Foundation, Inc.
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
;; This file is part of GNU Emacs.
|
|
|
|
|
|
|
|
|
|
;; GNU Emacs is free software: you can redistribute it and/or modify
|
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
|
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
2016-01-03 02:03:29 +02:00
|
|
|
|
;; NOTE: The xref API is still experimental and can change in major,
|
|
|
|
|
;; backward-incompatible ways. Everyone is encouraged to try it, and
|
2016-01-03 23:51:44 -08:00
|
|
|
|
;; report to us any problems or use cases we hadn't anticipated, by
|
2016-01-03 02:03:29 +02:00
|
|
|
|
;; sending an email to emacs-devel, or `M-x report-emacs-bug'.
|
|
|
|
|
;;
|
2014-12-25 22:08:19 +02:00
|
|
|
|
;; This file provides a somewhat generic infrastructure for cross
|
|
|
|
|
;; referencing commands, in particular "find-definition".
|
|
|
|
|
;;
|
|
|
|
|
;; Some part of the functionality must be implemented in a language
|
2015-11-14 02:37:01 +02:00
|
|
|
|
;; dependent way and that's done by defining an xref backend.
|
2014-12-25 22:08:19 +02:00
|
|
|
|
;;
|
2015-11-14 02:37:01 +02:00
|
|
|
|
;; That consists of a constructor function, which should return a
|
|
|
|
|
;; backend value, and a set of implementations for the generic
|
|
|
|
|
;; functions:
|
2014-12-25 22:08:19 +02:00
|
|
|
|
;;
|
2015-11-14 02:37:01 +02:00
|
|
|
|
;; `xref-backend-identifier-at-point',
|
|
|
|
|
;; `xref-backend-identifier-completion-table',
|
|
|
|
|
;; `xref-backend-definitions', `xref-backend-references',
|
|
|
|
|
;; `xref-backend-apropos', which see.
|
|
|
|
|
;;
|
|
|
|
|
;; A major mode would normally use `add-hook' to add the backend
|
|
|
|
|
;; constructor to `xref-backend-functions'.
|
|
|
|
|
;;
|
|
|
|
|
;; The last three methods operate with "xref" and "location" values.
|
2014-12-25 22:08:19 +02:00
|
|
|
|
;;
|
|
|
|
|
;; One would usually call `make-xref' and `xref-make-file-location',
|
|
|
|
|
;; `xref-make-buffer-location' or `xref-make-bogus-location' to create
|
2015-01-20 04:28:50 +02:00
|
|
|
|
;; them. More generally, a location must be an instance of an EIEIO
|
|
|
|
|
;; class inheriting from `xref-location' and implementing
|
|
|
|
|
;; `xref-location-group' and `xref-location-marker'.
|
2014-12-25 22:08:19 +02:00
|
|
|
|
;;
|
2015-11-08 05:01:05 +02:00
|
|
|
|
;; There's a special kind of xrefs we call "match xrefs", which
|
|
|
|
|
;; correspond to search results. For these values,
|
|
|
|
|
;; `xref-match-length' must be defined, and `xref-location-marker'
|
|
|
|
|
;; must return the beginning of the match.
|
|
|
|
|
;;
|
2014-12-25 22:08:19 +02:00
|
|
|
|
;; Each identifier must be represented as a string. Implementers can
|
|
|
|
|
;; use string properties to store additional information about the
|
|
|
|
|
;; identifier, but they should keep in mind that values returned from
|
2015-11-14 02:37:01 +02:00
|
|
|
|
;; `xref-backend-identifier-completion-table' should still be
|
2014-12-25 22:08:19 +02:00
|
|
|
|
;; distinct, because the user can't see the properties when making the
|
|
|
|
|
;; choice.
|
|
|
|
|
;;
|
2015-11-14 02:37:01 +02:00
|
|
|
|
;; See the etags and elisp-mode implementations for full examples.
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
|
|
(require 'cl-lib)
|
|
|
|
|
(require 'eieio)
|
|
|
|
|
(require 'ring)
|
2015-01-19 04:19:32 +02:00
|
|
|
|
(require 'pcase)
|
2015-07-10 04:34:41 +03:00
|
|
|
|
(require 'project)
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2015-11-06 05:14:45 +02:00
|
|
|
|
(eval-when-compile
|
|
|
|
|
(require 'semantic/symref)) ;; for hit-lines slot
|
|
|
|
|
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(defgroup xref nil "Cross-referencing commands"
|
2016-01-12 20:06:49 -05:00
|
|
|
|
:version "25.1"
|
2014-12-25 22:08:19 +02:00
|
|
|
|
:group 'tools)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Locations
|
|
|
|
|
|
|
|
|
|
(defclass xref-location () ()
|
|
|
|
|
:documentation "A location represents a position in a file or buffer.")
|
|
|
|
|
|
2015-01-22 04:24:31 +02:00
|
|
|
|
(cl-defgeneric xref-location-marker (location)
|
2014-12-25 22:08:19 +02:00
|
|
|
|
"Return the marker for LOCATION.")
|
|
|
|
|
|
2015-01-22 04:24:31 +02:00
|
|
|
|
(cl-defgeneric xref-location-group (location)
|
2014-12-25 22:08:19 +02:00
|
|
|
|
"Return a string used to group a set of locations.
|
|
|
|
|
This is typically the filename.")
|
|
|
|
|
|
2015-05-05 02:44:20 +03:00
|
|
|
|
(cl-defgeneric xref-location-line (_location)
|
|
|
|
|
"Return the line number corresponding to the location."
|
|
|
|
|
nil)
|
|
|
|
|
|
2015-11-08 05:01:05 +02:00
|
|
|
|
(cl-defgeneric xref-match-length (_item)
|
|
|
|
|
"Return the length of the match."
|
2015-07-20 04:32:58 +03:00
|
|
|
|
nil)
|
|
|
|
|
|
2014-12-25 22:08:19 +02:00
|
|
|
|
;;;; Commonly needed location classes are defined here:
|
|
|
|
|
|
|
|
|
|
;; FIXME: might be useful to have an optional "hint" i.e. a string to
|
|
|
|
|
;; search for in case the line number is sightly out of date.
|
|
|
|
|
(defclass xref-file-location (xref-location)
|
|
|
|
|
((file :type string :initarg :file)
|
2015-05-05 02:44:20 +03:00
|
|
|
|
(line :type fixnum :initarg :line :reader xref-location-line)
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(column :type fixnum :initarg :column :reader xref-file-location-column))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
:documentation "A file location is a file/line/column triple.
|
|
|
|
|
Line numbers start from 1 and columns from 0.")
|
|
|
|
|
|
|
|
|
|
(defun xref-make-file-location (file line column)
|
2015-11-04 10:07:25 +01:00
|
|
|
|
"Create and return a new `xref-file-location'."
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(make-instance 'xref-file-location :file file :line line :column column))
|
|
|
|
|
|
2015-01-22 04:24:31 +02:00
|
|
|
|
(cl-defmethod xref-location-marker ((l xref-file-location))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(with-slots (file line column) l
|
|
|
|
|
(with-current-buffer
|
|
|
|
|
(or (get-file-buffer file)
|
|
|
|
|
(let ((find-file-suppress-same-file-warnings t))
|
|
|
|
|
(find-file-noselect file)))
|
|
|
|
|
(save-restriction
|
|
|
|
|
(widen)
|
|
|
|
|
(save-excursion
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(beginning-of-line line)
|
2015-11-09 05:24:23 +02:00
|
|
|
|
(forward-char column)
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(point-marker))))))
|
|
|
|
|
|
2015-01-22 04:24:31 +02:00
|
|
|
|
(cl-defmethod xref-location-group ((l xref-file-location))
|
2015-06-24 16:32:09 -04:00
|
|
|
|
(oref l file))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
(defclass xref-buffer-location (xref-location)
|
|
|
|
|
((buffer :type buffer :initarg :buffer)
|
|
|
|
|
(position :type fixnum :initarg :position)))
|
|
|
|
|
|
|
|
|
|
(defun xref-make-buffer-location (buffer position)
|
2015-11-04 10:07:25 +01:00
|
|
|
|
"Create and return a new `xref-buffer-location'."
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(make-instance 'xref-buffer-location :buffer buffer :position position))
|
|
|
|
|
|
2015-01-22 04:24:31 +02:00
|
|
|
|
(cl-defmethod xref-location-marker ((l xref-buffer-location))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(with-slots (buffer position) l
|
|
|
|
|
(let ((m (make-marker)))
|
|
|
|
|
(move-marker m position buffer))))
|
|
|
|
|
|
2015-01-22 04:24:31 +02:00
|
|
|
|
(cl-defmethod xref-location-group ((l xref-buffer-location))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(with-slots (buffer) l
|
|
|
|
|
(or (buffer-file-name buffer)
|
|
|
|
|
(format "(buffer %s)" (buffer-name buffer)))))
|
|
|
|
|
|
|
|
|
|
(defclass xref-bogus-location (xref-location)
|
|
|
|
|
((message :type string :initarg :message
|
|
|
|
|
:reader xref-bogus-location-message))
|
|
|
|
|
:documentation "Bogus locations are sometimes useful to
|
|
|
|
|
indicate errors, e.g. when we know that a function exists but the
|
|
|
|
|
actual location is not known.")
|
|
|
|
|
|
|
|
|
|
(defun xref-make-bogus-location (message)
|
2015-11-04 10:07:25 +01:00
|
|
|
|
"Create and return a new `xref-bogus-location'."
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(make-instance 'xref-bogus-location :message message))
|
|
|
|
|
|
2015-01-22 04:24:31 +02:00
|
|
|
|
(cl-defmethod xref-location-marker ((l xref-bogus-location))
|
2015-06-24 16:32:09 -04:00
|
|
|
|
(user-error "%s" (oref l message)))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2015-01-22 04:24:31 +02:00
|
|
|
|
(cl-defmethod xref-location-group ((_ xref-bogus-location)) "(No location)")
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Cross-reference
|
|
|
|
|
|
2015-07-20 03:26:01 +03:00
|
|
|
|
(defclass xref-item ()
|
2015-07-20 03:12:32 +03:00
|
|
|
|
((summary :type string :initarg :summary
|
2015-07-20 03:26:01 +03:00
|
|
|
|
:reader xref-item-summary
|
|
|
|
|
:documentation "One line which will be displayed for
|
|
|
|
|
this item in the output buffer.")
|
2015-06-01 22:45:15 +03:00
|
|
|
|
(location :initarg :location
|
2015-07-20 03:26:01 +03:00
|
|
|
|
:reader xref-item-location
|
|
|
|
|
:documentation "An object describing how to navigate
|
|
|
|
|
to the reference's target."))
|
|
|
|
|
:comment "An xref item describes a reference to a location
|
|
|
|
|
somewhere.")
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2015-07-20 03:12:32 +03:00
|
|
|
|
(defun xref-make (summary location)
|
2015-11-04 10:07:25 +01:00
|
|
|
|
"Create and return a new `xref-item'.
|
2015-07-20 03:12:32 +03:00
|
|
|
|
SUMMARY is a short string to describe the xref.
|
2014-12-25 22:08:19 +02:00
|
|
|
|
LOCATION is an `xref-location'."
|
2015-07-20 03:26:01 +03:00
|
|
|
|
(make-instance 'xref-item :summary summary :location location))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(defclass xref-match-item ()
|
|
|
|
|
((summary :type string :initarg :summary
|
|
|
|
|
:reader xref-item-summary)
|
|
|
|
|
(location :initarg :location
|
|
|
|
|
:type xref-file-location
|
|
|
|
|
:reader xref-item-location)
|
2015-11-08 05:01:05 +02:00
|
|
|
|
(length :initarg :length :reader xref-match-length))
|
|
|
|
|
:comment "A match xref item describes a search result.")
|
2015-07-20 04:32:58 +03:00
|
|
|
|
|
2015-11-08 05:01:05 +02:00
|
|
|
|
(defun xref-make-match (summary location length)
|
2015-11-04 10:07:25 +01:00
|
|
|
|
"Create and return a new `xref-match-item'.
|
2015-07-20 04:32:58 +03:00
|
|
|
|
SUMMARY is a short string to describe the xref.
|
2015-11-08 05:01:05 +02:00
|
|
|
|
LOCATION is an `xref-location'.
|
|
|
|
|
LENGTH is the match length, in characters."
|
|
|
|
|
(make-instance 'xref-match-item :summary summary
|
|
|
|
|
:location location :length length))
|
2015-07-20 04:32:58 +03:00
|
|
|
|
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
;;; API
|
|
|
|
|
|
2015-11-21 01:57:00 +02:00
|
|
|
|
(defvar xref-backend-functions nil
|
2015-11-14 02:37:01 +02:00
|
|
|
|
"Special hook to find the xref backend for the current context.
|
2016-01-09 19:06:52 +02:00
|
|
|
|
Each function on this hook is called in turn with no arguments,
|
2015-11-14 02:37:01 +02:00
|
|
|
|
and should return either nil to mean that it is not applicable,
|
|
|
|
|
or an xref backend, which is a value to be used to dispatch the
|
|
|
|
|
generic functions.")
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2015-11-21 01:57:00 +02:00
|
|
|
|
;; We make the etags backend the default for now, until something
|
2015-12-02 04:12:03 +02:00
|
|
|
|
;; better comes along. Use APPEND so that any `add-hook' calls made
|
|
|
|
|
;; before this package is loaded put new items before this one.
|
|
|
|
|
(add-hook 'xref-backend-functions #'etags--xref-backend t)
|
2015-11-21 01:57:00 +02:00
|
|
|
|
|
2015-11-15 06:31:51 +02:00
|
|
|
|
;;;###autoload
|
2015-11-14 02:37:01 +02:00
|
|
|
|
(defun xref-find-backend ()
|
|
|
|
|
(run-hook-with-args-until-success 'xref-backend-functions))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2015-11-14 02:37:01 +02:00
|
|
|
|
(cl-defgeneric xref-backend-definitions (backend identifier)
|
|
|
|
|
"Find definitions of IDENTIFIER.
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2015-11-14 02:37:01 +02:00
|
|
|
|
The result must be a list of xref objects. If IDENTIFIER
|
|
|
|
|
contains sufficient information to determine a unique definition,
|
|
|
|
|
return only that definition. If there are multiple possible
|
|
|
|
|
definitions, return all of them. If no definitions can be found,
|
|
|
|
|
return nil.
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
IDENTIFIER can be any string returned by
|
2015-11-14 02:37:01 +02:00
|
|
|
|
`xref-backend-identifier-at-point', or from the table returned by
|
|
|
|
|
`xref-backend-identifier-completion-table'.
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
To create an xref object, call `xref-make'.")
|
|
|
|
|
|
2015-11-15 07:00:45 +02:00
|
|
|
|
(cl-defgeneric xref-backend-references (_backend identifier)
|
2015-11-14 02:37:01 +02:00
|
|
|
|
"Find references of IDENTIFIER.
|
|
|
|
|
The result must be a list of xref objects. If no references can
|
2015-11-15 07:00:45 +02:00
|
|
|
|
be found, return nil.
|
|
|
|
|
|
2015-11-17 07:46:11 -06:00
|
|
|
|
The default implementation uses `semantic-symref-tool-alist' to
|
|
|
|
|
find a search tool; by default, this uses \"find | grep\" in the
|
|
|
|
|
`project-current' roots."
|
2015-11-15 07:00:45 +02:00
|
|
|
|
(cl-mapcan
|
|
|
|
|
(lambda (dir)
|
|
|
|
|
(xref-collect-references identifier dir))
|
|
|
|
|
(let ((pr (project-current t)))
|
|
|
|
|
(append
|
|
|
|
|
(project-roots pr)
|
2015-12-28 06:17:19 +02:00
|
|
|
|
(project-external-roots pr)))))
|
2015-11-14 02:37:01 +02:00
|
|
|
|
|
|
|
|
|
(cl-defgeneric xref-backend-apropos (backend pattern)
|
|
|
|
|
"Find all symbols that match PATTERN.
|
|
|
|
|
PATTERN is a regexp")
|
|
|
|
|
|
|
|
|
|
(cl-defgeneric xref-backend-identifier-at-point (_backend)
|
|
|
|
|
"Return the relevant identifier at point.
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
The return value must be a string or nil. nil means no
|
|
|
|
|
identifier at point found.
|
|
|
|
|
|
2014-12-25 18:18:42 -08:00
|
|
|
|
If it's hard to determine the identifier precisely (e.g., because
|
2014-12-25 22:08:19 +02:00
|
|
|
|
it's a method call on unknown type), the implementation can
|
|
|
|
|
return a simple string (such as symbol at point) marked with a
|
2015-11-14 02:37:01 +02:00
|
|
|
|
special text property which e.g. `xref-backend-definitions' would
|
|
|
|
|
recognize and then delegate the work to an external process."
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(let ((thing (thing-at-point 'symbol)))
|
|
|
|
|
(and thing (substring-no-properties thing))))
|
|
|
|
|
|
2015-11-14 02:37:01 +02:00
|
|
|
|
(cl-defgeneric xref-backend-identifier-completion-table (backend)
|
|
|
|
|
"Returns the completion table for identifiers.")
|
|
|
|
|
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
;;; misc utilities
|
|
|
|
|
(defun xref--alistify (list key test)
|
|
|
|
|
"Partition the elements of LIST into an alist.
|
|
|
|
|
KEY extracts the key from an element and TEST is used to compare
|
|
|
|
|
keys."
|
|
|
|
|
(let ((alist '()))
|
|
|
|
|
(dolist (e list)
|
|
|
|
|
(let* ((k (funcall key e))
|
|
|
|
|
(probe (cl-assoc k alist :test test)))
|
|
|
|
|
(if probe
|
|
|
|
|
(setcdr probe (cons e (cdr probe)))
|
|
|
|
|
(push (cons k (list e)) alist))))
|
|
|
|
|
;; Put them back in order.
|
|
|
|
|
(cl-loop for (key . value) in (reverse alist)
|
|
|
|
|
collect (cons key (reverse value)))))
|
|
|
|
|
|
|
|
|
|
(defun xref--insert-propertized (props &rest strings)
|
|
|
|
|
"Insert STRINGS with text properties PROPS."
|
|
|
|
|
(let ((start (point)))
|
|
|
|
|
(apply #'insert strings)
|
|
|
|
|
(add-text-properties start (point) props)))
|
|
|
|
|
|
|
|
|
|
(defun xref--search-property (property &optional backward)
|
|
|
|
|
"Search the next text range where text property PROPERTY is non-nil.
|
|
|
|
|
Return the value of PROPERTY. If BACKWARD is non-nil, search
|
|
|
|
|
backward."
|
|
|
|
|
(let ((next (if backward
|
|
|
|
|
#'previous-single-char-property-change
|
|
|
|
|
#'next-single-char-property-change))
|
|
|
|
|
(start (point))
|
|
|
|
|
(value nil))
|
|
|
|
|
(while (progn
|
|
|
|
|
(goto-char (funcall next (point) property))
|
|
|
|
|
(not (or (setq value (get-text-property (point) property))
|
|
|
|
|
(eobp)
|
|
|
|
|
(bobp)))))
|
|
|
|
|
(cond (value)
|
|
|
|
|
(t (goto-char start) nil))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Marker stack (M-. pushes, M-, pops)
|
|
|
|
|
|
|
|
|
|
(defcustom xref-marker-ring-length 16
|
|
|
|
|
"Length of the xref marker ring."
|
2015-07-19 20:40:18 +03:00
|
|
|
|
:type 'integer)
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2015-05-26 19:28:38 +03:00
|
|
|
|
(defcustom xref-prompt-for-identifier '(not xref-find-definitions
|
|
|
|
|
xref-find-definitions-other-window
|
|
|
|
|
xref-find-definitions-other-frame)
|
|
|
|
|
"When t, always prompt for the identifier name.
|
2015-04-26 18:08:56 +03:00
|
|
|
|
|
2015-05-26 19:28:38 +03:00
|
|
|
|
When nil, prompt only when there's no value at point we can use,
|
|
|
|
|
or when the command has been called with the prefix argument.
|
|
|
|
|
|
|
|
|
|
Otherwise, it's a list of xref commands which will prompt
|
|
|
|
|
anyway (the value at point, if any, will be used as the default).
|
|
|
|
|
|
|
|
|
|
If the list starts with `not', the meaning of the rest of the
|
|
|
|
|
elements is negated."
|
2015-04-26 18:08:56 +03:00
|
|
|
|
:type '(choice (const :tag "always" t)
|
2015-05-26 19:28:38 +03:00
|
|
|
|
(const :tag "auto" nil)
|
|
|
|
|
(set :menu-tag "command specific" :tag "commands"
|
|
|
|
|
:value (not)
|
|
|
|
|
(const :tag "Except" not)
|
2015-07-19 20:40:18 +03:00
|
|
|
|
(repeat :inline t (symbol :tag "command")))))
|
2015-04-26 18:08:56 +03:00
|
|
|
|
|
2015-07-19 20:40:18 +03:00
|
|
|
|
(defcustom xref-after-jump-hook '(recenter
|
|
|
|
|
xref-pulse-momentarily)
|
|
|
|
|
"Functions called after jumping to an xref."
|
|
|
|
|
:type 'hook)
|
|
|
|
|
|
|
|
|
|
(defcustom xref-after-return-hook '(xref-pulse-momentarily)
|
|
|
|
|
"Functions called after returning to a pre-jump location."
|
|
|
|
|
:type 'hook)
|
2015-05-04 18:09:33 +03:00
|
|
|
|
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(defvar xref--marker-ring (make-ring xref-marker-ring-length)
|
|
|
|
|
"Ring of markers to implement the marker stack.")
|
|
|
|
|
|
2015-04-17 12:32:33 +08:00
|
|
|
|
(defun xref-push-marker-stack (&optional m)
|
|
|
|
|
"Add point M (defaults to `point-marker') to the marker stack."
|
|
|
|
|
(ring-insert xref--marker-ring (or m (point-marker))))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
|
(defun xref-pop-marker-stack ()
|
|
|
|
|
"Pop back to where \\[xref-find-definitions] was last invoked."
|
|
|
|
|
(interactive)
|
|
|
|
|
(let ((ring xref--marker-ring))
|
|
|
|
|
(when (ring-empty-p ring)
|
2015-11-14 21:13:12 +00:00
|
|
|
|
(user-error "Marker stack is empty"))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(let ((marker (ring-remove ring 0)))
|
|
|
|
|
(switch-to-buffer (or (marker-buffer marker)
|
2015-11-14 21:13:12 +00:00
|
|
|
|
(user-error "The marked buffer has been deleted")))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(goto-char (marker-position marker))
|
2015-05-04 18:09:33 +03:00
|
|
|
|
(set-marker marker nil nil)
|
2015-07-19 20:40:18 +03:00
|
|
|
|
(run-hooks 'xref-after-return-hook))))
|
|
|
|
|
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(defvar xref--current-item nil)
|
|
|
|
|
|
2015-07-19 20:40:18 +03:00
|
|
|
|
(defun xref-pulse-momentarily ()
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(pcase-let ((`(,beg . ,end)
|
|
|
|
|
(save-excursion
|
|
|
|
|
(or
|
2015-11-08 05:01:05 +02:00
|
|
|
|
(let ((length (xref-match-length xref--current-item)))
|
|
|
|
|
(and length (cons (point) (+ (point) length))))
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(back-to-indentation)
|
|
|
|
|
(if (eolp)
|
|
|
|
|
(cons (line-beginning-position) (1+ (point)))
|
|
|
|
|
(cons (point) (line-end-position)))))))
|
2015-07-19 20:40:18 +03:00
|
|
|
|
(pulse-momentary-highlight-region beg end 'next-error)))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
;; etags.el needs this
|
|
|
|
|
(defun xref-clear-marker-stack ()
|
|
|
|
|
"Discard all markers from the marker stack."
|
|
|
|
|
(let ((ring xref--marker-ring))
|
|
|
|
|
(while (not (ring-empty-p ring))
|
|
|
|
|
(let ((marker (ring-remove ring)))
|
|
|
|
|
(set-marker marker nil nil)))))
|
|
|
|
|
|
2015-01-12 18:26:39 +01:00
|
|
|
|
;;;###autoload
|
2015-01-03 16:02:04 +02:00
|
|
|
|
(defun xref-marker-stack-empty-p ()
|
|
|
|
|
"Return t if the marker stack is empty; nil otherwise."
|
|
|
|
|
(ring-empty-p xref--marker-ring))
|
|
|
|
|
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2015-08-05 15:18:25 +03:00
|
|
|
|
|
|
|
|
|
(defun xref--goto-char (pos)
|
|
|
|
|
(cond
|
|
|
|
|
((and (<= (point-min) pos) (<= pos (point-max))))
|
|
|
|
|
(widen-automatically (widen))
|
|
|
|
|
(t (user-error "Position is outside accessible part of buffer")))
|
|
|
|
|
(goto-char pos))
|
|
|
|
|
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(defun xref--goto-location (location)
|
|
|
|
|
"Set buffer and point according to xref-location LOCATION."
|
|
|
|
|
(let ((marker (xref-location-marker location)))
|
|
|
|
|
(set-buffer (marker-buffer marker))
|
2015-08-05 15:18:25 +03:00
|
|
|
|
(xref--goto-char marker)))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(defun xref--pop-to-location (item &optional action)
|
2015-07-20 04:32:58 +03:00
|
|
|
|
"Go to the location of ITEM and display the buffer.
|
2016-02-22 00:26:24 +02:00
|
|
|
|
ACTION controls how the buffer is displayed:
|
2014-12-25 22:08:19 +02:00
|
|
|
|
nil -- switch-to-buffer
|
2015-09-03 15:31:12 -07:00
|
|
|
|
`window' -- pop-to-buffer (other window)
|
2016-02-22 00:26:24 +02:00
|
|
|
|
`frame' -- pop-to-buffer (other frame)
|
|
|
|
|
If SELECT is non-nil, select the target window."
|
2015-08-05 15:18:25 +03:00
|
|
|
|
(let* ((marker (save-excursion
|
|
|
|
|
(xref-location-marker (xref-item-location item))))
|
|
|
|
|
(buf (marker-buffer marker)))
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(cl-ecase action
|
2015-08-05 15:18:25 +03:00
|
|
|
|
((nil) (switch-to-buffer buf))
|
|
|
|
|
(window (pop-to-buffer buf t))
|
|
|
|
|
(frame (let ((pop-up-frames t)) (pop-to-buffer buf t))))
|
|
|
|
|
(xref--goto-char marker))
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(let ((xref--current-item item))
|
|
|
|
|
(run-hooks 'xref-after-jump-hook)))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; XREF buffer (part of the UI)
|
|
|
|
|
|
|
|
|
|
;; The xref buffer is used to display a set of xrefs.
|
2016-03-17 12:55:09 -04:00
|
|
|
|
(defconst xref-buffer-name "*xref*"
|
|
|
|
|
"The name of the buffer to show xrefs.")
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(defmacro xref--with-dedicated-window (&rest body)
|
|
|
|
|
`(let* ((xref-w (get-buffer-window xref-buffer-name))
|
|
|
|
|
(xref-w-dedicated (window-dedicated-p xref-w)))
|
|
|
|
|
(unwind-protect
|
|
|
|
|
(progn
|
|
|
|
|
(when xref-w
|
|
|
|
|
(set-window-dedicated-p xref-w 'soft))
|
|
|
|
|
,@body)
|
|
|
|
|
(when xref-w
|
|
|
|
|
(set-window-dedicated-p xref-w xref-w-dedicated)))))
|
|
|
|
|
|
|
|
|
|
(defun xref--show-pos-in-buf (pos buf select)
|
|
|
|
|
(let ((xref-buf (current-buffer))
|
|
|
|
|
win)
|
|
|
|
|
(with-selected-window
|
|
|
|
|
(xref--with-dedicated-window
|
|
|
|
|
(display-buffer buf))
|
2015-08-10 04:30:33 +03:00
|
|
|
|
(xref--goto-char pos)
|
|
|
|
|
(run-hooks 'xref-after-jump-hook)
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(let ((buf (current-buffer)))
|
|
|
|
|
(setq win (selected-window))
|
2015-08-10 04:30:33 +03:00
|
|
|
|
(with-current-buffer xref-buf
|
2016-02-22 00:40:53 +02:00
|
|
|
|
(setq-local other-window-scroll-buffer buf))))
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(when select
|
|
|
|
|
(select-window win))))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(defun xref--show-location (location &optional select)
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(condition-case err
|
2015-11-03 02:36:50 +02:00
|
|
|
|
(let* ((marker (xref-location-marker location))
|
|
|
|
|
(buf (marker-buffer marker)))
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(xref--show-pos-in-buf marker buf select))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(user-error (message (error-message-string err)))))
|
|
|
|
|
|
2016-03-17 12:55:09 -04:00
|
|
|
|
(defvar-local xref--window nil
|
|
|
|
|
"The original window this xref buffer was created from.")
|
|
|
|
|
|
2015-01-03 15:53:18 +02:00
|
|
|
|
(defun xref-show-location-at-point ()
|
2016-02-22 00:26:24 +02:00
|
|
|
|
"Display the source of xref at point in the appropriate window, if any."
|
2015-01-03 15:53:18 +02:00
|
|
|
|
(interactive)
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(let* ((xref (xref--item-at-point))
|
|
|
|
|
(xref--current-item xref))
|
|
|
|
|
(when xref
|
2016-02-22 00:26:24 +02:00
|
|
|
|
;; Try to avoid the window the current xref buffer was
|
|
|
|
|
;; originally created from.
|
|
|
|
|
(if (window-live-p xref--window)
|
|
|
|
|
(with-selected-window xref--window
|
|
|
|
|
(xref--show-location (xref-item-location xref)))
|
|
|
|
|
(xref--show-location (xref-item-location xref))))))
|
2015-01-03 15:53:18 +02:00
|
|
|
|
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(defun xref-next-line ()
|
2016-02-22 00:26:24 +02:00
|
|
|
|
"Move to the next xref and display its source in the appropriate window."
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(interactive)
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(xref--search-property 'xref-item)
|
2015-01-03 15:53:18 +02:00
|
|
|
|
(xref-show-location-at-point))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
(defun xref-prev-line ()
|
2016-02-22 00:26:24 +02:00
|
|
|
|
"Move to the previous xref and display its source in the appropriate window."
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(interactive)
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(xref--search-property 'xref-item t)
|
2015-01-03 15:53:18 +02:00
|
|
|
|
(xref-show-location-at-point))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(defun xref--item-at-point ()
|
2015-05-05 05:05:02 +03:00
|
|
|
|
(save-excursion
|
|
|
|
|
(back-to-indentation)
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(get-text-property (point) 'xref-item)))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
(defun xref-goto-xref ()
|
2016-02-22 00:26:24 +02:00
|
|
|
|
"Jump to the xref on the current line and select its window."
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(interactive)
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(let ((xref (or (xref--item-at-point)
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(user-error "No reference at point"))))
|
|
|
|
|
(xref--show-location (xref-item-location xref) t)))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2016-01-24 05:17:52 +03:00
|
|
|
|
(defun xref-query-replace-in-results (from to)
|
2016-01-09 19:06:52 +02:00
|
|
|
|
"Perform interactive replacement of FROM with TO in all displayed xrefs.
|
|
|
|
|
|
|
|
|
|
This command interactively replaces FROM with TO in the names of the
|
|
|
|
|
references displayed in the current *xref* buffer."
|
2015-07-21 03:25:24 +03:00
|
|
|
|
(interactive
|
2016-01-09 19:06:52 +02:00
|
|
|
|
(let ((fr (read-regexp "Xref query-replace (regexp)" ".*")))
|
|
|
|
|
(list fr
|
|
|
|
|
(read-regexp (format "Xref query-replace (regexp) %s with: " fr)))))
|
2016-05-05 02:52:34 +03:00
|
|
|
|
(let* (item xrefs iter)
|
|
|
|
|
(save-excursion
|
|
|
|
|
(while (setq item (xref--search-property 'xref-item))
|
|
|
|
|
(when (xref-match-length item)
|
|
|
|
|
(push item xrefs))))
|
2015-07-21 03:25:24 +03:00
|
|
|
|
(unwind-protect
|
|
|
|
|
(progn
|
2016-05-05 02:52:34 +03:00
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(setq iter (xref--buf-pairs-iterator (nreverse xrefs)))
|
|
|
|
|
(xref--query-replace-1 from to iter))
|
|
|
|
|
(funcall iter :cleanup))))
|
|
|
|
|
|
|
|
|
|
(defun xref--buf-pairs-iterator (xrefs)
|
|
|
|
|
(let (chunk-done item next-pair file-buf pairs all-pairs)
|
|
|
|
|
(lambda (action)
|
|
|
|
|
(pcase action
|
|
|
|
|
(:next
|
|
|
|
|
(when (or xrefs next-pair)
|
|
|
|
|
(setq chunk-done nil)
|
|
|
|
|
(when next-pair
|
|
|
|
|
(setq file-buf (marker-buffer (car next-pair))
|
|
|
|
|
pairs (list next-pair)
|
|
|
|
|
next-pair nil))
|
|
|
|
|
(while (and (not chunk-done)
|
|
|
|
|
(setq item (pop xrefs)))
|
|
|
|
|
(save-excursion
|
|
|
|
|
(let* ((loc (xref-item-location item))
|
|
|
|
|
(beg (xref-location-marker loc))
|
|
|
|
|
(end (move-marker (make-marker)
|
|
|
|
|
(+ beg (xref-match-length item))
|
|
|
|
|
(marker-buffer beg))))
|
|
|
|
|
(let ((pair (cons beg end)))
|
|
|
|
|
(push pair all-pairs)
|
|
|
|
|
;; Perform sanity check first.
|
|
|
|
|
(xref--goto-location loc)
|
|
|
|
|
(if (xref--outdated-p item
|
|
|
|
|
(buffer-substring-no-properties
|
|
|
|
|
(line-beginning-position)
|
|
|
|
|
(line-end-position)))
|
|
|
|
|
(message "Search result out of date, skipping")
|
|
|
|
|
(cond
|
|
|
|
|
((null file-buf)
|
|
|
|
|
(setq file-buf (marker-buffer beg))
|
|
|
|
|
(push pair pairs))
|
|
|
|
|
((equal file-buf (marker-buffer beg))
|
|
|
|
|
(push pair pairs))
|
|
|
|
|
(t
|
|
|
|
|
(setq chunk-done t
|
|
|
|
|
next-pair pair))))))))
|
|
|
|
|
(cons file-buf pairs)))
|
|
|
|
|
(:cleanup
|
|
|
|
|
(dolist (pair all-pairs)
|
|
|
|
|
(move-marker (car pair) nil)
|
|
|
|
|
(move-marker (cdr pair) nil)))))))
|
|
|
|
|
|
|
|
|
|
(defun xref--outdated-p (item line-text)
|
|
|
|
|
;; FIXME: The check should probably be a generic function instead of
|
|
|
|
|
;; the assumption that all matches contain the full line as summary.
|
|
|
|
|
(let ((summary (xref-item-summary item))
|
|
|
|
|
(strip (lambda (s) (if (string-match "\r\\'" s)
|
|
|
|
|
(substring-no-properties s 0 -1)
|
|
|
|
|
s))))
|
|
|
|
|
(not
|
|
|
|
|
;; Sometimes buffer contents include ^M, and sometimes Grep
|
|
|
|
|
;; output includes it, and they don't always match.
|
|
|
|
|
(equal (funcall strip line-text)
|
|
|
|
|
(funcall strip summary)))))
|
2015-07-21 03:25:24 +03:00
|
|
|
|
|
2015-11-08 05:01:05 +02:00
|
|
|
|
;; FIXME: Write a nicer UI.
|
2016-05-05 02:52:34 +03:00
|
|
|
|
(defun xref--query-replace-1 (from to iter)
|
2015-07-21 03:25:24 +03:00
|
|
|
|
(let* ((query-replace-lazy-highlight nil)
|
2016-05-05 02:52:34 +03:00
|
|
|
|
(continue t)
|
|
|
|
|
did-it-once buf-pairs pairs
|
|
|
|
|
current-beg current-end
|
2015-07-21 03:25:24 +03:00
|
|
|
|
;; Counteract the "do the next match now" hack in
|
|
|
|
|
;; `perform-replace'. And still, it'll report that those
|
|
|
|
|
;; matches were "filtered out" at the end.
|
|
|
|
|
(isearch-filter-predicate
|
|
|
|
|
(lambda (beg end)
|
2015-11-08 05:01:05 +02:00
|
|
|
|
(and current-beg
|
|
|
|
|
(>= beg current-beg)
|
2015-11-15 05:59:34 +02:00
|
|
|
|
(<= end current-end))))
|
2015-07-21 03:25:24 +03:00
|
|
|
|
(replace-re-search-function
|
|
|
|
|
(lambda (from &optional _bound noerror)
|
2015-11-08 05:01:05 +02:00
|
|
|
|
(let (found pair)
|
2015-07-21 03:25:24 +03:00
|
|
|
|
(while (and (not found) pairs)
|
2015-11-08 05:01:05 +02:00
|
|
|
|
(setq pair (pop pairs)
|
|
|
|
|
current-beg (car pair)
|
2016-05-05 02:52:34 +03:00
|
|
|
|
current-end (cdr pair))
|
2015-11-08 05:01:05 +02:00
|
|
|
|
(goto-char current-beg)
|
2015-11-15 05:59:34 +02:00
|
|
|
|
(when (re-search-forward from current-end noerror)
|
2015-07-21 03:25:24 +03:00
|
|
|
|
(setq found t)))
|
|
|
|
|
found))))
|
2016-05-05 02:52:34 +03:00
|
|
|
|
(while (and continue (setq buf-pairs (funcall iter :next)))
|
|
|
|
|
(if did-it-once
|
|
|
|
|
;; Reuse the same window for subsequent buffers.
|
|
|
|
|
(switch-to-buffer (car buf-pairs))
|
|
|
|
|
(xref--with-dedicated-window
|
|
|
|
|
(pop-to-buffer (car buf-pairs)))
|
|
|
|
|
(setq did-it-once t))
|
|
|
|
|
(setq pairs (cdr buf-pairs))
|
|
|
|
|
(setq continue
|
|
|
|
|
(perform-replace from to t t nil nil multi-query-replace-map)))
|
|
|
|
|
(unless did-it-once (user-error "No suitable matches here"))))
|
2015-07-21 03:25:24 +03:00
|
|
|
|
|
2015-01-21 09:20:04 +02:00
|
|
|
|
(defvar xref--xref-buffer-mode-map
|
|
|
|
|
(let ((map (make-sparse-keymap)))
|
|
|
|
|
(define-key map (kbd "n") #'xref-next-line)
|
|
|
|
|
(define-key map (kbd "p") #'xref-prev-line)
|
2016-01-24 05:17:52 +03:00
|
|
|
|
(define-key map (kbd "r") #'xref-query-replace-in-results)
|
2015-01-21 09:20:04 +02:00
|
|
|
|
(define-key map (kbd "RET") #'xref-goto-xref)
|
|
|
|
|
(define-key map (kbd "C-o") #'xref-show-location-at-point)
|
|
|
|
|
;; suggested by Johan Claesson "to further reduce finger movement":
|
|
|
|
|
(define-key map (kbd ".") #'xref-next-line)
|
|
|
|
|
(define-key map (kbd ",") #'xref-prev-line)
|
|
|
|
|
map))
|
|
|
|
|
|
|
|
|
|
(define-derived-mode xref--xref-buffer-mode special-mode "XREF"
|
2014-12-25 18:18:42 -08:00
|
|
|
|
"Mode for displaying cross-references."
|
2015-04-30 03:41:34 +03:00
|
|
|
|
(setq buffer-read-only t)
|
2016-02-29 05:16:41 +02:00
|
|
|
|
(setq next-error-function #'xref--next-error-function)
|
|
|
|
|
(setq next-error-last-buffer (current-buffer)))
|
2015-04-30 03:41:34 +03:00
|
|
|
|
|
|
|
|
|
(defun xref--next-error-function (n reset?)
|
|
|
|
|
(when reset?
|
|
|
|
|
(goto-char (point-min)))
|
|
|
|
|
(let ((backward (< n 0))
|
|
|
|
|
(n (abs n))
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(xref nil))
|
2015-04-30 03:41:34 +03:00
|
|
|
|
(dotimes (_ n)
|
2015-07-21 00:44:41 +03:00
|
|
|
|
(setq xref (xref--search-property 'xref-item backward)))
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(cond (xref
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(xref--show-location (xref-item-location xref) t))
|
2015-04-30 03:41:34 +03:00
|
|
|
|
(t
|
|
|
|
|
(error "No %s xref" (if backward "previous" "next"))))))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2015-01-05 02:45:30 +03:00
|
|
|
|
(defvar xref--button-map
|
|
|
|
|
(let ((map (make-sparse-keymap)))
|
|
|
|
|
(define-key map [(control ?m)] #'xref-goto-xref)
|
|
|
|
|
(define-key map [mouse-1] #'xref-goto-xref)
|
|
|
|
|
(define-key map [mouse-2] #'xref--mouse-2)
|
|
|
|
|
map))
|
|
|
|
|
|
|
|
|
|
(defun xref--mouse-2 (event)
|
|
|
|
|
"Move point to the button and show the xref definition."
|
|
|
|
|
(interactive "e")
|
|
|
|
|
(mouse-set-point event)
|
|
|
|
|
(forward-line 0)
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(xref--search-property 'xref-item)
|
2015-01-05 02:45:30 +03:00
|
|
|
|
(xref-show-location-at-point))
|
|
|
|
|
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(defun xref--insert-xrefs (xref-alist)
|
|
|
|
|
"Insert XREF-ALIST in the current-buffer.
|
2015-11-04 10:07:25 +01:00
|
|
|
|
XREF-ALIST is of the form ((GROUP . (XREF ...)) ...), where
|
2014-12-25 22:08:19 +02:00
|
|
|
|
GROUP is a string for decoration purposes and XREF is an
|
2015-07-20 03:26:01 +03:00
|
|
|
|
`xref-item' object."
|
2015-05-05 05:17:14 +03:00
|
|
|
|
(require 'compile) ; For the compilation faces.
|
2015-05-05 02:44:20 +03:00
|
|
|
|
(cl-loop for ((group . xrefs) . more1) on xref-alist
|
|
|
|
|
for max-line-width =
|
|
|
|
|
(cl-loop for xref in xrefs
|
|
|
|
|
maximize (let ((line (xref-location-line
|
2015-06-24 16:32:09 -04:00
|
|
|
|
(oref xref location))))
|
2015-05-05 02:44:20 +03:00
|
|
|
|
(length (and line (format "%d" line)))))
|
|
|
|
|
for line-format = (and max-line-width
|
|
|
|
|
(format "%%%dd: " max-line-width))
|
|
|
|
|
do
|
2015-05-04 00:39:06 +03:00
|
|
|
|
(xref--insert-propertized '(face compilation-info) group "\n")
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(cl-loop for (xref . more2) on xrefs do
|
2015-07-20 03:12:32 +03:00
|
|
|
|
(with-slots (summary location) xref
|
2015-05-05 05:05:02 +03:00
|
|
|
|
(let* ((line (xref-location-line location))
|
|
|
|
|
(prefix
|
|
|
|
|
(if line
|
|
|
|
|
(propertize (format line-format line)
|
|
|
|
|
'face 'compilation-line-number)
|
|
|
|
|
" ")))
|
|
|
|
|
(xref--insert-propertized
|
2015-07-20 04:32:58 +03:00
|
|
|
|
(list 'xref-item xref
|
2015-05-05 05:05:02 +03:00
|
|
|
|
;; 'face 'font-lock-keyword-face
|
|
|
|
|
'mouse-face 'highlight
|
|
|
|
|
'keymap xref--button-map
|
|
|
|
|
'help-echo
|
|
|
|
|
(concat "mouse-2: display in another window, "
|
|
|
|
|
"RET or mouse-1: follow reference"))
|
2015-07-20 03:12:32 +03:00
|
|
|
|
prefix summary)))
|
2015-05-04 00:39:06 +03:00
|
|
|
|
(insert "\n"))))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
(defun xref--analyze (xrefs)
|
|
|
|
|
"Find common filenames in XREFS.
|
|
|
|
|
Return an alist of the form ((FILENAME . (XREF ...)) ...)."
|
|
|
|
|
(xref--alistify xrefs
|
|
|
|
|
(lambda (x)
|
2015-07-20 03:26:01 +03:00
|
|
|
|
(xref-location-group (xref-item-location x)))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
#'equal))
|
|
|
|
|
|
2015-01-21 08:43:39 +02:00
|
|
|
|
(defun xref--show-xref-buffer (xrefs alist)
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(let ((xref-alist (xref--analyze xrefs)))
|
|
|
|
|
(with-current-buffer (get-buffer-create xref-buffer-name)
|
2016-05-04 00:42:43 +03:00
|
|
|
|
(setq buffer-undo-list nil)
|
|
|
|
|
(let ((inhibit-read-only t)
|
|
|
|
|
(buffer-undo-list t))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(erase-buffer)
|
|
|
|
|
(xref--insert-xrefs xref-alist)
|
|
|
|
|
(xref--xref-buffer-mode)
|
|
|
|
|
(pop-to-buffer (current-buffer))
|
|
|
|
|
(goto-char (point-min))
|
2015-01-21 08:43:39 +02:00
|
|
|
|
(setq xref--window (assoc-default 'window alist))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(current-buffer)))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; This part of the UI seems fairly uncontroversial: it reads the
|
|
|
|
|
;; identifier and deals with the single definition case.
|
2015-11-06 05:08:51 +02:00
|
|
|
|
;; (FIXME: do we really want this case to be handled like that in
|
|
|
|
|
;; "find references" and "find regexp searches"?)
|
2014-12-25 22:08:19 +02:00
|
|
|
|
;;
|
|
|
|
|
;; The controversial multiple definitions case is handed off to
|
|
|
|
|
;; xref-show-xrefs-function.
|
|
|
|
|
|
|
|
|
|
(defvar xref-show-xrefs-function 'xref--show-xref-buffer
|
|
|
|
|
"Function to display a list of xrefs.")
|
|
|
|
|
|
2015-01-22 04:09:23 +02:00
|
|
|
|
(defvar xref--read-identifier-history nil)
|
|
|
|
|
|
|
|
|
|
(defvar xref--read-pattern-history nil)
|
|
|
|
|
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(defun xref--show-xrefs (xrefs display-action &optional always-show-list)
|
2015-11-06 05:08:51 +02:00
|
|
|
|
(cond
|
2016-01-18 22:11:46 +03:00
|
|
|
|
((and (not (cdr xrefs)) (not always-show-list))
|
2015-11-06 05:08:51 +02:00
|
|
|
|
(xref-push-marker-stack)
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(xref--pop-to-location (car xrefs) display-action))
|
2015-11-06 05:08:51 +02:00
|
|
|
|
(t
|
|
|
|
|
(xref-push-marker-stack)
|
|
|
|
|
(funcall xref-show-xrefs-function xrefs
|
2016-02-22 00:26:24 +02:00
|
|
|
|
`((window . ,(selected-window)))))))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
2015-05-26 19:28:38 +03:00
|
|
|
|
(defun xref--prompt-p (command)
|
|
|
|
|
(or (eq xref-prompt-for-identifier t)
|
|
|
|
|
(if (eq (car xref-prompt-for-identifier) 'not)
|
|
|
|
|
(not (memq command (cdr xref-prompt-for-identifier)))
|
|
|
|
|
(memq command xref-prompt-for-identifier))))
|
|
|
|
|
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(defun xref--read-identifier (prompt)
|
|
|
|
|
"Return the identifier at point or read it from the minibuffer."
|
2015-11-14 02:37:01 +02:00
|
|
|
|
(let* ((backend (xref-find-backend))
|
|
|
|
|
(id (xref-backend-identifier-at-point backend)))
|
2015-05-26 19:28:38 +03:00
|
|
|
|
(cond ((or current-prefix-arg
|
|
|
|
|
(not id)
|
|
|
|
|
(xref--prompt-p this-command))
|
2015-07-13 04:27:32 +03:00
|
|
|
|
(completing-read (if id
|
|
|
|
|
(format "%s (default %s): "
|
|
|
|
|
(substring prompt 0 (string-match
|
|
|
|
|
"[ :]+\\'" prompt))
|
|
|
|
|
id)
|
|
|
|
|
prompt)
|
2015-11-14 02:37:01 +02:00
|
|
|
|
(xref-backend-identifier-completion-table backend)
|
2015-05-06 04:35:46 +03:00
|
|
|
|
nil nil nil
|
2015-04-25 19:23:41 +03:00
|
|
|
|
'xref--read-identifier-history id))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(t id))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Commands
|
|
|
|
|
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(defun xref--find-xrefs (input kind arg display-action)
|
2015-11-14 02:37:01 +02:00
|
|
|
|
(let ((xrefs (funcall (intern (format "xref-backend-%s" kind))
|
|
|
|
|
(xref-find-backend)
|
|
|
|
|
arg)))
|
2015-11-06 05:08:51 +02:00
|
|
|
|
(unless xrefs
|
|
|
|
|
(user-error "No %s found for: %s" (symbol-name kind) input))
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(xref--show-xrefs xrefs display-action)))
|
2015-11-06 05:08:51 +02:00
|
|
|
|
|
2016-02-22 00:26:24 +02:00
|
|
|
|
(defun xref--find-definitions (id display-action)
|
|
|
|
|
(xref--find-xrefs id 'definitions id display-action))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
|
(defun xref-find-definitions (identifier)
|
|
|
|
|
"Find the definition of the identifier at point.
|
2014-12-30 21:54:03 +02:00
|
|
|
|
With prefix argument or when there's no identifier at point,
|
2015-08-11 14:28:17 -05:00
|
|
|
|
prompt for it.
|
|
|
|
|
|
2016-01-09 19:06:52 +02:00
|
|
|
|
If sufficient information is available to determine a unique
|
|
|
|
|
definition for IDENTIFIER, display it in the selected window.
|
|
|
|
|
Otherwise, display the list of the possible definitions in a
|
|
|
|
|
buffer where the user can select from the list."
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(interactive (list (xref--read-identifier "Find definitions of: ")))
|
|
|
|
|
(xref--find-definitions identifier nil))
|
|
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
|
(defun xref-find-definitions-other-window (identifier)
|
|
|
|
|
"Like `xref-find-definitions' but switch to the other window."
|
|
|
|
|
(interactive (list (xref--read-identifier "Find definitions of: ")))
|
|
|
|
|
(xref--find-definitions identifier 'window))
|
|
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
|
(defun xref-find-definitions-other-frame (identifier)
|
|
|
|
|
"Like `xref-find-definitions' but switch to the other frame."
|
|
|
|
|
(interactive (list (xref--read-identifier "Find definitions of: ")))
|
|
|
|
|
(xref--find-definitions identifier 'frame))
|
|
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
|
(defun xref-find-references (identifier)
|
|
|
|
|
"Find references to the identifier at point.
|
|
|
|
|
With prefix argument, prompt for the identifier."
|
|
|
|
|
(interactive (list (xref--read-identifier "Find references of: ")))
|
2015-11-06 05:08:51 +02:00
|
|
|
|
(xref--find-xrefs identifier 'references identifier nil))
|
2015-05-11 02:07:27 +03:00
|
|
|
|
|
2015-01-22 04:09:23 +02:00
|
|
|
|
(declare-function apropos-parse-pattern "apropos" (pattern))
|
|
|
|
|
|
2014-12-25 22:08:19 +02:00
|
|
|
|
;;;###autoload
|
|
|
|
|
(defun xref-find-apropos (pattern)
|
|
|
|
|
"Find all meaningful symbols that match PATTERN.
|
|
|
|
|
The argument has the same meaning as in `apropos'."
|
2015-05-23 12:05:47 -04:00
|
|
|
|
(interactive (list (read-string
|
2015-01-22 04:09:23 +02:00
|
|
|
|
"Search for pattern (word list or regexp): "
|
2015-05-23 12:05:47 -04:00
|
|
|
|
nil 'xref--read-pattern-history)))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(require 'apropos)
|
2015-11-06 05:08:51 +02:00
|
|
|
|
(xref--find-xrefs pattern 'apropos
|
2015-01-21 08:43:39 +02:00
|
|
|
|
(apropos-parse-pattern
|
|
|
|
|
(if (string-equal (regexp-quote pattern) pattern)
|
|
|
|
|
;; Split into words
|
|
|
|
|
(or (split-string pattern "[ \t]+" t)
|
|
|
|
|
(user-error "No word list given"))
|
|
|
|
|
pattern))
|
2014-12-25 22:08:19 +02:00
|
|
|
|
nil))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Key bindings
|
|
|
|
|
|
|
|
|
|
;;;###autoload (define-key esc-map "." #'xref-find-definitions)
|
|
|
|
|
;;;###autoload (define-key esc-map "," #'xref-pop-marker-stack)
|
2015-07-13 04:27:32 +03:00
|
|
|
|
;;;###autoload (define-key esc-map "?" #'xref-find-references)
|
2014-12-25 22:08:19 +02:00
|
|
|
|
;;;###autoload (define-key esc-map [?\C-.] #'xref-find-apropos)
|
|
|
|
|
;;;###autoload (define-key ctl-x-4-map "." #'xref-find-definitions-other-window)
|
|
|
|
|
;;;###autoload (define-key ctl-x-5-map "." #'xref-find-definitions-other-frame)
|
|
|
|
|
|
2015-02-23 04:00:01 +02:00
|
|
|
|
|
|
|
|
|
;;; Helper functions
|
|
|
|
|
|
|
|
|
|
(defvar xref-etags-mode--saved nil)
|
|
|
|
|
|
|
|
|
|
(define-minor-mode xref-etags-mode
|
|
|
|
|
"Minor mode to make xref use etags again.
|
|
|
|
|
|
|
|
|
|
Certain major modes install their own mechanisms for listing
|
|
|
|
|
identifiers and navigation. Turn this on to undo those settings
|
|
|
|
|
and just use etags."
|
|
|
|
|
:lighter ""
|
|
|
|
|
(if xref-etags-mode
|
|
|
|
|
(progn
|
2015-11-14 02:40:06 +02:00
|
|
|
|
(setq xref-etags-mode--saved xref-backend-functions)
|
|
|
|
|
(kill-local-variable 'xref-backend-functions))
|
|
|
|
|
(setq-local xref-backend-functions xref-etags-mode--saved)))
|
2015-02-23 04:00:01 +02:00
|
|
|
|
|
2016-04-12 21:08:22 +03:00
|
|
|
|
(declare-function semantic-symref-instantiate "semantic/symref")
|
|
|
|
|
(declare-function semantic-symref-perform-search "semantic/symref")
|
2015-07-13 04:27:32 +03:00
|
|
|
|
(declare-function grep-expand-template "grep")
|
2016-01-29 17:43:26 -06:00
|
|
|
|
(defvar ede-minor-mode) ;; ede.el
|
2015-05-01 21:54:33 +03:00
|
|
|
|
|
2015-06-02 18:46:42 +03:00
|
|
|
|
(defun xref-collect-references (symbol dir)
|
|
|
|
|
"Collect references to SYMBOL inside DIR.
|
|
|
|
|
This function uses the Semantic Symbol Reference API, see
|
2016-04-12 21:08:22 +03:00
|
|
|
|
`semantic-symref-tool-alist' for details on which tools are used,
|
|
|
|
|
and when."
|
2015-06-02 18:46:42 +03:00
|
|
|
|
(cl-assert (directory-name-p dir))
|
2015-05-01 21:54:33 +03:00
|
|
|
|
(require 'semantic/symref)
|
|
|
|
|
(defvar semantic-symref-tool)
|
2016-01-22 01:53:05 -06:00
|
|
|
|
|
|
|
|
|
;; Some symref backends use `ede-project-root-directory' as the root
|
|
|
|
|
;; directory for the search, rather than `default-directory'. Since
|
|
|
|
|
;; the caller has specified `dir', we bind `ede-minor-mode' to nil
|
|
|
|
|
;; to force the backend to use `default-directory'.
|
|
|
|
|
(let* ((ede-minor-mode nil)
|
|
|
|
|
(default-directory dir)
|
2016-04-12 21:08:22 +03:00
|
|
|
|
;; FIXME: Remove CScope and Global from the recognized tools?
|
|
|
|
|
;; The current implementations interpret the symbol search as
|
|
|
|
|
;; "find all calls to the given function", but not function
|
|
|
|
|
;; definition. And they return nothing when passed a variable
|
|
|
|
|
;; name, even a global one.
|
2015-05-01 21:54:33 +03:00
|
|
|
|
(semantic-symref-tool 'detect)
|
2016-03-03 02:36:27 +02:00
|
|
|
|
(case-fold-search nil)
|
2016-04-12 21:08:22 +03:00
|
|
|
|
(inst (semantic-symref-instantiate :searchfor symbol
|
|
|
|
|
:searchtype 'symbol
|
|
|
|
|
:searchscope 'subdirs
|
|
|
|
|
:resulttype 'line-and-text)))
|
|
|
|
|
(xref--convert-hits (semantic-symref-perform-search inst)
|
|
|
|
|
(format "\\_<%s\\_>" (regexp-quote symbol)))))
|
2015-06-02 18:46:42 +03:00
|
|
|
|
|
2016-01-18 22:11:46 +03:00
|
|
|
|
;;;###autoload
|
2015-07-12 18:35:08 +03:00
|
|
|
|
(defun xref-collect-matches (regexp files dir ignores)
|
|
|
|
|
"Collect matches for REGEXP inside FILES in DIR.
|
|
|
|
|
FILES is a string with glob patterns separated by spaces.
|
|
|
|
|
IGNORES is a list of glob patterns."
|
2016-01-18 22:11:46 +03:00
|
|
|
|
;; DIR can also be a regular file for now; let's not advertise that.
|
2015-06-02 18:46:42 +03:00
|
|
|
|
(require 'semantic/fw)
|
|
|
|
|
(grep-compute-defaults)
|
|
|
|
|
(defvar grep-find-template)
|
2015-07-11 18:56:42 +03:00
|
|
|
|
(defvar grep-highlight-matches)
|
2015-06-26 20:21:50 +03:00
|
|
|
|
(let* ((grep-find-template (replace-regexp-in-string "-e " "-E "
|
|
|
|
|
grep-find-template t t))
|
2015-07-11 18:56:42 +03:00
|
|
|
|
(grep-highlight-matches nil)
|
2015-07-12 17:18:09 +03:00
|
|
|
|
(command (xref--rgrep-command (xref--regexp-to-extended regexp)
|
2016-01-07 20:14:40 +03:00
|
|
|
|
files
|
|
|
|
|
(expand-file-name dir)
|
|
|
|
|
ignores))
|
2015-06-02 18:46:42 +03:00
|
|
|
|
(buf (get-buffer-create " *xref-grep*"))
|
|
|
|
|
(grep-re (caar grep-regexp-alist))
|
|
|
|
|
hits)
|
|
|
|
|
(with-current-buffer buf
|
|
|
|
|
(erase-buffer)
|
2015-07-10 04:38:16 +03:00
|
|
|
|
(call-process-shell-command command nil t)
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(while (re-search-forward grep-re nil t)
|
2016-04-12 21:08:22 +03:00
|
|
|
|
(push (list (string-to-number (match-string 2))
|
|
|
|
|
(match-string 1)
|
|
|
|
|
(buffer-substring-no-properties (point) (line-end-position)))
|
2015-07-10 04:38:16 +03:00
|
|
|
|
hits)))
|
2016-05-02 02:38:01 +03:00
|
|
|
|
(xref--convert-hits (nreverse hits) regexp)))
|
2015-05-01 21:54:33 +03:00
|
|
|
|
|
2015-07-12 17:18:09 +03:00
|
|
|
|
(defun xref--rgrep-command (regexp files dir ignores)
|
|
|
|
|
(require 'find-dired) ; for `find-name-arg'
|
|
|
|
|
(defvar grep-find-template)
|
|
|
|
|
(defvar find-name-arg)
|
|
|
|
|
(grep-expand-template
|
|
|
|
|
grep-find-template
|
|
|
|
|
regexp
|
|
|
|
|
(concat (shell-quote-argument "(")
|
|
|
|
|
" " find-name-arg " "
|
|
|
|
|
(mapconcat
|
|
|
|
|
#'shell-quote-argument
|
|
|
|
|
(split-string files)
|
|
|
|
|
(concat " -o " find-name-arg " "))
|
|
|
|
|
" "
|
|
|
|
|
(shell-quote-argument ")"))
|
|
|
|
|
dir
|
2016-01-07 20:14:40 +03:00
|
|
|
|
(xref--find-ignores-arguments ignores dir)))
|
|
|
|
|
|
|
|
|
|
(defun xref--find-ignores-arguments (ignores dir)
|
2016-01-29 17:43:26 -06:00
|
|
|
|
"Convert IGNORES and DIR to a list of arguments for 'find'.
|
|
|
|
|
IGNORES is a list of glob patterns. DIR is an absolute
|
|
|
|
|
directory, used as the root of the ignore globs."
|
2016-01-07 20:14:40 +03:00
|
|
|
|
;; `shell-quote-argument' quotes the tilde as well.
|
|
|
|
|
(cl-assert (not (string-match-p "\\`~" dir)))
|
2016-01-18 22:11:46 +03:00
|
|
|
|
(when ignores
|
|
|
|
|
(concat
|
|
|
|
|
(shell-quote-argument "(")
|
|
|
|
|
" -path "
|
|
|
|
|
(mapconcat
|
|
|
|
|
(lambda (ignore)
|
|
|
|
|
(when (string-match-p "/\\'" ignore)
|
|
|
|
|
(setq ignore (concat ignore "*")))
|
|
|
|
|
(if (string-match "\\`\\./" ignore)
|
|
|
|
|
(setq ignore (replace-match dir t t ignore))
|
|
|
|
|
(unless (string-prefix-p "*" ignore)
|
|
|
|
|
(setq ignore (concat "*/" ignore))))
|
|
|
|
|
(shell-quote-argument ignore))
|
|
|
|
|
ignores
|
|
|
|
|
" -o -path ")
|
|
|
|
|
" "
|
|
|
|
|
(shell-quote-argument ")")
|
|
|
|
|
" -prune -o ")))
|
2015-07-12 17:18:09 +03:00
|
|
|
|
|
2015-05-11 02:07:27 +03:00
|
|
|
|
(defun xref--regexp-to-extended (str)
|
|
|
|
|
(replace-regexp-in-string
|
|
|
|
|
;; FIXME: Add tests. Move to subr.el, make a public function.
|
|
|
|
|
;; Maybe error on Emacs-only constructs.
|
|
|
|
|
"\\(?:\\\\\\\\\\)*\\(?:\\\\[][]\\)?\\(?:\\[.+?\\]\\|\\(\\\\?[(){}|]\\)\\)"
|
|
|
|
|
(lambda (str)
|
|
|
|
|
(cond
|
|
|
|
|
((not (match-beginning 1))
|
|
|
|
|
str)
|
|
|
|
|
((eq (length (match-string 1 str)) 2)
|
|
|
|
|
(concat (substring str 0 (match-beginning 1))
|
|
|
|
|
(substring (match-string 1 str) 1 2)))
|
|
|
|
|
(t
|
|
|
|
|
(concat (substring str 0 (match-beginning 1))
|
|
|
|
|
"\\"
|
|
|
|
|
(match-string 1 str)))))
|
|
|
|
|
str t t))
|
|
|
|
|
|
2016-04-12 21:08:22 +03:00
|
|
|
|
(defvar xref--last-visiting-buffer nil)
|
|
|
|
|
(defvar xref--temp-buffer-file-name nil)
|
|
|
|
|
|
|
|
|
|
(defun xref--convert-hits (hits regexp)
|
|
|
|
|
(let (xref--last-visiting-buffer
|
|
|
|
|
(tmp-buffer (generate-new-buffer " *xref-temp*")))
|
|
|
|
|
(unwind-protect
|
|
|
|
|
(cl-mapcan (lambda (hit) (xref--collect-matches hit regexp tmp-buffer))
|
|
|
|
|
hits)
|
|
|
|
|
(kill-buffer tmp-buffer))))
|
|
|
|
|
|
|
|
|
|
(defun xref--collect-matches (hit regexp tmp-buffer)
|
|
|
|
|
(pcase-let* ((`(,line ,file ,text) hit)
|
|
|
|
|
(buf (xref--find-buffer-visiting file)))
|
|
|
|
|
(if buf
|
|
|
|
|
(with-current-buffer buf
|
|
|
|
|
(save-excursion
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(forward-line (1- line))
|
|
|
|
|
(xref--collect-matches-1 regexp file line
|
|
|
|
|
(line-beginning-position)
|
|
|
|
|
(line-end-position))))
|
|
|
|
|
;; Using the temporary buffer is both a performance and a buffer
|
|
|
|
|
;; management optimization.
|
|
|
|
|
(with-current-buffer tmp-buffer
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(unless (equal file xref--temp-buffer-file-name)
|
|
|
|
|
(insert-file-contents file nil 0 200)
|
|
|
|
|
;; Can't (setq-local delay-mode-hooks t) because of
|
|
|
|
|
;; bug#23272, but the performance penalty seems minimal.
|
|
|
|
|
(let ((buffer-file-name file)
|
|
|
|
|
(inhibit-message t)
|
|
|
|
|
message-log-max)
|
|
|
|
|
(ignore-errors
|
|
|
|
|
(set-auto-mode t)))
|
|
|
|
|
(setq-local xref--temp-buffer-file-name file)
|
|
|
|
|
(setq-local inhibit-read-only t)
|
|
|
|
|
(erase-buffer))
|
|
|
|
|
(insert text)
|
2015-05-01 21:54:33 +03:00
|
|
|
|
(goto-char (point-min))
|
2016-04-12 21:08:22 +03:00
|
|
|
|
(xref--collect-matches-1 regexp file line
|
|
|
|
|
(point)
|
|
|
|
|
(point-max))))))
|
|
|
|
|
|
|
|
|
|
(defun xref--collect-matches-1 (regexp file line line-beg line-end)
|
|
|
|
|
(let (matches)
|
|
|
|
|
(syntax-propertize line-end)
|
|
|
|
|
;; FIXME: This results in several lines with the same
|
|
|
|
|
;; summary. Solve with composite pattern?
|
2016-05-04 01:59:29 +03:00
|
|
|
|
(while (and
|
|
|
|
|
;; REGEXP might match an empty string. Or line.
|
|
|
|
|
(or (null matches)
|
|
|
|
|
(> (point) line-beg))
|
|
|
|
|
(re-search-forward regexp line-end t))
|
2016-04-12 21:08:22 +03:00
|
|
|
|
(let* ((beg-column (- (match-beginning 0) line-beg))
|
|
|
|
|
(end-column (- (match-end 0) line-beg))
|
|
|
|
|
(loc (xref-make-file-location file line beg-column))
|
|
|
|
|
(summary (buffer-substring line-beg line-end)))
|
|
|
|
|
(add-face-text-property beg-column end-column 'highlight
|
|
|
|
|
t summary)
|
|
|
|
|
(push (xref-make-match summary loc (- end-column beg-column))
|
|
|
|
|
matches)))
|
|
|
|
|
(nreverse matches)))
|
|
|
|
|
|
|
|
|
|
(defun xref--find-buffer-visiting (file)
|
|
|
|
|
(unless (equal (car xref--last-visiting-buffer) file)
|
|
|
|
|
(setq xref--last-visiting-buffer
|
|
|
|
|
(cons file (find-buffer-visiting file))))
|
|
|
|
|
(cdr xref--last-visiting-buffer))
|
2015-05-01 21:54:33 +03:00
|
|
|
|
|
2014-12-25 22:08:19 +02:00
|
|
|
|
(provide 'xref)
|
|
|
|
|
|
|
|
|
|
;;; xref.el ends here
|