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
+25
View File
@@ -0,0 +1,25 @@
;;; tools/ansible/config.el -*- lexical-binding: t; -*-
(use-package! ansible
:commands ansible-auto-decrypt-encrypt
:init
(put 'ansible-vault-password-file 'safe-local-variable #'stringp)
:config
(setq ansible-section-face 'font-lock-variable-name-face
ansible-task-label-face 'font-lock-doc-face)
(map! :map ansible-key-map
:localleader
:desc "Decrypt buffer" "d" #'ansible-decrypt-buffer
:desc "Encrypt buffer" "e" #'ansible-encrypt-buffer
:desc "Look up in Ansible docs" "h" #'ansible-doc))
(after! ansible-doc
(set-evil-initial-state! '(ansible-doc-module-mode) 'emacs))
(use-package! jinja2-mode
:mode "\\.j2$")
(def-project-mode! +ansible-yaml-mode
:modes '(yaml-mode)
:add-hooks '(ansible ansible-auto-decrypt-encrypt ansible-doc-mode)
:files ("roles/"))
@@ -0,0 +1,10 @@
;; -*- no-byte-compile: t; -*-
;;; tools/ansible/packages.el
(package! yaml-mode)
(package! ansible :recipe (:nonrecursive t))
(package! ansible-doc)
(package! jinja2-mode)
(when (featurep! :completion company)
(package! company-ansible))
@@ -0,0 +1,85 @@
;;; tools/debugger/autoload/debugger.el -*- lexical-binding: t; -*-
(defvar +debugger--last nil)
(defun +debugger-list-for-dap ()
(when (and (bound-and-true-p lsp-mode)
(bound-and-true-p lsp--buffer-deferred)
(require 'dap-mode nil t)
dap-mode)
(mapcar #'car dap-debug-template-configurations)))
(defun +debugger-list-for-realgud ()
(cl-loop for (sym . plist) in +debugger--realgud-alist
for sym-name = (symbol-name sym)
for modes = (plist-get plist :modes)
if (or (null modes) (apply #'derived-mode-p modes))
collect sym))
(defun +debugger-list-available ()
"TODO"
(append (+debugger-list-for-dap)
(+debugger-list-for-realgud)
nil))
;;
;;; Interactive commands
;;;###autoload
(defun +debugger/start-last ()
"Relaunch the last debugger session."
(interactive)
(unless +debugger--last
(user-error "No last debugger to invoke"))
(call-interactively +debugger--last))
;;;###autoload
(defun +debugger/start (arg)
"Launch a debugger session.
Launches the last used debugger, if one exists. Otherwise, you will be prompted
for what debugger to use. If the prefix ARG is set, prompt anyway."
(interactive "P")
(if (or arg (null +debugger--last))
(let ((debugger (intern-soft (completing-read "Start debugger: " (+debugger-list-available)))))
(unless debugger
(user-error "No debugging session to quit"))
(unless (fboundp debugger)
(user-error "Couldn't find debugger backend %S" debugger))
(setq-local +debugger--last debugger)
(if (assoc debugger dap-debug-template-configurations)
(dap-debug debugger)
(call-interactively debugger)))
(+debugger/start-last)))
;;;###autoload
(defun +debugger/quit ()
"Quit the active debugger, if any."
(interactive)
(cond ((and (fboundp 'dap--cur-session) (dap--cur-session))
(dap-disconnect))
((and (fboundp 'realgud-get-cmdbuf) (realgud-get-cmdbuf))
(let ((buf (realgud-get-cmdbuf)))
(ignore-errors
(call-interactively #'realgud:cmd-quit))
(let (realgud-safe-mode)
(kill-buffer buf))))
((user-error "No debugging session to quit"))))
;; TODO debugger breakpoint commands
;; ;;;###autoload
;; (defun +debugger/toggle-breakpoint ()
;; (interactive)
;; (user-error "not implemented yet"))
;; ;;;###autoload
;; (defun +debugger/next-breakpoint ()
;; (interactive)
;; (user-error "not implemented yet"))
;; ;;;###autoload
;; (defun +debugger/previous-breakpoint ()
;; (interactive)
;; (user-error "not implemented yet"))
@@ -0,0 +1,34 @@
;; tools/debugger/autoload/evil.el -*- lexical-binding: t; -*-
;;;###if (featurep! :editor evil)
;;;###autoload (autoload '+debugger:start "tools/debugger/autoload/evil" nil t)
(evil-define-command +debugger:start (&optional path)
"Initiate debugger for current major mode"
(interactive "<f>")
;; TODO Add python debugging
(let ((default-directory (doom-project-root)))
(pcase major-mode
((or 'c-mode 'c++-mode)
(realgud:gdb (if path (concat "gdb " path))))
((or 'ruby-mode 'enh-ruby-mode)
;; FIXME
(doom:repl nil (format "run '%s'" (file-name-nondirectory (or path buffer-file-name)))))
('sh-mode
(let ((shell sh-shell))
(when (string= shell "sh")
(setq shell "bash"))
(pcase shell
("bash"
(realgud:bashdb (if path (concat "bashdb " path))))
("zsh"
(realgud:zshdb (if path (concat "zshdb " path))))
(_ (user-error "No shell debugger for %s" shell)))))
((or 'js-mode 'js2-mode 'js3-mode)
(realgud:trepanjs))
('haskell-mode (haskell-debug))
(_ (user-error "No debugger for %s" major-mode)))))
;;;###autoload (autoload '+debugger:toggle-breakpoint "tools/debugger/autoload/evil" nil t)
(evil-define-command +debugger:toggle-breakpoint (&optional bang)
(interactive "<!>")
(call-interactively (if bang #'realgud:cmd-clear #'realgud:cmd-break)))
+112
View File
@@ -0,0 +1,112 @@
;;; tools/debugger/config.el -*- lexical-binding: t; -*-
(defvar +debugger--realgud-alist
'((realgud:zshdb :modes (sh-mode))
(realgud:kshdb :modes (sh-mode))
(realgud:rdebug :modes (ruby-mode enh-ruby-mode))
(realgud:pdb :modes (python-mode))
(realgud:trepan2 :modes (python-mode))
(realgud:gub :modes (go-mode))
(realgud:gdb)
(realgud:trepan :modes (perl-mode perl6-mode))
(realgud:trepanpl :modes (perl-mode perl6-mode))
(realgud:trepanjs :modes (javascript-mode js2-mode js3-mode))
(realgud:remake)
(realgud:trepan3k :modes (python-mode))
(realgud:bashdb :modes (sh-mode))
(realgud:perldb :modes (perl-mode perl6-mode))))
;;
;;; Packages
(use-package! dap-mode
:when (featurep! :tools lsp)
:hook (dap-mode . dap-ui-mode)
:after lsp-mode
:init
(setq dap--breakpoints-file (concat doom-etc-dir "dap-breakpoints"))
:config
(dap-mode 1)
(dolist (module '(((:lang . cc) ccls dap-lldb dap-gdb-lldb)
((:lang . elixir) elixir-mode dap-elixir)
((:lang . go) go-mode dap-go)
((:lang . java) lsp-java dap-java)
((:lang . php) php-mode dap-php)
((:lang . python) python dap-python)
((:lang . ruby) enh-ruby-mode dap-ruby)
((:lang . rust) rust-mode dap-lldb)))
(when (doom-module-p (caar module) (cdar module) '+lsp)
(with-eval-after-load (nth 1 module)
(mapc #'require (cddr module)))))
(when (featurep! :lang javascript +lsp)
(with-eval-after-load 'js2-mode
(require 'dap-node)
(require 'dap-chrome)
(require 'dap-firefox)
(when IS-WINDOWS
(require 'dap-edge)))))
(use-package! realgud
:defer t
:init
(use-package! realgud-trepan-ni
:defer t
:init (add-to-list '+debugger--realgud-alist
'(realgud:trepan-ni :modes (javascript-mode js2-mode js3-mode)
:package realgud-trepan-ni)))
;; Realgud doesn't generate its autoloads properly so we do it ourselves
(dolist (debugger +debugger--realgud-alist)
(autoload (car debugger)
(if-let (sym (plist-get (cdr debugger) :package))
(symbol-name sym)
"realgud")
nil t))
:config
(set-popup-rule! "^\\*\\(?:trepanjs:\\(?:g\\|zsh\\|bash\\)db\\|pdb \\)"
:size 20 :select nil :quit nil)
(defadvice! +debugger--cleanup-after-realgud-a (&optional buf)
"Kill command buffer when debugging session ends (which closes its popup)."
:after #'realgud:terminate
(when (stringp buf)
(setq buf (get-buffer buf)))
(when-let (cmdbuf (realgud-get-cmdbuf buf))
(let (kill-buffer-hook)
(kill-buffer buf))))
;; Monkey-patch `realgud:run-process' to run in a popup.
;; TODO Find a more elegant solution
;; FIXME Causes realgud:cmd-* to focus popup on every invocation
(defadvice! +debugger--realgud-open-in-other-window-a
(debugger-name script-filename cmd-args minibuffer-history-var &optional no-reset)
:override #'realgud:run-process
(let* ((cmd-buf (apply #'realgud-exec-shell debugger-name script-filename
(car cmd-args) no-reset (cdr cmd-args)))
(process (get-buffer-process cmd-buf)))
(cond ((and process (eq 'run (process-status process)))
(pop-to-buffer cmd-buf)
(when (boundp 'evil-emacs-state-local-map)
(define-key evil-emacs-state-local-map (kbd "ESC ESC") #'+debugger/quit))
(realgud:track-set-debugger debugger-name)
(realgud-cmdbuf-info-in-debugger?= 't)
(realgud-cmdbuf-info-cmd-args= cmd-args)
(when cmd-buf
(switch-to-buffer cmd-buf)
(when realgud-cmdbuf-info
(let* ((info realgud-cmdbuf-info)
(cmd-args (realgud-cmdbuf-info-cmd-args info))
(cmd-str (mapconcat #'identity cmd-args " ")))
(if (boundp 'starting-directory)
(realgud-cmdbuf-info-starting-directory= starting-directory))
(set minibuffer-history-var
(cl-remove-duplicates (cons cmd-str minibuffer-history)
:from-end t))))))
(t
(if cmd-buf (switch-to-buffer cmd-buf))
(message "Error running command: %s" (mapconcat #'identity cmd-args " "))))
cmd-buf)))
@@ -0,0 +1,9 @@
;; -*- no-byte-compile: t; -*-
;;; tools/debugger/packages.el
(when (package! realgud)
(when (featurep! :lang javascript)
(package! realgud-trepan-ni)))
(when (featurep! :tools lsp)
(package! dap-mode))
+93
View File
@@ -0,0 +1,93 @@
#+TITLE: tools/direnv
#+DATE: July 10, 2019
#+SINCE: v2.1.0
#+STARTUP: inlineimages
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#hacks][Hacks]]
- [[#prerequisites][Prerequisites]]
- [[#macos][MacOS]]
- [[#arch-linux][Arch Linux]]
- [[#nixos][NixOS]]
- [[#troubleshooting][Troubleshooting]]
- [[#direnv--nix-is-slow][direnv + nix is slow]]
* Description
This module integrates direnv into Emacs.
#+begin_quote
direnv is an environment switcher for the shell. It knows how to hook into bash,
zsh, tcsh, fish shell and elvish to load or unload environment variables
depending on the current directory. This allows project-specific environment
variables without cluttering the ~/.profile file.
Before each prompt, direnv checks for the existence of a ".envrc" file in the
current and parent directories. If the file exists (and is authorized), it is
loaded into a bash sub-shell and all exported variables are then captured by
direnv and then made available to the current shell.
#+end_quote
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/wbolster/emacs-direnv][direnv]]
** Hacks
+ Normally, the direnv environment is updated on ~post-command-hook~. We've
changed it to update on ~doom-switch-buffer-hook~, ~doom-switch-window-hook~
and ~doom-switch-frame-hook~ instead.
+ Special direnv keywords/commands are highlighted in direnv-envrc-mode.
+ A fix has been applied to ensure flycheck searches for executables from within
the direnv environment, if any.
* Prerequisites
This module requires the ~direnv~ utility.
** MacOS
#+BEGIN_SRC bash
brew install direnv
#+END_SRC
** Arch Linux
#+BEGIN_SRC bash
sudo pacman -S direnv
#+END_SRC
** NixOS
#+BEGIN_SRC nix
environment.systemPackages = [ pkgs.direnv ];
#+END_SRC
Or ~nix-env -i direnv~
* Troubleshooting
** direnv + nix is slow
Consider augmenting direnv with [[https://github.com/target/lorri][lorri]], which will cache nix builds and speed up
direnv tremendously.
At the time of writing, the lorri package in nixpkgs simply emits an error
message, telling you to install it manually. You can get around this with:
#+BEGIN_SRC nix
nixpkgs.overlays = [
(self: super: {
lorri =
let src = (super.fetchFromGitHub {
owner = "target";
repo = "lorri";
rev = "38eae3d487526ece9d1b8c9bb0d27fb45cf60816";
sha256 = "11k9lxg9cv6dlxj4haydvw4dhcfyszwvx7jx9p24jadqsy9jmbj4";
});
in import src { inherit src; };
})
];
environment.systemPackages = [ pkgs.lorri ];
#+END_SRC
Otherwise, follow [[https://github.com/target/lorri#installing-lorri][the instructions in lorri's README]] on how to install it
manually.
+69
View File
@@ -0,0 +1,69 @@
;;; tools/direnv/config.el -*- lexical-binding: t; -*-
(defvar +direnv--keywords
'("direnv_layout_dir" "PATH_add" "path_add" "log_status" "log_error" "has"
"join_args" "expand_path" "dotenv" "user_rel_path" "find_up" "source_env"
"watch_file" "source_up" "direnv_load" "MANPATH_add" "load_prefix" "layout"
"use" "rvm" "use_nix" "use_guix")
"TODO")
(use-package! direnv
:after-call after-find-file dired-initial-position-hook
:config
(add-hook! 'direnv-mode-hook
(defun +direnv-init-h ()
"Instead of checking for direnv on `post-command-hook', check only once,
when the file is first opened/major mode is activated. This is significantly
less expensive, but is less sensitive to changes to .envrc done outside of
Emacs."
(direnv--disable)
(when direnv-mode
(add-hook 'after-change-major-mode-hook
#'direnv--maybe-update-environment))))
(defadvice! +direnv--make-process-environment-buffer-local-a (items)
:filter-return #'direnv--export
(when items
(mapc 'kill-local-variable '(process-environment exec-path))
(mapc 'make-local-variable '(process-environment exec-path)))
items)
;; Fontify special .envrc keywords; it's a good indication of whether or not
;; we've typed them correctly.
(add-hook! 'direnv-envrc-mode-hook
(defun +direnv-envrc-fontify-keywords-h ()
(font-lock-add-keywords
nil `((,(regexp-opt +direnv--keywords 'symbols)
(0 font-lock-keyword-face)))))
(defun +direnv-update-on-save-h ()
(add-hook 'after-save-hook #'direnv--maybe-update-environment
nil 'local)))
(defadvice! +direnv-update-a (&rest _)
"Update direnv. Useful to advise functions that may run
environment-sensitive logic like `flycheck-default-executable-find'. This fixes
flycheck issues with direnv and on nix."
:before #'flycheck-default-executable-find
(direnv--maybe-update-environment))
(defadvice! +direnv--fail-gracefully-a (orig-fn)
"Don't try to update direnv if the executable isn't present."
:around #'direnv--maybe-update-environment
(if (executable-find "direnv")
(when (file-readable-p (or buffer-file-name default-directory))
(funcall orig-fn))
(doom-log "Couldn't find direnv executable")))
(defadvice! +direnv-update-async-shell-command-a (command &optional output-buffer _error-buffer)
:before #'shell-command
(when (string-match "[ \t]*&[ \t]*\\'" command)
(let ((environment process-environment)
(path exec-path)
(shell shell-file-name))
(with-current-buffer
(get-buffer-create (or output-buffer "*Async Shell Command*"))
(setq-local process-environment environment)
(setq-local exec-path path)
(setq-local shell-file-name shell)))))
(direnv-mode +1))
+4
View File
@@ -0,0 +1,4 @@
;;; tools/direnv/doctor.el -*- lexical-binding: t; -*-
(unless (executable-find "direnv")
(warn! "Couldn't find direnv executable"))
@@ -0,0 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; tools/direnv/packages.el
(package! direnv)
+135
View File
@@ -0,0 +1,135 @@
#+TITLE: tools/docker
#+DATE: April 30, 2019
#+SINCE: v2.0.9
#+STARTUP: inlineimages
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#docker-control][Docker control]]
- [[#supported-commands][Supported commands]]
- [[#tramp][TRAMP]]
- [[#configuration][Configuration]]
- [[#popups][Popups]]
- [[#other-useful-variables][Other useful variables]]
- [[#completion-in-dockerfiles][Completion in Dockerfiles]]
- [[#troubleshooting][Troubleshooting]]
- [[#tramp-hangs-on-alpine-container][Tramp hangs on Alpine container]]
* Description
This module allows you to manipulate Docker images, containers & more from
Emacs.
Provides a major =dockerfile-mode= to edit =Dockerfiles=. Additional
convenience functions allow images to be built easily.
=docker-tramp.el= offers a [[https://www.gnu.org/software/tramp/][TRAMP]] method for Docker containers.
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/Silex/docker.el][docker]]
+ [[https://github.com/emacs-pe/docker-tramp.el][docker-tramp]]
+ [[https://github.com/spotify/dockerfile-mode][dockerfile-mode]]
* Prerequisites
This module assumes =docker=, =docker-compose= and =docker-machine= binaries
are installed and accessible from your PATH.
* Features
** Docker control
Use =M-x docker=, select a resource, and then mark or unmark items using the
following keybindings (for more marking possibilities, check out
https://github.com/politza/tablist):
| Binding | Description |
|-----------+----------------------|
| =?= | List actions |
| =l= | Configure listing |
| =m= | Mark item |
| =u= | Unmark item |
| =t= | Toggle marks |
| =U= | Unmark all |
| =s= | Sort |
| =* r= | Mark items by regexp |
| =<= | Shrink column |
| =>= | Enlarge column |
| =C-c C-e= | Export to csv |
*** Supported commands
- =docker container=: =attach=, =cp=, =diff=, =inspect=, =kill=, =logs=,
=pause=, =rename=, =restart=, =rm=, =start=, =stop=, =unpause=
- =docker image=: =inspect=, =pull=, =push=, =rm=, =run=, =tag=
- =docker network=: =rm=
- =docker volume=: =rm=
- =docker-machine=: =create=, =env=, =restart=, =rm=, =start=, =stop=
- =docker-compose=: =build=, =config=, =create=, =down=, =exec=, =logs=,
=pull=, =push=, =remove=, =restart=, =run=, =start=, =stop=, =up=
You can also enter =dired= or open a file inside a container or volume.
** TRAMP
Offers the [[https://www.gnu.org/software/tramp/][TRAMP]] method =docker= to access running containers.
#+BEGIN_EXAMPLE
C-x C-f /docker:user@container:/path/to/file
#+END_EXAMPLE
| =user= | the user that you want to use (optional) |
| =container= | the id or name of the container |
* Configuration
** Popups
Thanks to [[https://github.com/magit/magit-popup][magit-popup]], all the popups default arguments can be customized. For
example, here is how to customize the arguments for =docker-image-run-popup=:
#+BEGIN_SRC emacs-lisp
(setq docker-image-run-arguments '("-i" "-t" "--rm"))
#+END_SRC
or inside a =use-package= declaration:
#+BEGIN_SRC emacs-lisp
(use-package! docker
:bind ("C-c d" . docker)
:custom (docker-image-run-arguments '("-i" "-t" "--rm")))
#+END_SRC
You can also customize these using =M-x customize-variable=.
*** Other useful variables
| Variable | Description | Default |
|-------------------------------------+---------------------------------------+------------------|
| =docker-command= | The docker binary to use | =docker= |
| =docker-container-default-sort-key= | Sort key for docker containers | =("Image")= |
| =docker-container-shell-file-name= | Shell to use when entering containers | =/bin/bash= |
| =docker-image-default-sort-key= | Sort key for docker images | =("Repository")= |
| =docker-machine-default-sort-key= | Sort key for docker machines | =("Name")= |
| =docker-network-default-sort-key= | Sort key for docker networks | =("Name")= |
| =docker-run-as-root= | Run docker as root | =nil= |
| =docker-volume-default-sort-key= | Sort key for docker volumes | =("Driver")= |
** Completion in Dockerfiles
By default, the keyword completion function detects the =docker-compose=
version of the current buffer and suggests the appropriate keywords.
You can change the candidates offered by the backend by customizing
=docker-compose-keywords=.
* Troubleshooting
** Tramp hangs on Alpine container
Busyboxes built with the =ENABLE_FEATURE_EDITING_ASK_TERMINAL= config option
also send escape sequences, which =tramp-wait-for-output= doesn't ignore
correctly. This was [[http://git.savannah.gnu.org/cgit/tramp.git/commit/?id=98a511248a9405848ed44de48a565b0b725af82c][fixed in TRAMP upstream]] and is available since TRAMP 2.3.
For older versions of TRAMP you can dump [[https://github.com/emacs-pe/docker-tramp.el/blob/master/docker-tramp-compat.el][docker-tramp-compat.el]] in your
=load-path= somewhere and add the following to your =init.el= to overwrite
=tramp-wait-for-output= with the patch applied:
#+BEGIN_SRC emacs-lisp
(require 'docker-tramp-compat)
#+END_SRC
+10
View File
@@ -0,0 +1,10 @@
;;; tools/docker/config.el -*- lexical-binding: t; -*-
(after! docker
(set-evil-initial-state!
'(docker-container-mode
docker-image-mode
docker-network-mode
docker-volume-mode
docker-machine-mode)
'emacs))
@@ -0,0 +1,6 @@
;; -*- no-byte-compile: t; -*-
;;; tools/docker/packages.el
(package! docker)
(package! docker-tramp)
(package! dockerfile-mode)
@@ -0,0 +1,18 @@
#+TITLE: :tools editorconfig
Editorconfig integration for Doom.
* Table of Contents :TOC:
- [[Module Flags][Module Flags]]
- [[Prerequisites][Prerequisites]]
- [[Configuration][Configuration]]
* Module Flags
This module provides no flags.
* Prerequisites
~editorconfig~ is an optional requirement of this package. The elisp-only
implementation may be sufficient, but has fewer features.
* Configuration
@@ -0,0 +1,7 @@
;;; tools/editorconfig/autoload.el -*- lexical-binding: t; -*-
;;;###autodef
(defun set-editorconfig-indent-var! (mode &rest vars)
"Add (MODE VARS...) to `editorconfig-indentation-alist'."
(after! editorconfig
(nconc editorconfig-indentation-alist (cons mode vars))))
@@ -0,0 +1,55 @@
;;; tools/editorconfig/config.el -*- lexical-binding: t; -*-
;; editorconfig cannot procure the correct settings for extension-less files.
;; Executable scripts with a shebang line, for example. So why not use Emacs'
;; major mode to drop editorconfig a hint? This is accomplished by temporarily
;; appending an extension to `buffer-file-name' when we talk to editorconfig.
(defvar +editorconfig-mode-alist
'((emacs-lisp-mode . "el")
(enh-ruby-mode . "rb")
(js2-mode . "js")
(perl-mode . "pl")
(php-mode . "php")
(python-mode . "py")
(ruby-mode . "rb")
(sh-mode . "sh"))
"An alist mapping major modes to extensions. Used by
`doom--editorconfig-smart-detection-a' to give editorconfig filetype hints.")
;; Handles whitespace (tabs/spaces) settings externally. This way projects can
;; specify their own formatting rules.
(use-package! editorconfig
:after-call doom-switch-buffer-hook after-find-file
:config
(defadvice! +editorconfig--smart-detection-a (orig-fn)
"Retrieve the properties for the current file. If it doesn't have an
extension, try to guess one."
:around #'editorconfig-call-editorconfig-exec
(let ((buffer-file-name
(if (and (not (bound-and-true-p org-src-mode))
(file-name-extension buffer-file-name))
buffer-file-name
(format "%s%s" (buffer-file-name (buffer-base-buffer))
(if-let* ((ext (cdr (assq major-mode +editorconfig-mode-alist))))
(concat "." ext)
"")))))
(funcall orig-fn)))
(add-hook! 'editorconfig-after-apply-functions
(defun +editorconfig-disable-ws-butler-maybe-h (props)
"Disable `ws-butler-mode' if trim_trailing_whitespace is true."
(when (and (equal (gethash 'trim_trailing_whitespace props) "true")
(bound-and-true-p ws-butler-mode))
(ws-butler-mode -1))))
(add-hook! 'editorconfig-after-apply-functions
(defun +editorconfig-disable-indent-detection-h (props)
"Inhibit `dtrt-indent' if an explicit indent_style and indent_size is
specified by editorconfig."
(when (or (gethash 'indent_style props)
(gethash 'indent_size props))
(setq doom-inhibit-indent-detection 'editorconfig))))
;;
(editorconfig-mode +1))
@@ -0,0 +1,5 @@
;; -*- lexical-binding: t; no-byte-compile: t; -*-
;;; tools/editorconfig/doctor.el
(unless (executable-find "editorconfig")
(warn! "Couldn't find the editorconfig binary. Using native elisp version (slower)"))
@@ -0,0 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; tools/editorconfig/packages.el
(package! editorconfig :recipe (:nonrecursive t))
+67
View File
@@ -0,0 +1,67 @@
#+TITLE: tools/ein
#+DATE: April 11, 2018
#+SINCE: v2.0
#+STARTUP: inlineimages
* Table of Contents :TOC:
- [[#description][Description]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#interaction-with-a-jupyter-server][Interaction with a Jupyter server]]
- [[#configuration][Configuration]]
- [[#setting-the-default-location-of-your-notebooks][Setting the default location of your notebooks]]
- [[#using-hydra][Using hydra]]
* Description
Adds Jupyter notebook integration into emacs.
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/millejoh/emacs-ipython-notebook][ein]]
* Prerequisites
This module has no prereqisites.
* Features
** Interaction with a Jupyter server
Three functions are available to start EIN:
1. ~ein:jupyter-server-start~ --- Start a jupyter server within emacs
2. ~ein:notebooklist-login~ --- Login to an existing jupyter server
3. ~ein:notebooklist-open~ --- Open the list of jupyter notebooks
These functions do not have default key bindings.
When ~ein:jupyter-server-start~ is called, after successfully finishing,
~ein:notebooklist-login~ and ~ein:notebooklist-open~ will be automatically
called.
When in the ~Notebook List~ buffer, the key =o= calls ~ace-link~ to speed up the
process of selecting links in the buffer.
If ~company-mode~ is enabled as a module, ~company-ein~ will handle completion.
* Configuration
** Setting the default location of your notebooks
Change ~+ein-notebook-dir~ to tell ein where to find your Jupityr notebooks.
#+BEGIN_SRC emacs-lisp
(setq +ein-notebook-dir "~/my-notebooks")
#+END_SRC
** Using hydra
This module provides a batteries-included hydra - ~+ein/hydra~ - to make using ein
easier. Things like navigating between cells, workbook management etc, are greatly
simplified and are easily accessible. However, by default, it's not bound to any key.
Here's an example of how to bind it:
#+BEGIN_SRC emacs-lisp
(map! :map ein:notebook-mode-map
:localleader
"," #'+ein/hydra/body)
#+END_SRC
@@ -0,0 +1,24 @@
;;; tools/ein/autoload.el -*- lexical-binding: t; -*-
(defun +ein--collect-ein-buffer-links ()
(let ((end (window-end))
points)
(save-excursion
(goto-char (window-start))
(while (re-search-forward "~?/.+\\|\s\\[" end t)
(push (+ (match-beginning 0) 1) points))
(nreverse points))))
;;;###autoload
(defun +ein/ace-link-ein ()
"Ace jump to links in ein notebooklist."
(interactive)
(require 'avy)
(let ((res (avy-with +ein/ace-link-ein
(avy--process
(+ein--collect-ein-buffer-links)
#'avy--overlay-pre))))
;(avy--style-fn avy-style)))))
(when (numberp res)
(goto-char (1+ res))
(widget-button-press (point)))))
@@ -0,0 +1,47 @@
;;; tools/ein/autoload/hydra.el -*- lexical-binding: t; -*-
;;;###if (featurep! :ui hydra)
;;;###autoload (autoload '+ein/hydra/body "tools/ein/autoload/hydra" nil t)
(defhydra +ein/hydra (:hint t :color red)
"
Operations on Cells^^^^^^ Other
----------------------------^^^^^^ ----------------------------------^^^^
[_k_/_j_]^^ select prev/next [_t_]^^ toggle output
[_K_/_J_]^^ move up/down [_C-l_/_C-S-l_] clear/clear all output
[_C-k_/_C-j_]^^ merge above/below [_C-o_]^^ open console
[_O_/_o_]^^ insert above/below [_C-s_/_C-r_] save/rename notebook
[_y_/_p_/_d_] copy/paste [_x_]^^ close notebook
[_u_]^^^^ change type [_q_]^^ quit
[_RET_]^^^^ execute
"
("q" nil :exit t)
("h" ein:notebook-worksheet-open-prev-or-last)
("j" ein:worksheet-goto-next-input)
("k" ein:worksheet-goto-prev-input)
("l" ein:notebook-worksheet-open-next-or-first)
("H" ein:notebook-worksheet-move-prev)
("J" ein:worksheet-move-cell-down)
("K" ein:worksheet-move-cell-up)
("L" ein:notebook-worksheet-move-next)
("t" ein:worksheet-toggle-output)
("d" ein:worksheet-kill-cell)
("R" ein:worksheet-rename-sheet)
("y" ein:worksheet-copy-cell)
("p" ein:worksheet-yank-cell)
("o" ein:worksheet-insert-cell-below)
("O" ein:worksheet-insert-cell-above)
("u" ein:worksheet-change-cell-type)
("RET" ein:worksheet-execute-cell-and-goto-next)
;; Output
("C-l" ein:worksheet-clear-output)
("C-S-l" ein:worksheet-clear-all-output)
;;Console
("C-o" ein:console-open :exit t)
;; Merge and split cells
("C-k" ein:worksheet-merge-cell)
("C-j" spacemacs/ein:worksheet-merge-cell-next)
("s" ein:worksheet-split-cell-at-point)
;; Notebook
("C-s" ein:notebook-save-notebook-command)
("C-r" ein:notebook-rename-command)
("x" ein:notebook-close :exit t))
+43
View File
@@ -0,0 +1,43 @@
;;; tools/ein/config.el -*- lexical-binding: t; -*-
(defvar +ein-notebook-dir "~/"
"Default directory from where Jupyter notebooks are to be opened.")
;;
;; Packages
(after! ein
(setq ein:notebook-modes
'(ein:notebook-multilang-mode
ein:notebook-python-mode
ein:notebook-plain-mode)
;; Slice images into rows; easier to navigate around images
ein:slice-image t)
(set-popup-rules!
'(("\\*ein: .*" :ignore t)
("\\*ein:tb .*" :side 'bottom :size 0.3 :quit t :ttl nil :select nil)
("\\*ein:notebooklist *" :side 'left :size 50 :select nil)))
(when (featurep! :completion company)
;; Code completion with company
(setq ein:completion-backend 'ein:use-company-backend)
(set-company-backend! '(ein:notebook-multilang-mode
ein:notebook-python-mode
ein:notebook-plain-mode)
'ein:company-backend))
(after! ein-jupyter
(setq ein:jupyter-server-args '("--no-browser"))
(unless ein:jupyter-default-notebook-directory
(setq ein:jupyter-default-notebook-directory "~/")))
(defun +ein-buffer-p (buf)
(string-match-p "^\\*ein: .*" (buffer-name buf)))
(add-to-list 'doom-real-buffer-functions #'+ein-buffer-p nil #'eq)
(map! :map ein:notebook-mode-map
"M-s" #'ein:notebook-save-notebook-command
:map ein:notebooklist-mode-map
"o" #'+ein/ace-link-ein))
+5
View File
@@ -0,0 +1,5 @@
;; -*- no-byte-compile: t; -*-
;;; tools/ein/packages.el
(package! ein)
(package! avy)
+125
View File
@@ -0,0 +1,125 @@
#+TITLE: tools/eval
#+DATE: February 13, 2017
#+SINCE: v2.0
#+STARTUP: inlineimages
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#hacks][Hacks]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#inline-code-evaluation][Inline Code Evaluation]]
- [[#repls][REPLs]]
- [[#configuration][Configuration]]
- [[#register-a-repl-for-a-major-mode][Register a REPL for a major-mode]]
- [[#change-how-code-is-evaluated-in-a-major-mode][Change how code is evaluated in a major mode]]
- [[#troubleshooting][Troubleshooting]]
* Description
This modules adds inline code evaluation support to Emacs and a universal
interface for opening and interacting with REPLs.
** Module Flags
+ =+overlay= Enables the use of overlays (near the cursor) to display the result
of inline code evaluation (rather than the minibuffer). That is, unless the
results are too big, in which case it will still fall back to popup buffers.
** Plugins
+ [[https://github.com/syohex/emacs-quickrun][quickrun]]
** Hacks
+ Quickrun has been modified to:
+ Use only one output window, in case of consecutive execution of code.
+ The quickrun window will resize itself to fit its output, once the
underlying process is finished executing the code.
* Prerequisites
This module has no direct prerequisites.
However, many languages will require that you install their interpreters, code
runners and/or repls to power the functionality of this module. Visit the
documentation of their respective =:lang= module for instructions.
* Features
** Inline Code Evaluation
Quickrun can be invoked via:
+ ~M-x +eval/buffer~ (or ~gR~, or ~M-r~)
+ ~M-x +eval/region~
+ ~M-x +eval/region-and-replace~
+ Evil users can use the ~gr~ operator to select and run a region.
** REPLs
Invoked via:
+ =SPC o r= or ~:repl~ will open a REPL in a popup window. =SPC o R= or ~:repl!~
will open a REPL in the current window. If a REPL is already open and a
selection is active, it will be sent to the REPL.
+ ~M-x +eval/open-repl-other-window~ (=SPC o r=)
+ ~M-x +eval/open-repl-same-window~ (=SPC o R=)
+ ~M-x +eval/send-region-to-repl~ (=SPC c s=) while a selection (and REPL) is
active
* Configuration
** Register a REPL for a major-mode
REPLs are defined for most languages Doom supports. Check that language module's
README.org to see if it does (and if it requires additional setup).
To use them, you may use ~M-x +eval/open-repl-other-window~, ~M-x
+eval/open-repl-same-window~, ~:repl~ (for evil users) or the default binding:
=SPC o r=. These will open a REPL in a popup window.
#+begin_quote
You can simply call that mode's REPL command manually. e.g. ~M-x ielm~, but this
will bar you from the benefits of Doom's REPL system (like send-to-repl
functionality).
#+end_quote
Otherwise, you can define your own for a specified major mode:
~(set-repl-handler! MAJOR-MODES FUNCTION)~
MAJOR-MODES is a single major mode symbol or a list of them.
FUNCTION should return a repl buffer. Any window changes in this function are
ignored, then the REPL is opened in a popup window.
#+BEGIN_SRC emacs-lisp
(defun +lua/open-repl ()
(interactive)
(lua-start-process "lua" "lua")
(pop-to-buffer lua-process-buffer))
(set-repl-handler! 'lua-mode #'+lua/open-repl)
#+END_SRC
** Change how code is evaluated in a major mode
Run regions or entire buffers with [[https://github.com/syohex/emacs-quickrun][Quickrun]]. Output is show in a popup window.
Quickrun includes support for many languages, usually by sending text directly
to interpreters or compilers. However, occasionally, you'll find a language
without support (like [[https://crystal-lang.org/][Crystal]]), or a language with better Emacs integration
(like elisp).
Here's how you define a "runner":
#+BEGIN_SRC emacs-lisp
(set-eval-handler! 'crystal-mode
'((:command . "crystal")
(:exec . "%c %s")
(:description . "Run Crystal script")))
#+END_SRC
A simpler version is simply to use the path to the binary:
#+BEGIN_SRC emacs-lisp
(set-eval-handler! 'groovy-mode "groovy")
#+END_SRC
Or if you'd rather run an elisp command:
#+BEGIN_SRC emacs-lisp
(set-eval-handler! 'emacs-lisp-mode #'+emacs-lisp-eval)
#+END_SRC
* TODO Troubleshooting
@@ -0,0 +1,111 @@
;;; tools/eval/autoload/eval.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +eval-display-results-in-popup (output &optional _source-buffer)
"Display OUTPUT in a popup buffer."
(if (with-temp-buffer
(insert output)
(>= (count-lines (point-min) (point-max))
+eval-popup-min-lines))
(let ((output-buffer (get-buffer-create "*doom eval*"))
(origin (selected-window)))
(with-current-buffer output-buffer
(setq-local scroll-margin 0)
(erase-buffer)
(insert output)
(goto-char (point-min))
(if (fboundp '+word-wrap-mode)
(+word-wrap-mode +1)
(visual-line-mode +1)))
(when-let (win (display-buffer output-buffer))
(fit-window-to-buffer
win (/ (frame-height) 2)
nil (/ (frame-width) 2)))
(select-window origin)
output-buffer)
(message "%s" output)))
;;;###autoload
(defun +eval-display-results-in-overlay (output &optional source-buffer)
"Display OUTPUT in a floating overlay next to the cursor."
(require 'eros)
(let ((this-command #'+eval/buffer-or-region)
eros-overlays-use-font-lock)
(with-current-buffer (or source-buffer (current-buffer))
(eros--make-result-overlay output
:where (line-end-position)
:duration eros-eval-result-duration))))
;;;###autoload
(defun +eval-display-results (output &optional source-buffer)
"Display OUTPUT in an overlay or a popup buffer."
(funcall (if (or current-prefix-arg
(with-temp-buffer
(insert output)
(>= (count-lines (point-min) (point-max))
+eval-popup-min-lines))
(not (require 'eros nil t)))
#'+eval-display-results-in-popup
#'+eval-display-results-in-overlay)
output source-buffer)
output)
;;
;;; Commands
;;;###autoload
(defun +eval/buffer ()
"Evaluate the whole buffer."
(interactive)
(if (or (assq major-mode +eval-runners)
(and (fboundp '+eval--ensure-in-repl-buffer)
(ignore-errors
(get-buffer-window (or (+eval--ensure-in-repl-buffer)
t)))))
(+eval/region (point-min) (point-max))
(quickrun)))
;;;###autoload
(defun +eval/region (beg end)
"Evaluate a region between BEG and END and display the output."
(interactive "r")
(let ((load-file-name buffer-file-name))
(if (and (fboundp '+eval--ensure-in-repl-buffer)
(ignore-errors
(get-buffer-window (or (+eval--ensure-in-repl-buffer)
t))))
(+eval/send-region-to-repl beg end)
(if-let (runner (cdr (assq major-mode +eval-runners)))
(funcall runner beg end)
(quickrun-region beg end)))))
;;;###autoload
(defun +eval/line-or-region ()
"Evaluate the current line or selected region."
(interactive)
(if (use-region-p)
(call-interactively #'+eval/region)
(+eval/region (line-beginning-position) (line-end-position))))
;;;###autoload
(defun +eval/buffer-or-region ()
"Evaluate the whole buffer."
(interactive)
(call-interactively
(if (use-region-p)
#'+eval/region
#'+eval/buffer)))
;;;###autoload
(defun +eval/region-and-replace (beg end)
"Evaluation a region between BEG and END, and replace it with the result."
(interactive "r")
(cond ((eq major-mode 'emacs-lisp-mode)
(kill-region beg end)
(condition-case nil
(prin1 (eval (read (current-kill 0)))
(current-buffer))
(error (message "Invalid expression")
(insert (current-kill 0)))))
((quickrun-replace-region beg end))))
@@ -0,0 +1,23 @@
;; tools/eval/autoload/evil.el -*- lexical-binding: t; -*-
;;;###if (featurep! :editor evil)
;;;###autoload (autoload '+eval:region "tools/eval/autoload/evil" nil t)
(evil-define-operator +eval:region (beg end)
"Evaluate selection or sends it to the open REPL, if available."
:move-point nil
(interactive "<r>")
(+eval/region beg end))
;;;###autoload (autoload '+eval:replace-region "tools/eval/autoload/evil" nil t)
(evil-define-operator +eval:replace-region (beg end)
"Evaluate selection and replace it with its result."
:move-point nil
(interactive "<r>")
(+eval/region-and-replace beg end))
;;;###autoload (autoload '+eval:repl "tools/eval/autoload/evil" nil t)
(evil-define-operator +eval:repl (_beg _end)
"Open REPL and send the current selection to it."
:move-point nil
(interactive "<r>")
(+eval/open-repl-other-window))
@@ -0,0 +1,142 @@
;;; tools/eval/autoload/repl.el -*- lexical-binding: t; -*-
(defvar +eval-repl-buffers (make-hash-table :test 'equal)
"The buffer of the last open repl.")
(defvar-local +eval-repl-plist nil)
(define-minor-mode +eval-repl-mode
"A minor mode for REPL buffers.")
(defun +eval--ensure-in-repl-buffer (&optional fn plist displayfn)
(maphash (lambda (key buffer)
(unless (buffer-live-p buffer)
(remhash key +eval-repl-buffers)))
+eval-repl-buffers)
(let* ((project-root (doom-project-root))
(key (cons major-mode project-root))
(buffer (gethash key +eval-repl-buffers)))
(cl-check-type buffer (or buffer null))
(unless (or (eq buffer (current-buffer))
(null fn))
(setq buffer
(funcall (or displayfn #'get-buffer-create)
(if (buffer-live-p buffer)
buffer
(setq buffer
(save-window-excursion
(if (commandp fn)
(call-interactively fn)
(funcall fn))))
(cond ((null buffer)
(error "REPL handler %S couldn't open the REPL buffer" fn))
((not (bufferp buffer))
(error "REPL handler %S failed to return a buffer" fn)))
(with-current-buffer buffer
(when plist
(setq +eval-repl-plist plist))
(+eval-repl-mode +1))
(puthash key buffer +eval-repl-buffers)
buffer))))
(when (bufferp buffer)
(with-current-buffer buffer
(unless (or (derived-mode-p 'term-mode)
(eq (current-local-map) (bound-and-true-p term-raw-map)))
(goto-char (if (and (derived-mode-p 'comint-mode)
(cdr comint-last-prompt))
(cdr comint-last-prompt)
(point-max)))))
buffer)))
(defun +eval-open-repl (prompt-p &optional displayfn)
(cl-destructuring-bind (_mode fn . plist)
(or (assq major-mode +eval-repls)
(list))
(when (or (not fn) prompt-p)
(let* ((choices (or (cl-loop for sym being the symbols
for sym-name = (symbol-name sym)
if (string-match "^\\(?:\\+\\)?\\([^/]+\\)/open-\\(?:\\(.+\\)-\\)?repl$" sym-name)
collect
(format "%s (%s)"
(match-string-no-properties 1 sym-name)
(or (match-string-no-properties 2 sym-name) "default")))
(user-error "There are no known available REPLs")))
(choice (or (completing-read "Open a REPL for: " choices)
(user-error "Aborting")))
(choice-split (split-string choice " " t))
(module (car choice-split))
(repl (substring (cadr choice-split) 1 -1)))
(setq fn
(intern-soft
(format "+%s/open-%srepl" module
(if (string= repl "default")
""
repl))))))
(let ((region (if (use-region-p)
(buffer-substring-no-properties (region-beginning)
(region-end)))))
(unless (commandp fn)
(error "Couldn't find a valid REPL for %s" major-mode))
(with-current-buffer (+eval--ensure-in-repl-buffer fn plist displayfn)
(when (bound-and-true-p evil-mode)
(call-interactively #'evil-append-line))
(when region
(insert region))
t))))
;;
;;; Commands
;;;###autoload
(defun +eval/open-repl-same-window (&optional arg)
"Opens (or reopens) the REPL associated with the current major-mode and place
the cursor at the prompt.
If ARG (universal argument), prompt for a specific REPL to open."
(interactive "P")
(+eval-open-repl arg #'switch-to-buffer))
;;;###autoload
(defun +eval/open-repl-other-window (&optional arg)
"Does `+eval/open-repl', but in a popup window.
If ARG (universal argument), prompt for a specific REPL to open."
(interactive "P")
(+eval-open-repl arg #'pop-to-buffer))
;;;###autoload
(defun +eval/send-region-to-repl (beg end &optional inhibit-auto-execute-p)
"Execute the selected region in the REPL.
Opens a REPL if one isn't already open. If AUTO-EXECUTE-P, then execute it
immediately after."
(interactive "rP")
(let ((selection (buffer-substring-no-properties beg end))
(buffer (+eval--ensure-in-repl-buffer)))
(unless buffer
(error "No REPL open"))
(let ((origin-window (selected-window))
(selection
(with-temp-buffer
(insert selection)
(goto-char (point-min))
(when (> (skip-chars-forward "\n") 0)
(delete-region (point-min) (point)))
(indent-rigidly (point) (point-max)
(- (skip-chars-forward " \t")))
(concat (string-trim-right (buffer-string))
"\n"))))
(with-selected-window (get-buffer-window buffer)
(with-current-buffer buffer
(dolist (line (split-string selection "\n"))
(insert line)
(if inhibit-auto-execute-p
(insert "\n")
;; `comint-send-input' isn't enough because some REPLs may not use
;; comint, so just emulate the keypress.
(execute-kbd-macro (kbd "RET")))
(sit-for 0.001)
(redisplay 'force)))
(when (and (eq origin-window (selected-window))
(bound-and-true-p evil-local-mode))
(call-interactively #'evil-append-line))))))
@@ -0,0 +1,66 @@
;;; tools/eval/autoload/settings.el -*- lexical-binding: t; -*-
;;
;; REPLs
(defvar +eval-repls nil
"An alist mapping major modes to plists that describe REPLs. Used by
`+eval/open-repl-other-window' and filled with the `:repl' setting.")
;;;###autodef
(defun set-repl-handler! (modes command &rest plist)
"Defines a REPL for MODES.
MODES is either a single major mode symbol or a list of them. COMMAND is a
function that creates and returns the REPL buffer.
COMMAND can either be a function that takes no arguments, or an interactive
command that will be called interactively. COMMANDS must return either the repl
buffer or a function that takes no arguments and returns the repl buffer.
PLIST is a property list that map special attributes to this repl. These are
recognized:
:persist BOOL
If non-nil, this REPL won't be killed when its window is closed."
(declare (indent defun))
(dolist (mode (doom-enlist modes))
(setf (alist-get mode +eval-repls)
(cons command plist))))
;;
;; Evaluation
;;;###autoload
(defvar +eval-runners nil
"Alist mapping major modes to interactive runner functions.")
;;;###autodef
(defun set-eval-handler! (mode command)
"Define a code evaluator for major mode MODE with `quickrun'.
1. If MODE is a string and COMMAND is the string, MODE is a file regexp and
COMMAND is a string key for an entry in `quickrun-file-alist'.
2. If MODE is not a string and COMMAND is a string, MODE is a major-mode symbol
and COMMAND is a key (for `quickrun--language-alist'), and will be registered
in `quickrun--major-mode-alist'.
3. If MODE is not a string and COMMAND is an alist, see `quickrun-add-command':
(quickrun-add-command MODE COMMAND :mode MODE).
4. If MODE is not a string and COMMANd is a symbol, add it to
`+eval-runners', which is used by `+eval/region'."
(declare (indent defun))
(cond ((symbolp command)
(push (cons mode command) +eval-runners))
((stringp command)
(after! quickrun
(push (cons mode command)
(if (stringp mode)
quickrun-file-alist
quickrun--major-mode-alist))))
((listp command)
(after! quickrun
(quickrun-add-command
(or (cdr (assq mode quickrun--major-mode-alist))
(string-remove-suffix "-mode" (symbol-name mode)))
command :mode mode)))))
+92
View File
@@ -0,0 +1,92 @@
;;; tools/eval/config.el -*- lexical-binding: t; -*-
(defvar +eval-popup-min-lines 4
"The output height threshold (inclusive) before output is displayed in a popup
buffer rather than an overlay on the line at point or the minibuffer.")
;; remove ellipsis when printing sexps in message buffer
(setq eval-expression-print-length nil
eval-expression-print-level nil)
;;
;;; Packages
(set-popup-rule!
(lambda (bufname _)
(when (boundp '+eval-repl-mode)
(buffer-local-value '+eval-repl-mode (get-buffer bufname))))
:ttl (lambda (buf)
(unless (plist-get +eval-repl-plist :persist)
(when-let (process (get-buffer-process buf))
(set-process-query-on-exit-flag process nil)
(kill-process process)
(kill-buffer buf))))
:size 0.25 :quit nil)
(after! quickrun
(setq quickrun-focus-p nil)
(set-popup-rule! "^\\*quickrun" :size 0.3 :ttl 0)
(defadvice! +eval--quickrun-fix-evil-visual-region-a ()
"Make `quickrun-replace-region' recognize evil visual selections."
:override #'quickrun--outputter-replace-region
(let ((output (buffer-substring-no-properties (point-min) (point-max))))
(with-current-buffer quickrun--original-buffer
(cl-destructuring-bind (beg . end)
;; Because `deactivate-mark', the function, was used in
;; `quickrun--region-command-common' instead of `deactivate-mark',
;; the variable, the selection is disabled by this point.
(if (bound-and-true-p evil-local-mode)
(cons evil-visual-beginning evil-visual-end)
(cons (region-beginning) (region-end)))
(delete-region beg end)
(insert output))
(setq quickrun-option-outputter quickrun--original-outputter))))
(defadvice! +eval--quickrun-auto-close-a (&rest _)
"Silently re-create the quickrun popup when re-evaluating."
:before '(quickrun quickrun-region)
(when-let (win (get-buffer-window quickrun--buffer-name))
(let ((inhibit-message t))
(quickrun--kill-running-process)
(message ""))
(delete-window win)))
(add-hook! 'quickrun-after-run-hook
(defun +eval-quickrun-shrink-window-h ()
"Shrink the quickrun output window once code evaluation is complete."
(when-let (win (get-buffer-window quickrun--buffer-name))
(with-selected-window (get-buffer-window quickrun--buffer-name)
(let ((ignore-window-parameters t))
(shrink-window-if-larger-than-buffer)))))
(defun +eval-quickrun-scroll-to-bof-h ()
"Ensures window is scrolled to BOF on invocation."
(when-let (win (get-buffer-window quickrun--buffer-name))
(with-selected-window win
(goto-char (point-min))))))
;; Display evaluation results in an overlay next to the cursor. If the output
;; is more than 4 lines long, it is displayed in a popup.
(when (featurep! +overlay)
(defadvice! +eval--inhibit-quickrun-popup-a (buf cb)
:override #'quickrun--pop-to-buffer
(setq quickrun--original-buffer (current-buffer))
(with-current-buffer buf
(setq quickrun-option-outputter #'ignore)
(funcall cb)))
(advice-add #'quickrun--recenter :override #'ignore)
(add-hook! 'quickrun-after-run-hook
(defun +eval-display-in-popup-overlay-h ()
(+eval-display-results
(with-current-buffer quickrun--buffer-name
(string-trim (buffer-string)))
quickrun--original-buffer)))))
(use-package! eros
:when (featurep! +overlay)
:hook (emacs-lisp-mode . eros-mode))
+6
View File
@@ -0,0 +1,6 @@
;; -*- no-byte-compile: t; -*-
;;; tools/eval/packages.el
(package! quickrun)
(when (featurep! +overlay)
(package! eros))
@@ -0,0 +1,13 @@
;;; tools/flycheck/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +flycheck-init-popups-h ()
"Activate `flycheck-posframe-mode' if available and in GUI Emacs.
Activate `flycheck-popup-tip-mode' otherwise.
Do nothing if `lsp-ui-mode' is active and `lsp-ui-sideline-enable' is non-nil."
(unless (and (bound-and-true-p lsp-ui-mode)
lsp-ui-sideline-enable)
(if (and (fboundp 'flycheck-posframe-mode)
(display-graphic-p))
(flycheck-posframe-mode +1)
(flycheck-popup-tip-mode +1))))
+80
View File
@@ -0,0 +1,80 @@
;;; tools/flycheck/config.el -*- lexical-binding: t; -*-
(defvar +flycheck-lazy-idle-delay 3.0
"The delay before flycheck checks the buffer, after a check that produces no
errors.")
;;
;;; Packages
(use-package! flycheck
:commands flycheck-list-errors flycheck-buffer
:after-call doom-switch-buffer-hook after-find-file
:config
(setq flycheck-emacs-lisp-load-path 'inherit)
;; Check only when saving or opening files. Newline & idle checks are a mote
;; excessive, especially when that can easily catch code in an incomplete
;; state, so we removed them.
(setq flycheck-check-syntax-automatically '(save mode-enabled))
;; Display errors a little quicker (default is 0.9s)
(setq flycheck-display-errors-delay 0.25)
;; Don't commandeer input focus if the error message pops up (happens when
;; tooltips and childframes are disabled).
(set-popup-rule! "^\\*Flycheck error messages\\*" :select nil)
(add-hook! 'doom-escape-hook :append
(defun +flycheck-buffer-h ()
"Flycheck buffer on ESC in normal mode."
(when flycheck-mode
(ignore-errors (flycheck-buffer))
nil)))
(map! :map flycheck-error-list-mode-map
:n "C-n" #'flycheck-error-list-next-error
:n "C-p" #'flycheck-error-list-previous-error
:n "j" #'flycheck-error-list-next-error
:n "k" #'flycheck-error-list-previous-error
:n "RET" #'flycheck-error-list-goto-error
:n [return] #'flycheck-error-list-goto-error)
(global-flycheck-mode +1))
(use-package! flycheck-popup-tip
:commands flycheck-popup-tip-show-popup flycheck-popup-tip-delete-popup
:init (add-hook 'flycheck-mode-hook #'+flycheck-init-popups-h)
:config
(setq flycheck-popup-tip-error-prefix "")
(after! evil
;; Don't display popups while in insert or replace mode, as it can affect
;; the cursor's position or cause disruptive input delays.
(add-hook! '(evil-insert-state-entry-hook evil-replace-state-entry-hook)
#'flycheck-popup-tip-delete-popup)
(defadvice! +flycheck--disable-popup-tip-maybe-a (&rest _)
:before-while #'flycheck-popup-tip-show-popup
(if evil-local-mode
(eq evil-state 'normal)
(not (bound-and-true-p company-backend))))))
(use-package! flycheck-posframe
:when (featurep! +childframe)
:defer t
:init (add-hook 'flycheck-mode-hook #'+flycheck-init-popups-h)
:config
(setq flycheck-posframe-warning-prefix ""
flycheck-posframe-info-prefix "··· "
flycheck-posframe-error-prefix "")
(after! company
;; Don't display popups if company is open
(add-hook 'flycheck-posframe-inhibit-functions #'company--active-p))
(after! evil
;; Don't display popups while in insert or replace mode, as it can affect
;; the cursor's position or cause disruptive input delays.
(add-hook! 'flycheck-posframe-inhibit-functions
#'evil-insert-state-p
#'evil-replace-state-p)))
@@ -0,0 +1,7 @@
;; -*- no-byte-compile: t; -*-
;;; tools/flycheck/packages.el
(package! flycheck)
(package! flycheck-popup-tip)
(when (featurep! +childframe)
(package! flycheck-posframe))
@@ -0,0 +1,27 @@
;;; tools/flyspell/autoload.el -*- lexical-binding: t; -*-
;;;###autodef
(defalias 'flyspell-mode! #'flyspell-mode)
(defvar +flyspell--predicate-alist nil
"TODO")
;;;###autodef
(defun set-flyspell-predicate! (modes predicate)
"TODO"
(declare (indent defun))
(dolist (mode (doom-enlist modes) +flyspell--predicate-alist)
(add-to-list '+flyspell--predicate-alist (cons mode predicate))))
;;;###autoload
(defun +flyspell-init-predicate-h ()
"TODO"
(when-let (pred (assq major-mode +flyspell--predicate-alist))
(setq-local flyspell-generic-check-word-predicate (cdr pred))))
;;;###autoload
(defun +flyspell-correction-at-point-p (&optional point)
"TODO"
(cl-loop for ov in (overlays-at (or point (point)))
if (overlay-get ov 'flyspell-overlay)
return t))
+84
View File
@@ -0,0 +1,84 @@
;;; tools/flyspell/config.el -*- lexical-binding: t; -*-
;;
;;; Packages
(after! ispell
(add-to-list 'ispell-extra-args "--dont-tex-check-comments")
;; Don't spellcheck org blocks
(pushnew! ispell-skip-region-alist
'(":\\(PROPERTIES\\|LOGBOOK\\):" . ":END:")
'("#\\+BEGIN_SRC" . "#\\+END_SRC")
'("#\\+BEGIN_EXAMPLE" . "#\\+END_EXAMPLE"))
;; Enable either aspell or hunspell.
;; If no module flags are given, enable either aspell or hunspell if their
;; binary is found.
;; If one of the flags `+aspell' or `+hunspell' is given, only enable that
;; spell checker.
(pcase (cond ((featurep! +aspell) 'aspell)
((featurep! +hunspell) 'hunspell)
((executable-find "aspell") 'aspell)
((executable-find "hunspell") 'hunspell))
(`aspell
(setq ispell-program-name "aspell"
ispell-extra-args '("--sug-mode=ultra" "--run-together"))
(add-hook! 'text-mode-hook
(defun +flyspell-remove-run-together-switch-for-aspell-h ()
(setq-local ispell-extra-args (remove "--run-together" ispell-extra-args))))
(defun +flyspell-setup-ispell-extra-args-a (orig-fun &rest args)
:around '(ispell-word flyspell-auto-correct-word)
(let ((ispell-extra-args (remove "--run-together" ispell-extra-args)))
(ispell-kill-ispell t)
(apply orig-fun args)
(ispell-kill-ispell t))))
(`hunspell
(setq ispell-program-name "hunspell"))
(_ (doom-log "Spell checker not found. Either install `aspell' or `hunspell'"))))
;;;###package flyspell
(progn ; built-in
(setq flyspell-issue-welcome-flag nil
;; Significantly speeds up flyspell, which would otherwise print
;; messages for every word when checking the entire buffer
flyspell-issue-message-flag nil)
(add-hook 'text-mode-hook #'flyspell-mode)
(when (featurep! +prog)
(add-hook 'prog-mode-hook #'flyspell-prog-mode))
(add-hook! 'flyspell-mode-hook
(defun +flyspell-inhibit-duplicate-detection-maybe-h ()
"Don't mark duplicates when style/grammar linters are present.
e.g. proselint and langtool."
(when (or (and (bound-and-true-p flycheck-mode)
(executable-find "proselint"))
(featurep 'langtool))
(setq-local flyspell-mark-duplications-flag nil))))
;; Ensure mode-local predicates declared with `set-flyspell-predicate!' are
;; used in their respective major modes.
(add-hook 'flyspell-mode-hook #'+flyspell-init-predicate-h)
(map! :map flyspell-mouse-map
"RET" #'flyspell-correct-word-generic
[return] #'flyspell-correct-word-generic
[mouse-1] #'flyspell-correct-word-generic))
(use-package! flyspell-correct
:commands flyspell-correct-word-generic flyspell-correct-previous-word-generic
:config
(cond ((and (featurep! :completion helm)
(require 'flyspell-correct-helm nil t)))
((and (featurep! :completion ivy)
(require 'flyspell-correct-ivy nil t)))
((require 'flyspell-correct-popup nil t)
(setq flyspell-popup-correct-delay 0.8)
(define-key popup-menu-keymap [escape] #'keyboard-quit))))
@@ -0,0 +1,4 @@
(unless (or (executable-find "aspell")
(executable-find "hunspell"))
(warn! "Could not find aspell or hunspell. Flyspell will fall back to ispell, which may not work."))
@@ -0,0 +1,9 @@
;; -*- no-byte-compile: t; -*-
;;; tools/flyspell/packages.el
(package! flyspell-correct)
(cond ((featurep! :completion ivy)
(package! flyspell-correct-ivy))
((featurep! :completion helm)
(package! flyspell-correct-helm))
((package! flyspell-correct-popup)))
@@ -0,0 +1,19 @@
;;; tools/gist/autoload/evil.el -*- lexical-binding: t; -*-
;;;###if (featurep! :editor evil)
;;;###autoload (autoload '+gist:send "tools/gist/autoload/evil" nil t)
(evil-define-operator +gist:send (bang)
"Create a private gist from the buffer. If BANG then make it public."
:type inclusive :repeat nil
(interactive "<!>")
(if bang
(gist-region-or-buffer)
(gist-region-or-buffer-private)))
;;;###autoload (autoload '+gist:list "tools/gist/autoload/evil" nil t)
(evil-define-command +gist:list (&optional username)
"Pop up a listing of gists."
(interactive "<a>")
(if username
(gist-list-user username)
(gist-list)))
+28
View File
@@ -0,0 +1,28 @@
;;; tools/gist/config.el -*- lexical-binding: t; -*-
;; NOTE On occasion, the cache gets corrupted, causing wrong-type-argument
;; errors. If that happens, try `+gist/kill-cache'. You may have to restart
;; Emacs.
(after! gist
(set-evil-initial-state! 'gist-list-mode 'normal)
(set-popup-rule! "^\\*gist-" :ignore t)
(defadvice! +gist--open-in-popup-a (orig-fn &rest args)
:around #'gist-list-render
(funcall orig-fn (car args) t)
(unless (cadr args)
(pop-to-buffer (current-buffer))))
(map! :map gist-list-menu-mode-map
:n "go" #'gist-browse-current-url
:n "gr" #'gist-list-reload
:n "c" #'gist-add-buffer
:n "d" #'gist-kill-current
:n "e" #'gist-edit-current-description
:n "f" #'gist-fork
:n "q" #'kill-current-buffer
:n "s" #'gist-star
:n "S" #'gist-unstar
:n "y" #'gist-print-current-url))
+4
View File
@@ -0,0 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; tools/gist/packages.el
(package! gist)
+197
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~
@@ -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))
@@ -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)))
@@ -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))))))
@@ -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)))
+167
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)))
+25
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)))
+73
View File
@@ -0,0 +1,73 @@
#+TITLE: tools/lsp
#+DATE: March 05, 2019
#+SINCE: v2.1
#+STARTUP: inlineimages
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#configuration][Configuration]]
- [[#troubleshooting][Troubleshooting]]
* Description
This module integrates [[https://langserver.org/][language servers]] into Doom Emacs. They provide features
you'd expect from IDEs, like code completion, realtime linting, language-aware
imenu/xref integration, jump-to-definition/references support, and more.
To get LSP working, you'll need to do three things:
1. Enable this module,
2. Install a language server appropriate for your targeted language(s) (you'll
find a table mapping languages to available servers [[https://github.com/emacs-lsp/lsp-mode#supported-languages][in the lsp-mode project
README]]).
3. Enable the =+lsp= flag on the =:lang= modules you want to enable LSP support
for. If your language's module doesn't have LSP support, and you know it can
(or should), please let us know! In the meantime, you must configure it
yourself (described in the Configuration section).
As of this writing, this is the state of LSP support in Doom Emacs:
| Module | Major modes | Default language server |
|------------------+---------------------------------------------------------+---------------------------------------------------------------|
| [[../../lang/cc/README.org][:lang cc]] | c-mode, c++-mode, objc-mode | ccls |
| [[../../lang/csharp/README.org][:lang csharp]] | csharp-mode | omnisharp |
| [[../../lang/elixir/README.org][:lang elixir]] | elixir-mode | elixir-ls |
| [[../../lang/fsharp/README.org][:lang fsharp]] | fsharp-mode | Mono, .NET core |
| [[../../lang/go/README.org][:lang go]] | go-mode | go-langserver |
| [[../../lang/haskell/README.org][:lang haskell]] | haskell-mode | haskell-ide-engine |
| [[../../lang/java/README.org][:lang java]] | java-mode | lsp-java |
| [[../../lang/javascript/README.org][:lang javascript]] | js2-mode, rjsx-mode, typescript-mode | typescript-language-server |
| [[../../lang/ocaml/README.org][:lang ocaml]] | taureg-mode | ocaml-language-server |
| [[../../lang/php/README.org][:lang php]] | php-mode | php-language-server |
| [[../../lang/python/README.org][:lang python]] | python-mode | lsp-python-ms |
| [[../../lang/ruby/README.org][:lang ruby]] | ruby-mode, enh-ruby-mode | solargraph |
| [[../../lang/rust/README.org][:lang rust]] | rust-mode | rls |
| [[../../lang/scala/README.org][:lang scala]] | scala-mode | metals |
| [[../../lang/sh/README.org][:lang sh]] | sh-mode | bash-language-server |
| [[../../lang/swift/README.org][:lang swift]] | swift-mode | sourcekit |
| [[../../lang/web/README.org][:lang web]] | web-mode, css-mode, scss-mode, sass-mode, less-css-mode | vscode-css-languageserver-bin, vscode-html-languageserver-bin |
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/emacs-lsp/lsp-mode][lsp-mode]]
+ [[https://github.com/emacs-lsp/lsp-ui][lsp-ui]]
+ [[https://github.com/tigersoldier/company-lsp][company-lsp]]*
* Prerequisites
This module has no direct prerequisites, but major-modes require you to install
language servers.
You'll find a table that lists available language servers and how to install
them [[https://github.com/emacs-lsp/lsp-mode#supported-languages][in the lsp-mode project README]]. The documentation of the module for your
targeted language will contain brief instructions as well.
* TODO Features
* TODO Configuration
* TODO Troubleshooting
+4
View File
@@ -0,0 +1,4 @@
;;; feature/lsp/autoload.el -*- lexical-binding: t; -*-
;;;###autodef
(defalias 'lsp! #'lsp-deferred)
+150
View File
@@ -0,0 +1,150 @@
;;; tools/lsp/config.el -*- lexical-binding: t; -*-
(defvar +lsp-company-backend 'company-lsp
"What backend to prepend to `company-backends' when `lsp-mode' is active.
This can be a single company backend or a list thereof. It can be anything
`company-backends' will accept.")
;;
;;; Packages
(use-package! lsp-mode
:defer t
:init
(setq lsp-session-file (concat doom-etc-dir "lsp-session"))
;; Don't prompt the user for the project root every time we open a new
;; lsp-worthy file, instead, try to guess it with projectile.
(setq lsp-auto-guess-root t)
;; Auto-kill LSP server once you've killed the last buffer associated with its
;; project.
(setq lsp-keep-workspace-alive nil)
;; For `lsp-clients'
(setq lsp-fsharp-server-install-dir (concat doom-etc-dir "lsp-fsharp/")
lsp-groovy-server-install-dir (concat doom-etc-dir "lsp-groovy/")
lsp-intelephense-storage-path (concat doom-cache-dir "lsp-intelephense/"))
:config
(set-lookup-handlers! 'lsp-mode :async t
:documentation 'lsp-describe-thing-at-point
:definition 'lsp-find-definition
:references 'lsp-find-references)
(defvar +lsp--deferred-shutdown-timer nil)
(defadvice! +lsp-defer-server-shutdown-a (orig-fn)
"Defer server shutdown for a few seconds.
This gives the user a chance to open other project files before the server is
auto-killed (which is usually an expensive process)."
:around #'lsp--shutdown-workspace
(if (or lsp-keep-workspace-alive
(eq (lsp--workspace-shutdown-action lsp--cur-workspace)
'restart))
(funcall orig-fn)
(when (timerp +lsp--deferred-shutdown-timer)
(cancel-timer +lsp--deferred-shutdown-timer))
(setq +lsp--deferred-shutdown-timer
(run-at-time
3 nil (lambda (workspace)
(let ((lsp--cur-workspace workspace))
(unless (lsp--workspace-buffers lsp--cur-workspace)
(funcall orig-fn))))
lsp--cur-workspace))))
(defadvice! +lsp-prompt-if-no-project-a (session file-name)
"Prompt for the project root only if no project was found."
:after-until #'lsp--calculate-root
(cond ((not lsp-auto-guess-root)
nil)
((cl-find-if (lambda (dir)
(and (lsp--files-same-host dir file-name)
(file-in-directory-p file-name dir)))
(lsp-session-folders-blacklist session))
nil)
((lsp--find-root-interactively session))))
(defadvice! +lsp-init-a (&optional arg)
"Enable `lsp-mode' in the current buffer.
Meant to be a lighter alternative to `lsp', which is too eager about
initializing lsp-ui-mode, company, yasnippet and flycheck. Instead, these have
been moved out to their respective modules, or these hooks:
+ `+lsp-init-company-h' (on `lsp-mode-hook')
+ `+lsp-init-ui-flycheck-or-flymake-h' (on `lsp-ui-mode-hook')
Also logs the resolved project root, if found."
:override #'lsp
(interactive "P")
(require 'lsp-mode)
(when lsp-auto-configure
(require 'lsp-clients))
(and (buffer-file-name)
(setq-local
lsp--buffer-workspaces
(or (lsp--try-open-in-library-workspace)
(lsp--try-project-root-workspaces
(equal arg '(4))
(and arg (not (equal arg 1))))))
(prog1 (lsp-mode 1)
;; Announce what project root we're using, for diagnostic purposes
(if-let (root (lsp--calculate-root (lsp-session) (buffer-file-name)))
(lsp--info "Guessed project root is %s" (abbreviate-file-name root))
(lsp--info "Could not guess project root."))
(lsp--info "Connected to %s."
(apply #'concat
(mapcar
(lambda (it) (format "[%s]" (lsp--workspace-print it)))
lsp--buffer-workspaces))))))
;; Don't prompt to restart LSP servers while quitting Emacs
(add-hook! 'kill-emacs-hook (setq lsp-restart 'ignore)))
(use-package! lsp-ui
:hook (lsp-mode . lsp-ui-mode)
:init
(add-hook! 'lsp-ui-mode-hook
(defun +lsp-init-ui-flycheck-or-flymake-h ()
"Sets up flymake-mode or flycheck-mode, depending on `lsp-prefer-flymake'."
(cond ((eq :none lsp-prefer-flymake))
(lsp-prefer-flymake
(lsp--flymake-setup))
((require 'flycheck nil t)
(require 'lsp-ui-flycheck)
(lsp-ui-flycheck-enable t)))))
:config
(setq lsp-prefer-flymake nil
lsp-ui-doc-max-height 8
lsp-ui-doc-max-width 35
lsp-ui-sideline-ignore-duplicate t
;; lsp-ui-doc is redundant with and more invasive than
;; `+lookup/documentation'
lsp-ui-doc-enable nil
;; Don't show symbol definitions in the sideline. They are pretty noisy,
;; and there is a bug preventing Flycheck errors from being shown (the
;; errors flash briefly and then disappear).
lsp-ui-sideline-show-hover nil)
(set-lookup-handlers! 'lsp-ui-mode :async t
:definition 'lsp-ui-peek-find-definitions
:references 'lsp-ui-peek-find-references))
(use-package! company-lsp
:when (featurep! :completion company)
:defer t
:init
;; Make sure that `company-capf' is disabled since it is incompatible with
;; `company-lsp' (see lsp-mode#884)
(add-hook! 'lsp-mode-hook
(defun +lsp-init-company-h ()
(if (not (bound-and-true-p company-mode))
(add-hook 'company-mode-hook #'+lsp-init-company-h t t)
(setq-local company-backends
(cons +lsp-company-backend
(remq 'company-capf company-backends)))
(remove-hook 'company-mode-hook #'+lsp-init-company-h t))))
:config
(setq company-lsp-cache-candidates 'auto)) ;; cache candidates for better performance
+7
View File
@@ -0,0 +1,7 @@
;; -*- no-byte-compile: t; -*-
;;; tools/lsp/packages.el
(package! lsp-mode)
(package! lsp-ui)
(when (featurep! :completion company)
(package! company-lsp))
+51
View File
@@ -0,0 +1,51 @@
;;; tools/macos/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(setq locate-command "mdfind")
;;;###autoload
(defun +macos-open-with (&optional app-name path)
"Send PATH to APP-NAME on OSX."
(interactive)
(let* ((path (expand-file-name
(replace-regexp-in-string
"'" "\\'"
(or path (if (derived-mode-p 'dired-mode)
(dired-get-file-for-visit)
(buffer-file-name)))
nil t)))
(command (format "open %s"
(if app-name
(format "-a %s '%s'" (shell-quote-argument app-name) path)
(format "'%s'" path)))))
(message "Running: %s" command)
(shell-command command)))
;;;###autoload
(defmacro +macos--open-with (id &optional app dir)
`(defun ,(intern (format "+macos/%s" id)) ()
(interactive)
(+macos-open-with ,app ,dir)))
;;;###autoload (autoload '+macos/open-in-default-program "tools/macos/autoload" nil t)
(+macos--open-with open-in-default-program)
;;;###autoload (autoload '+macos/reveal-in-finder "tools/macos/autoload" nil t)
(+macos--open-with reveal-in-finder "Finder" default-directory)
;;;###autoload (autoload '+macos/reveal-project-in-finder "tools/macos/autoload" nil t)
(+macos--open-with reveal-project-in-finder "Finder"
(or (doom-project-root) default-directory))
;;;###autoload (autoload '+macos/send-to-transmit "tools/macos/autoload" nil t)
(+macos--open-with send-to-transmit "Transmit")
;;;###autoload (autoload '+macos/send-cwd-to-transmit "tools/macos/autoload" nil t)
(+macos--open-with send-cwd-to-transmit "Transmit" default-directory)
;;;###autoload (autoload '+macos/send-to-launchbar "tools/macos/autoload" nil t)
(+macos--open-with send-to-launchbar "LaunchBar")
;;;###autoload (autoload '+macos/send-project-to-launchbar "tools/macos/autoload" nil t)
(+macos--open-with send-project-to-launchbar "LaunchBar"
(or (doom-project-root) default-directory))
+137
View File
@@ -0,0 +1,137 @@
;;; tools/magit/autoload.el -*- lexical-binding: t; -*-
;; HACK Magit complains loudly when it can't determine its own version, which is
;; the case when magit is built through straight. The warning is harmless,
;; however, so we just need it to shut up.
;;;###autoload
(advice-add #'magit-version :override #'ignore)
;;;###autoload
(defun +magit-display-buffer-fn (buffer)
"Same as `magit-display-buffer-traditional', except...
- If opened from a commit window, it will open below it.
- Magit process windows are always opened in small windows below the current.
- Everything else will reuse the same window."
(let ((buffer-mode (buffer-local-value 'major-mode buffer)))
(display-buffer
buffer (cond
((and (eq buffer-mode 'magit-status-mode)
(get-buffer-window buffer))
'(display-buffer-reuse-window))
;; Any magit buffers opened from a commit window should open below
;; it. Also open magit process windows below.
((or (bound-and-true-p git-commit-mode)
(eq buffer-mode 'magit-process-mode))
(let ((size (if (eq buffer-mode 'magit-process-mode)
0.35
0.7)))
`(display-buffer-below-selected
. ((window-height . ,(truncate (* (window-height) size)))))))
;; Everything else should reuse the current window.
((or (not (derived-mode-p 'magit-mode))
(not (memq (with-current-buffer buffer major-mode)
'(magit-process-mode
magit-revision-mode
magit-diff-mode
magit-stash-mode
magit-status-mode))))
'(display-buffer-same-window))))))
;;
;; Commands
(defun +magit--refresh-vc-in-buffer (buffer)
(with-current-buffer buffer
(when (and vc-mode (fboundp 'vc-refresh-state))
(vc-refresh-state))
(when (and (bound-and-true-p git-gutter-mode)
(fboundp '+version-control|update-git-gutter))
(+version-control|update-git-gutter))
(setq +magit--vc-is-stale-p nil)))
;;;###autoload
(defvar-local +magit--vc-is-stale-p nil)
;;;###autoload
(defun +magit-refresh-vc-state-maybe-h ()
"Update `vc' and `git-gutter' if out of date."
(when +magit--vc-is-stale-p
(+magit--refresh-vc-in-buffer (current-buffer))))
;;;###autoload
(add-hook 'doom-switch-buffer-hook #'+magit-refresh-vc-state-maybe-h)
;;;###autoload
(defun +magit/quit (&optional kill-buffer)
"Clean up magit buffers after quitting `magit-status' and refresh version
control in buffers."
(interactive "P")
(funcall magit-bury-buffer-function kill-buffer)
(unless (delq nil
(mapcar (lambda (win)
(with-selected-window win
(eq major-mode 'magit-status-mode)))
(window-list)))
(mapc #'+magit--kill-buffer (magit-mode-get-buffers))
(dolist (buffer (buffer-list))
(when (buffer-live-p buffer)
(if (get-buffer-window buffer)
(+magit--refresh-vc-in-buffer buffer)
(with-current-buffer buffer
(setq +magit--vc-is-stale-p t)))))))
(defun +magit--kill-buffer (buf)
"TODO"
(when (and (bufferp buf) (buffer-live-p buf))
(let ((process (get-buffer-process buf)))
(if (not (processp process))
(kill-buffer buf)
(with-current-buffer buf
(if (process-live-p process)
(run-with-timer 5 nil #'+magit--kill-buffer buf)
(kill-process process)
(kill-buffer buf)))))))
;;;###autoload
(defun +magit/start-github-review (arg)
(interactive "P")
(call-interactively
(if (or arg (not (featurep 'forge)))
#'github-review-start
#'github-review-forge-pr-at-point)))
(defvar +magit-clone-history nil
"History for `+magit/clone' prompt.")
;;;###autoload
(defun +magit/clone (url-or-repo dir)
"Like `magit-clone', but supports additional formats on top of absolute URLs:
+ USER/REPO: assumes {`+magit-default-clone-url'}/USER/REPO
+ REPO: assumes {`+magit-default-clone-url'}/{USER}/REPO, where {USER} is
ascertained from your global gitconfig."
(interactive
(progn
(require 'ghub)
(let* ((user (ghub--username (ghub--host)))
(repo (read-from-minibuffer
"Clone repository (user/repo or url): "
(if user (concat user "/"))
nil nil '+magit-clone-history))
(name (car (last (split-string repo "/" t)))))
(list repo
(read-directory-name
"Destination: "
magit-clone-default-directory
name nil name)))))
(magit-clone-regular
(cond ((string-match-p "^[^/]+$" url-or-repo)
(require 'ghub)
(format +magit-default-clone-url (ghub--username (ghub--host)) url-or-repo))
((string-match-p "^\\([^/]+\\)/\\([^/]+\\)/?$" url-or-repo)
(apply #'format +magit-default-clone-url (split-string url-or-repo "/" t)))
(url-or-repo))
dir
nil))
+167
View File
@@ -0,0 +1,167 @@
;;; tools/magit/config.el -*- lexical-binding: t; -*-
;;
;;; Packages
(use-package! magit
:commands magit-file-delete
:defer-incrementally (dash f s with-editor git-commit package eieio lv transient)
:init
(setq magit-auto-revert-mode nil) ; we do this ourselves
;; Must be set early to prevent ~/.emacs.d/transient from being created
(setq transient-levels-file (concat doom-etc-dir "transient/levels")
transient-values-file (concat doom-etc-dir "transient/values")
transient-history-file (concat doom-etc-dir "transient/history"))
:config
(setq transient-default-level 5
magit-revision-show-gravatars '("^Author: " . "^Commit: ")
magit-diff-refine-hunk t ; show granular diffs in selected hunk
;; Don't autosave repo buffers. This is too magical, and saving can
;; trigger a bunch of unwanted side-effects, like save hooks and
;; formatters. Trust us to know what we're doing.
magit-save-repository-buffers nil)
(defadvice! +magit-invalidate-projectile-cache-a (&rest _args)
;; We ignore the args to `magit-checkout'.
:after '(magit-checkout magit-branch-and-checkout)
(projectile-invalidate-cache nil))
;; The default location for git-credential-cache is in
;; ~/.cache/git/credential. However, if ~/.git-credential-cache/ exists, then
;; it is used instead. Magit seems to be hardcoded to use the latter, so here
;; we override it to have more correct behavior.
(unless (file-exists-p "~/.git-credential-cache/")
(setq magit-credential-cache-daemon-socket
(doom-glob (or (getenv "XDG_CACHE_HOME")
"~/.cache/")
"git/credential/socket")))
;; Magit uses `magit-display-buffer-traditional' to display windows, by
;; default, which is a little primitive. `+magit-display-buffer' marries
;; `magit-display-buffer-fullcolumn-most-v1' with
;; `magit-display-buffer-same-window-except-diff-v1', except:
;;
;; 1. Magit sub-buffers (like `magit-log') that aren't spawned from a status
;; screen are opened as popups.
;; 2. The status screen isn't buried when viewing diffs or logs from the
;; status screen.
(setq transient-display-buffer-action '(display-buffer-below-selected)
magit-display-buffer-function #'+magit-display-buffer-fn)
(set-popup-rule! "^\\(?:\\*magit\\|magit:\\| \\*transient\\*\\)" :ignore t)
(add-hook 'magit-popup-mode-hook #'hide-mode-line-mode)
;; Add --tags switch
(transient-append-suffix 'magit-fetch "-p"
'("-t" "Fetch all tags" ("-t" "--tags")))
(transient-append-suffix 'magit-pull "-r"
'("-a" "Autostash" "--autostash"))
;; so magit buffers can be switched to (except for process buffers)
(add-hook! 'doom-real-buffer-functions
(defun +magit-buffer-p (buf)
(with-current-buffer buf
(and (derived-mode-p 'magit-mode)
(not (eq major-mode 'magit-process-mode))))))
;; properly kill leftover magit buffers on quit
(define-key magit-status-mode-map [remap magit-mode-bury-buffer] #'+magit/quit)
;; Close transient with ESC
(define-key transient-map [escape] #'transient-quit-one))
(use-package! forge
;; We defer loading even further because forge's dependencies will try to
;; compile emacsql, which is a slow and blocking operation.
:after-call magit-status
:init
(setq forge-database-file (concat doom-etc-dir "forge/forge-database.sqlite"))
:config
;; All forge list modes are derived from `forge-topic-list-mode'
(map! :map forge-topic-list-mode-map :n "q" #'kill-current-buffer)
(set-popup-rule! "^\\*?[0-9]+:\\(?:new-\\|[0-9]+$\\)" :size 0.45 :modeline t :ttl 0 :quit nil)
(set-popup-rule! "^\\*\\(?:[^/]+/[^ ]+ #[0-9]+\\*$\\|Issues\\|Pull-Requests\\|forge\\)" :ignore t)
(defadvice! +magit--forge-get-repository-lazily-a (&rest _)
"Make `forge-get-repository' return nil if the binary isn't built yet.
This prevents emacsql getting compiled, which appears to come out of the blue
and blocks Emacs for a short while."
:before-while #'forge-get-repository
(file-executable-p emacsql-sqlite-executable))
(defadvice! +magit--forge-build-binary-lazily-a (&rest _)
"Make `forge-dispatch' only build emacsql if necessary.
Annoyingly, the binary gets built as soon as Forge is loaded. Since we've
disabled that in `+magit--forge-get-repository-lazily-a', we must manually
ensure it is built when we actually use Forge."
:before #'forge-dispatch
(unless (file-executable-p emacsql-sqlite-executable)
(emacsql-sqlite-compile 2)
(if (not (file-executable-p emacsql-sqlite-executable))
(message (concat "Failed to build emacsql; forge may not work correctly.\n"
"See *Compile-Log* buffer for details"))
;; HACK Due to changes upstream, forge doesn't initialize completely if
;; it doesn't find `emacsql-sqlite-executable', so we have to do it
;; manually after installing it.
(setq forge--sqlite-available-p t)
(magit-add-section-hook 'magit-status-sections-hook 'forge-insert-pullreqs nil t)
(magit-add-section-hook 'magit-status-sections-hook 'forge-insert-issues nil t)
(after! forge-topic
(dolist (hook forge-bug-reference-hooks)
(add-hook hook #'forge-bug-reference-setup)))))))
(use-package! github-review
:after magit
:config
(transient-append-suffix 'magit-merge "i"
'("y" "Review pull request" +magit/start-github-review))
(after! forge
(transient-append-suffix 'forge-dispatch "c u"
'("c r" "Review pull request" +magit/start-github-review))))
(use-package! magit-todos
:after magit
:config
(setq magit-todos-keyword-suffix "\\(?:([^)]+)\\)?:?") ; make colon optional
(define-key magit-todos-section-map "j" nil)
;; Warns that jT isn't bound. Well, yeah, you don't need to tell me, that was
;; on purpose ya goose.
(advice-add #'magit-todos-mode :around #'doom-shut-up-a))
(use-package! magit-gitflow
:hook (magit-mode . turn-on-magit-gitflow))
(use-package! evil-magit
:when (featurep! :editor evil +everywhere)
:after magit
:init
(setq evil-magit-state 'normal
evil-magit-use-z-for-folds t)
:config
(unmap! magit-mode-map
;; Replaced by z1, z2, z3, etc
"M-1" "M-2" "M-3" "M-4"
"1" "2" "3" "4"
"0") ; moved to g=
(evil-define-key* 'normal magit-status-mode-map [escape] nil) ; q is enough
(evil-define-key* '(normal visual) magit-mode-map
"%" #'magit-gitflow-popup
"zz" #'evil-scroll-line-to-center
"g=" #'magit-diff-default-context)
(define-key! 'normal
(magit-status-mode-map
magit-stash-mode-map
magit-revision-mode-map
magit-diff-mode-map)
[tab] #'magit-section-toggle)
(after! git-rebase
(dolist (key '(("M-k" . "gk") ("M-j" . "gj")))
(when-let (desc (assoc (car key) evil-magit-rebase-commands-w-descriptions))
(setcar desc (cdr key))))
(evil-define-key* evil-magit-state git-rebase-mode-map
"gj" #'git-rebase-move-line-down
"gk" #'git-rebase-move-line-up)))
+10
View File
@@ -0,0 +1,10 @@
;; -*- no-byte-compile: t; -*-
;;; tools/magit/packages.el
(when (package! magit)
(package! forge)
(package! magit-gitflow)
(package! magit-todos)
(package! github-review)
(when (featurep! :editor evil +everywhere)
(package! evil-magit)))
+25
View File
@@ -0,0 +1,25 @@
;;; tools/make/autoload.el -*- lexical-binding: t; -*-
(require 'makefile-executor)
;;;###autoload
(defun +make/run ()
"Run a make task in the current project. If multiple makefiles are available,
you'll be prompted to select one."
(interactive)
(if (doom-project-p)
(makefile-executor-execute-project-target)
(let ((makefile (cl-loop with buffer-file = (or buffer-file-name default-directory)
for file in (list "Makefile" "makefile")
if (locate-dominating-file buffer-file file)
return file)))
(unless makefile
(user-error "Cannot find a makefile in the current project"))
(let ((default-directory (file-name-directory makefile)))
(makefile-executor-execute-target makefile)))))
;;;###autoload
(defun +make/run-last ()
"TODO"
(interactive)
(makefile-executor-execute-last nil))
+4
View File
@@ -0,0 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; tools/make/packages.el
(package! makefile-executor)
+140
View File
@@ -0,0 +1,140 @@
;;; tools/pass/autoload.el -*- lexical-binding: t; -*-
(defun +pass--open-url (entry)
(if-let* ((url (+pass-get-field entry +pass-url-fields)))
(and (or (string-match-p "https?://" url)
(error "Field for %s doesn't look like an url" item))
(browse-url url))
(error "url not found.")))
(defun +pass--copy (entry field text)
(password-store-clear)
(message "Copied %s's %s field to clipboard. Will clear in %s seconds"
entry field (password-store-timeout))
(kill-new text)
(setq password-store-timeout-timer
(run-at-time (password-store-timeout) nil 'password-store-clear)))
(defun +pass--copy-username (entry)
(if-let* ((user (+pass-get-field entry +pass-user-fields)))
(progn (password-store-clear)
(message "Copied username to the kill ring.")
(kill-new user))
(error "Username not found.")))
(defun +pass--completing-read-field (entry)
(let* ((data (+pass-get-entry entry))
(field (if data (completing-read "Field: " (mapcar #'car data) nil t))))
(+pass-get-field data field)))
;;
;; API
;;;###autoload (autoload 'auth-source-pass-parse-entry "auth-source-pass")
;;;###autoload
(defalias '+pass-get-entry #'auth-source-pass-parse-entry)
;;;###autoload
(defun +pass-get-field (entry fields &optional noerror)
"Fetches the value of a field. FIELDS can be a list of string field names or a
single one. If a list, the first field found will be returned. Will error out
otherwise, unless NOERROR is non-nill."
(if-let* ((data (if (listp entry) entry (+pass-get-entry entry))))
(cl-loop for key in (doom-enlist fields)
when (assoc key data)
return (cdr it))
(unless noerror
(error "Couldn't find entry: %s" entry))))
;;;###autoload
(defun +pass-get-user (entry)
"Fetches the user field from ENTRY. Each of `+pass-user-fields' are tried in
search of your username. May prompt for your gpg passphrase."
(+pass-get-field entry +pass-user-fields))
;;;###autoload
(defun +pass-get-secret (entry)
"Fetches your secret from ENTRY. May prompt for your gpg passphrase."
(+pass-get-field entry 'secret))
;;
;; Commands
;;;###autoload (autoload 'password-store-dir "password-store")
;;;###autoload (autoload 'password-store-list "password-store")
;;;###autoload (autoload 'password-store--completing-read "password-store")
;;;###autoload
(defun +pass/edit-entry (entry)
"Interactively search for and open a pass entry for editing."
(interactive
(list (password-store--completing-read)))
(find-file (concat (expand-file-name entry (password-store-dir)) ".gpg")))
;;;###autoload
(defun +pass/copy-field (entry)
"Interactively search for an entry and copy a particular field to your
clipboard."
(interactive
(list (password-store--completing-read)))
(let* ((data (+pass-get-entry entry))
(field (if data (completing-read "Field: " (mapcar #'car data) nil t))))
(+pass--copy entry field (+pass-get-field data field))))
;;;###autoload
(defun +pass/copy-secret (entry)
"Interactively search for an entry and copy its secret/password to your
clipboard."
(interactive
(list (password-store--completing-read)))
(+pass--copy entry 'secret (+pass-get-secret entry)))
;;;###autoload
(defun +pass/copy-user (entry)
"Interactively search for an entry and copy the login to your clipboard. The
fields in `+pass-user-fields' is used to find the login field."
(interactive
(list (password-store--completing-read)))
(+pass--copy-username entry))
;;;###autoload
(defun +pass/browse-url (entry)
"Interactively search for an entry and open its url in your browser. The
fields in `+pass-url-fields' is used to find the url field."
(interactive
(list (password-store--completing-read)))
(+pass--open-url entry))
;;
;; Ivy interface
;;;###autoload
(defun +pass/ivy (arg)
"TODO"
(interactive "P")
(ivy-read "Pass: " (password-store-list)
:action (if arg
#'password-store-url
#'password-store-copy)
:caller '+pass/ivy))
(after! ivy
(ivy-add-actions
'+pass/ivy
'(("o" password-store-copy "copy password")
("e" +pass/edit-entry "edit entry")
("u" +pass/copy-user "copy username")
("b" +pass/copy-url "open url in browser")
("f" +pass/copy-field "get field"))))
;;
;; TODO Helm interface
;; (defun +pass/helm ()
;; (interactive)
;; )
+39
View File
@@ -0,0 +1,39 @@
;;; tools/pass/config.el -*- lexical-binding: t; -*-
(defvar +pass-user-fields '("login" "user" "username" "email")
"A list of fields for `+pass/ivy' to search for the username.")
(defvar +pass-url-fields '("url" "site" "location")
"A list of fields for `+pass/ivy' to search for the username.")
;;
;; Packages
;;;###package password-store
(setq password-store-password-length 12)
;; Fix hard-coded password-store location; respect PASSWORD_STORE_DIR envvar
(defadvice! +pass--respect-pass-dir-envvar-a (entry)
"Return a string with the file content of ENTRY."
:override #'auth-source-pass--read-entry
(with-temp-buffer
(insert-file-contents
(expand-file-name (format "%s.gpg" entry) (password-store-dir)))
(buffer-substring-no-properties (point-min) (point-max))))
(after! pass
(set-evil-initial-state! 'pass-mode 'emacs)
(set-popup-rule! "^\\*Password-Store" :side 'left :size 0.25 :quit nil)
(define-key! pass-mode-map
"j" #'pass-next-entry
"k" #'pass-prev-entry
"d" #'pass-kill
"\C-j" #'pass-next-directory
"\C-k" #'pass-prev-directory))
;; Is built into Emacs 26+
(when (featurep! +auth)
(auth-source-pass-enable))
+16
View File
@@ -0,0 +1,16 @@
;; -*- no-byte-compile: t; -*-
;;; tools/pass/packages.el
(package! pass)
(package! password-store)
(package! password-store-otp)
;; an older version of `auto-source-pass' is built into Emacs 26+, so we must
;; install the new version directly from the source and with a psuedonym.
(package! auth-source-pass
:recipe (:host github :repo "DamienCassou/auth-password-store"))
(when (featurep! :completion ivy)
(package! ivy-pass))
(when (featurep! :completion helm)
(package! helm-pass))
@@ -0,0 +1,41 @@
;; -*- no-byte-compile: t; -*-
;;; tools/pass/test/test-pass.el
(describe "tools/pass"
(before-all
(load! "../autoload"))
(before-each
(spy-on 'auth-source-pass-parse-entry :and-call-fake
(lambda (entry)
(when (equal entry "fake/source")
'((secret . "defuse-account-gad")
("login" . "HL2532")
("alt-login" . "hlissner")
("email" . "henrik@lissner.net")
("url" . "https://some-place.net/login"))))))
(describe "get field"
(it "returns specific fields"
(expect (+pass-get-field "fake/source" "email")
:to-equal "henrik@lissner.net"))
(it "returns first existing of a list of fields"
(expect (+pass-get-field "fake/source" '("alt-login" "email"))
:to-equal "hlissner")
(expect (+pass-get-field "fake/source" '("username" "email"))
:to-equal "henrik@lissner.net"))
(it "returns nil for missing fields"
(expect (+pass-get-field "fake/source" '("x" "y" "z"))
:to-be nil))
(it "throws error on missing entries"
(expect (+pass-get-field "nonexistent/source" "login")
:to-throw)))
(describe "get user/secret"
(it "returns the user"
(let ((+pass-user-fields '("login" "user" "username" "email")))
(expect (+pass-get-user "fake/source")
:to-equal "HL2532")))
(it "returns the secret"
(expect (+pass-get-secret "fake/source")
:to-equal "defuse-account-gad"))))
@@ -0,0 +1,30 @@
;;; tools/pdf/autoload/pdf.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +pdf--supply-width-to-create-image-calls-a (orig-fn &rest args)
(cl-letf* ((old-create-image (symbol-function #'create-image))
((symbol-function #'create-image)
(lambda (file-or-data &optional type data-p &rest props)
(apply old-create-image file-or-data type data-p
:width (car (pdf-view-image-size))
props))))
(apply orig-fn args)))
;;;###autoload
(defun +pdf--util-frame-scale-factor-a (orig-fn)
(if (and pdf-view-use-scaling
(memq (pdf-view-image-type) '(imagemagick image-io))
(fboundp 'frame-monitor-attributes))
(funcall orig-fn)
;; Add special support for retina displays on MacOS
(if (and (eq (framep-on-display) 'ns)
EMACS27+)
2
1)))
;;;###autoload
(defun +pdf--view-use-scaling-p-a ()
"Returns t if on ns window-system on Emacs 27+."
(and (eq (framep-on-display) 'ns)
EMACS27+
pdf-view-use-scaling))
+74
View File
@@ -0,0 +1,74 @@
;;; tools/pdf/config.el -*- lexical-binding: t; -*-
(use-package! pdf-tools
:mode ("\\.[pP][dD][fF]\\'" . pdf-view-mode)
:magic ("%PDF" . pdf-view-mode)
:config
(map! :map pdf-view-mode-map :gn "q" #'kill-current-buffer)
(after! pdf-annot
(defun +pdf-cleanup-windows-h ()
"Kill left-over annotation buffers when the document is killed."
(when (buffer-live-p pdf-annot-list-document-buffer)
(pdf-info-close pdf-annot-list-document-buffer))
(when (buffer-live-p pdf-annot-list-buffer)
(kill-buffer pdf-annot-list-buffer))
(let ((contents-buffer (get-buffer "*Contents*")))
(when (and contents-buffer (buffer-live-p contents-buffer))
(kill-buffer contents-buffer))))
(add-hook! 'pdf-view-mode-hook
(add-hook 'kill-buffer-hook #'+pdf-cleanup-windows-h nil t)))
(setq-default pdf-view-display-size 'fit-page
pdf-view-use-scaling t
pdf-view-use-imagemagick nil)
;; Add retina support for MacOS users
(when IS-MAC
(advice-add #'pdf-util-frame-scale-factor :around #'+pdf--util-frame-scale-factor-a)
(advice-add #'pdf-view-use-scaling-p :before-until #'+pdf--view-use-scaling-p-a)
(defadvice! +pdf--supply-width-to-create-image-calls-a (orig-fn &rest args)
:around '(pdf-annot-show-annotation
pdf-isearch-hl-matches
pdf-view-display-region)
(cl-letf* ((old-create-image (symbol-function #'create-image))
((symbol-function #'create-image)
(lambda (file-or-data &optional type data-p &rest props)
(apply old-create-image file-or-data type data-p
:width (car (pdf-view-image-size))
props))))
(apply orig-fn args))))
;; Turn off cua so copy works
(add-hook! 'pdf-view-mode-hook (cua-mode 0))
;; Handle PDF-tools related popups better
(set-popup-rule! "^\\*Outline*" :side 'right :size 40 :select nil)
(set-popup-rule! "\\(?:^\\*Contents\\|'s annots\\*$\\)" :ignore t)
(add-hook 'pdf-annot-list-mode-hook #'hide-mode-line-mode)
;; Fix #1107: flickering pdfs when evil-mode is enabled
(setq-hook! 'pdf-view-mode-hook evil-normal-state-cursor (list nil))
;; Install epdfinfo binary if needed, blocking until it is finished
(unless (file-executable-p pdf-info-epdfinfo-program)
(let ((wconf (current-window-configuration)))
(pdf-tools-install)
(message "Building epdfinfo, this will take a moment...")
;; HACK We reset all `pdf-view-mode' buffers to fundamental mode so that
;; `pdf-tools-install' has a chance to reinitialize them as
;; `pdf-view-mode' buffers. This is necessary because `pdf-tools-install'
;; won't do this to buffers that are already in pdf-view-mode.
(dolist (buffer (doom-buffers-in-mode 'pdf-view-mode))
(with-current-buffer buffer (fundamental-mode)))
(while compilation-in-progress
;; Block until `pdf-tools-install' is done
(sleep-for 1))
;; HACK If pdf-tools was loaded by you opening a pdf file, once
;; `pdf-tools-install' completes, `pdf-view-mode' will throw an error
;; because the compilation buffer is focused, not the pdf buffer.
;; Therefore, it is imperative that the window config is restored.
(when (file-executable-p pdf-info-epdfinfo-program)
(set-window-configuration wconf))))
;; Sets up `pdf-tools-enable-minor-modes', `pdf-occur-global-minor-mode' and
;; `pdf-virtual-global-minor-mode'.
(pdf-tools-install-noverify))
+4
View File
@@ -0,0 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; tools/pdf/packages.el
(package! pdf-tools)
@@ -0,0 +1,34 @@
;;; tools/prodigy/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +prodigy/create ()
"Interactively create a new prodigy service."
(interactive)
;; TODO
)
;;;###autoload
(defun +prodigy/delete (arg)
"Delete service at point. Asks for confirmation."
(interactive "P")
(prodigy-with-refresh
(when-let (service (prodigy-service-at-pos))
(let ((name (plist-get service :name)))
(cond ((or arg
(y-or-n-p (format "Delete '%s' service?" name)))
(setq prodigy-services (delete service prodigy-services))
(ignore-errors
(prodigy-goto-next-line))
(message "Successfully deleted service: %s" name))
(t
(message "Aborted")))))))
;;;###autoload
(defun +prodigy/cleanup ()
"Delete all services associated with projects that don't exist."
(interactive)
(cl-loop for service in prodigy-services
if (and (plist-member service :project)
(file-directory-p (plist-get service :project)))
collect service into services
finally do (setq prodigy-service services)))
+21
View File
@@ -0,0 +1,21 @@
;;; tools/prodigy/config.el -*- lexical-binding: t; -*-
(after! prodigy
(set-evil-initial-state! 'prodigy-mode 'emacs)
(defadvice! +prodigy--add-project-property-a (orig-fn &rest args)
"Adds a new :project property to prodigy services, which hides the service
unless invoked from the relevant project."
:around #'prodigy-services
(let ((project-root (downcase (or (doom-project-root) default-directory)))
(services (apply orig-fn args)))
(if current-prefix-arg
services
(cl-remove-if-not (lambda (service)
(let ((project (plist-get service :project)))
(or (not project)
(file-in-directory-p project-root project))))
services))))
(define-key prodigy-mode-map "d" #'+prodigy/delete))
@@ -0,0 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; tools/prodigy/packages.el
(package! prodigy)
+14
View File
@@ -0,0 +1,14 @@
;;; tools/rgb/autoload.el -*- lexical-binding: t; -*-
;;;###if (featurep! :ui hydra)
;;;###autoload (autoload '+rgb/kurecolor-hydra/body "tools/rgb/autoload" nil t)
(defhydra +rgb/kurecolor-hydra (:color pink :hint nil)
"
Inc/Dec _w_/_W_ brightness _d_/_D_ saturation _e_/_E_ hue "
("w" kurecolor-decrease-brightness-by-step)
("W" kurecolor-increase-brightness-by-step)
("d" kurecolor-decrease-saturation-by-step)
("D" kurecolor-increase-saturation-by-step)
("e" kurecolor-decrease-hue-by-step)
("E" kurecolor-increase-hue-by-step)
("q" nil "cancel" :color blue))
+5
View File
@@ -0,0 +1,5 @@
;; -*- no-byte-compile: t; -*-
;;; tools/rgb/packages.el
(package! rainbow-mode)
(package! kurecolor)
@@ -0,0 +1,69 @@
#+TITLE: tools/terraform
#+DATE: November 21, 2019
#+SINCE: v2.1.0
#+STARTUP: inlineimages
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#syntax-highlighting][Syntax highlighting]]
- [[#code-formatting][Code formatting]]
- [[#code-navigation][Code navigation]]
- [[#code-completion][Code completion]]
- [[#documentation][Documentation]]
- [[#executing-terraform-commands][Executing Terraform commands]]
- [[#appendix][Appendix]]
- [[#keybindings][Keybindings]]
- [[#localleader][:localleader]]
* Description
This module adds support for working with [[https://www.terraform.io][Terraform]] files in Doom Emacs. This
includes syntax highlighting, intelligent code completion, and the ability to run
Terraform commands directly from Emacs.
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/syohex/emacs-terraform-mode][terraform-mode]]
+ [[https://github.com/rafalcieslak/emacs-company-terraform][company-terraform*]]
* Prerequisites
The =terraform= executable must be installed and accessible from your PATH.
* Features
** Syntax highlighting
Syntax highlighting is provided from =terraform-mode= and =hcl-mode=.
** Code formatting
=:tools terraform= does not provide code formatting directly, but =:editor
format= works with Terraform files.
** Code navigation
Code navigation is supported through =imenu= from =terraform-mode=.
** Code completion
Code completion of Terraform builtins is provided from =company-terraform= and
generally works well despite being generated through a [[https://github.com/rafalcieslak/emacs-company-terraform/blob/master/company-terraform-data.el][static (outdated) file]].
=company-terraform= also provides code completion of resources within your project.
** Documentation
Documentation is accessible through the normal =company-mode= show documentation
functionality, thanks to =company-terraform=.
** Executing Terraform commands
=:tools terraform= provides commands under the =localleader= to run the most
common Terraform operations (see Keybindings below).
* Appendix
** Keybindings
*** :localleader
| key | description |
|-----+-----------------------|
| =i= | Run =terraform init= |
| =p= | Run =terraform plan= |
| =a= | Run =terraform apply= |
@@ -0,0 +1,15 @@
;;; tools/terraform/config.el -*- lexical-binding: t; -*-
(map! :after terraform-mode
:map terraform-mode-map
:localleader
:desc "terraform apply" "a" (λ! (compile "terraform apply"))
:desc "terraform init" "i" (λ! (compile "terraform init"))
:desc "terraform plan" "p" (λ! (compile "terraform plan")))
(use-package! company-terraform
:when (featurep! :completion company)
:after terraform-mode
:config
(set-company-backend! 'terraform-mode 'company-terraform))
@@ -0,0 +1,4 @@
;;; tools/terraform/doctor.el -*- lexical-binding: t; -*-
(unless (executable-find "terraform")
(warn! "Couldn't find terraform."))
@@ -0,0 +1,6 @@
;; -*- no-byte-compile: t; -*-
;;; tools/terraform/packages.el
(package! terraform-mode)
(when (featurep! :completion company)
(package! company-terraform))
@@ -0,0 +1,17 @@
;;; tools/tmux/autoload/evil.el -*- lexical-binding: t; -*-
;;;###if (featurep! :editor evil)
;;;###autoload (autoload '+tmux:run "tools/tmux/autoload/evil" nil t)
(evil-define-command +tmux:run (bang &optional command)
(interactive "<!><fsh>")
(if (evil-visual-state-p)
(+tmux/send-region evil-visual-beginning evil-visual-end bang)
(+tmux/run command bang)))
;;;###autoload (autoload '+tmux:cd-here "tools/tmux/autoload/evil" nil t)
(evil-define-command +tmux:cd-here (bang)
(interactive "<!>")
(if bang
(+tmux/cd-to-here)
(+tmux/cd-to-project)))
@@ -0,0 +1,136 @@
;;; tools/tmux/autoload/tmux.el -*- lexical-binding: t; -*-
;; This library offers:
;; + A way of communicating with a tmux instance
;; + TODO A way to manage tmuxifier from emacs
(defvar +tmux-last-command nil
"The last command ran by `+tmux'. Used by `+tmux/rerun'")
(defvar +tmux-last-retcode nil
"The last tmux return code.")
;;
;; Commands
;;;###autoload
(defun +tmux (command &rest args)
"Execute COMMAND in tmux"
(let ((bin (executable-find "tmux")))
(unless bin
(error "Could not find tmux executable"))
(let* ((args (mapcar #'shell-quote-argument (delq nil args)))
(cmdstr (format "%s %s" bin (if args (apply #'format command args) command)))
(output (get-buffer-create " *tmux stdout*"))
(errors (get-buffer-create " *tmux stderr*"))
code)
(unwind-protect
(if (= 0 (setq code (quiet! (shell-command cmdstr output errors))))
(with-current-buffer output
(setq +tmux-last-command `(,cmdstr ,@args))
(buffer-string))
(error "[%d] tmux $ %s (%s)"
code
(with-current-buffer errors
(buffer-string))
cmdstr))
(and (kill-buffer output)
(kill-buffer errors))))))
;;;###autoload
(defun +tmux/run (command &optional noreturn)
"Run COMMAND in tmux. If NORETURN is non-nil, send the commands as keypresses
but do not execute them."
(interactive
(list (read-string "tmux $ ")
current-prefix-arg))
(+tmux (concat "send-keys C-u "
(shell-quote-argument command)
(unless noreturn " Enter"))))
;;;###autoload
(defun +tmux/send-region (beg end &optional noreturn)
"Send region to tmux."
(interactive "rP")
(+tmux/run (string-trim (buffer-substring-no-properties beg end))
noreturn))
;;;###autoload
(defun +tmux/rerun ()
"Rerun the last command executed by `+tmux' and `+tmux/run'."
(interactive "P")
(unless +tmux-last-command
(user-error "No last command to run"))
(apply #'+tmux +tmux-last-command))
;;;###autoload
(defun +tmux/cd (&optional arg directory)
"Change the pwd of the currently active tmux pane to DIRECTORY (defaults to
`default-directory', or to `doom-project-root' with the universal argument)."
(interactive "PD")
(+tmux/run (format "cd %s" (or directory default-directory)) arg))
;;;###autoload
(defun +tmux/cd-to-here ()
"cd into `default-directory' in tmux."
(interactive)
(+tmux/cd default-directory))
;;;###autoload
(defun +tmux/cd-to-project ()
"cd into `doom-project-root' in tmux."
(interactive)
(+tmux/cd (doom-project-root)))
;;
;; Data functions
;;;###autoload
(defun +tmux-list-sessions ()
(let ((lines (+tmux "list-sessions -F '#{session_id};#{session_name};#{session_attached}'")))
(if lines
(cl-loop for line in (split-string lines "\n" t)
collect
(let ((sess (split-string line ";")))
(list (nth 0 sess)
:name (nth 1 sess)
:attached (equal (nth 2 sess) "1"))))
(error "There are no sessions"))))
;;;###autoload
(defun +tmux-list-windows (&optional session)
(if-let* ((lines
(+tmux (format "list-windows %s -F '#{window_id};#{session_id};#{window_active};#{window_name};#{window_activity_flag}'"
(if session
(concat "-t " (car session))
"-a")))))
(cl-loop for line in (split-string lines "\n" t)
collect (let ((window (split-string line ";")))
(list (nth 0 window)
:session-id (nth 1 window)
:name (nth 3 window)
:active (equal (nth 2 window) "1")
:activity (equal (nth 4 window) "1"))))
(error "There are no windows")))
;;;###autoload
(defun +tmux-list-panes (&optional sess-or-win)
(if-let* ((lines
(+tmux (format "list-panes %s -F '#{pane_id};#{window_id};#{session_id};#{pane_active};#{pane_title};#{pane_current_path}'"
(if sess-or-win
(concat (if (string-prefix-p "$" (car sess-or-win)) "-s ")
"-t "
(car sess-or-win))
"-a")))))
(cl-loop for line in (split-string lines "\n" t)
collect (let ((pane (split-string line ";")))
(list (nth 0 pane)
:window-id (nth 1 pane)
:session-id (nth 2 pane)
:name (nth 4 pane)
:active (equal (nth 3 pane) "1")
:pwd (nth 5 pane))))
(error "There are no panes")))
+51
View File
@@ -0,0 +1,51 @@
;;; tools/upload/config.el -*- lexical-binding: t; -*-
;; Uses `ssh-deploy' to map a local folder to a remote one. Set
;; `ssh-deploy-root-remote' and `ssh-deploy-root-local' in a .dir-locals.el file
;; to establish this mapping.
;;
;; Example:
;; ((nil . ((ssh-deploy-root-local . "/local/path/to/project")
;; (ssh-deploy-root-remote . "/ssh:user@server:/remote/project/")
;; (ssh-deploy-on-explicity-save . t))))
;;
;; Note: `ssh-deploy-root-local' is optional, and will resort to
;; `doom-project-root' if unspecified.
(use-package! ssh-deploy
:commands (ssh-deploy-upload-handler
ssh-deploy-upload-handler-forced
ssh-deploy-diff-handler
ssh-deploy-browse-remote-handler
ssh-deploy-remote-changes-handler)
:init
(setq ssh-deploy-revision-folder (concat doom-cache-dir "ssh-revisions/")
ssh-deploy-on-explicit-save t
ssh-deploy-automatically-detect-remote-changes nil)
;; Make these safe as file-local variables
(dolist (sym '((ssh-deploy-root-local . stringp)
(ssh-deploy-root-remote . stringp)
(ssh-deploy-script . functionp)
(ssh-deploy-on-explicitly-save . booleanp)
(ssh-deploy-async . booleanp)
(ssh-deploy-exclude-list . listp)))
(put (car sym) 'safe-local-variable (cdr sym)))
;; Maybe auto-upload on save
(add-hook! 'after-save-hook
(defun +upload-init-after-save-h ()
(when (and (bound-and-true-p ssh-deploy-root-remote)
ssh-deploy-on-explicit-save)
(ssh-deploy-upload-handler))))
;; Enable ssh-deploy if variables are set, and check for changes on open file
;; (if possible)
(add-hook! 'find-file-hook
(defun +upload-init-find-file-h ()
(when (bound-and-true-p ssh-deploy-root-remote)
(require 'ssh-deploy)
(unless ssh-deploy-root-local
(setq ssh-deploy-root-local (doom-project-root)))
(when ssh-deploy-automatically-detect-remote-changes
(ssh-deploy-remote-changes-handler))))))
@@ -0,0 +1,5 @@
;; -*- no-byte-compile: t; -*-
;;; tools/upload/packages.el
(package! ssh-deploy)