mirror of
https://gitlab.com/dwt1/dotfiles.git
synced 2026-08-10 01:51:15 +10:00
Moving to Doom Emacs!
This commit is contained in:
@@ -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)))
|
||||
@@ -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))
|
||||
@@ -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.
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -0,0 +1,5 @@
|
||||
;; -*- no-byte-compile: t; -*-
|
||||
;;; tools/ein/packages.el
|
||||
|
||||
(package! ein)
|
||||
(package! avy)
|
||||
@@ -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)))))
|
||||
@@ -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))
|
||||
@@ -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))))
|
||||
@@ -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))
|
||||
@@ -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)))
|
||||
@@ -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))
|
||||
@@ -0,0 +1,4 @@
|
||||
;; -*- no-byte-compile: t; -*-
|
||||
;;; tools/gist/packages.el
|
||||
|
||||
(package! gist)
|
||||
@@ -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)))
|
||||
@@ -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)))
|
||||
@@ -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)))
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
;;; feature/lsp/autoload.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;;###autodef
|
||||
(defalias 'lsp! #'lsp-deferred)
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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)))
|
||||
@@ -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)))
|
||||
@@ -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))
|
||||
@@ -0,0 +1,4 @@
|
||||
;; -*- no-byte-compile: t; -*-
|
||||
;;; tools/make/packages.el
|
||||
|
||||
(package! makefile-executor)
|
||||
@@ -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)
|
||||
;; )
|
||||
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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)))
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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")))
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user