Use generic dispatch for xref backends

* lisp/progmodes/xref.el (xref-backend-functions):
New variable.
(xref-find-function): Remove.
(xref-find-backend)
(xref--etags-backend): New functions.
(xref-identifier-at-point-function)
(xref-identifier-completion-table-function): Remove.
(xref-backend-definitions, xref-backend-references)
(xref-backend-apropos, xref-backend-identifier-at-point)
(xref-backend-identifier-completion-table):
New generic functions.

* lisp/progmodes/elisp-mode.el (emacs-lisp-mode): Add
`elisp--xref-backend' to the beginning of
`xref-backend-functions', locally.  Delete references to
removed functions and vars.
(elisp-xref-find): Remove.
(elisp--xref-backend): New function.
(elisp--xref-find-references, elisp--xref-find-apropos)
(elisp--xref-identifier-completion-table):
Turn into appropriately named generic methods.

* lisp/progmodes/etags.el (etags-xref-find): Remove.
(xref-backend-identifier-completion-table)
(xref-backend-references, xref-backend-definitions)
(xref-backend-apropos): New generic methods.
This commit is contained in:
Dmitry Gutov 2015-11-14 02:37:01 +02:00
parent 31f6e93933
commit 246d6605f7
3 changed files with 87 additions and 72 deletions

View file

@ -228,8 +228,7 @@ Blank lines separate paragraphs. Semicolons start comments.
\\{emacs-lisp-mode-map}"
:group 'lisp
(defvar xref-find-function)
(defvar xref-identifier-completion-table-function)
(defvar xref-backend-functions)
(defvar project-library-roots-function)
(lisp-mode-variables nil nil 'elisp)
(add-hook 'after-load-functions #'elisp--font-lock-flush-elisp-buffers)
@ -239,9 +238,7 @@ Blank lines separate paragraphs. Semicolons start comments.
(setq imenu-case-fold-search nil)
(add-function :before-until (local 'eldoc-documentation-function)
#'elisp-eldoc-documentation-function)
(setq-local xref-find-function #'elisp-xref-find)
(setq-local xref-identifier-completion-table-function
#'elisp--xref-identifier-completion-table)
(add-hook 'xref-backend-functions #'elisp--xref-backend nil t)
(setq-local project-library-roots-function #'elisp-library-roots)
(add-hook 'completion-at-point-functions
#'elisp-completion-at-point nil 'local))
@ -588,21 +585,7 @@ It can be quoted, or be inside a quoted form."
(declare-function xref-make "xref" (summary location))
(declare-function xref-collect-references "xref" (symbol dir))
(defun elisp-xref-find (action id)
(require 'find-func)
;; FIXME: use information in source near point to filter results:
;; (dvc-log-edit ...) - exclude 'feature
;; (require 'dvc-log-edit) - only 'feature
;; Semantic may provide additional information
(pcase action
(`definitions
(let ((sym (intern-soft id)))
(when sym
(elisp--xref-find-definitions sym))))
(`references
(elisp--xref-find-references id))
(`apropos
(elisp--xref-find-apropos id))))
(defun elisp--xref-backend () 'elisp)
;; WORKAROUND: This is nominally a constant, but the text properties
;; are not preserved thru dump if use defconst. See bug#21237.
@ -638,7 +621,17 @@ Each function should return a list of xrefs, or nil; the first
non-nil result supercedes the xrefs produced by
`elisp--xref-find-definitions'.")
;; FIXME: name should be singular; match xref-find-definition
(cl-defmethod xref-backend-definitions ((_backend (eql elisp)) identifier)
(require 'find-func)
;; FIXME: use information in source near point to filter results:
;; (dvc-log-edit ...) - exclude 'feature
;; (require 'dvc-log-edit) - only 'feature
;; Semantic may provide additional information
;;
(let ((sym (intern-soft identifier)))
(when sym
(elisp--xref-find-definitions sym))))
(defun elisp--xref-find-definitions (symbol)
;; The file name is not known when `symbol' is defined via interactive eval.
(let (xrefs)
@ -805,7 +798,7 @@ non-nil result supercedes the xrefs produced by
(declare-function project-roots "project")
(declare-function project-current "project")
(defun elisp--xref-find-references (symbol)
(cl-defmethod xref-backend-references ((_backend (eql elisp)) symbol)
"Find all references to SYMBOL (a string) in the current project."
(cl-mapcan
(lambda (dir)
@ -815,7 +808,7 @@ non-nil result supercedes the xrefs produced by
(project-roots pr)
(project-library-roots pr)))))
(defun elisp--xref-find-apropos (regexp)
(cl-defmethod xref-backend-apropos ((_backend (eql elisp)) regexp)
(apply #'nconc
(let (lst)
(dolist (sym (apropos-internal regexp))
@ -832,7 +825,7 @@ non-nil result supercedes the xrefs produced by
(facep sym)))
'strict))
(defun elisp--xref-identifier-completion-table ()
(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql elisp)))
elisp--xref-identifier-completion-table)
(cl-defstruct (xref-elisp-location

View file

@ -2084,17 +2084,12 @@ for \\[find-tag] (which see)."
(defvar etags-xref-find-definitions-tag-order '(tag-exact-match-p
tag-implicit-name-match-p)
"Tag order used in `etags-xref-find' to look for definitions.")
"Tag order used in `xref-backend-definitions' to look for definitions.")
;;;###autoload
(defun etags-xref-find (action id)
(pcase action
(`definitions (etags--xref-find-definitions id))
(`references (etags--xref-find-references id))
(`apropos (etags--xref-find-definitions id t))))
(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql etags)))
(tags-lazy-completion-table))
(defun etags--xref-find-references (symbol)
;; TODO: Merge together with the Elisp impl.
(cl-defmethod xref-backend-references ((_backend (eql etags)) symbol)
(cl-mapcan
(lambda (dir)
(xref-collect-references symbol dir))
@ -2103,6 +2098,12 @@ for \\[find-tag] (which see)."
(project-roots pr)
(project-library-roots pr)))))
(cl-defmethod xref-backend-definitions ((_backend (eql etags)) symbol)
(etags--xref-find-definitions symbol))
(cl-defmethod xref-backend-apropos ((_backend (eql etags)) symbol)
(etags--xref-find-definitions symbol t))
(defun etags--xref-find-definitions (pattern &optional regexp?)
;; This emulates the behaviour of `find-tag-in-order' but instead of
;; returning one match at a time all matches are returned as list.

View file

@ -23,14 +23,21 @@
;; referencing commands, in particular "find-definition".
;;
;; Some part of the functionality must be implemented in a language
;; dependent way and that's done by defining `xref-find-function',
;; `xref-identifier-at-point-function' and
;; `xref-identifier-completion-table-function', which see.
;; dependent way and that's done by defining an xref backend.
;;
;; A major mode should make these variables buffer-local first.
;; That consists of a constructor function, which should return a
;; backend value, and a set of implementations for the generic
;; functions:
;;
;; `xref-find-function' can be called in several ways, see its
;; description. It has to operate with "xref" and "location" values.
;; `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.
;;
;; One would usually call `make-xref' and `xref-make-file-location',
;; `xref-make-buffer-location' or `xref-make-bogus-location' to create
@ -46,12 +53,11 @@
;; 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
;; `xref-identifier-completion-table-function' should still be
;; `xref-backend-identifier-completion-table' should still be
;; distinct, because the user can't see the properties when making the
;; choice.
;;
;; See the functions `etags-xref-find' and `elisp-xref-find' for full
;; examples.
;; See the etags and elisp-mode implementations for full examples.
;;; Code:
@ -195,35 +201,46 @@ LENGTH is the match length, in characters."
;;; API
(declare-function etags-xref-find "etags" (action id))
(declare-function tags-lazy-completion-table "etags" ())
;; We make the etags backend the default for now, until something
;; better comes along.
(defvar xref-backend-functions (list #'xref--etags-backend)
"Special hook to find the xref backend for the current context.
Each functions on this hook is called in turn with no arguments
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.")
;; For now, make the etags backend the default.
(defvar xref-find-function #'etags-xref-find
"Function to look for cross-references.
It can be called in several ways:
(defun xref-find-backend ()
(run-hook-with-args-until-success 'xref-backend-functions))
(definitions IDENTIFIER): Find definitions of IDENTIFIER. The
result must be a list of xref objects. If IDENTIFIER contains
sufficient information to determine a unique definition, returns
only that definition. If there are multiple possible definitions,
return all of them. If no definitions can be found, return nil.
(defun xref--etags-backend () 'etags)
(references IDENTIFIER): Find references of IDENTIFIER. The
result must be a list of xref objects. If no references can be
found, return nil.
(cl-defgeneric xref-backend-definitions (backend identifier)
"Find definitions of IDENTIFIER.
(apropos PATTERN): Find all symbols that match PATTERN. PATTERN
is a regexp.
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.
IDENTIFIER can be any string returned by
`xref-identifier-at-point-function', or from the table returned
by `xref-identifier-completion-table-function'.
`xref-backend-identifier-at-point', or from the table returned by
`xref-backend-identifier-completion-table'.
To create an xref object, call `xref-make'.")
(defvar xref-identifier-at-point-function #'xref-default-identifier-at-point
"Function to get the relevant identifier at point.
(cl-defgeneric xref-backend-references (backend identifier)
"Find references of IDENTIFIER.
The result must be a list of xref objects. If no references can
be found, return nil.")
(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.
The return value must be a string or nil. nil means no
identifier at point found.
@ -231,16 +248,14 @@ identifier at point found.
If it's hard to determine the identifier precisely (e.g., because
it's a method call on unknown type), the implementation can
return a simple string (such as symbol at point) marked with a
special text property which `xref-find-function' would recognize
and then delegate the work to an external process.")
(defvar xref-identifier-completion-table-function #'tags-lazy-completion-table
"Function that returns the completion table for identifiers.")
(defun xref-default-identifier-at-point ()
special text property which e.g. `xref-backend-definitions' would
recognize and then delegate the work to an external process."
(let ((thing (thing-at-point 'symbol)))
(and thing (substring-no-properties thing))))
(cl-defgeneric xref-backend-identifier-completion-table (backend)
"Returns the completion table for identifiers.")
;;; misc utilities
(defun xref--alistify (list key test)
@ -690,7 +705,8 @@ Return an alist of the form ((FILENAME . (XREF ...)) ...)."
(defun xref--read-identifier (prompt)
"Return the identifier at point or read it from the minibuffer."
(let ((id (funcall xref-identifier-at-point-function)))
(let* ((backend (xref-find-backend))
(id (xref-backend-identifier-at-point backend)))
(cond ((or current-prefix-arg
(not id)
(xref--prompt-p this-command))
@ -700,7 +716,7 @@ Return an alist of the form ((FILENAME . (XREF ...)) ...)."
"[ :]+\\'" prompt))
id)
prompt)
(funcall xref-identifier-completion-table-function)
(xref-backend-identifier-completion-table backend)
nil nil nil
'xref--read-identifier-history id))
(t id))))
@ -709,7 +725,9 @@ Return an alist of the form ((FILENAME . (XREF ...)) ...)."
;;; Commands
(defun xref--find-xrefs (input kind arg window)
(let ((xrefs (funcall xref-find-function kind arg)))
(let ((xrefs (funcall (intern (format "xref-backend-%s" kind))
(xref-find-backend)
arg)))
(unless xrefs
(user-error "No %s found for: %s" (symbol-name kind) input))
(xref--show-xrefs xrefs window)))
@ -824,6 +842,8 @@ tools are used, and when."
(cl-mapcan (lambda (hit) (xref--collect-matches
hit (format "\\_<%s\\_>" (regexp-quote symbol))))
hits)
;; TODO: Implement "lightweight" buffer visiting, so that we
;; don't have to kill them.
(mapc #'kill-buffer
(cl-set-difference (buffer-list) orig-buffers)))))
@ -856,6 +876,7 @@ IGNORES is a list of glob patterns."
(unwind-protect
(cl-mapcan (lambda (hit) (xref--collect-matches hit regexp))
(nreverse hits))
;; TODO: Same as above.
(mapc #'kill-buffer
(cl-set-difference (buffer-list) orig-buffers)))))