I will be walking through my personal Emacs' minibuffer UI (which means packages like Selectrum, Ido, Helm, Vertico, and Ivy) configuration, which includes the following packages:
Marginalia — minibuffer annotations, i.e., auxiliary candidate information
Orderless — a flexible completion-style with multi-component matching
Note: I use straight.el for package management and general.el to set my keybindings. Both of these packages have integration with use-package which come in the form of the :straight and :general keywords, respectively.
Marginalia
The following is vertico with marginalia annotations right-aligned in the minibuffer.
Marginalia is painless to set up. Remember, I use general.el to set keybindings:
Note: All-the-icons-completion depends on an already installed all-the-icons.
Vertico
Vertico is a minibuffer interface, that is, it changes the minibuffer looks and how you interact with it.
Basic
This is a very basic Vertico configuration.
1
2
3
4
5
6
7
(use-packagevertico:custom(vertico-count13); Number of candidates to display(vertico-resizet)(vertico-cyclenil); Go from last to first candidate and first to last (cycle)?:config(vertico-mode))
Now we can add a few changes to the default keybindings (again, I use general.el to set keybindings):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(use-packagevertico:custom(vertico-count13); Number of candidates to display(vertico-resizet)(vertico-cyclenil); Go from last to first candidate and first to last (cycle)?:general(:keymaps'vertico-map"<tab>"#'vertico-insert; Insert selected candidate into text area"<escape>"#'minibuffer-keyboard-quit; Close minibuffer;; NOTE 2022-02-05: Cycle through candidate groups"C-M-n"#'vertico-next-group"C-M-p"#'vertico-previous-group):config(vertico-mode))
Extensions
Vertico becomes much more interesting with its extensions. These extensions have to manually be cloned from the repo with a corresponding require invocation. However, with straight.el, we can do something like this to install and load them:
(use-packagevertico;; Special recipe to load extensions conveniently:straight(vertico:files(:defaults"extensions/*"):includes(vertico-indexedvertico-flatvertico-gridvertico-mousevertico-quickvertico-buffervertico-repeatvertico-reversevertico-directoryvertico-multiformvertico-unobtrusive)):general(:keymaps'vertico-map"<tab>"#'vertico-insert; Choose selected candidate"<escape>"#'minibuffer-keyboard-quit; Close minibuffer;; NOTE 2022-02-05: Cycle through candidate groups"C-M-n"#'vertico-next-group"C-M-p"#'vertico-previous-group):custom(vertico-count13); Number of candidates to display(vertico-resizet)(vertico-cyclenil); Go from last to first candidate and first to last (cycle)?:config(vertico-mode))
With the extensions installed, we have a lot of room to set their variables to our liking. In particular, vertico-multiform is the most interesting and, in my opinion, useful. You can choose how the minibuffer appears, which is provided by Vertico’s extensions (see more here). The current options are
buffer — minibuffer treated as a normal, separate buffer,
flat — a flat format (like ido),
grid — a grid format,
reverse — moves the area in which you type above the candidates, like selectrum,
unobtrusive — like the vertico-flat format, but only showing the selected candidate,
These formats each have a separate minor-mode which can be activated if you want to use that format singly. However, with vertico-multiform-mode as well as configuration of vertico-multiform-categories, vertico-multiform-commands, and keybinds, you can enable/disable multiple of these formats simultaneously:
1
(vertico-multiform-mode)
Here are the extension variables I set in the :custom block of my use-package:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(vertico-grid-separator" ")(vertico-grid-lookahead50)(vertico-buffer-display-action'(display-buffer-reuse-window)); Default(vertico-multiform-categories; Choose a multiform'((filereverse)(consult-grepbuffer)(consult-location)(imenubuffer)(libraryreverseindexed)(org-roam-nodereverseindexed)(treverse)))(vertico-multiform-commands'(("flyspell-correct-*"gridreverse)(org-refilegridreverseindexed)(consult-yank-popindexed)(consult-flycheck)(consult-lsp-diagnostics)))
Here are the vertico-multiform related keybinds I have in my :general block:
1
2
3
4
5
6
7
8
(:keymaps'vertico-map;; Toggle Vertico multiforms in active minibuffer"C-i"#'vertico-quick-insert"C-o"#'vertico-quick-exit"M-G"#'vertico-multiform-grid"M-F"#'vertico-multiform-flat"M-R"#'vertico-multiform-reverse"M-U"#'vertico-multiform-unobtrusive)
These are rarely used, but can be handy.
Finally, the other Vertico extensions I configure are vertico-repeat and vertico-directory. I add the following keybinds:
1
2
3
4
5
6
7
8
9
(:keymaps'(normalinsertvisualmotion)"M-."#'vertico-repeat); Perfectly return to the state of the last Vertico minibuffer usage(:keymaps'vertico-map;; Vertico-directory which makes typing file paths in the minibuffer;; more convenient. Use it to get a sense of what these do"<backspace>"#'vertico-directory-delete-char"C-w"#'vertico-directory-delete-word"C-<backspace>"#'vertico-directory-delete-word"RET"#'vertico-directory-enter)
Additionally, we need to add the following hook for vertico-repeat:
1
:hook(minibuffer-setup.vertico-repeat-save); Make sure vertico state is saved for `vertico-repeat'
Vertico extension commands
If you’d like, you can set up a toggle between two multiforms in the minibuffer. For instance, the following function definition with a corresponding keybind in vertico-map toggles between vertico-flat-mode and vertico-reverse-mode:
1
2
3
4
5
6
7
(defunkb/vertico-multiform-flat-toggle()"Toggle between flat and reverse."(interactive)(vertico-multiform--display-toggle'vertico-flat-mode)(ifvertico-flat-mode(vertico-multiform--temporary-mode'vertico-reverse-mode-1)(vertico-multiform--temporary-mode'vertico-reverse-mode1)))
The following function uses vertico-quick-jump (like avy but for minibuffer candidates) to embark-act on a candidate without having to first hover over the candidate:
1
2
3
4
5
(defunkb/vertico-quick-embark(&optionalarg)"Embark on candidate using quick keys."(interactive)(when(vertico-quick-jump)(embark-actarg)))
Niceties
Finally, I add a few niceties.
The following prefixes (i.e. in the left fringe) and arrow character on the currently selected candidate1:
1
2
3
4
5
6
7
8
9
10
;; Prefix the current candidate with “» ”. From;; https://github.com/minad/vertico/wiki#prefix-current-candidate-with-arrow(advice-add#'vertico--format-candidate:around(lambda(origcandprefixsuffixindex_start)(setqcand(funcallorigcandprefixsuffixindex_start))(concat(if(=vertico--indexindex)(propertize"» "'face'vertico-current)" ")cand)))
When using a command for selecting a file in the minibuffer, the following fixes the path so the path you select doesn’t have prepended junk left behind2:
1
:hook(rfn-eshadow-update-overlay.vertico-directory-tidy); Correct file path when changed
Finally, the following makes working with remote files via tramp easier.
1
2
3
4
5
6
7
8
9
10
11
12
;; Workaround for problem with `tramp' hostname completions. This overrides;; the completion style specifically for remote files! See;; https://github.com/minad/vertico#tramp-hostname-completion(defunkb/basic-remote-try-completion(stringtablepredpoint)(and(vertico--remote-pstring)(completion-basic-try-completionstringtablepredpoint)))(defunkb/basic-remote-all-completions(stringtablepredpoint)(and(vertico--remote-pstring)(completion-basic-all-completionsstringtablepredpoint)))(add-to-list'completion-styles-alist'(basic-remote; Name of `completion-style'kb/basic-remote-try-completionkb/basic-remote-all-completionsnil))
(use-packagevertico:demandt; Otherwise won't get loaded immediately:straight(vertico:files(:defaults"extensions/*"); Special recipe to load extensions conveniently:includes(vertico-indexedvertico-flatvertico-gridvertico-mousevertico-quickvertico-buffervertico-repeatvertico-reversevertico-directoryvertico-multiformvertico-unobtrusive)):general(:keymaps'(normalinsertvisualmotion)"M-."#'vertico-repeat)(:keymaps'vertico-map"<tab>"#'vertico-insert; Set manually otherwise setting `vertico-quick-insert' overrides this"<escape>"#'minibuffer-keyboard-quit"?"#'minibuffer-completion-help"C-M-n"#'vertico-next-group"C-M-p"#'vertico-previous-group;; Multiform toggles"<backspace>"#'vertico-directory-delete-char"C-w"#'vertico-directory-delete-word"C-<backspace>"#'vertico-directory-delete-word"RET"#'vertico-directory-enter"C-i"#'vertico-quick-insert"C-o"#'vertico-quick-exit"M-o"#'kb/vertico-quick-embark"M-G"#'vertico-multiform-grid"M-F"#'vertico-multiform-flat"M-R"#'vertico-multiform-reverse"M-U"#'vertico-multiform-unobtrusive"C-l"#'kb/vertico-multiform-flat-toggle):hook((rfn-eshadow-update-overlay.vertico-directory-tidy); Clean up file path when typing(minibuffer-setup.vertico-repeat-save); Make sure vertico state is saved):custom(vertico-count13)(vertico-resizet)(vertico-cyclenil);; Extensions(vertico-grid-separator" ")(vertico-grid-lookahead50)(vertico-buffer-display-action'(display-buffer-reuse-window))(vertico-multiform-categories'((filereverse)(consult-grepbuffer)(consult-location)(imenubuffer)(libraryreverseindexed)(org-roam-nodereverseindexed)(treverse)))(vertico-multiform-commands'(("flyspell-correct-*"gridreverse)(org-refilegridreverseindexed)(consult-yank-popindexed)(consult-flycheck)(consult-lsp-diagnostics))):init(defunkb/vertico-multiform-flat-toggle()"Toggle between flat and reverse."(interactive)(vertico-multiform--display-toggle'vertico-flat-mode)(ifvertico-flat-mode(vertico-multiform--temporary-mode'vertico-reverse-mode-1)(vertico-multiform--temporary-mode'vertico-reverse-mode1)))(defunkb/vertico-quick-embark(&optionalarg)"Embark on candidate using quick keys."(interactive)(when(vertico-quick-jump)(embark-actarg)));; Workaround for problem with `tramp' hostname completions. This overrides;; the completion style specifically for remote files! See;; https://github.com/minad/vertico#tramp-hostname-completion(defunkb/basic-remote-try-completion(stringtablepredpoint)(and(vertico--remote-pstring)(completion-basic-try-completionstringtablepredpoint)))(defunkb/basic-remote-all-completions(stringtablepredpoint)(and(vertico--remote-pstring)(completion-basic-all-completionsstringtablepredpoint)))(add-to-list'completion-styles-alist'(basic-remote; Name of `completion-style'kb/basic-remote-try-completionkb/basic-remote-all-completionsnil)):config(vertico-mode);; Extensions(vertico-multiform-mode);; Prefix the current candidate with “» ”. From;; https://github.com/minad/vertico/wiki#prefix-current-candidate-with-arrow(advice-add#'vertico--format-candidate:around(lambda(origcandprefixsuffixindex_start)(setqcand(funcallorigcandprefixsuffixindex_start))(concat(if(=vertico--indexindex)(propertize"» "'face'vertico-current)" ")cand))))
Orderless
Orderless is an alternative and powerful completion style, that is, it is an alternative to Emacs’s basic candidate-filtering capacities.
Basic
To use orderless you simply need the following.
1
2
3
4
5
6
7
(use-packageorderless:custom(completion-styles'(orderless)); Use orderless(completion-category-defaultsnil); I want to be in control!(completion-category-overrides'((file(stylesbasic-remote; For `tramp' hostname completion with `vertico'orderless)))))
However, we want to be more interesting! The following configures the matching styles that orderless uses. A matching style is a criterion for what is a valid candidate (a description of what each matching style does can be found in Orderless’s readme):
Orderless becomes much more powerful when using its style dispatchers. A description of style dispatches can be found here. Essentially, you can choose which matching style is used for a particular orderless component. Thus, one component can use the orderless-initialism matching style while the next can use the orderless-literal matching style, and the result will be candidates which match both styles:
;; The following is taken directly from Protesilaos's Emacs configuration, with;; very minor changes. See;; https://gitlab.com/protesilaos/dotfiles/-/blob/master/emacs/.emacs.d/prot-emacs.el:custom(orderless-style-dispatchers'(prot-orderless-literal-dispatcher; = suffix for literalprot-orderless-strict-initialism-dispatcher; , suffix for initialismprot-orderless-flex-dispatcher; . suffix for flex)):init(defunorderless--strict-*-initialism(component&optionalanchored)"Match a COMPONENT as a strict initialism, optionally ANCHORED.
The characters in COMPONENT must occur in the candidate in that
order at the beginning of subsequent words comprised of letters.
Only non-letters can be in between the words that start with the
initials.
If ANCHORED is `start' require that the first initial appear in
the first word of the candidate. If ANCHORED is `both' require
that the first and last initials appear in the first and last
words of the candidate, respectively."(orderless--separated-by'(seq(zero-or-morealpha)word-end(zero-or-more(notalpha)))(cl-loopforcharacrosscomponentcollect`(seqword-start,char))(whenanchored'(seq(groupbuffer-start)(zero-or-more(notalpha))))(when(eqanchored'both)'(seq(zero-or-morealpha)word-end(zero-or-more(notalpha))eol))))(defunorderless-strict-initialism(component)"Match a COMPONENT as a strict initialism.
This means the characters in COMPONENT must occur in the
candidate in that order at the beginning of subsequent words
comprised of letters. Only non-letters can be in between the
words that start with the initials."(orderless--strict-*-initialismcomponent))(defunprot-orderless-literal-dispatcher(pattern_index_total)"Literal style dispatcher using the equals sign as a suffix.
It matches PATTERN _INDEX and _TOTAL according to how Orderless
parses its input."(when(string-suffix-p"="pattern)`(orderless-literal.,(substringpattern0-1))))(defunprot-orderless-strict-initialism-dispatcher(pattern_index_total)"Leading initialism dispatcher using the comma suffix.
It matches PATTERN _INDEX and _TOTAL according to how Orderless
parses its input."(when(string-suffix-p","pattern)`(orderless-strict-initialism.,(substringpattern0-1))))(defunprot-orderless-flex-dispatcher(pattern_index_total)"Flex dispatcher using the tilde suffix.
It matches PATTERN _INDEX and _TOTAL according to how Orderless
parses its input."(when(string-suffix-p"."pattern)`(orderless-flex.,(substringpattern0-1))))
Finally, rather than using the default “+ “ to separate components, I use a space instead:
1
2
:custom(orderless-component-separator'orderless-escapable-split-on-space); Use backslash for literal space
(use-packageorderless:custom(completion-styles'(orderless))(completion-category-defaultsnil); I want to be in control!(completion-category-overrides'((file(stylesbasic-remote; For `tramp' hostname completion with `vertico'orderless))))(orderless-component-separator'orderless-escapable-split-on-space)(orderless-matching-styles'(orderless-literalorderless-prefixesorderless-initialismorderless-regexp;; orderless-flex;; orderless-strict-leading-initialism;; orderless-strict-initialism;; orderless-strict-full-initialism;; orderless-without-literal ; Recommended for dispatches instead))(orderless-style-dispatchers'(prot-orderless-literal-dispatcherprot-orderless-strict-initialism-dispatcherprot-orderless-flex-dispatcher)):init(defunorderless--strict-*-initialism(component&optionalanchored)"Match a COMPONENT as a strict initialism, optionally ANCHORED.
The characters in COMPONENT must occur in the candidate in that
order at the beginning of subsequent words comprised of letters.
Only non-letters can be in between the words that start with the
initials.
If ANCHORED is `start' require that the first initial appear in
the first word of the candidate. If ANCHORED is `both' require
that the first and last initials appear in the first and last
words of the candidate, respectively."(orderless--separated-by'(seq(zero-or-morealpha)word-end(zero-or-more(notalpha)))(cl-loopforcharacrosscomponentcollect`(seqword-start,char))(whenanchored'(seq(groupbuffer-start)(zero-or-more(notalpha))))(when(eqanchored'both)'(seq(zero-or-morealpha)word-end(zero-or-more(notalpha))eol))))(defunorderless-strict-initialism(component)"Match a COMPONENT as a strict initialism.
This means the characters in COMPONENT must occur in the
candidate in that order at the beginning of subsequent words
comprised of letters. Only non-letters can be in between the
words that start with the initials."(orderless--strict-*-initialismcomponent))(defunprot-orderless-literal-dispatcher(pattern_index_total)"Literal style dispatcher using the equals sign as a suffix.
It matches PATTERN _INDEX and _TOTAL according to how Orderless
parses its input."(when(string-suffix-p"="pattern)`(orderless-literal.,(substringpattern0-1))))(defunprot-orderless-strict-initialism-dispatcher(pattern_index_total)"Leading initialism dispatcher using the comma suffix.
It matches PATTERN _INDEX and _TOTAL according to how Orderless
parses its input."(when(string-suffix-p","pattern)`(orderless-strict-initialism.,(substringpattern0-1))))(defunprot-orderless-flex-dispatcher(pattern_index_total)"Flex dispatcher using the tilde suffix.
It matches PATTERN _INDEX and _TOTAL according to how Orderless
parses its input."(when(string-suffix-p"."pattern)`(orderless-flex.,(substringpattern0-1)))))
Changelog
February 22, 2022 Added demonstrative images and a GIF. Added section on all-the-icons-completion.
This does add width to the left side of the minibuffer, which may interfere with your aesthetic-related configurations of other packages. ↩︎
This relies on the vertico-directory extension. ↩︎
This is a direct copy and paste from my configuration file. There are slight differences in comments compared to the code snippets above. ↩︎
Again, taken verbatim from my Emacs configuration, with fewer useful comments. ↩︎
The Text Completion and Minibuffer UI series
This post is just one installation of the Text Completion and Minibuffer UI series. Below is a list of all the posts in this series: