💥 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
This commit is contained in:
Henrik Lissner 2018-01-04 17:05:37 -05:00
parent 4af4803e0b
commit 1371d34149
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
14 changed files with 666 additions and 284 deletions

View file

@ -34,7 +34,9 @@
eval ; run code, run (also, repls) eval ; run code, run (also, repls)
evil ; come to the dark side, we have cookies evil ; come to the dark side, we have cookies
file-templates ; auto-snippets for empty files 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 services ; TODO managing external services & code builders
snippets ; my elves. They type so I don't have to snippets ; my elves. They type so I don't have to
spellcheck ; tasing you for misspelling mispelling spellcheck ; tasing you for misspelling mispelling

View file

@ -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 "<a><!>")
(+jump/online (or query (thing-at-point 'symbol t))
(+jump--online-get-provider bang)))

View file

@ -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)))

View file

@ -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))))

View file

@ -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))

View file

@ -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~

View file

@ -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 "<a><!>")
(+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 "<a><!>")
(+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 "<a><!>")
(+lookup/in-devdocs query (if bang 'blank)))

View file

@ -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)))

View file

@ -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))

View file

@ -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))

View file

@ -45,7 +45,9 @@
;; A find-{definition,references} backend for js2-mode. NOTE The xref API is ;; A find-{definition,references} backend for js2-mode. NOTE The xref API is
;; unstable and may break with an Emacs update. ;; 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) (def-package! nodejs-repl :commands nodejs-repl)

View file

@ -16,6 +16,6 @@
(when (featurep! :completion company) (when (featurep! :completion company)
(package! company-tern)) (package! company-tern))
(when (featurep! :feature jump) (when (featurep! :feature lookup)
(package! xref-js2)) (package! xref-js2))

View file

@ -113,7 +113,7 @@
:desc "Swiper" :nv "/" #'swiper :desc "Swiper" :nv "/" #'swiper
:desc "Imenu" :nv "i" #'imenu :desc "Imenu" :nv "i" #'imenu
:desc "Imenu across buffers" :nv "I" #'imenu-anywhere :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 "workspace" :prefix "TAB"
:desc "Display tab bar" :n "TAB" #'+workspace/display :desc "Display tab bar" :n "TAB" #'+workspace/display
@ -159,8 +159,8 @@
:v "e" #'+eval/region :v "e" #'+eval/region
:desc "Evaluate & replace region" :nv "E" #'+eval:replace-region :desc "Evaluate & replace region" :nv "E" #'+eval:replace-region
:desc "Build tasks" :nv "b" #'+eval/build :desc "Build tasks" :nv "b" #'+eval/build
:desc "Jump to definition" :n "d" #'+jump/definition :desc "Jump to definition" :n "d" #'+lookup/definition
:desc "Jump to references" :n "D" #'+jump/references :desc "Jump to references" :n "D" #'+lookup/references
:desc "Open REPL" :n "r" #'+eval/open-repl :desc "Open REPL" :n "r" #'+eval/open-repl
:v "r" #'+eval:repl) :v "r" #'+eval:repl)
@ -205,9 +205,9 @@
:desc "Describe face" :n "F" #'describe-face :desc "Describe face" :n "F" #'describe-face
:desc "Describe DOOM setting" :n "s" #'doom/describe-setting :desc "Describe DOOM setting" :n "s" #'doom/describe-setting
:desc "Describe DOOM module" :n "d" #'doom/describe-module :desc "Describe DOOM module" :n "d" #'doom/describe-module
:desc "Find definition" :n "." #'+jump/definition :desc "Find definition" :n "." #'+lookup/definition
:desc "Find references" :n "/" #'+jump/references :desc "Find references" :n "/" #'+lookup/references
:desc "Find documentation" :n "h" #'+jump/documentation :desc "Find documentation" :n "h" #'+lookup/documentation
:desc "What face" :n "'" #'doom/what-face :desc "What face" :n "'" #'doom/what-face
:desc "What minor modes" :n ";" #'doom/what-minor-mode :desc "What minor modes" :n ";" #'doom/what-minor-mode
:desc "Info" :n "i" #'info :desc "Info" :n "i" #'info
@ -297,9 +297,9 @@
:n "[w" #'+workspace/switch-left :n "[w" #'+workspace/switch-left
:m "gt" #'+workspace/switch-right :m "gt" #'+workspace/switch-right
:m "gT" #'+workspace/switch-left :m "gT" #'+workspace/switch-left
:m "gd" #'+jump/definition :m "gd" #'+lookup/definition
:m "gD" #'+jump/references :m "gD" #'+lookup/references
:m "gh" #'+jump/documentation :m "gh" #'+lookup/documentation
:n "gp" #'+evil/reselect-paste :n "gp" #'+evil/reselect-paste
:n "gr" #'+eval:region :n "gr" #'+eval:region
:n "gR" #'+eval/buffer :n "gR" #'+eval/buffer

View file

@ -29,7 +29,9 @@
;; TODO (ex! "db" #'doom:db) ;; TODO (ex! "db" #'doom:db)
;; TODO (ex! "dbu[se]" #'doom:db-select) ;; TODO (ex! "dbu[se]" #'doom:db-select)
;; TODO (ex! "go[ogle]" #'doom:google-search) ;; 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! "http" #'httpd-start) ; start http server
(ex! "repl" #'+eval:repl) ; invoke or send to repl (ex! "repl" #'+eval:repl) ; invoke or send to repl
;; TODO (ex! "rx" 'doom:regex) ; open re-builder ;; TODO (ex! "rx" 'doom:regex) ; open re-builder