Complete commands and code.
;;; wal-complete.el --- Completion. -*- lexical-binding: t -*-
;;; Commentary:
;;
;; Provide Emacs completion packages.
;;; Code:
(eval-when-compile
(require 'savehist)
(require 'wal-key-bindings nil t))
(declare-function cape-dabbrev "ext:cape.el")
(declare-function cape-file "ext:cape.el")
(declare-function cape-history "ext:cape.el")
(declare-function cape-wrap-super "ext:cape.el")
(declare-function consult--buffer-query "ext:consult.el")
(declare-function consult--buffer-state "ext:consult.el")
(declare-function consult-line "ext:consult.el")
(declare-function consult-org-agenda "ext:consult.el")
(declare-function consult-org-heading "ext:consult.el")
(declare-function corfu-complete "ext:corfu.el")
(declare-function corfu-mode "ext:corfu.el")
(declare-function junk--stringify "ext:junk.el")
(declare-function junk--parts "ext:junk.el")
(declare-function org-clock-in "ext:org-clock.el")
(declare-function org-clock-out "ext:org-clock.el")
(declare-function org-clocking-buffer "ext:org-clock.el")
(declare-function org-clocking-p "ext:org-clock.el")
(declare-function org-up-heading-safe "ext:org.el")
(declare-function wal-agenda-buffer-p "wal-org.el")
(declare-function wal-append "wal-useful.el")
(declare-function wal-org-clock-in-from-now "wal-org.el")
(declare-function wal-project-buffer-root "wal-workspace.el")
(declare-function wal-list-from "wal-useful.el")
(declare-function wal-switch-to-buffer-obeying-display-actions "wal-useful.el")
(declare-function wal-univ-p "wal-useful.el")
(defvar consult-register--narrow)
(defvar junk-expansion-packs)
(defvar project--list)
(defvar savehist-additional-variables)
(defvar vertico-map)
(defvar wal-active-theme)
(defvar wal-default-path)
;;; Customization:
(defgroup wal-complete nil
"Change completion settings."
:group 'wal
:tag "Completion")
Light-weight inline completion. The custom function is taken from the
wiki to make sure that corfu
is enabled in the minibuffer when it
makes sense.
(defun wal-corfu-enable-in-minibuffer ()
"Enable Corfu in the minibuffer if `completion-at-point' is bound."
(when (where-is-internal #'completion-at-point (list (current-local-map)))
(corfu-mode 1)))
(use-package corfu
:hook
(((org-mode prog-mode harpoon-prog-like eshell-mode inferior-python-mode) . corfu-mode)
(minibuffer-setup . wal-corfu-enable-in-minibuffer))
:custom
(corfu-cycle t)
(corfu-auto t)
:wal-bind
(("k" . completion-at-point)
;; M-k binds `completionist'.
:map corfu-map
("k" . corfu-quit)))
Jump to a completion candidate with a single letter.
(defun wal-corfu-quick-exit-in-case-of-single-match (&rest _args)
"Complete immediately if there is only one match."
(defvar corfu--total)
(when (= 1 corfu--total)
(corfu-complete)))
(use-package corfu-quick
:ensure nil
:demand t
:after corfu
:config
(advice-add
'corfu-quick-complete :before-until
#'wal-corfu-quick-exit-in-case-of-single-match)
:custom
(corfu-quick1 "jkl;h")
(corfu-quick2 "asdfg")
:wal-bind
(:map corfu-map
("j" . corfu-quick-complete))
:defines (corfu-map))
Save the completion history.
(use-package corfu-history
:ensure nil
:demand t
:after corfu
:config
(add-to-list 'savehist-additional-variables 'corfu-history)
(corfu-history-mode)
:custom
(corfu-history-length 200)
:defines (corfu-map)
:functions (corfu-history-mode))
Show docs or location during selection.
Key bindings:
M-t
to toggleM-g
for locationM-h
for documentation
Scrolling works like for other-window
.
(use-package corfu-popupinfo
:after corfu
:demand t
:ensure nil
:config
(corfu-popupinfo-mode)
:functions (corfu-popupinfo-mode))
Allows using corfu
in a terminal.
(use-package corfu-terminal)
Provides additional completion function as well as functionality to
jerry-rig company
backends to work with corfu
.
(defun wal-cape-history-file ()
"Combined `cape-history' and `cape-file'."
(cape-wrap-super #'cape-history #'cape-file))
(defun wal-cape-eshell-setup ()
"Set up `cape' fro Eshell."
(setq-local completion-at-point-functions
(cons #'wal-cape-history-file
completion-at-point-functions)))
(use-package cape
:hook (eshell-mode . wal-cape-eshell-setup)
:general
(completionist "/" 'cape-dabbrev
"f" 'cape-file
"h" 'cape-history
"a" 'cape-abbrev
"d" 'cape-dict
"k" 'cape-keyword
"l" 'cape-line))
Don’t reduce the boilerplate, reduce your involvement in it with snippets. Completing these snippets is bound to a custom binding.
(defun wal-tempel-comment (elt)
"Comment the element ELT according to mode."
(when (eq (car-safe elt) 'c)
(let ((cs (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start)))
(concat cs (cadr elt)))))
(use-package tempel
:config
(setq tempel-path (wal-list-from
'tempel-path
(expand-file-name
"data/tempel.eld"
wal-default-path)))
:custom
(tempel-user-elements '(wal-tempel-comment))
(tempel-mark (propertize "░" 'face 'mode-line-highlight))
:bind
(:map tempel-map
("M-k" . tempel-kill)
("M-a" . tempel-beginning)
("M-e" . tempel-end)
("M-n" . tempel-next)
("M-p" . tempel-previous))
:general
(completionist "t" 'tempel-complete)
:defines (tempel-path tempel-map)
:functions (tempel-complete))
Provide global option narrowing using the vertico
family of
products. The setup is mostly as per the official docs. This just sets
up vertico-multiform-mode
to make certain completion tasks
unobtrusive.
(use-package vertico
:defer 1
:wal-ways t
:hook (minibuffer-setup . cursor-intangible-mode)
:config
;; Set up minibuffer.
(setq read-extended-command-predicate #'command-completion-default-include-p
minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt))
(vertico-mode)
(vertico-multiform-mode)
:custom
(vertico-cycle t)
(vertico-resize t)
(vertico-multiform-categories '((consult-grep buffer)
(consult-location buffer)
(org-heading buffer)
(consult-outline buffer)
(imenu buffer)))
(vertico-multiform-commands '((tab-switch flat)
(partial-recall-switch-to-buffer flat)
(wal-project-find-in-here unobtrusive)))
:wal-bind
(:map vertico-multiform-map
("l" . vertico-multiform-vertical))
:functions (vertico-mode vertico-multiform-mode))
Make sure navigating directories during completion is a breeze. This makes sure that we don’t go into directories when jumping in Dired.
(defvar-local wal-command nil
"Command that started completion session.")
(defun wal-record-this-command ()
"Record the command which opened the minibuffer."
(setq-local wal-command this-command))
(defun wal-with-dired-goto-file-ignored (fun &rest args)
"Advise FUN using ARGS to exit if we came from `dired-goto-file'."
(unless (and (eq (car args) 'category)
(eq wal-command 'dired-goto-file))
(apply fun args)))
(use-package vertico-directory
:ensure nil
:demand t
:after vertico
:hook
((rfn-eshadow-update-overlay . vertico-directory-tidy)
(minibuffer-setup . wal-record-this-command))
:config
;; We don't want to enter directories when we go to file with Dired.
(advice-add
'vertico--metadata-get :around
#'wal-with-dired-goto-file-ignored)
:bind
(:map vertico-map
("RET" . vertico-directory-enter)
("DEL" . vertico-directory-delete-char)
("M-DEL" . vertico-directory-delete-word))
:functions (vertico-exit))
Quickly jump to a candidate with a single letter.
(defun wal-vertico-quick-exit-in-case-of-single-match (&rest _args)
"Exit immediately if there is only one match."
(defvar vertico--total)
(when (= 1 vertico--total)
(vertico-exit)))
(use-package vertico-quick
:ensure nil
:demand t
:after vertico
:config
(advice-add
'vertico-quick-jump :before-until
'wal-vertico-quick-exit-in-case-of-single-match)
:custom
(vertico-quick1 "jkl;h")
(vertico-quick2 "asdfg")
:wal-bind
(:map vertico-map
("j" . vertico-quick-exit)))
Fuzzy matching while completing. The completion
settings are as per
official docs.
(use-package orderless
:demand t
:after vertico
:config
;; Setup basic completion and category defaults/overrides.
(setq completion-styles '(orderless partial-completion basic)
completion-category-defaults nil
completion-category-overrides '((file (styles partial-completion)))))
Contextual information during completion, partial completion and completion actions.
(use-package marginalia
:demand t
:after vertico
:config
(marginalia-mode)
:bind
(:map minibuffer-local-map
("C-," . marginalia-cycle))
:functions (marginalia-mode)
:defines (marginalia-annotator-registry marginalia-command-categories))
Act upon thing-at-point
, be it in a buffer or minibuffer. Sets a few
more commands in various maps. The entry point command is created
using parallel.
(defun wal-browse-html-file (filename)
"Browse FILENAME provided it's an HTML file."
(when (not (string= (file-name-extension filename) "html"))
(user-error "Can only browse HTML files"))
(browse-url (expand-file-name filename)))
(use-package embark
:config
;; Search using region.
(define-key embark-region-map
(kbd "g")
#'wal-duck-duck-go-region)
(define-key embark-file-map
(kbd "x")
#'wal-browse-html-file)
(define-key embark-buffer-map
(kbd "t")
#'wal-tab-bar-switch-to-buffer-tab)
:custom
(embark-mixed-indicator-delay 0.8)
(embark-cycle-key "C-,")
:wal-bind
(("." . embark-act)
("M-." . embark-dwim)))
Integration for consult
.
(use-package embark-consult
:demand t
:after (embark consult)
:hook (embark-collect-mode . consult-preview-at-point-mode))
Beautiful completion and narrowing within completion. This adds a new
source for projects while switching to differentiate open and closed
projects. Since consult
provides many useful commands a transient
combining the most useful ones is bound to the eponymous leader key.
(defun wal-consult-ripgrep-ignored (&optional dir initial)
"Search for regexp with rg in DIR with INITIAL input.
Do not ignore hidden files."
(interactive "P")
(declare-function consult--grep "ext:consult.el")
(declare-function consult--ripgrep-builder "ext:consult.el")
(defvar consult-ripgrep-args)
(let ((consult-ripgrep-args
(concat (substring consult-ripgrep-args 0 -1) "--no-ignore .")))
(consult--grep "Ripgrep (ignored)" #'consult--ripgrep-builder dir initial)))
(defun wal-consult-unregister ()
"Remove KEY from the register."
(interactive)
(let ((key (with-no-warnings
(consult--read
(consult-register--candidates)
:prompt "Unregister: "
:category 'multi-category
:group (consult--type-group consult-register--narrow)
:narrow (consult--type-narrow consult-register--narrow)
:sort nil
:require-match t
:history t
:lookup #'consult--lookup-candidate))))
(setq register-alist (assoc-delete-all key register-alist))))
(defun wal-consult-clock (&optional arg)
"Clock in (or out).
Only non-archived and active headings are matched.
Optional argument ARG can have one of two meanings. If it has the
numeric value of 0 this will call `wal-clock-in-from-now'. If it
has numeric value 4 (the default `universal-argument')
`org-clock-out' is called."
(interactive "p")
(require 'org-clock nil t)
(let ((stop (and arg (eq 4 arg)))
(discontinue (and arg (eq 0 arg)))
(previous (when (org-clocking-p)
(org-clocking-buffer))))
(if stop
(org-clock-out)
(save-window-excursion
(consult-org-agenda "-ARCHIVE/-DONE")
(if discontinue
(wal-org-clock-in-from-now)
(org-clock-in))))
(when previous
(with-current-buffer previous
(save-buffer)))
(when-let ((current (and (org-clocking-p) (org-clocking-buffer))))
(with-current-buffer current
(save-buffer)))))
(defun wal-consult-place (&optional prefer-outline)
"Go to a place with `consult'.
In Org buffers this is done using `consult-org-heading', in
`prog-mode' buffers this is done using `consult-imenu', otherwise
`consult-outline' is used. The latter can be forced if
PREFER-OUTLINE is t."
(interactive "P")
(if prefer-outline
(call-interactively 'consult-outline)
(cond
((derived-mode-p 'org-mode)
(consult-org-heading "-ARCHIVE"))
((derived-mode-p 'prog-mode)
(call-interactively 'consult-imenu))
(t
(call-interactively 'consult-outline)))))
(defun wal-consult-error ()
"Jump to error.
Use either `flymake' or `flycheck'."
(interactive)
(cond
((and (bound-and-true-p flycheck-mode)
(fboundp 'consult-flycheck))
(call-interactively 'consult-flycheck))
((bound-and-true-p flymake-mode)
(call-interactively 'consult-flymake))
(t
(user-error "No syntax checker"))))
(defun wal-consult-project ()
"Enhanced `project-switch-project' command."
(interactive)
(declare-function consult--multi "ext:consult.el")
(consult--multi
'(consult--source-open-projects consult--source-projects)
:prompt "Select project: "
:require-match t))
(defvar consult--project-history nil)
(defvar consult--source-projects
(list :name "Projects"
:category 'project
:history 'consult--project-history
:action 'project-switch-project
:items (lambda ()
(let ((open (consult--open-project-items))
(all (mapcar #'car project--list)))
(seq-filter (lambda (it) (not (member it open))) all)))))
(defun consult--open-project-items ()
"Get the open projects."
(cl-remove-duplicates
(cl-loop for buffer being the buffers
for project = (wal-project-buffer-root buffer)
if project
collect project)
:test 'string=))
(defvar consult--source-open-projects
(list :name "Open projects"
:category 'project
:narrow ?o
:history 'consult--project-history
:action 'project-switch-project
:items 'consult--open-project-items))
(defvar wal-consult--source-agenda-buffer
(list :name "Agenda Buffer"
:narrow ?a
:category 'buffer
:state #'consult--buffer-state
:history 'buffer-name-history
:face 'font-lock-keyword-face
:items #'wal-consult-agenda-buffer--query))
(defun wal-consult-agenda-buffer--query ()
"Get contributing Org Agenda buffer names."
(consult--buffer-query
:sort 'visibility
:as #'buffer-name
:predicate #'wal-agenda-buffer-p))
(defvar wal-consult--source-compilation-buffer
(list :name "Compilations"
:narrow ?c
:category 'buffer
:history 'buffer-name-history
:action #'wal-switch-to-buffer-obeying-display-actions
:items #'wal-consult-compilation-buffer--query))
(defun wal-compilation-buffer-p (buffer)
"Check if buffer BUFFER is a compilation buffer."
(with-current-buffer buffer
(derived-mode-p 'compilation-mode 'comint-mode)))
(defun wal-consult-compilation-buffer--query ()
"Get compilation buffer names."
(consult--buffer-query
:sort 'visibility
:as #'buffer-name
:predicate #'wal-compilation-buffer-p))
(defun wal-then-set-active-theme (theme)
"Advise to set `wal-active-theme' to THEME."
(setq wal-active-theme theme)
(when current-prefix-arg
(customize-save-variable 'wal-theme theme))
(run-hooks 'wal-theme-hook))
(use-package consult
:commands (consult--multi consult)
:config
;; Integrate with `xref'.
(setq xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
;; Customize sources.
(consult-customize
consult--source-recent-file
consult--source-project-recent-file
consult--source-project-recent-file-hidden
consult--source-bookmark
consult-recent-file
:preview-key "C-."
wal-consult-clock
:prompt "Clock in: ")
;; Be sure to set the active theme after switching.
(advice-add 'consult-theme :after #'wal-then-set-active-theme)
(advice-add 'consult :around 'wal-with-delayed-transient-popup)
(with-eval-after-load 'org-agenda
(wal-insert
'consult-buffer-sources
'consult--source-buffer
'wal-consult--source-agenda-buffer
:quiet t))
(with-eval-after-load 'org-keys
(wal-replace-in-alist
'org-speed-commands
'(("j" . consult-org-heading))))
(with-eval-after-load 'compile
(wal-insert
'consult-buffer-sources
'consult--source-buffer
'wal-consult--source-compilation-buffer
:quiet t))
(transient-define-prefix consult ()
"Run `consult' commands."
[["Goto"
("SPC" "mark" consult-mark)
("r" "register" consult-register)
("o" "outline" consult-outline)
("e" "error" wal-consult-error)]
["Find"
("m" "bookmark" consult-bookmark)
("@" "global mark" consult-global-mark)
("f" "recent file" consult-recent-file)
("a" "agenda" consult-org-agenda)]
["Search"
("n" "grep" consult-ripgrep)
("l" "locate" consult-locate)]
["Do"
("k" "call macro" consult-kmacro)
("t" "change theme" consult-theme)]]
[["Modes"
("+" "major mode command" consult-mode-command)
("-" "toggle minor mode" consult-minor-mode-menu)]])
:general
(adjunct "u" 'wal-consult-unregister)
:bind
(:map isearch-mode-map
("M-s ," . consult-line))
:wal-bind
(("<SPC>" . wal-consult-clock)
("i" . wal-consult-place)
("'" . wal-consult-project)
("M-l" . consult-goto-line)
("," . consult-buffer)
("M-," . consult)))
(provide 'wal-complete)
;;; wal-complete.el ends here