Rewrite lookup handling

- Rewrite documentation for set-lookup-handlers!
- Remove opening lookup targets in other-window; sorry, but there is no
  consistent, stable way to do this, when many jump handlers are
  asynchronous. If you want to open a jump target in another window,
  create a split beforehand.
- Add support for jump handlers returning 'fail or 'deferred
- Fix xref backends when using async UIs like ivy or helm
- Conditionalize creating a better-jump jump point, and create it in the
  spot we jumped *from*, not where we jumped *to*.
This commit is contained in:
Henrik Lissner 2019-05-02 17:53:59 -04:00
parent 0f21e2b44a
commit d17764366e
No known key found for this signature in database
GPG Key ID: 5F6C0EA160557395

View File

@ -1,43 +1,42 @@
;;; tools/lookup/autoload/lookup.el -*- lexical-binding: t; -*-
(defvar +lookup--handler-alist nil)
;;;###autodef
(cl-defun set-lookup-handlers!
(modes &rest plist &key definition references documentation file xref-backend async)
"Define jump handlers for major or minor MODES.
This overwrites previously defined handlers for MODES. If used on minor modes,
they are stacked onto handlers defined for other minor modes or the major mode
it's activated in.
A handler is either an interactive command that changes the current buffer
and/or location of the cursor, or a function that takes one argument: the
identifier being looked up, and returns either nil (failed to find it), t
(succeeded at changing the buffer/moving the cursor), or 'deferred (assume this
handler has succeeded, but expect changes not to be visible yet).
This can be passed nil as its second argument to unset handlers for MODES. e.g.
(set-lookup-handlers! 'python-mode nil)
Otherwise, these properties are available to be set:
There are several kinds of handlers, which can be defined with the following
properties:
:definition FN
Run when jumping to a symbol's definition.
Used by `+lookup/definition'.
Run when jumping to a symbol's definition. Used by `+lookup/definition'.
:references FN
Run when looking for usage references of a symbol in the current project.
Used by `+lookup/references'.
Run when looking for usage references of a symbol in the current project. Used
by `+lookup/references'.
:documentation FN
Run when looking up documentation for a symbol.
Used by `+lookup/documentation'.
Run when looking up documentation for a symbol. Used by
`+lookup/documentation'.
:file FN
Run when looking up the file for a symbol/string. Typically a file path.
Used by `+lookup/file'.
Run when looking up the file for a symbol/string. Typically a file path. Used
by `+lookup/file'.
:xref-backend FN
Defines an xref backend for a major-mode. If you define :definition and
:references along with :xref-backend, those will have higher precedence.
Defines an xref backend for a major-mode. A :definition and :references
handler isn't necessary with a :xref-backend, but will have higher precedence
if they exist.
:async BOOL
Indicates that *all* supplied FNs are asynchronous. Note: async handlers do
not fall back to the default handlers, due to their nature. To get around
this, you must write specialized wrappers to wait for the async response.
Indicates that *all* supplied FNs are asynchronous. Note: lookups will not try
any handlers after async ones, due to their nature. To get around this, you
must write a specialized wrapper to await the async response, or use a
different heuristic to determine, ahead of time, whether the async call will
succeed or not.
If you only want to specify one FN is async, use a PLIST instead:
If you only want to specify one FN is async, declare it inline instead:
(set-lookup-handlers! 'rust-mode
:definition '(racer-find-definition :async t))
@ -49,14 +48,21 @@ change the current buffer or window or return non-nil when it succeeds.
If it doesn't change the current buffer, or it returns nil, the lookup module
will fall back to the next handler in `+lookup-definition-functions',
`+lookup-references-functions', `+lookup-file-functions' or
`+lookup-documentation-functions'."
`+lookup-documentation-functions'.
Consecutive `set-lookup-handlers!' calls will overwrite previously defined
handlers for MODES. If used on minor modes, they are stacked onto handlers
defined for other minor modes or the major mode it's activated in.
This can be passed nil as its second argument to unset handlers for MODES. e.g.
(set-lookup-handlers! 'python-mode nil)"
(declare (indent defun))
(dolist (mode (doom-enlist modes))
(let ((hook (intern (format "%s-hook" mode)))
(fn (intern (format "+lookup|init-%s" mode))))
(fn (intern (format "+lookup|init-%s-handlers" mode))))
(cond ((null (car plist))
(remove-hook hook fn)
(delq! mode +lookup--handler-alist 'assq)
(unintern fn nil))
((fset fn
(lambda ()
@ -85,16 +91,19 @@ will fall back to the next handler in `+lookup-definition-functions',
(when spec
(cl-destructuring-bind (fn . plist)
(doom-enlist spec)
(put fn '+lookup-plist (plist-put plist :async async))
(put fn '+lookup-async (or (plist-get plist :async) async))
(add-hook functions-var fn nil t))))
(defun +lookup--symbol-or-region (&optional initial)
"Grab the symbol at point or selected region."
(cond ((stringp initial)
initial)
((use-region-p)
(buffer-substring-no-properties (region-beginning)
(region-end)))
((require 'xref nil t)
;; A little smarter than using `symbol-at-point', though in most cases,
;; xref ends up using `symbol-at-point' anyway.
(xref-backend-identifier-at-point (xref-find-backend)))))
(defun +lookup--run-handler (handler identifier)
@ -102,60 +111,75 @@ will fall back to the next handler in `+lookup-definition-functions',
(call-interactively handler)
(funcall handler identifier)))
(defun +lookup--run-handlers (handler identifier origin &optional other-window)
(defun +lookup--run-handlers (handler identifier origin)
(doom-log "Looking up '%s' with '%s'" identifier handler)
(condition-case e
(let ((plist (get handler '+lookup-plist)))
(cond ((plist-get plist :async)
(when other-window
;; If async, we can't catch the window change or destination
;; buffer reliably, so we set up the new window ahead of time.
(switch-to-buffer-other-window (current-buffer))
(goto-char (marker-position origin)))
(+lookup--run-handler handler identifier)
t)
((save-window-excursion
(and (or (+lookup--run-handler handler identifier)
(null origin)
(/= (point-marker) origin))
(point-marker))))))
(let ((wconf (current-window-configuration))
(result (condition-case e
(+lookup--run-handler handler identifier)
(error
(message "Lookup handler %S threw an error: %s" handler e)
'fail))))
(cond ((eq result 'fail)
(set-window-configuration wconf)
nil)
((or (get handler '+lookup-async)
(eq result 'deferred)))
((or result
(null origin)
(/= (point-marker) origin))
(prog1 (point-marker)
(set-window-configuration wconf)))))
((error user-error debug)
(message "Lookup handler %S: %s" handler e)
nil)))
(defun +lookup--jump-to (prop identifier &optional other-window)
(let ((result
(run-hook-wrapped
(plist-get (list :definition '+lookup-definition-functions
:references '+lookup-references-functions
:documentation '+lookup-documentation-functions
:file '+lookup-file-functions)
prop)
#'+lookup--run-handlers
identifier
(point-marker)
other-window)))
(if (not (markerp result))
(ignore (message "No lookup handler could find %S" identifier))
(funcall (if other-window
#'switch-to-buffer-other-window
#'switch-to-buffer)
(marker-buffer result))
(goto-char result)
(better-jumper-set-jump)
(defun +lookup--jump-to (prop identifier &optional display-fn)
(let* ((origin (point-marker))
(result
(run-hook-wrapped
(plist-get (list :definition '+lookup-definition-functions
:references '+lookup-references-functions
:documentation '+lookup-documentation-functions
:file '+lookup-file-functions)
prop)
#'+lookup--run-handlers
identifier
origin)))
(when (cond ((null result)
(message "No lookup handler could find %S" identifier)
nil)
((markerp result)
(funcall (or display-fn #'switch-to-buffer)
(marker-buffer result))
(goto-char result)
result)
(result))
(with-current-buffer (marker-buffer origin)
(better-jumper-set-jump (marker-position origin)))
result)))
;;
;;; Lookup backends
(defun +lookup--xref-show (fn identifier)
(let ((xrefs (funcall fn
(xref-find-backend)
identifier)))
(when xrefs
(xref--show-xrefs xrefs nil)
(if (cdr xrefs)
'deferred
t))))
(defun +lookup-xref-definitions-backend (identifier)
"Non-interactive wrapper for `xref-find-definitions'"
(xref-find-definitions identifier))
(+lookup--xref-show 'xref-backend-definitions identifier))
(defun +lookup-xref-references-backend (identifier)
"Non-interactive wrapper for `xref-find-references'"
(xref-find-references identifier))
(+lookup--xref-show 'xref-backend-references identifier))
(defun +lookup-dumb-jump-backend (_identifier)
"Look up the symbol at point (or selection) with `dumb-jump', which conducts a
@ -206,60 +230,47 @@ accessed via `+lookup/in-docsets'."
(let ((docsets (+lookup-docsets-for-buffer)))
(when (cl-some #'+lookup-docset-installed-p docsets)
(+lookup/in-docsets identifier docsets)
t))))
'deferred))))
;;
;;; Main commands
;;;###autoload
(defun +lookup/definition (identifier &optional other-window)
(defun +lookup/definition (identifier)
"Jump to the definition of IDENTIFIER (defaults to the symbol at point).
If OTHER-WINDOW (universal argument), open the result in another window.
Each function in `+lookup-definition-functions' is tried until one changes the
point or current buffer. Falls back to dumb-jump, naive
ripgrep/the_silver_searcher text search, then `evil-goto-definition' if
evil-mode is active."
(interactive
(list (+lookup--symbol-or-region)
current-prefix-arg))
(interactive (list (+lookup--symbol-or-region)))
(cond ((null identifier) (user-error "Nothing under point"))
((+lookup--jump-to :definition identifier other-window))
((error "Couldn't find the definition of '%s'" identifier))))
((+lookup--jump-to :definition identifier))
((error "Couldn't find the definition of %S" identifier))))
;;;###autoload
(defun +lookup/references (identifier &optional other-window)
(defun +lookup/references (identifier)
"Show a list of usages of IDENTIFIER (defaults to the symbol at point)
Tries each function in `+lookup-references-functions' until one changes the
point and/or current buffer. Falls back to a naive ripgrep/the_silver_searcher
search otherwise."
(interactive
(list (+lookup--symbol-or-region)
current-prefix-arg))
(interactive (list (+lookup--symbol-or-region)))
(cond ((null identifier) (user-error "Nothing under point"))
((+lookup--jump-to :references identifier other-window))
((error "Couldn't find references of '%s'" identifier))))
((+lookup--jump-to :references identifier))
((error "Couldn't find references of %S" identifier))))
;;;###autoload
(defun +lookup/documentation (identifier &optional _arg)
(defun +lookup/documentation (identifier)
"Show documentation for IDENTIFIER (defaults to symbol at point or selection.
First attempts the :documentation handler specified with `set-lookup-handlers!'
for the current mode/buffer (if any), then falls back to the backends in
`+lookup-documentation-functions'."
(interactive
(list (+lookup--symbol-or-region)
current-prefix-arg))
(cond ((+lookup--jump-to :documentation identifier t))
((user-error "Couldn't find documentation for '%s'" identifier))))
(interactive (list (+lookup--symbol-or-region)))
(cond ((+lookup--jump-to :documentation identifier 'pop-to-buffer))
((user-error "Couldn't find documentation for %S" identifier))))
(defvar ffap-file-finder)
;;;###autoload