From 1371d341492c5b72e7c08365c1e55622e11b288d Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Thu, 4 Jan 2018 17:05:37 -0500 Subject: [PATCH] :boom: Replace feature/jump with feature/lookup + Adds Dash docset integration (with helm or ivy support) + Adds devdocs.io integration + Three new settings: :lookup, :devdocs and :docset --- init.example.el | 4 +- modules/feature/jump/autoload/evil.el | 11 - modules/feature/jump/autoload/jump.el | 146 ------------- modules/feature/jump/config.el | 106 ---------- modules/feature/jump/packages.el | 8 - modules/feature/lookup/README.org | 182 +++++++++++++++++ modules/feature/lookup/autoload/evil.el | 22 ++ modules/feature/lookup/autoload/lookup.el | 237 ++++++++++++++++++++++ modules/feature/lookup/config.el | 189 +++++++++++++++++ modules/feature/lookup/packages.el | 17 ++ modules/lang/javascript/config.el | 4 +- modules/lang/javascript/packages.el | 2 +- modules/private/default/+bindings.el | 18 +- modules/private/default/+evil-commands.el | 4 +- 14 files changed, 666 insertions(+), 284 deletions(-) delete mode 100644 modules/feature/jump/autoload/evil.el delete mode 100644 modules/feature/jump/autoload/jump.el delete mode 100644 modules/feature/jump/config.el delete mode 100644 modules/feature/jump/packages.el create mode 100644 modules/feature/lookup/README.org create mode 100644 modules/feature/lookup/autoload/evil.el create mode 100644 modules/feature/lookup/autoload/lookup.el create mode 100644 modules/feature/lookup/config.el create mode 100644 modules/feature/lookup/packages.el diff --git a/init.example.el b/init.example.el index 3a7a691a7..b683638f8 100644 --- a/init.example.el +++ b/init.example.el @@ -34,7 +34,9 @@ eval ; run code, run (also, repls) evil ; come to the dark side, we have cookies file-templates ; auto-snippets for empty files - jump ; helping you get around + (lookup ; helps you navigate your code and documentation + +devdocs ; ...on devdocs.io online + +docsets) ; ...or in Dash docsets locally services ; TODO managing external services & code builders snippets ; my elves. They type so I don't have to spellcheck ; tasing you for misspelling mispelling diff --git a/modules/feature/jump/autoload/evil.el b/modules/feature/jump/autoload/evil.el deleted file mode 100644 index 51142c206..000000000 --- a/modules/feature/jump/autoload/evil.el +++ /dev/null @@ -1,11 +0,0 @@ -;;; feature/jump/autoload/evil.el -*- lexical-binding: t; -*- -;;;###if (featurep! :feature evil) - -;;;###autoload (autoload '+jump:online "feature/jump/autoload/evil" nil t) -(evil-define-command +jump:online (query &optional bang) - "Look up QUERY online. Will prompt for search engine the first time, then -reuse it on consecutive uses of this command. If BANG, always prompt for search -engine." - (interactive "") - (+jump/online (or query (thing-at-point 'symbol t)) - (+jump--online-get-provider bang))) diff --git a/modules/feature/jump/autoload/jump.el b/modules/feature/jump/autoload/jump.el deleted file mode 100644 index 36ad3c49c..000000000 --- a/modules/feature/jump/autoload/jump.el +++ /dev/null @@ -1,146 +0,0 @@ -;;; feature/jump/autoload.el -*- lexical-binding: t; -*- - -(defvar +jump--rg-installed-p (executable-find "rg")) -(defvar +jump--ag-installed-p (executable-find "ag")) - -(defun +jump-to (prop identifier &optional other-window) - (with-selected-window - (if other-window - (save-excursion (other-window 1) (selected-window)) - (selected-window)) - (let ((fn (plist-get +jump-current-functions prop))) - (if (commandp fn) - (call-interactively fn) - (funcall fn identifier))))) - -;;;###autoload -(defun +jump/definition (identifier &optional other-window) - "Jump to the definition of the symbol at point. - -Tries xref and falls back to `dumb-jump', then rg/ag, then -`evil-goto-definition' (if evil is active)." - (interactive - (list (thing-at-point 'symbol t) - current-prefix-arg)) - (cond ((null identifier) - (user-error "Nothing under point")) - - ((plist-member +jump-current-functions :definition) - (+jump-to :definition identifier)) - - ((ignore-errors (if other-window - (xref-find-definitions-other-window identifier) - (xref-find-definitions identifier)) - t)) - - ((and (require 'dumb-jump nil t) - ;; dumb-jump doesn't tell us if it succeeded or not - (let ((old-fn (symbol-function 'dumb-jump-get-results)) - successful) - (cl-letf (((symbol-function 'dumb-jump-get-results) - (lambda (&optional prompt) - (let* ((plist (funcall old-fn prompt)) - (results (plist-get plist :results))) - (when (and results (> (length results) 0)) - (setq successful t)) - plist)))) - (if other-window - (dumb-jump-go-other-window) - (dumb-jump-go)) - successful)))) - - ((and identifier - (featurep 'counsel) - (let ((regex (rxt-quote-pcre identifier))) - (or (and +jump--rg-installed-p - (counsel-rg regex (doom-project-root))) - (and +jump--ag-installed-p - (counsel-ag regex (doom-project-root))))))) - - ((and (featurep 'evil) - evil-mode - (cl-destructuring-bind (beg . end) - (bounds-of-thing-at-point 'symbol) - (evil-goto-definition) - (let ((pt (point))) - (not (and (>= pt beg) - (< pt end))))))) - - (t (user-error "Couldn't find '%s'" identifier)))) - -;;;###autoload -(defun +jump/references (identifier) - "Show a list of references to the symbol at point. - -Tries `xref-find-references' and falls back to rg/ag." - (interactive (list (thing-at-point 'symbol t))) - (cond ((plist-member +jump-current-functions :references) - (+jump-to :references identifier)) - - ((ignore-errors (xref-find-references identifier) - t)) - - ((and identifier - (featurep 'counsel) - (let ((regex (rxt-quote-pcre identifier))) - (or (and (executable-find "rg") - (counsel-rg regex (doom-project-root))) - (and (executable-find "ag") - (counsel-ag regex (doom-project-root))))))) - - (t (error "Couldn't find '%s'" identifier)))) - -;;;###autoload -(defun +jump/documentation (identifier) - "Show documentation for the symbol at point, if available." - (interactive (list (thing-at-point 'symbol t))) - (cond ((plist-member +jump-current-functions :documentation) - (+jump-to :documentation identifier)) - (t - (+jump/online - identifier - (+jump--online-get-provider (not current-prefix-arg)))))) - -(defun +jump--online-get-provider (&optional force-p) - (or (and (not force-p) - +jump--online-last) - (completing-read "Search on: " - (mapcar #'car +jump-search-provider-alist) - nil t))) - -(defvar +jump--online-last nil) -;;;###autoload -(defun +jump/online (search &optional provider) - "Looks up SEARCH (a string) in you browser using PROVIDER. - -PROVIDER should be a key of `+jump-search-provider-alist'. - -When used interactively, it will prompt for a query and, for the first time, the -provider from `+jump-search-provider-alist'. On consecutive uses, the last -provider will be reused. If the universal argument is supplied, always prompt -for the provider." - (interactive - (list (or (and (region-active-p) - (buffer-substring-no-properties (region-beginning) - (region-end))) - (read-string "Search for: " (thing-at-point 'symbol t))) - (+jump--online-get-provider current-prefix-arg))) - (condition-case _ex - (let ((url (cdr (assoc provider +jump-search-provider-alist)))) - (unless url - (error "'%s' is an invalid search engine" provider)) - (when (or (functionp url) (symbolp url)) - (setq url (funcall url))) - (cl-assert (and (stringp url) (not (string-empty-p url)))) - (when (string-empty-p search) - (user-error "The search query is empty")) - (setq +jump--online-last provider) - (funcall +jump-search-browser-fn (format url (url-encode-url search)))) - ('error (setq +jump--online-last nil)))) - -;;;###autoload -(defun +jump/online-select () - "Runs `+jump/online', but always prompts for the provider to use." - (interactive) - (let ((current-prefix-arg t)) - (call-interactively #'+jump/online))) diff --git a/modules/feature/jump/config.el b/modules/feature/jump/config.el deleted file mode 100644 index f4e005446..000000000 --- a/modules/feature/jump/config.el +++ /dev/null @@ -1,106 +0,0 @@ -;;; feature/jump/config.el -*- lexical-binding: t; -*- - -;; "What am I looking at?" -;; -;; This module helps you answer that question. It helps you look up whatever -;; you're looking at. -;; -;; + `+jump/definition': a jump-to-definition that should 'just work' -;; + `+jump/references': find a symbol's references in the current project -;; + `+jump/online'; look up a symbol on online resources, like stackoverflow, -;; devdocs.io or google. -;; -;; This module uses `xref', an experimental new library in Emacs. It may change -;; in the future. When xref can't be depended on it will fall back to -;; `dumb-jump' to find what you want. - -(defvar +jump-search-provider-alist - '(("Google" . "https://google.com/search?q=%s") - ("Google images" . "https://google.com/images?q=%s") - ("Google maps" . "https://maps.google.com/maps?q=%s") - ("Project Gutenberg" . "http://www.gutenberg.org/ebooks/search/?query=%s") - ("DuckDuckGo" . "https://duckduckgo.com/?q=%s") - ("DevDocs.io" . "http://devdocs.io/#q=%s") - ("StackOverflow" . "https://stackoverflow.com/search?q=%s") - ("Github" . "https://github.com/search?ref=simplesearch&q=%s") - ("Youtube" . "https://youtube.com/results?aq=f&oq=&search_query=%s") - ("Wolfram alpha" . "https://wolframalpha.com/input/?i=%s") - ("Wikipedia" . "https://wikipedia.org/search-redirect.php?language=en&go=Go&search=%s")) - "An alist that maps online resources to their search url or a function that -produces an url. Used by `+jump/online'.") - -(defvar +jump-search-browser-fn #'browse-url - "Function to use to open search urls.") - -(defvar +jump-function-alist nil - "An alist mapping major modes to jump function plists, describing what to do -with `+jump/definition', `+jump/references' and `+jump/documentation' are -called.") - -(defvar-local +jump-current-functions nil - "The enabled jump functions for the current buffer.") - -(def-setting! :jump (modes &rest plist) - "Definies a jump target for major MODES. PLIST accepts the following -properties: - - :definition FN - Run when jumping to a symbol's definition. - Used by `+jump/definition'. - :references FN - Run when looking for usage references of a symbol in the current project. - Used by `+jump/references'. - :documentation FN - Run when looking up documentation for a symbol. - Used by `+jump/documentation'." - `(dolist (mode (doom-enlist ,modes)) - (push (cons mode (list ,@plist)) +jump-function-alist))) - -;; Let me control what backends to fall back on -(setq-default xref-backend-functions '(t)) - -(set! :popup "*xref*" :noselect t :autokill t :autoclose t) - -;; Recenter after certain jumps -(add-hook! - '(imenu-after-jump-hook evil-jumps-post-jump-hook - counsel-grep-post-action-hook dumb-jump-after-jump-hook) - #'recenter) - -(defun +jump|init () - "Initialize `+jump-current-functions', used by `+jump/definition', -`+jump/references' and `+jump/documentation'." - (when-let* ((plist (cdr (assq major-mode +jump-function-alist)))) - (when-let* ((backend (plist-get plist :xref-backend))) - (make-variable-buffer-local 'xref-backend-functions) - (cl-pushnew backend xref-backend-functions :test #'eq)) - (setq-local +jump-current-functions plist))) -(add-hook 'after-change-major-mode-hook #'+jump|init) - - -;; -;; Packages -;; - -(def-package! ivy-xref - :when (featurep! :completion ivy) - :after xref - :config (setq xref-show-xrefs-function #'ivy-xref-show-xrefs)) - - -(def-package! helm-xref - :when (featurep! :completion helm) - :after xref - :config (setq xref-show-xrefs-function #'helm-xref-show-xrefs)) - - -(def-package! dumb-jump - :commands (dumb-jump-go dumb-jump-quick-look - dumb-jump-back dumb-jump-result-follow) - :config - (setq dumb-jump-default-project doom-emacs-dir - dumb-jump-aggressive nil - dumb-jump-selector (cond ((featurep! :completion ivy) 'ivy) - ((featurep! :completion helm) 'helm) - (t 'popup)))) - diff --git a/modules/feature/jump/packages.el b/modules/feature/jump/packages.el deleted file mode 100644 index 712158bb2..000000000 --- a/modules/feature/jump/packages.el +++ /dev/null @@ -1,8 +0,0 @@ -;; -*- no-byte-compile: t; -*- -;;; feature/jump/packages.el - -(package! dumb-jump) -(when (featurep! :completion ivy) - (package! ivy-xref)) -(when (featurep! :completion helm) - (package! helm-xref)) diff --git a/modules/feature/lookup/README.org b/modules/feature/lookup/README.org new file mode 100644 index 000000000..8ca362370 --- /dev/null +++ b/modules/feature/lookup/README.org @@ -0,0 +1,182 @@ +#+TITLE: :feature lookup + +Integrates with code navigation and documentation tools to help you quickly look +up definitions, references and documentation. + ++ Integration with devdocs.io ++ Integration with Dash.app docsets. ++ Jump-to-definition and find-references implementations that just work. ++ Powerful xref integration for languages that support it. + +* Table of Contents :TOC: +- [[#install][Install]] + - [[#module-flags][Module flags]] + - [[#dependencies][Dependencies]] +- [[#usage][Usage]] + - [[#jump-to-definition][Jump to definition]] + - [[#find-references][Find references]] + - [[#look-up-documentation][Look up documentation]] + - [[#search-a-specific-documentation-backend][Search a specific documentation backend]] +- [[#configuration][Configuration]] + - [[#settings][Settings]] + - [[#open-in-eww-instead-of-browser][Open in eww instead of browser]] +- [[#appendix][Appendix]] + - [[#commands][Commands]] + +* Install +To enable the module add =:feature lookup= to your ~doom!~ block in +=~/.emacs.d/init.el=. + +** Module flags +This module provides two flags: + ++ ~+docsets~ Enables integration with Dash docsets. ++ ~+devdocs~ Enables integration with devdocs.io search. + +** Dependencies +This module has several soft dependencies: + ++ ~the_silver_searcher~ and/or ~ripgrep~ as a last-resort fallback for + jump-to-definition/find-references. ++ ~sqlite3~ for Dash docset support. + +*** MacOS +#+BEGIN_SRC sh :tangle (if (doom-system-os 'macos) "yes") +brew install the_silver_searcher ripgrep + +# An older version of sqlite is included in MacOS. If it causes you problems (and +# it has been reported that it will), install it through homebrew: +brew install sqlite +# Note that it's keg-only, meaning it isn't symlinked to /usr/local/bin. You'll +# have to add it to PATH yourself (or symlink it into your PATH somewhere). e.g. +export PATH="/usr/local/opt/sqlite/bin:$PATH" +#+END_SRC + +*** Arch Linux +#+BEGIN_SRC sh :dir /sudo:: :tangle (if (doom-system-os 'arch) "yes") +sudo pacman --needed --noconfirm -S sqlite the_silver_searcher ripgrep +#+END_SRC + +* Usage +** Jump to definition +Use ~+lookup/definition~ (bound to =gd= in normal mode) to jump to the +definition of the symbol at point + +This module provides a goto-definition implementation that will try the +following sources before giving up: + +1. Whatever ~:definition~ function is registered for the current buffer with the + ~:lookup~ setting (see "Configuration" section). +2. Any available xref backends. +3. ~dumb-jump~ (a text search with aides to reduce false positives). +3. An ordinary project-wide text search with ripgrep or the_silver_searcher. +5. If ~evil-mode~ is active, use ~evil-goto-definition~, which preforms a simple + text search within the current buffer. + +If there are multiple results, you will be prompted to select one. + +** Find references +Use ~+lookup/references~ (bound to =gD= in normal mode) to see a list of +references for the symbol at point from throughout your project. + +Like ~+lookup/definition~, this tries a number of sources before giving up. It +will try: + +1. Whatever ~:references~ function is registered for the current buffer with the + ~:lookup~ setting (see "Configuration" section). +2. Any available xref backends. +3. An ordinary project-wide text search with ripgrep or the_silver_searcher. + +If there are multiple results, you will be prompted to select one. + +** Look up documentation +~+lookup/documentation~ (bound to =gh= in normal mode) will open documentation +for the symbol at point. + +Depending on your configuration, this will try a list of sources: + +1. Whatever ~:documentation~ function is registered for the current buffer with + the ~:lookup~ setting (see "Configuration" section). +2. Any Dash.app docsets, if any are installed for the current major mode. +3. devdocs.io, if it has a docset for the current mode. +4. An online search; using the last engine used (it will prompt you the first + time, or if ~current-prefix-arg~ is non-nil). + +** Search a specific documentation backend +You can perform a documentation lookup on any backends directly: + ++ Dash Docsets: ~+lookup/in-docsets~, or ~:dash QUERY~ for evil users. ++ devdocs.io: ~+lookup/in-devdocs~, or ~:dd QUERY~ for evil users. ++ Online (generic): ~+lookup/online~ or ~+lookup/online-select~ (bound to =SPC / + o=), or ~:lo[okup] QUERY~ for evil users. + +* Configuration +** Settings +This module provides three settings: ~:lookup~, ~:docset~ and ~:devdocs~. + +*** ~:lookup MODES &rest PLIST~ +Defines a lookup target for major MODES (one major-mode symbol or a list +thereof). PLIST accepts the following optional properties: + ++ ~:definition FN~ :: 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~. ++ ~:documentation FN~ :: Run when looking up documentation for a symbol. Used by + ~+lookup/documentation~. ++ ~:xref-backend FN~ :: Defines an xref backend for a major-mode. With this, + :definition and :references are unnecessary. + +**** Example +#+BEGIN_SRC emacs-lisp +;; For python-mode, anaconda-mode offers a backend for all three lookup +;; functions. We can register them like so: +(set! :lookup 'python-mode + :definition #'anaconda-mode-find-definitions + :references #'anaconda-mode-find-references + :documentation #'anaconda-mode-show-doc) + +;; If a language or plugin provides a custom xref backend available for it, use +;; that instead. It will provide the best jump-to-definition and find-references +;; experience. You can specify custom xref backends with: +(set! :lookup 'js2-mode :xref-backend #'xref-js2-xref-backend) +;; NOTE: xref doesn't provide a :documentation backend. +#+END_SRC + +*** ~:docset MODES &rest DOCSETS~ +Registers a list of DOCSETS (strings) for MODES (either one major mode symbol or +a list of them). Used by ~+lookup/in-docsets~ and ~+lookup/documentation~. + +#+BEGIN_SRC emacs-lisp +(set! :docset 'js2-mode "JavaScript" "JQuery") +;; Add docsets to minor modes by starting DOCSETS with :add +(set! :docset 'rjsx-mode :add "React") +;; Or remove docsets from minor modes +(set! :docset 'nodejs-mode :remove "JQuery") +#+END_SRC + +*** ~:devdocs MODES DOCSET~ +Registers a devdocs DOCset (one string) to search when in MODES (either one +major mode symbol or a list). Used by ~+lookup/in-devdocs~ and +~+lookup/documentation~. With devdocs you can only search one docset at a time. + +#+BEGIN_SRC emacs-lisp +(set! :devdocs 'js2-mode "javascript") +;; works on minor modes too +(set! :devdocs 'rjsx-mode "react") +#+END_SRC + +** Open in eww instead of browser +#+BEGIN_SRC emacs-lisp +(setq +lookup-open-url-fn 'eww) +#+END_SRC + +* Appendix +** Commands ++ ~+lookup/definition~ ++ ~+lookup/references~ ++ ~+lookup/documentation~ ++ ~+lookup/online~ ++ ~+lookup/online-select~ ++ ~+lookup/in-devdocs~ ++ ~+lookup/in-docsets~ diff --git a/modules/feature/lookup/autoload/evil.el b/modules/feature/lookup/autoload/evil.el new file mode 100644 index 000000000..72c193fcc --- /dev/null +++ b/modules/feature/lookup/autoload/evil.el @@ -0,0 +1,22 @@ +;;; feature/lookup/autoload/evil.el -*- lexical-binding: t; -*- +;;;###if (featurep! :feature evil) + +;;;###autoload (autoload '+lookup:online "feature/lookup/autoload/evil" nil t) +(evil-define-command +lookup:online (query &optional bang) + "Look up QUERY online. Will prompt for search engine the first time, then +reuse it on consecutive uses of this command. If BANG, always prompt for search +engine." + (interactive "") + (+lookup/online query (+lookup--online-provider bang 'evil-ex))) + +;;;###autoload (autoload '+lookup:dash "feature/lookup/autoload/evil" nil t) +(evil-define-command +lookup:dash (query &optional bang) + "TODO" + (interactive "") + (+lookup/in-docsets query (if bang 'blank))) + +;;;###autoload (autoload '+lookup:devdocs "feature/lookup/autoload/evil" nil t) +(evil-define-command +lookup:devdocs (query &optional bang) + "TODO" + (interactive "") + (+lookup/in-devdocs query (if bang 'blank))) diff --git a/modules/feature/lookup/autoload/lookup.el b/modules/feature/lookup/autoload/lookup.el new file mode 100644 index 000000000..03b19fe12 --- /dev/null +++ b/modules/feature/lookup/autoload/lookup.el @@ -0,0 +1,237 @@ +;;; feature/lookup/autoload/lookup.el -*- lexical-binding: t; -*- + +(defvar +lookup--rg-installed-p (executable-find "rg")) +(defvar +lookup--ag-installed-p (executable-find "ag")) +(defvar +lookup--last-provider nil) + +;; Helpers +(defun +lookup--online-provider (&optional force-p namespace) + (let ((key (or namespace major-mode))) + (or (and (not force-p) + (cdr (assq key +lookup--last-provider))) + (when-let* ((provider + (completing-read + "Search on: " + (mapcar #'car +lookup-provider-url-alist) + nil t))) + (push (cons key provider) + (assq-delete-all key +lookup--last-provider)))))) + +(defun +lookup--symbol-or-region (&optional initial) + (cond (initial) + ((use-region-p) + (buffer-substring-no-properties (region-beginning) + (region-end))) + ((thing-at-point 'symbol t)))) + +(defun +lookup--jump-to (prop identifier &optional other-window) + (with-selected-window + (if other-window + (save-excursion (other-window 1) (selected-window)) + (selected-window)) + (let ((fn (plist-get +lookup-current-functions prop)) + (origin (point-marker))) + (condition-case e + (or (if (commandp fn) + (call-interactively fn) + (funcall fn identifier)) + (/= (point-marker) origin)) + ('error + (message "%s" e) + nil))))) + + +;; +;; Main commands +;; + +;;;###autoload +(defun +lookup/definition (identifier &optional other-window) + "Jump to the definition of the symbol at point. It will try several things +to find it: + +1. It will try whatever function that has been set for the current buffer, in + `+lookup-current-functions'. +2. Then try any available xref backends, +3. Then `dumb-jump', +4. Then a plain project-wide text search, using ripgrep or the_silver_searcher. +5. Then, if `evil-mode' is active, use `evil-goto-definition', + +Failing all that, it will give up with an error." + (interactive + (list (thing-at-point 'symbol t) + current-prefix-arg)) + (cond ((null identifier) + (user-error "Nothing under point")) + + ((and (plist-member +lookup-current-functions :definition) + (+lookup--jump-to :definition identifier))) + + ((ignore-errors (if other-window + (xref-find-definitions-other-window identifier) + (xref-find-definitions identifier)) + t)) + + ((and (require 'dumb-jump nil t) + ;; dumb-jump doesn't tell us if it succeeded or not + (let ((old-fn (symbol-function 'dumb-jump-get-results)) + successful) + (cl-letf (((symbol-function 'dumb-jump-get-results) + (lambda (&optional prompt) + (let* ((plist (funcall old-fn prompt)) + (results (plist-get plist :results))) + (when (and results (> (length results) 0)) + (setq successful t)) + plist)))) + (if other-window + (dumb-jump-go-other-window) + (dumb-jump-go)) + successful)))) + + ((and identifier + (featurep 'counsel) + (let ((regex (rxt-quote-pcre identifier))) + (or (and +lookup--rg-installed-p + (counsel-rg regex (doom-project-root))) + (and +lookup--ag-installed-p + (counsel-ag regex (doom-project-root))))))) + + ((and (featurep 'evil) + evil-mode + (cl-destructuring-bind (beg . end) + (bounds-of-thing-at-point 'symbol) + (evil-goto-definition) + (let ((pt (point))) + (not (and (>= pt beg) + (< pt end))))))) + + (t (user-error "Couldn't find '%s'" identifier)))) + +;;;###autoload +(defun +lookup/references (identifier) + "Show a list of references to the symbol at point. + +Tries `xref-find-references' and falls back to rg/ag." + (interactive (list (thing-at-point 'symbol t))) + (cond ((and (plist-member +lookup-current-functions :references) + (+lookup--jump-to :references identifier))) + + ((ignore-errors (xref-find-references identifier) + t)) + + ((and identifier + (featurep 'counsel) + (let ((regex (rxt-quote-pcre identifier))) + (or (and (executable-find "rg") + (counsel-rg regex (doom-project-root))) + (and (executable-find "ag") + (counsel-ag regex (doom-project-root))))))) + + (t (error "Couldn't find '%s'" identifier)))) + +;;;###autoload +(defun +lookup/documentation (identifier) + "Show documentation for IDENTIFIER (defaults to symbol at point or selection. + +Goes down a list of possible backends: + +1. The :documentation spec defined with by `doom--set:lookup' +2. If the +docsets flag is active for :feature lookup, use `+lookup/in-docsets' +3. If the +devdocs flag is active for :feature lookup, run `+lookup/in-devdocs' +4. Fall back to an online search, with `+lookup/online'" + (interactive + (list (+lookup--symbol-or-region))) + (cond ((and (plist-member +lookup-current-functions :documentation) + (+lookup--jump-to :documentation identifier))) + + ((and (featurep! :feature lookup +docsets) + (cl-find-if #'helm-dash-docset-installed-p + (or (bound-and-true-p counsel-dash-docsets) + (bound-and-true-p helm-dash-docsets)))) + (+lookup/in-docsets identifier)) + + ((featurep! :feature lookup +devdocs) + (+lookup/in-devdocs identifier)) + + ((+lookup/online + identifier + (+lookup--online-provider (not current-prefix-arg)))))) + + +;; +;; Source-specific commands +;; + +;;;###autoload +(defun +lookup/in-devdocs (&optional query docs) + "TODO" + (interactive) + (require 'devdocs) + (let* ((docs + (unless (eq docs 'blank) + (or docs (cdr (assq major-mode devdocs-alist)) ""))) + (query (or query (+lookup--symbol-or-region) "")) + (pattern (string-trim-left (format "%s %s" docs query)))) + (unless (and current-prefix-arg docs) + (setq pattern (read-string "Lookup on devdocs.io: " pattern))) + (funcall +lookup-open-url-fn + (format "%s/#q=%s" devdocs-url + (url-hexify-string pattern))) + (unless (string-empty-p pattern) + (cl-pushnew pattern devdocs-search-history)))) + +;;;###autoload +(defun +lookup/in-docsets (&optional query docsets) + "TODO" + (interactive) + (let* ((counsel-dash-docsets + (unless (eq docsets 'blank) + (or docsets + (or (bound-and-true-p counsel-dash-docsets) + (bound-and-true-p helm-dash-docsets))))) + (helm-dash-docsets counsel-dash-docsets) + (query (or query (+lookup--symbol-or-region) ""))) + (cond ((featurep! :completion helm) + (helm-dash query)) + ((featurep! :completion ivy) + (counsel-dash query)) + (t + (user-error "No dash backend is installed, enable ivy or helm."))))) + +;;;###autoload +(defun +lookup/online (search &optional provider) + "Looks up SEARCH (a string) in you browser using PROVIDER. + +PROVIDER should be a key of `+lookup-provider-url-alist'. + +When used interactively, it will prompt for a query and, for the first time, the +provider from `+lookup-provider-url-alist'. On consecutive uses, the last +provider will be reused. If the universal argument is supplied, always prompt +for the provider." + (interactive + (list (or (and (use-region-p) + (buffer-substring-no-properties (region-beginning) + (region-end))) + (read-string "Search for: " (thing-at-point 'symbol t))) + (+lookup--online-provider current-prefix-arg))) + (condition-case _ex + (let ((url (cdr (assoc provider +lookup-provider-url-alist)))) + (unless url + (error "'%s' is an invalid search engine" provider)) + (when (or (functionp url) (symbolp url)) + (setq url (funcall url))) + (cl-assert (and (stringp url) (not (string-empty-p url)))) + (when (string-empty-p search) + (user-error "The search query is empty")) + (funcall +lookup-open-url-fn (format url (url-encode-url search)))) + ('error + (setq +lookup--last-provider + (assq-delete-all major-mode +lookup--last-provider))))) + +;;;###autoload +(defun +lookup/online-select () + "Runs `+lookup/online', but always prompts for the provider to use." + (interactive) + (let ((current-prefix-arg t)) + (call-interactively #'+lookup/online))) + diff --git a/modules/feature/lookup/config.el b/modules/feature/lookup/config.el new file mode 100644 index 000000000..b51032a28 --- /dev/null +++ b/modules/feature/lookup/config.el @@ -0,0 +1,189 @@ +;;; feature/lookup/config.el -*- lexical-binding: t; -*- + +;; "What am I looking at?" This module helps you answer this question. +;; +;; + `+lookup/definition': a jump-to-definition that should 'just work' +;; + `+lookup/references': find a symbol's references in the current project +;; + `+lookup/online'; look up a symbol on online resources +;; + `+lookup/docs-at-point' +;; + `+lookup/docs-dash' +;; + `+lookup/docs-dash-at-point' +;; + `+lookup/devdocs' +;; + `+lookup/devdocs-at-point' +;; +;; This module uses `xref', an experimental new library in Emacs. It may change +;; in the future. When xref can't be depended on it will fall back to +;; `dumb-jump' to find what you want. + +(defvar +lookup-provider-url-alist + '(("Google" . "https://google.com/search?q=%s") + ("Google images" . "https://google.com/images?q=%s") + ("Google maps" . "https://maps.google.com/maps?q=%s") + ("Project Gutenberg" . "http://www.gutenberg.org/ebooks/search/?query=%s") + ("DuckDuckGo" . "https://duckduckgo.com/?q=%s") + ("DevDocs.io" . "https://devdocs.io/#q=%s") + ("StackOverflow" . "https://stackoverflow.com/search?q=%s") + ("Github" . "https://github.com/search?ref=simplesearch&q=%s") + ("Youtube" . "https://youtube.com/results?aq=f&oq=&search_query=%s") + ("Wolfram alpha" . "https://wolframalpha.com/input/?i=%s") + ("Wikipedia" . "https://wikipedia.org/search-redirect.php?language=en&go=Go&search=%s")) + "An alist that maps online resources to their search url or a function that +produces an url. Used by `+lookup/online'.") + +(defvar +lookup-open-url-fn #'browse-url + "Function to use to open search urls.") + +(defvar +lookup-function-alist nil + "An alist mapping major modes to jump function plists, describing what to do +with `+lookup/definition', `+lookup/references' and `+lookup/documentation' are +called.") + +(defvar-local +lookup-current-functions nil + "The enabled jump functions for the current buffer.") + +(def-setting! :lookup (modes &rest plist) + "Defines a jump target for major MODES. PLIST accepts the following +properties: + + :definition FN + 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'. + :documentation FN + Run when looking up documentation for a symbol. + Used by `+lookup/documentation'. + :xref-backend FN + Defines an xref backend for a major-mode. With this, :definition and + :references are unnecessary." + `(dolist (mode (doom-enlist ,modes)) + (push (cons mode (list ,@plist)) + +lookup-function-alist))) + +;; Recenter buffer after certain jumps +(add-hook! + '(imenu-after-jump-hook evil-jumps-post-jump-hook + counsel-grep-post-action-hook dumb-jump-after-jump-hook) + #'recenter) + + +;; +;; dumb-jump +;; + +(def-package! dumb-jump + :commands (dumb-jump-go dumb-jump-quick-look + dumb-jump-back dumb-jump-result-follow) + :config + (setq dumb-jump-default-project doom-emacs-dir + dumb-jump-aggressive nil + dumb-jump-selector + (cond ((featurep! :completion ivy) 'ivy) + ((featurep! :completion helm) 'helm) + (t 'popup)))) + + +;; +;; xref +;; + +(after! xref + ;; By default, `etags--xref-backend' is the default xref backend. No need. + ;; We'll set these up ourselves in other modules. + (setq-default xref-backend-functions '(t)) + (set! :popup "*xref*" :noselect t :autokill t :autoclose t)) + +(defun +lookup|init-xref-backends () + "Set `+lookup-current-functions' for the current buffer. + +This variable is used by `+lookup/definition',`+lookup/references' and +`+lookup/documentation'." + (when-let* ((plist (cdr (assq major-mode +lookup-function-alist)))) + (when-let* ((backend (plist-get plist :xref-backend))) + (make-variable-buffer-local 'xref-backend-functions) + (cl-pushnew backend xref-backend-functions :test #'eq)) + (setq-local +lookup-current-functions plist))) +(add-hook 'after-change-major-mode-hook #'+lookup|init-xref-backends) + + +(def-package! ivy-xref + :when (featurep! :completion ivy) + :after xref + :config (setq xref-show-xrefs-function #'ivy-xref-show-xrefs)) + + +(def-package! helm-xref + :when (featurep! :completion helm) + :after xref + :config (setq xref-show-xrefs-function #'helm-xref-show-xrefs)) + + +;; +;; Dash docset integration +;; + +(when (featurep! +docsets) + (def-setting! :docset (modes &rest docsets) + "Registers a list of DOCSETS (strings) for MODES (either one major mode +symbol or a list of them). + +If MODES is a minor mode, you can use :add or :remove as the first element of +DOCSETS, to instruct it to append (or remove) those from the docsets already set +by a major-mode, if any. + +Used by `+lookup/in-docsets' and `+lookup/documentation'." + (cl-loop with ivy-p = (featurep! :completion ivy) + for mode in (doom-enlist (doom-unquote modes)) + for hook-sym = (intern (format "+docs|init-for-%s" mode)) + for var-sym = (if ivy-p 'counsel-dash-docsets 'helm-dash-docsets) + collect hook-sym into hooks + collect + `(defun ,hook-sym () + (make-variable-buffer-local ',var-sym) + (cond ((eq (car docsets) :add) + `(setq ,var-sym (append ,var-sym (list ,@(cdr docsets))))) + ((eq (car docsets) :remove) + `(setq ,var-sym + (cl-loop with to-delete = (list ,@(cdr docsets)) + for docset in ,var-sym + unless (member docset to-delete) + collect docset))) + (`(setq ,var-sym (list ,@docsets))))) + into forms + finally return `(progn ,@forms (add-hook! ,modes ',hooks)))) + + ;; Both packages depend on helm-dash + (def-package! helm-dash + :commands (helm-dash helm-dash-install-docset helm-dash-at-point + helm-dash-docset-installed-p) + :config + ;; Obey XDG conventions + (when-let* ((xdg-data-home (getenv "XDG_DATA_HOME"))) + (setq helm-dash-docsets-path (expand-file-name "docsets" xdg-data-home))) + (unless (file-directory-p helm-dash-docsets-path) + (make-directory helm-dash-docsets-path t)) + (setq helm-dash-enable-debugging doom-debug-mode)) + + (def-package! counsel-dash + :when (featurep! :completion ivy) + :commands (counsel-dash counsel-dash-install-docset) + :after helm-dash + :config (setq counsel-dash-min-length 2))) + + +;; +;; devdocs.io integration +;; + +(when (featurep! +devdocs) + (def-setting! :devdocs (modes docset) + "Map major MODES (one major-mode symbol or a list of them) to a devdocs +DOCSET (a string). + +See `devdocs-alist' for the defaults. " + `(dolist (mode ',modes) + (push (cons mode ,docset) devdocs-alist))) + + (def-package! devdocs :defer t)) + diff --git a/modules/feature/lookup/packages.el b/modules/feature/lookup/packages.el new file mode 100644 index 000000000..3d54bd1a4 --- /dev/null +++ b/modules/feature/lookup/packages.el @@ -0,0 +1,17 @@ +;; -*- no-byte-compile: t; -*- +;;; feature/lookup/packages.el + +(package! dumb-jump) +(when (featurep! :completion ivy) + (package! ivy-xref)) +(when (featurep! :completion helm) + (package! helm-xref)) + +(when (featurep! +docsets) + (when (featurep! :completion helm) + (package! helm-dash)) + (when (featurep! :completion ivy) + (package! counsel-dash))) + +(when (featurep! +devdocs) + (package! devdocs)) diff --git a/modules/lang/javascript/config.el b/modules/lang/javascript/config.el index 772976ae9..c775bd034 100644 --- a/modules/lang/javascript/config.el +++ b/modules/lang/javascript/config.el @@ -45,7 +45,9 @@ ;; A find-{definition,references} backend for js2-mode. NOTE The xref API is ;; unstable and may break with an Emacs update. -(def-package! xref-js2 :commands xref-js2-xref-backend) +(def-package! xref-js2 + :when (featurep! :feature lookup) + :commands xref-js2-xref-backend) (def-package! nodejs-repl :commands nodejs-repl) diff --git a/modules/lang/javascript/packages.el b/modules/lang/javascript/packages.el index c89f3bd09..22358eb9a 100644 --- a/modules/lang/javascript/packages.el +++ b/modules/lang/javascript/packages.el @@ -16,6 +16,6 @@ (when (featurep! :completion company) (package! company-tern)) -(when (featurep! :feature jump) +(when (featurep! :feature lookup) (package! xref-js2)) diff --git a/modules/private/default/+bindings.el b/modules/private/default/+bindings.el index 75c59770a..759d1e967 100644 --- a/modules/private/default/+bindings.el +++ b/modules/private/default/+bindings.el @@ -113,7 +113,7 @@ :desc "Swiper" :nv "/" #'swiper :desc "Imenu" :nv "i" #'imenu :desc "Imenu across buffers" :nv "I" #'imenu-anywhere - :desc "Online providers" :nv "o" #'+jump/online-select) + :desc "Online providers" :nv "o" #'+lookup/online-select) (:desc "workspace" :prefix "TAB" :desc "Display tab bar" :n "TAB" #'+workspace/display @@ -159,8 +159,8 @@ :v "e" #'+eval/region :desc "Evaluate & replace region" :nv "E" #'+eval:replace-region :desc "Build tasks" :nv "b" #'+eval/build - :desc "Jump to definition" :n "d" #'+jump/definition - :desc "Jump to references" :n "D" #'+jump/references + :desc "Jump to definition" :n "d" #'+lookup/definition + :desc "Jump to references" :n "D" #'+lookup/references :desc "Open REPL" :n "r" #'+eval/open-repl :v "r" #'+eval:repl) @@ -205,9 +205,9 @@ :desc "Describe face" :n "F" #'describe-face :desc "Describe DOOM setting" :n "s" #'doom/describe-setting :desc "Describe DOOM module" :n "d" #'doom/describe-module - :desc "Find definition" :n "." #'+jump/definition - :desc "Find references" :n "/" #'+jump/references - :desc "Find documentation" :n "h" #'+jump/documentation + :desc "Find definition" :n "." #'+lookup/definition + :desc "Find references" :n "/" #'+lookup/references + :desc "Find documentation" :n "h" #'+lookup/documentation :desc "What face" :n "'" #'doom/what-face :desc "What minor modes" :n ";" #'doom/what-minor-mode :desc "Info" :n "i" #'info @@ -297,9 +297,9 @@ :n "[w" #'+workspace/switch-left :m "gt" #'+workspace/switch-right :m "gT" #'+workspace/switch-left - :m "gd" #'+jump/definition - :m "gD" #'+jump/references - :m "gh" #'+jump/documentation + :m "gd" #'+lookup/definition + :m "gD" #'+lookup/references + :m "gh" #'+lookup/documentation :n "gp" #'+evil/reselect-paste :n "gr" #'+eval:region :n "gR" #'+eval/buffer diff --git a/modules/private/default/+evil-commands.el b/modules/private/default/+evil-commands.el index 2ee09ea6c..9dfda8bc8 100644 --- a/modules/private/default/+evil-commands.el +++ b/modules/private/default/+evil-commands.el @@ -29,7 +29,9 @@ ;; TODO (ex! "db" #'doom:db) ;; TODO (ex! "dbu[se]" #'doom:db-select) ;; TODO (ex! "go[ogle]" #'doom:google-search) -(ex! "lo[okup]" #'+jump:online) +(ex! "lo[okup]" #'+lookup:online) +(ex! "dash" #'+lookup:dash) +(ex! "dd" #'+lookup:devdocs) (ex! "http" #'httpd-start) ; start http server (ex! "repl" #'+eval:repl) ; invoke or send to repl ;; TODO (ex! "rx" 'doom:regex) ; open re-builder