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