Moving to Doom Emacs!

This commit is contained in:
Derek Taylor
2019-12-16 20:21:19 -06:00
parent d9f2f456f1
commit d4b4c33550
683 changed files with 51877 additions and 100 deletions

View File

@@ -0,0 +1,197 @@
#+TITLE: tools/lookup
#+DATE: January 4, 2018
#+SINCE: v2.0.9
#+STARTUP: inlineimages
* Table of Contents :TOC:
- [[#description][Description]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#prerequisites][Prerequisites]]
- [[#macos][MacOS]]
- [[#arch-linux][Arch Linux]]
- [[#features][Features]]
- [[#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]]
- [[#dashapp-docset-integration][Dash.app Docset integration]]
- [[#configuration][Configuration]]
- [[#associating-lookup-handlers-with-major-modes][Associating lookup handlers with major modes]]
- [[#associating-dash-docsets-with-major-modes][Associating Dash docsets with major modes]]
- [[#open-in-eww-instead-of-browser][Open in eww instead of browser]]
- [[#appendix][Appendix]]
- [[#commands][Commands]]
* Description
Integrates with code navigation and documentation tools to help you quickly look
up definitions, references and documentation.
+ Jump-to-definition and find-references implementations that just work.
+ Powerful xref integration for languages that support it.
+ Documentation lookup for a variety of online sources (like devdocs.io,
stackoverflow or youtube).
+ Integration with Dash.app docsets.
** Module Flags
+ ~+docsets~ Enable integration with Dash.app docsets.
** Plugins
+ [[https://github.com/jacktasia/dumb-jump][dumb-jump]]
+ [[https://github.com/alexmurray/ivy-xref][ivy-xref]] or [[https://github.com/brotzeit/helm-xref][helm-xref]]
+ [[https://github.com/nathankot/counsel-dash][counsel-dash]] or [[https://github.com/areina/helm-dash][helm-dash]]
* Prerequisites
This module has several soft dependencies:
+ ~the_silver_searcher~ or ~ripgrep~ (recommended) as a last-resort fallback for
jump-to-definition/find-references.
+ ~sqlite3~ for Dash docset support.
** MacOS
#+BEGIN_SRC sh
brew install the_silver_searcher ripgrep
# An older version of sqlite is included in MacOS. If it causes you problems (and
# folks have reported 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
sudo pacman -S sqlite the_silver_searcher ripgrep
#+END_SRC
* Features
** 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 =K= 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.
+ Online (generic): ~+lookup/online~ or ~+lookup/online-select~ (bound to =SPC /
o=), or ~:lo[okup] QUERY~ for evil users.
** Dash.app Docset integration
You can install dash docsets with ~M-x +lookup/install-docset~ and search them
offline with ~M-x +lookup/in-docsets~, or with ~+lookup/documentation~ in modes
that don't have a specialized :documentation lookup handler.
* Configuration
** Associating lookup handlers with major modes
~set-lookup-handlers! MODES &key DEFINITION REFERENCES DOCUMENTATION FILE XREF-BACKEND ASYNC~
Use ~set-lookup-handlers!~ to register lookup targets for MODES (a major or
minor mode symbol or 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~.
+ ~:file FN~ :: 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, which implicitly provides
:definition and :references handlers. If you specify them anyway, they will
take precedence over the xref backend, however.
e.g.
#+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-handlers! '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-handlers! 'js2-mode :xref-backend #'xref-js2-xref-backend)
;; NOTE: xref doesn't provide a :documentation backend.
#+END_SRC
** Associating Dash docsets with major modes
~set-docsets! MODES &rest DOCSETS...~
Use ~set-docsets!~ to register DOCSETS (one string or list of strings) for MODES
(one major mode symbol or a list of them). It is used by ~+lookup/in-docsets~
and ~+lookup/documentation~.
e.g.
#+BEGIN_SRC emacs-lisp
(set-docsets! 'js2-mode "JavaScript" "JQuery")
;; Add docsets to minor modes by starting DOCSETS with :add
(set-docsets! 'rjsx-mode :add "React")
;; Or remove docsets from minor modes
(set-docsets! 'nodejs-mode :remove "JQuery")
#+END_SRC
This determines what docsets to implicitly search for when you use
~+lookup/documentation~ in a mode with no ~:documentation~ handler. Those
docsets must be installed with ~+lookup/install-docset~.
** Open in eww instead of browser
To open results from ~+lookup/online~ in EWW instead of your system browser,
change ~+lookup-open-url-fn~ (default: ~#'browse-url~):
#+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,105 @@
;;; tools/lookup/autoload/docsets.el -*- lexical-binding: t; -*-
;;;###if (featurep! +docsets)
(defvar dash-docs-docsets nil)
;;;###autodef
(defun set-docsets! (modes &rest docsets)
"Registers a list of DOCSETS for MODES.
MODES can be one major mode, or a list thereof.
DOCSETS can be strings, each representing a dash docset, or a vector with the
structure [DOCSET FORM]. If FORM evaluates to nil, the DOCSET is omitted. If it
is non-nil, (format DOCSET FORM) is used as the docset.
The first element in DOCSETS can be :add or :remove, making it easy for users to
add to or remove default docsets from modes.
DOCSETS can also contain sublists, which will be flattened.
Example:
(set-docsets! '(js2-mode rjsx-mode) \"JavaScript\"
[\"React\" (eq major-mode 'rjsx-mode)]
[\"TypeScript\" (bound-and-true-p tide-mode)])
Used by `+lookup/in-docsets' and `+lookup/documentation'."
(declare (indent defun))
(let ((action (if (keywordp (car docsets)) (pop docsets))))
(dolist (mode (doom-enlist modes))
(let ((hook (intern (format "%s-hook" mode)))
(fn (intern (format "+lookup|init--%s-%s" (or action "set") mode))))
(if (null docsets)
(remove-hook hook fn)
(fset fn
(lambda ()
(make-local-variable 'dash-docs-docsets)
(unless (memq action '(:add :remove))
(setq dash-docs-docset nil))
(dolist (spec docsets)
(cl-destructuring-bind (docset . pred)
(cl-typecase spec
(string (cons spec nil))
(vector (cons (aref spec 0) (aref spec 1)))
(otherwise (signal 'wrong-type-arguments (list spec '(vector string)))))
(when (or (null pred)
(eval pred t))
(if (eq action :remove)
(setq dash-docs-docsets (delete docset dash-docs-docsets))
(cl-pushnew docset dash-docs-docsets)))))))
(add-hook hook fn 'append))))))
;;;###autoload
(defun +lookup-dash-docsets-backend-fn (identifier)
"Looks up IDENTIFIER in available Dash docsets, if any are installed.
This backend is meant for `+lookup-documentation-functions'.
Docsets must be installed with one of the following commands:
+ `dash-docs-install-docset'
+ `dash-docs-install-docset-from-file'
+ `dash-docs-install-user-docset'
+ `dash-docs-async-install-docset'
+ `dash-docs-async-install-docset-from-file'
Docsets can be searched directly via `+lookup/in-docsets'."
(when (require 'dash-docs nil t)
(when-let (docsets (cl-remove-if-not #'dash-docs-docset-path (dash-docs-buffer-local-docsets)))
(+lookup/in-docsets nil identifier docsets)
'deferred)))
;;
;;; Commands
;;;###autoload
(defun +lookup/in-docsets (arg &optional query docsets)
"Lookup QUERY in dash DOCSETS.
QUERY is a string and docsets in an array of strings, each a name of a Dash
docset. Requires either helm or ivy.
If prefix ARG is supplied, search all installed installed docsets. They can be
installed with `dash-docs-install-docset'."
(interactive "P")
(require 'dash-docs)
(let ((dash-docs-common-docsets)
(dash-docs-docsets
(if arg
(dash-docs-installed-docsets)
(cl-remove-if-not #'dash-docs-docset-path (or docsets dash-docs-docsets))))
(query (or query (+lookup-symbol-or-region) "")))
(doom-log "Searching docsets %s" dash-docs-docsets)
(cond ((featurep! :completion helm)
(helm-dash query))
((featurep! :completion ivy)
(counsel-dash query))
((user-error "No dash backend is installed, enable ivy or helm.")))))
;;;###autoload
(defun +lookup/in-all-docsets (&optional query)
"TODO"
(interactive)
(+lookup/in-docsets t query))

View File

@@ -0,0 +1,22 @@
;;; tools/lookup/autoload/evil.el -*- lexical-binding: t; -*-
;;;###if (featurep! :editor evil)
;;;###autoload (autoload '+lookup:online "tools/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 "tools/lookup/autoload/evil" nil t)
(evil-define-command +lookup:dash (query &optional bang)
"Look up QUERY in your dash docsets. If BANG, prompt to select a docset (and
install it if necessary)."
(interactive "<a><!>")
(let (selected)
(when bang
(setq selected (helm-dash-read-docset "Select docset" (helm-dash-official-docsets)))
(unless (dash-docs-docset-path selected)
(+lookup/install-docset selected)))
(+lookup/in-docsets query selected)))

View File

@@ -0,0 +1,322 @@
;;; tools/lookup/autoload/lookup.el -*- lexical-binding: t; -*-
;;;###autodef
(defun set-lookup-handlers! (modes &rest plist)
"Define jump handlers for major or minor MODES.
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).
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'.
: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'.
:file FN
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. 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: 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, declare it inline instead:
(set-lookup-handlers! 'rust-mode
:definition '(racer-find-definition :async t))
Handlers can either be interactive or non-interactive. Non-interactive handlers
must take one argument: the identifier being looked up. This function must
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'.
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)
\(fn MODES &key DEFINITION REFERENCES DOCUMENTATION FILE XREF-BACKEND ASYNC)"
(declare (indent defun))
(dolist (mode (doom-enlist modes))
(let ((hook (intern (format "%s-hook" mode)))
(fn (intern (format "+lookup--init-%s-handlers-h" mode))))
(cond ((null (car plist))
(remove-hook hook fn)
(unintern fn nil))
((fset
fn
(lambda ()
(cl-destructuring-bind (&key definition references documentation file xref-backend async)
plist
(cl-mapc #'+lookup--set-handler
(list definition
references
documentation
file
xref-backend)
(list '+lookup-definition-functions
'+lookup-references-functions
'+lookup-documentation-functions
'+lookup-file-functions
'xref-backend-functions)
(make-list 5 async)
(make-list 5 (or (eq major-mode mode)
(and (boundp mode)
(symbol-value mode))))))))
(add-hook hook fn))))))
;;
;;; Helpers
(defun +lookup--set-handler (spec functions-var &optional async enable)
(when spec
(cl-destructuring-bind (fn . plist)
(doom-enlist spec)
(if (not enable)
(remove-hook functions-var fn 'local)
(put fn '+lookup-async (or (plist-get plist :async) async))
(add-hook functions-var fn nil 'local)))))
(defun +lookup--run-handler (handler identifier)
(if (commandp handler)
(call-interactively handler)
(funcall handler identifier)))
(defun +lookup--run-handlers (handler identifier origin)
(doom-log "Looking up '%s' with '%s'" identifier handler)
(condition-case-unless-debug e
(let ((wconf (current-window-configuration))
(result (condition-case-unless-debug e
(+lookup--run-handler handler identifier)
(error
(doom-log "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)
(message "Lookup handler %S: %s" handler e)
nil)))
(defun +lookup--jump-to (prop identifier &optional display-fn arg)
(let* ((origin (point-marker))
(handlers (plist-get (list :definition '+lookup-definition-functions
:references '+lookup-references-functions
:documentation '+lookup-documentation-functions
:file '+lookup-file-functions)
prop))
(result
(if arg
(if-let*
((handler (intern-soft
(completing-read "Select lookup handler: "
(delete-dups
(remq t (append (symbol-value handlers)
(default-value handlers))))
nil t))))
(+lookup--run-handlers handler identifier origin)
(user-error "No lookup handler selected"))
(run-hook-wrapped handlers #'+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)))
;;;###autoload
(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)))))
;;
;;; 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-fn (identifier)
"Non-interactive wrapper for `xref-find-definitions'"
(+lookup--xref-show 'xref-backend-definitions identifier))
(defun +lookup-xref-references-backend-fn (identifier)
"Non-interactive wrapper for `xref-find-references'"
(+lookup--xref-show 'xref-backend-references identifier))
(defun +lookup-dumb-jump-backend-fn (_identifier)
"Look up the symbol at point (or selection) with `dumb-jump', which conducts a
project search with ag, rg, pt, or git-grep, combined with extra heuristics to
reduce false positives.
This backend prefers \"just working\" over accuracy."
(and (require 'dumb-jump nil t)
(dumb-jump-go)))
(defun +lookup-project-search-backend-fn (identifier)
"Conducts a simple project text search for IDENTIFIER.
Uses and requires `+ivy-file-search' or `+helm-file-search'. Will return nil if
neither is available. These require ripgrep to be installed."
(unless identifier
(let ((query (rxt-quote-pcre identifier)))
(ignore-errors
(cond ((featurep! :completion ivy)
(+ivy-file-search :query query)
t)
((featurep! :completion helm)
(+helm-file-search :query query)
t))))))
(defun +lookup-evil-goto-definition-backend-fn (_identifier)
"Uses `evil-goto-definition' to conduct a text search for IDENTIFIER in the
current buffer."
(and (fboundp 'evil-goto-definition)
(ignore-errors
(cl-destructuring-bind (beg . end)
(bounds-of-thing-at-point 'symbol)
(evil-goto-definition)
(let ((pt (point)))
(not (and (>= pt beg)
(< pt end))))))))
;;
;;; Main commands
;;;###autoload
(defun +lookup/definition (identifier &optional arg)
"Jump to the definition of IDENTIFIER (defaults to the symbol at point).
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))
(cond ((null identifier) (user-error "Nothing under point"))
((+lookup--jump-to :definition identifier nil arg))
((error "Couldn't find the definition of %S" identifier))))
;;;###autoload
(defun +lookup/references (identifier &optional arg)
"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))
(cond ((null identifier) (user-error "Nothing under point"))
((+lookup--jump-to :references identifier nil arg))
((error "Couldn't find references of %S" identifier))))
;;;###autoload
(defun +lookup/documentation (identifier &optional arg)
"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 #'pop-to-buffer arg))
((user-error "Couldn't find documentation for %S" identifier))))
(defvar ffap-file-finder)
;;;###autoload
(defun +lookup/file (path)
"Figure out PATH from whatever is at point and open it.
Each function in `+lookup-file-functions' is tried until one changes the point
or the current buffer.
Otherwise, falls back on `find-file-at-point'."
(interactive
(progn
(require 'ffap)
(list
(or (ffap-guesser)
(ffap-read-file-or-url
(if ffap-url-regexp "Find file or URL: " "Find file: ")
(+lookup-symbol-or-region))))))
(require 'ffap)
(cond ((not path)
(call-interactively #'find-file-at-point))
((ffap-url-p path)
(find-file-at-point path))
((not (+lookup--jump-to :file path))
(let ((fullpath (doom-path path)))
(when (and buffer-file-name (file-equal-p fullpath buffer-file-name))
(user-error "Already here"))
(let* ((insert-default-directory t)
(project-root (doom-project-root))
(ffap-file-finder
(cond ((not (doom-glob fullpath))
#'find-file)
((ignore-errors (file-in-directory-p fullpath project-root))
(lambda (dir)
(let* ((default-directory dir)
projectile-project-name
projectile-project-root
(projectile-project-root-cache (make-hash-table :test 'equal))
(file (projectile-completing-read "Find file: "
(projectile-current-project-files)
:initial-input path)))
(find-file (expand-file-name file (doom-project-root)))
(run-hooks 'projectile-find-file-hook))))
(#'doom-project-browse))))
(find-file-at-point path))))))

View File

@@ -0,0 +1,71 @@
;;; tools/lookup/autoload/online.el -*- lexical-binding: t; -*-
(defvar +lookup--last-provider nil)
(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))
(setf (alist-get key +lookup--last-provider) provider)
provider))))
;;;###autoload
(defun +lookup-online-backend-fn (identifier)
"Opens the browser and searches for IDENTIFIER online.
Will prompt for which search engine to use the first time (or if the universal
argument is non-nil)."
(+lookup/online
identifier
(+lookup--online-provider (not current-prefix-arg))))
;;;###autoload
(defun +lookup/online (arg &optional query provider)
"Looks up QUERY (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 "P")
(let* ((provider (or provider (+lookup--online-provider arg)))
(query (or query (+lookup-symbol-or-region)))
(backend (cl-find-if (lambda (x) (or (stringp x) (fboundp x)))
(cdr (assoc provider +lookup-provider-url-alist)))))
(if (and (functionp backend)
(commandp backend))
(call-interactively backend)
(unless backend
(user-error "%S is an invalid query engine backend for %S provider"
backend provider))
(cl-check-type backend (or string function))
(condition-case-unless-debug e
(progn
(unless query
(setq query
(read-string (format "Search for (on %s): " provider)
(thing-at-point 'symbol t))))
(when (or (functionp backend) (symbolp backend))
(setq backend (funcall backend)))
(when (string-empty-p query)
(user-error "The query query is empty"))
(funcall +lookup-open-url-fn (format backend (url-encode-url query))))
(error
(setq +lookup--last-provider
(delq (assq major-mode +lookup--last-provider)
+lookup--last-provider))
(signal (car e) (cdr e)))))))
;;;###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,167 @@
;;; tools/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/file': open the file referenced at point
;; + `+lookup/online'; look up a symbol on online resources
;; + `+lookup/in-docsets': look up in Dash docsets
;;
;; 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
(append '(("Google" counsel-search helm-google-suggest "https://google.com/search?q=%s")
("Google images" "https://www.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" counsel-search "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"))
(when (featurep! :lang rust)
'(("Rust Docs" "https://doc.rust-lang.org/edition-guide/?search=%s"))))
"An alist that maps online resources to either:
1. A search url (needs on '%s' to substitute with an url encoded query),
2. A non-interactive function that returns the search url in #1,
3. An interactive command that does its own search for that provider.
Used by `+lookup/online'.")
(defvar +lookup-open-url-fn #'browse-url
"Function to use to open search urls.")
(defvar +lookup-definition-functions
'(+lookup-xref-definitions-backend-fn
+lookup-dumb-jump-backend-fn
+lookup-project-search-backend-fn
+lookup-evil-goto-definition-backend-fn)
"Functions for `+lookup/definition' to try, before resorting to `dumb-jump'.
Stops at the first function to return non-nil or change the current
window/point.
If the argument is interactive (satisfies `commandp'), it is called with
`call-interactively' (with no arguments). Otherwise, it is called with one
argument: the identifier at point. See `set-lookup-handlers!' about adding to
this list.")
(defvar +lookup-references-functions
'(+lookup-xref-references-backend-fn
+lookup-project-search-backend-fn)
"Functions for `+lookup/references' to try, before resorting to `dumb-jump'.
Stops at the first function to return non-nil or change the current
window/point.
If the argument is interactive (satisfies `commandp'), it is called with
`call-interactively' (with no arguments). Otherwise, it is called with one
argument: the identifier at point. See `set-lookup-handlers!' about adding to
this list.")
(defvar +lookup-documentation-functions
'(+lookup-online-backend-fn)
"Functions for `+lookup/documentation' to try, before resorting to
`dumb-jump'. Stops at the first function to return non-nil or change the current
window/point.
If the argument is interactive (satisfies `commandp'), it is called with
`call-interactively' (with no arguments). Otherwise, it is called with one
argument: the identifier at point. See `set-lookup-handlers!' about adding to
this list.")
(defvar +lookup-file-functions ()
"Function for `+lookup/file' to try, before restoring to `find-file-at-point'.
Stops at the first function to return non-nil or change the current
window/point.
If the argument is interactive (satisfies `commandp'), it is called with
`call-interactively' (with no arguments). Otherwise, it is called with one
argument: the identifier at point. See `set-lookup-handlers!' about adding to
this list.")
;;
;;; dumb-jump
(use-package! dumb-jump
:commands 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)
('popup)))
(add-hook 'dumb-jump-after-jump-hook #'better-jumper-set-jump))
;;
;;; xref
;; The lookup commands are superior, and will consult xref if there are no
;; better backends available.
(global-set-key [remap xref-find-definitions] #'+lookup/definition)
(global-set-key [remap xref-find-references] #'+lookup/references)
(after! xref
;; We already have `projectile-find-tag' and `evil-jump-to-tag', no need for
;; xref to be one too.
(remove-hook 'xref-backend-functions #'etags--xref-backend)
;; ...however, it breaks `projectile-find-tag', unless we put it back.
(defadvice! +lookup--projectile-find-tag-a (orig-fn)
:around #'projectile-find-tag
(let ((xref-backend-functions '(etags--xref-backend t)))
(funcall orig-fn)))
;; Use `better-jumper' instead of xref's marker stack
(advice-add #'xref-push-marker-stack :around #'doom-set-jump-a)
(use-package! ivy-xref
:when (featurep! :completion ivy)
:config
(setq xref-show-xrefs-function #'ivy-xref-show-xrefs)
(set-popup-rule! "^\\*xref\\*$" :ignore t))
(use-package! helm-xref
:when (featurep! :completion helm)
:config (setq xref-show-xrefs-function #'helm-xref-show-xrefs)))
;;
;;; Dash docset integration
(use-package! dash-docs
:when (featurep! +docsets)
:defer t
:init
(add-hook '+lookup-documentation-functions #'+lookup-dash-docsets-backend-fn)
:config
(setq dash-docs-enable-debugging doom-debug-mode
dash-docs-docsets-path (concat doom-etc-dir "docsets/")
dash-docs-min-length 2
dash-docs-browser-func #'eww)
;; Before `gnutls' is loaded, `gnutls-algorithm-priority' is treated as a
;; lexical variable, which breaks `+lookup*fix-gnutls-error'
(defvar gnutls-algorithm-priority)
(defadvice! +lookup--fix-gnutls-error-a (orig-fn url)
"Fixes integer-or-marker-p errors emitted from Emacs' url library,
particularly, the `url-retrieve-synchronously' call in
`dash-docs-read-json-from-url'. This is part of a systemic issue with Emacs 26's
networking library (fixed in Emacs 27+, apparently).
See https://github.com/magit/ghub/issues/81"
:around #'dash-docs-read-json-from-url
(let ((gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3"))
(funcall orig-fn url)))
(use-package! helm-dash
:when (featurep! :completion helm))
(use-package! counsel-dash
:when (featurep! :completion ivy)))

View File

@@ -0,0 +1,25 @@
;; -*- no-byte-compile: t; -*-
;;; tools/lookup/packages.el
;; HACK `dumb-jump' uses the `helm-build-sync-source' macro, but this requires
;; helm be loaded before `dumb-jump' is byte-compiled during installation.
;; To ensure this, we declare helm before dumb-jump.
(when (featurep! :completion helm)
(package! helm))
;;
(package! dumb-jump)
(when (featurep! :completion ivy)
(package! ivy-xref)
;; Need for Google/DuckDuckGo auto-completion on `+lookup/online'
(package! request))
(when (featurep! :completion helm)
(package! helm-google)
(package! helm-xref))
(when (featurep! +docsets)
(package! dash-docs)
(when (featurep! :completion helm)
(package! helm-dash))
(when (featurep! :completion ivy)
(package! counsel-dash)))