Skip to content
Daniel Mendler edited this page Aug 7, 2022 · 76 revisions

Vertico offers a set of small extension packages, which are included in the Vertico package. They can also be installed individually, e.g., only vertico.el and vertico-directory.el. Here we document a few additional tweaks and helpful commands, which can be added to user configurations.

Restrict the set of candidates

If you use Orderless you can restrict the set of candidates to the currently visible candidates. This functionality is present in Ivy and bound to the key S-SPC. In Vertico we can use this small command:

(defun +vertico-restrict-to-matches ()
  (interactive)
  (let ((inhibit-read-only t))
    (goto-char (point-max))
    (insert " ")
    (add-text-properties (minibuffer-prompt-end) (point-max)
                         '(invisible t read-only t cursor-intangible t rear-nonsticky t))))

(define-key vertico-map (kbd "S-SPC") #'+vertico-restrict-to-matches)

Automatically shrink Vertico for embark-live

(defun +embark-live-vertico ()
  "Shrink Vertico minibuffer when `embark-live' is active."
  (when-let (win (and (string-prefix-p "*Embark Live" (buffer-name))
                      (active-minibuffer-window)))
    (with-selected-window win
      (when (and (bound-and-true-p vertico--input)
                 (fboundp 'vertico-multiform-unobtrusive))
        (vertico-multiform-unobtrusive)))))

(add-hook 'embark-collect-mode-hook #'+embark-live-vertico)

Adjust number of visible candidates when buffer is resized

When resizing the minibuffer (e.g., via the mouse), adjust the number of visible candidates in Vertico automatically.

(defun vertico-resize--minibuffer ()
  (add-hook 'window-size-change-functions
            (lambda (win)
              (let ((height (window-height win)))
                (when (/= (1- height) vertico-count)
                  (setq-local vertico-count (1- height))
                  (vertico--exhibit))))
            t t))

(advice-add #'vertico--setup :before #'vertico-resize--minibuffer)

Prefix current candidate with arrow

Prefix the current candidate with “» “.

(advice-add #'vertico--format-candidate :around
            (lambda (orig cand prefix suffix index _start)
              (setq cand (funcall orig cand prefix suffix index _start))
              (concat
               (if (= vertico--index index)
                   (propertize "» " 'face 'vertico-current)
                 "  ")
               cand)))

Customize sorting based on completion category

The default sorting function vertico-sort-function can be adjusted based on the completion category if you use vertico-multiform-mode. See the README for more details regarding vertico-multiform-mode.

Note that the default sorting function vertico-sort-function is only used if the completion command (completion table) doesn’t specify its own display-sort-function. If you still want to override this setting you can use set the vertico-sort-override-function.

;; Configure the default sorting function for symbols and files
;; See `vertico-sort-function'.
(setq vertico-multiform-categories
      '((symbol (vertico-sort-function . vertico-sort-alpha))
        (file (vertico-sort-function . sort-directories-first))))

;; Forcibly override the sorting function for `consult-line'.
;; See `vertico-sort-override-function'.
(setq vertico-multiform-commands
      '((consult-line (vertico-sort-override-function . vertico-sort-alpha))))

(defun sort-directories-first (files)
  ;; Still sort by history position, length and alphabetically
  (setq files (vertico-sort-history-length-alpha files))
  ;; But then move directories first
  (nconc (seq-filter (lambda (x) (string-suffix-p "/" x)) files)
         (seq-remove (lambda (x) (string-suffix-p "/" x)) files)))

Candidate display transformations, custom highlighting

(defvar +vertico-transform-functions nil)

(defun +vertico-transform (args)
  (dolist (fun (ensure-list +vertico-transform-functions) args)
    (setcar args (funcall fun (car args)))))

(advice-add #'vertico--format-candidate :filter-args #'+vertico-transform)

(defun +vertico-highlight-directory (file)
  "Highlight FILE if it ends with a slash."
  (if (string-suffix-p "/" file)
      (propertize file 'face 'marginalia-file-priv-dir)
    file))

(setq vertico-multiform-commands
      '(("find-file" flat
         (vertico-sort-function . sort-directories-first)
         (+vertico-transform-functions . +vertico-highlight-directory))))

Customize highlighting based on completion-category

You can customise the highlighting of completion candidates based on completion category. For example those coming from ivy/counsel maybe missing the ability to visually distinguish directories from files in counsel-find-file. You can set up something similar with vertico.

(defun +completion-category-highlight-files (cand)
  (let ((len (length cand)))
    (when (and (> len 0)
               (eq (aref cand (1- len)) ?/))
      (add-face-text-property 0 len 'dired-directory 'append cand)))
  cand)

(defvar +completion-category-hl-func-overrides
  `((file . ,#'+completion-category-highlight-files))
  "Alist mapping category to highlight functions.")

(advice-add #'vertico--arrange-candidates :around
            (defun vertico-format-candidates+ (func)
              (let ((hl-func (or (alist-get (vertico--metadata-get 'category)
                                            +completion-category-hl-func-overrides)
                                 #'identity)))
                (cl-letf* (((symbol-function 'actual-vertico-format-candidate)
                            (symbol-function #'vertico--format-candidate))
                           ((symbol-function #'vertico--format-candidate)
                            (lambda (cand &rest args)
                              (apply #'actual-vertico-format-candidate
                                     (funcall hl-func cand) args))))
                  (funcall func)))))

And the following could be added to the previous code to highlight enabled modes in the command palette (M-x).

(defun +completion-category-highlight-commands (cand)
  (let ((len (length cand)))
    (when (and (> len 0)
               (with-current-buffer (nth 1 (buffer-list)) ; get buffer before minibuffer
                 (or (eq major-mode (intern cand)) ; check major mode
                     (ignore-errors (auto-minor-mode-enabled-p (intern cand)))))) ; check minor modes
      (add-face-text-property 0 len '(:foreground "red") 'append cand))) ; choose any color or face you like
  cand)

(add-to-list '+completion-category-hl-func-overrides `(command . ,#'+completion-category-highlight-commands))

Useful commands from outside minibuffer

These are useful if you start doing something in the minibuffer and go to another window before the minibuffer command is finished; eg if you’re using consult-line to move around to different search matches, but also edit the buffer being searched. These commands probably work with other styles of minibuffer in addition to vertico.

(defun down-from-outside ()
  "Move to next candidate in minibuffer, even when minibuffer isn't selected."
  (interactive)
  (with-selected-window (active-minibuffer-window)
    (execute-kbd-macro [down])))

(defun up-from-outside ()
  "Move to previous candidate in minibuffer, even when minibuffer isn't selected."
  (interactive)
  (with-selected-window (active-minibuffer-window)
    (execute-kbd-macro [up])))

(defun to-and-fro-minibuffer ()
  "Go back and forth between minibuffer and other window."
  (interactive)
  (if (window-minibuffer-p (selected-window))
      (select-window (minibuffer-selected-window))
    (select-window (active-minibuffer-window))))

Move annotations in front of the candidate

(defun vertico--swap-annotations (result)
  ;; Move annotations only for files
  (if minibuffer-completing-file-name
      (mapcar (lambda (x)
                ;; Swap prefix/suffix annotations
                (list (car x) (concat (string-trim-left (caddr x)) " ") (cadr x)))
              result)
    result))
(advice-add #'vertico--affixate :filter-return #'vertico--swap-annotations)

Additions for moving up and down directories in find-file

Altered from vertico-directory-up to alternatively remove the entire entry after the last “/”.

(defun vertico-directory-delete-entry ()
  "Delete directory or entire entry before point."
  (interactive)
  (when (and (> (point) (minibuffer-prompt-end))
             (eq 'file (vertico--metadata-get 'category)))
    (save-excursion
      (goto-char (1- (point)))
      (when (search-backward "/" (minibuffer-prompt-end) t)
        (delete-region (1+ (point)) (point-max))
        t))))

You can use vertico-directory-enter to enter a directory or open a file depending on what is selected.

Update minibuffer history with candidate insertions

This advice to vertico-insert can be used to keep track of which directories you have visited in find-file. Normally candidates are only added to the history on vertico-exit (viewing them in a buffer). With this and sorting by history, the most recently visited folders will show up on top.

(defadvice vertico-insert
    (after vertico-insert-add-history activate)
  "Make vertico-insert add to the minibuffer history."
  (unless (eq minibuffer-history-variable t)
    (add-to-history minibuffer-history-variable (minibuffer-contents))))

Pre-select previous directory when entering parent directory from within find-file

Advise vertico-directory-up to save the directory being exited.

(defvar previous-directory nil
    "The directory that was just left. It is set when leaving a directory and
    set back to nil once it is used in the parent directory.")

(defun set-previous-directory ()
  "Set the directory that was just exited from within find-file."
  (when (> (minibuffer-prompt-end) (point))
    (save-excursion
      (goto-char (1- (point)))
      (when (search-backward "/" (minibuffer-prompt-end) t)
        ;; set parent directory
        (setq previous-directory (buffer-substring (1+ (point)) (point-max)))
        ;; set back to nil if not sorting by directories or what was deleted is not a directory
        (when (not (string-suffix-p "/" previous-directory))
          (setq previous-directory nil))
        t))))

(advice-add #'vertico-directory-up :before #'set-previous-directory)

Advise vertico--update-candidates to select the previous directory.

(define-advice vertico--update-candidates (:after (&rest _) choose-candidate)
    "Pick the previous directory rather than the prompt after updating candidates."
    (cond
     (previous-directory ; select previous directory
      (setq vertico--index (or (seq-position vertico--candidates previous-directory)
                               vertico--index))
      (setq previous-directory nil))))

Left-truncate recentf filename candidates (e.g. for consult-buffer)

Marginalia does a nice job left-truncating filenames associated with buffers. But recentf files can also be quite long, crowding out the marginalia info. To left-truncate long filenames with vertico, you can use (adjusting the amount of room saved as needed):

(defun my/vertico-truncate-candidates (args)
  (if-let ((arg (car args))
           (type (get-text-property 0 'multi-category arg))
           ((eq (car-safe type) 'file))
           (w (max 30 (- (window-width) 38)))
           (l (length arg))
           ((> l w)))
      (setcar args (concat "" (truncate-string-to-width arg l (- l w)))))
  args)
(advice-add #'vertico--format-candidate :filter-args #'my/vertico-truncate-candidates)

Input at bottom of completion list

;; Adapted from vertico-reverse
(defun vertico-bottom--display-candidates (lines)
  "Display LINES in bottom."
  (move-overlay vertico--candidates-ov (point-min) (point-min))
  (unless (eq vertico-resize t)
    (setq lines (nconc (make-list (max 0 (- vertico-count (length lines))) "\n") lines)))
  (let ((string (apply #'concat lines)))
    (add-face-text-property 0 (length string) 'default 'append string)
    (overlay-put vertico--candidates-ov 'before-string string)
    (overlay-put vertico--candidates-ov 'after-string nil))
  (vertico--resize-window (length lines)))

(advice-add #'vertico--display-candidates :override #'vertico-bottom--display-candidates)

Ding when wrapping around

https://old.reddit.com/r/emacs/comments/rbmfwk/weekly_tips_tricks_c_thread/hof7rz7/

(advice-add #'vertico-next
            :around
            #'(lambda (origin &rest args)
                (let ((beg-index vertico--index))
                  (apply origin args)
                  (if (not (eq 1 (abs (- beg-index vertico--index))))
                      (ding)))))

Restore old TAB behavior when completing TRAMP paths

Binding this function to TAB in vertico-map temporarily disables vertico while completing remote paths to restore the shell-like TAB-completes-common-prefix behavior. This is a usability trade-off to work around a peculiarity in TRAMP’s hostname completion.

https://github.com/minad/vertico/issues/240

 (defun my-vertico-insert-unless-tramp ()
   "Insert current candidate in minibuffer, except for tramp."
   (interactive)
   (if (vertico--remote-p (vertico--candidate))
	(minibuffer-complete)
     (vertico-insert)))

Using prescient.el

prescient.el is a sorting and filtering package. prescient.el’s filtering feature cannot currently be used with Vertico, as prescient.el is not implemented as a completion style (unlike Orderless).

Sorting in prescient.el is based on frecency, a combination of frequency and recency. Unlike Vertico’s default sorting method, which uses a command’s minibuffer history, with prescient.el, all commands (except for those specifying their own sort order) are sorted in the same way, using the same frecency history. For example, A candidate chosen in command foo1 using history variable foo1-hist will be sorted near the top when running command foo2 using history variable foo2-hist.

Sorting is performed by the function prescient-sort, which can be used as Vertico’s sort function. In order to make prescient.el remember a string, it must be passed to the function prescient-remember. For most cases, it is sufficient to advise the command vertico-insert (which is used by vertico-exit). This will not record the submission of arbitrary input using M-RET, but such arbitrary input usually does not occur in candidate lists anyway.

The below code enables prescient.el sorting for Vertico:

(autoload 'prescient-sort "prescient")
(setq vertico-sort-function #'prescient-sort)
(advice-add #'vertico-insert :after
            (lambda () (prescient-remember (vertico--candidate))))
(prescient-persist-mode 1)

The above mode prescient-persist-mode causes prescient.el’s history to be saved to disk, allowing the sorting to improve over a longer period of time.

Because filtering with prescient.el is not supported, certain sorting features that rely on both sorting and filtering, such as prescient-sort-full-matches-first, are not supported.

Clone this wiki locally