diff --git a/modules/tools/lookup/autoload/lookup.el b/modules/tools/lookup/autoload/lookup.el index c73db1480..2ccfd2789 100644 --- a/modules/tools/lookup/autoload/lookup.el +++ b/modules/tools/lookup/autoload/lookup.el @@ -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