vertico-buffer.el (9929B)
1 ;;; vertico-buffer.el --- Display Vertico like a regular buffer -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2021-2024 Free Software Foundation, Inc. 4 5 ;; Author: Daniel Mendler <mail@daniel-mendler.de> 6 ;; Maintainer: Daniel Mendler <mail@daniel-mendler.de> 7 ;; Created: 2021 8 ;; Package-Requires: ((emacs "28.1") (compat "30") (vertico "1.9")) 9 ;; URL: https://github.com/minad/vertico 10 11 ;; This file is part of GNU Emacs. 12 13 ;; This program is free software: you can redistribute it and/or modify 14 ;; it under the terms of the GNU General Public License as published by 15 ;; the Free Software Foundation, either version 3 of the License, or 16 ;; (at your option) any later version. 17 18 ;; This program is distributed in the hope that it will be useful, 19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 ;; GNU General Public License for more details. 22 23 ;; You should have received a copy of the GNU General Public License 24 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 25 26 ;;; Commentary: 27 28 ;; This package is a Vertico extension, which displays Vertico like a 29 ;; regular buffer in a large window instead of the miniwindow. The 30 ;; buffer display can be enabled by the `vertico-buffer-mode'. 31 32 ;; The mode can be enabled globally or via `vertico-multiform-mode' 33 ;; per command or completion category. Alternatively the buffer 34 ;; display can be toggled temporarily with M-B if 35 ;; `vertico-multiform-mode' is enabled. 36 37 ;;; Code: 38 39 (require 'vertico) 40 41 (defcustom vertico-buffer-hide-prompt t 42 "Hide prompt in the minibuffer." 43 :group 'vertico 44 :type 'boolean) 45 46 (defcustom vertico-buffer-display-action 47 '(display-buffer-use-least-recent-window) 48 "Display action for the Vertico buffer." 49 :group 'vertico 50 :type `(choice 51 (const :tag "Least recently used window" 52 (display-buffer-use-least-recent-window)) 53 (const :tag "Reuse some window" 54 (display-buffer-reuse-window)) 55 (const :tag "Left of current window" 56 (display-buffer-in-direction 57 (direction . left) 58 (window-width . 0.3))) 59 (const :tag "Right of current window" 60 (display-buffer-in-direction 61 (direction . right) 62 (window-height . 0.3))) 63 (const :tag "Above current window" 64 (display-buffer-in-direction 65 (direction . above) 66 (window-height . ,(+ 3 vertico-count)))) 67 (const :tag "Below current window" 68 (display-buffer-in-direction 69 (direction . below) 70 (window-height . ,(+ 3 vertico-count)))) 71 (const :tag "Bottom of frame" 72 (display-buffer-at-bottom 73 (window-height . ,(+ 3 vertico-count)))) 74 (const :tag "Side window on the right" 75 (display-buffer-in-side-window 76 (side . right) 77 (window-width . 0.3))) 78 (const :tag "Side window on the left" 79 (display-buffer-in-side-window 80 (side . left) 81 (window-width . 0.3))) 82 (const :tag "Side window at the top" 83 (display-buffer-in-side-window 84 (window-height . ,(+ 3 vertico-count)) 85 (side . top))) 86 (const :tag "Side window at the bottom" 87 (display-buffer-in-side-window 88 (window-height . ,(+ 3 vertico-count)) 89 (side . bottom))) 90 (sexp :tag "Other"))) 91 92 (defvar-local vertico-buffer--restore nil) 93 94 (defun vertico-buffer--redisplay (win) 95 "Redisplay window WIN." 96 (when-let ((mbwin (active-minibuffer-window)) 97 ((eq (window-buffer mbwin) (current-buffer)))) 98 (unless (eq win mbwin) 99 (setq-local truncate-lines (< (window-point win) 100 (* 0.8 (window-width win)))) 101 (set-window-point win (point)) 102 (set-window-hscroll win 0)) 103 (when vertico-buffer-hide-prompt 104 (window-resize mbwin (- (window-pixel-height mbwin)) nil nil 'pixelwise) 105 (set-window-vscroll mbwin 3)) 106 (when transient-mark-mode 107 (with-silent-modifications 108 (vertico--remove-face (point-min) (point-max) 'region) 109 (when (use-region-p) 110 (add-face-text-property 111 (max (minibuffer-prompt-end) (region-beginning)) 112 (region-end) 'region)))) 113 (let ((old cursor-in-non-selected-windows) 114 (new (and (eq (selected-window) mbwin) 115 (if (memq cursor-type '(nil t)) 'box cursor-type)))) 116 (unless (eq new old) 117 (setq-local cursor-in-non-selected-windows new) 118 (force-mode-line-update t))))) 119 120 (defun vertico-buffer--setup () 121 "Setup buffer display." 122 (let* ((action vertico-buffer-display-action) 123 (old-wins (mapcar (lambda (w) (cons w (window-buffer w))) (window-list))) 124 win old-buf tmp-buf 125 (_ (unwind-protect 126 (progn 127 (with-current-buffer 128 (setq tmp-buf (generate-new-buffer "*vertico-buffer*")) 129 ;; Set a fake major mode such that 130 ;; `display-buffer-reuse-mode-window' does not take over! 131 (setq major-mode 'vertico-buffer-mode)) 132 ;; Temporarily select the original window such that 133 ;; `display-buffer-same-window' works. 134 (setq win (with-minibuffer-selected-window 135 (display-buffer tmp-buf action)) 136 old-buf (alist-get win old-wins)) 137 (set-window-buffer win (current-buffer))) 138 (kill-buffer tmp-buf))) 139 (old-no-other (window-parameter win 'no-other-window)) 140 (old-no-delete (window-parameter win 'no-delete-other-windows)) 141 (old-state (buffer-local-set-state 142 cursor-in-non-selected-windows cursor-in-non-selected-windows 143 show-trailing-whitespace nil 144 truncate-lines t 145 face-remapping-alist (copy-tree `((mode-line-inactive mode-line) 146 ,@face-remapping-alist)) 147 mode-line-format 148 (list (format #(" %s%s " 1 3 (face mode-line-buffer-id)) 149 (replace-regexp-in-string ":? *\\'" "" 150 (minibuffer-prompt)) 151 (let ((depth (recursion-depth))) 152 (if (< depth 2) "" (format " [%s]" depth))))) 153 vertico-count (- (/ (window-pixel-height win) 154 (default-line-height)) 2)))) 155 (set-window-parameter win 'no-other-window t) 156 (set-window-parameter win 'no-delete-other-windows t) 157 (set-window-dedicated-p win t) 158 (overlay-put vertico--candidates-ov 'window win) 159 (when (and vertico-buffer-hide-prompt vertico--count-ov) 160 (overlay-put vertico--count-ov 'window win)) 161 (setq-local vertico-buffer--restore (make-symbol "vertico-buffer--restore")) 162 (fset vertico-buffer--restore 163 (lambda () 164 (with-selected-window (active-minibuffer-window) 165 (when vertico-buffer--restore 166 (when transient-mark-mode 167 (with-silent-modifications 168 (vertico--remove-face (point-min) (point-max) 'region))) 169 (remove-hook 'pre-redisplay-functions #'vertico-buffer--redisplay 'local) 170 (remove-hook 'minibuffer-exit-hook vertico-buffer--restore) 171 (fset vertico-buffer--restore nil) 172 (kill-local-variable 'vertico-buffer--restore) 173 (buffer-local-restore-state old-state) 174 (overlay-put vertico--candidates-ov 'window nil) 175 (when vertico--count-ov (overlay-put vertico--count-ov 'window nil)) 176 (cond 177 ((and (window-live-p win) (buffer-live-p old-buf)) 178 (set-window-parameter win 'no-other-window old-no-other) 179 (set-window-parameter win 'no-delete-other-windows old-no-delete) 180 (set-window-dedicated-p win nil) 181 (set-window-buffer win old-buf)) 182 ;; Check `window-parent' since the window may be a sole window 183 ;; (gh:minad/vertico#496). 184 ((and (window-live-p win) (window-parent win)) 185 (delete-window win))) 186 (when vertico-buffer-hide-prompt 187 (set-window-vscroll nil 0)))))) 188 ;; We cannot use a buffer-local minibuffer-exit-hook here. The hook will 189 ;; not be called when abnormally exiting the minibuffer from another buffer 190 ;; via `keyboard-escape-quit'. 191 (add-hook 'minibuffer-exit-hook vertico-buffer--restore) 192 (add-hook 'pre-redisplay-functions #'vertico-buffer--redisplay nil 'local))) 193 194 ;;;###autoload 195 (define-minor-mode vertico-buffer-mode 196 "Display Vertico like a regular buffer in a large window." 197 :global t :group 'vertico 198 ;; Shrink current minibuffer window 199 (when-let ((win (active-minibuffer-window))) 200 (unless (frame-root-window-p win) 201 (window-resize win (- (window-pixel-height win)) nil nil 'pixelwise)) 202 (with-selected-window win 203 (cond 204 ((and vertico-buffer-mode vertico--input (not vertico-buffer--restore)) 205 (vertico-buffer--setup)) 206 ((and (not vertico-buffer-mode) vertico-buffer--restore) 207 (funcall vertico-buffer--restore)))))) 208 209 (cl-defmethod vertico--resize-window (_height &context (vertico-buffer-mode (eql t)))) 210 211 (cl-defmethod vertico--setup :after (&context (vertico-buffer-mode (eql t))) 212 (vertico-buffer--setup)) 213 214 (provide 'vertico-buffer) 215 ;;; vertico-buffer.el ends here