Skip to content

Instantly share code, notes, and snippets.

@rougier
Created March 25, 2022 09:54
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save rougier/126e358464e12aa28fac5b4f3dd5eb9c to your computer and use it in GitHub Desktop.
Save rougier/126e358464e12aa28fac5b4f3dd5eb9c to your computer and use it in GitHub Desktop.
Minibuffer frame for Nano Emacs
;; Nicolas .P Rougier emacs configuration - mini-frame configuration
;; ---------------------------------------------------------------------
(require 'vertico)
(require 'marginalia)
(require 'mini-frame)
(defun minibuffer-setup ()
;; This prevents the header line to spill over second line
(let ((inhibit-message t))
(toggle-truncate-lines 1))
(setq enable-recursive-minibuffers t)
;; This allows to have a consistent full width (fake) header like
(setq display-table (make-display-table))
(set-display-table-slot display-table
'truncation (make-glyph-code ?\ 'nano-subtle))
(set-display-table-slot display-table
'wrap (make-glyph-code ?\ 'nano-subtle))
(setq buffer-display-table display-table)
(cursor-intangible-mode)
(face-remap-add-relative 'default :foreground "black")
(face-remap-add-relative 'completions-first-difference :foreground "black")
(let* ((left (concat (propertize " "
'face '(nano-subtle)
'display '(raise +0.20))
(propertize " Minibuffer"
'face 'nano-subtle)
(propertize " "
'face 'nano-subtle
'display '(raise -0.30))))
(right (propertize "C-g: abort"
'face '(:inherit (nano-faded nano-subtle)
:weight light)))
(spacer (propertize (make-string (- (window-width)
(length left)
(length right)
1) ?\ )
'face 'nano-subtle))
(header (concat left spacer right " "))
(overlay (make-overlay (+ (point-min) 0) (+ (point-min) 0))))
(overlay-put overlay 'before-string
(concat
(propertize " " 'display header)
"\n"
;; This provides a vertical gap (half a line) above the prompt.
(propertize " " 'face `(:extend t)
'display '(raise .33)
'read-only t 'cursor-intangible t)))))
(add-hook 'minibuffer-setup-hook #'minibuffer-setup)
;; (defun minibuffer-exit ())
;; (add-hook 'minibuffer-exit-hook #'minibuffer-exit)
;; Prefix/Affix the current candidate. From
;; https://github.com/minad/vertico/wiki#prefix-current-candidate-with-arrow
(defun minibuffer-format-candidate (orig cand prefix suffix index _start)
(let ((prefix (if (= vertico--index index)
"  " " ")))
(funcall orig cand prefix suffix index _start)))
(advice-add #'vertico--format-candidate
:around #'minibuffer-format-candidate)
(with-eval-after-load 'vertico
(setq completion-styles '(basic substring partial-completion flex))
(setq vertico-count 10)
(setq vertico-count-format nil)
(setq vertico-grid-separator
#(" | " 2 3 (display (space :width (1))
face (:background "#ECEFF1"))))
(define-key vertico-map (kbd "<backtab>") #'minibuffer-complete)
(set-face-attribute 'vertico-current nil
:inherit '(nano-strong nano-subtle))
(set-face-attribute 'completions-first-difference nil
:inherit '(nano-default))
(set-face-attribute 'minibuffer-prompt nil
:inherit '(nano-default nano-strong))
(setq minibuffer-prompt-properties
'(read-only t cursor-intangible t face minibuffer-prompt))
(defun vertico--prompt-selection ()
"Highlight the prompt"
(let ((inhibit-modification-hooks t))
(set-text-properties (minibuffer-prompt-end) (point-max)
'(face (nano-strong nano-salient))))))
(with-eval-after-load 'marginalia
(setq truncate-string-ellipsis "…")
(setq marginalia--ellipsis "…")
(setq marginalia-align 'right)
(setq marginalia-align-offset -1))
(with-eval-after-load 'mini-frame
(set-face-background 'child-frame-border (face-foreground 'nano-faded))
(setq mini-frame-default-height vertico-count)
(setq mini-frame-create-lazy t)
(setq mini-frame-show-parameters 'mini-frame-dynamic-parameters)
(setq mini-frame-ignore-commands
'("edebug-eval-expression" debugger-eval-expression))
(setq mini-frame-internal-border-color (face-foreground 'nano-subtle-i))
;; (setq mini-frame-resize 'grow-only) ;; -> buggy as of 01/05/2021
;; (setq mini-frame-resize 'not-set)
;; (setq mini-frame-resize nil)
(setq mini-frame-resize t)
(setq mini-frame-resize-min-height 3)
(defun mini-frame-dynamic-parameters ()
(let* ((edges (window-pixel-edges)) ;; (left top right bottom)
(body-edges (window-body-pixel-edges)) ;; (left top right bottom)
(left (nth 0 edges)) ;; Take margins into account
(top (nth 1 edges)) ;; Drop header line
(right (nth 2 edges)) ;; Take margins into account
(bottom (nth 3 body-edges)) ;; Drop header line
(left (if (eq left-fringe-width 0)
left
(- left (frame-parameter nil 'left-fringe))))
(right (nth 2 edges))
(right (if (eq right-fringe-width 0)
right
(+ right (frame-parameter nil 'right-fringe))))
(fringe-left 0)
(fringe-right 0)
(border 1)
;; (width (- (frame-pixel-width) (* 2 (+ fringe border))))
(width (- right left fringe-left fringe-right (* 0 border)))
(y (- top border)))
`((left . ,(- left border))
(top . ,y)
(alpha . 1.0)
(width . (text-pixels . ,width))
(left-fringe . ,fringe-left)
(right-fringe . ,fringe-right)
(child-frame-border-width . ,border)
(internal-border-width . ,border)
(foreground-color . ,(face-foreground 'nano-default))
(background-color . ,(face-background 'highlight)))))
)
(vertico-mode)
(marginalia-mode)
(mini-frame-mode t)
(provide 'nano-minibuffer)
@rougier
Copy link
Author

rougier commented Mar 25, 2022

Screenshot 2022-03-25 at 10 52 26

@minad
Copy link

minad commented Mar 30, 2022

Hello @rougier!

Did you consider vertico-posframe instead of mini-frame? From my experience vertico-posframe is slightly more robust. However overall I am critical of using child frames for various technical reasons as documented here. For this particular use case one could try to use a regular window placed at the top. The vertico-buffer display extension of Vertico provides just that.

(vertico-buffer-mode)
(advice-add #'vertico-buffer--setup :after #'my-vertico-buffer-setup)
(setq vertico-buffer-display-action
      '(display-buffer-in-side-window
        (window-height . 12)
        (side . top)))
(defun my-vertico-buffer-setup ()
  (dolist (win (get-buffer-window-list))
    (set-window-parameter win 'my-mini-window (window-minibuffer-p win)))
  (face-remap-add-relative 'default '(:filtered (:window my-mini-window nil) (:background "gray98")))
  (face-remap-add-relative 'fringe '(:filtered (:window my-mini-window nil) (:background "gray90")))
  (face-remap-add-relative 'mode-line-active :height 1.0 :box nil :background "white" :overline "gray90")
  (face-remap-add-relative 'mode-line-inactive :height 1.0 :box nil :background "white" :overline "gray90")
  (face-remap-add-relative 'header-line :height 1 :box nil :background "white" :underline "gray90")
  (setq-local
   left-fringe-width 1
   right-fringe-width 1
   left-margin-width 1
   right-margin-width 1
   fringes-outside-margins t
   mode-line-format ""
   header-line-format "")
  (dolist (win (get-buffer-window-list))
    (set-window-buffer win (current-buffer))))

screenshot-frame-2022-03-30

(The theme on the screenshot is modus-operandi with a few tweaks.)

@rougier
Copy link
Author

rougier commented Mar 30, 2022

Thanks for the code. I've considered using the vertico-buffer-mode but didn't go too far, you code will be helpful for further experiments. I didn't know about the vertico-posframe but I'm not sure to use it since I want to extend the concept to other "disruptive" action. Here is an example wth capture:

Screenshot 2022-03-30 at 19 26 47

@minad
Copy link

minad commented Mar 30, 2022

If one uses a regular window, the small disadvantage (or advantage depending on preference) is that the window below the minibuffer window will be pushed down, since Emacs ensures that the (point) stays visible at all times. This window pushing does not happen with the mini-frame/child frame. However the child frame hides the window below it, which may hurt usage scenarios where both windows should be fully visible, e.g., swiper/consult-line.

My code snippet from above and the code from vertico-buffer could be generalized such that it applies to general minibuffer windows, not only to Vertico. This would then allow you to implement other non-disruptive actions in the same way. So far it was a bit easier for me to restrict this feature to Vertico only, but there shouldn't be a fundamental limitation.

@rougier
Copy link
Author

rougier commented Mar 30, 2022

I agree and I would prefer to use a regular buffer because as you mentioned, the mini-frame is not totally stable + this would work without UI. I'll look more thoroughly at your buffer code. But already, the snippet is useful because it demonstrates it can be done without too much work.

@rougier
Copy link
Author

rougier commented Dec 26, 2023

I've finally made it through vertico buffer mode. See https://github.com/rougier/nano-vertico

nano-vertico

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment