emacs: write prot-minibuffer.el and make relevant changes to the configuration

This commit is contained in:
Protesilaos Stavrou 2025-12-19 17:48:16 +02:00
parent 357c29ba34
commit 4a94b3a81d
No known key found for this signature in database
GPG key ID: 99BD6459CD5CA3EA
3 changed files with 740 additions and 456 deletions

View file

@ -1,7 +1,7 @@
;;; General minibuffer settings
(prot-emacs-configure
;;;; Completion styles
(setq completion-styles '(basic)) ; also see `completion-category-overrides'
(setq completion-styles '(basic flex)) ; also see `completion-category-overrides'
(setq completion-pcm-leading-wildcard nil) ; Emacs 31
(with-eval-after-load 'orderless
(setq completion-styles (append completion-styles '(orderless)))))
@ -14,93 +14,55 @@
;; explicitly override everything.
(setq completion-category-defaults nil)
;; Add some missing completion categories to let me configure the
;; relevant prompts via the `completion-category-overrides'.
(define-advice read-from-kill-ring (:around (&rest args) prot)
(let ((completion-extra-properties (list :category 'kill-ring)))
(apply args)))
(define-advice read-library-name (:around (&rest args) prot)
(let ((completion-extra-properties (list :category 'library)))
(apply args)))
(define-advice emoji--read-emoji (:around (&rest args) prot)
(let ((completion-extra-properties (list :category 'emoji)))
(apply args)))
(defun prot/file-sort-directories-first (files)
"Sort FILES to have directories first and then `completions-sort' sorting."
(setq files (delete "../" files))
(setq files
(pcase completions-sort
('nil files)
('alphabetical (minibuffer-sort-alphabetically files))
('historical (minibuffer-sort-by-history files))
(_ (funcall completions-sort files))))
(let ((directory-p (lambda (file) (string-suffix-p "/" file))))
(nconc (seq-filter directory-p files)
(seq-remove directory-p files))))
(defun prot/file-group (file transform)
"Return FILE group name unless TRANSFORM is non-nil."
(cond
(transform file)
((string-suffix-p "/" file) "Directories")
((string-match-p "\\.\\(tar\\|rar\\|zip\\)\\'" file) "Archives")
((string-match-p "\\.\\(txt\\|md\\|org\\)\\'" file) "Text")
((string-match-p "\\.\\(jpg\\|jpeg\\|png\\)\\'" file) "Images")
((string-match-p "\\.\\(mkv\\|webm\\|mp4\\|mp3\\|ogg\\|flac\\|wav\\)\\'" file) "Media")
((string-match-p "\\.\\(pdf\\|epub\\|texi\\|info\\)\\'" file) "Documents")
(t "Other")))
(defun prot/symbol-group (symbol transform)
"Return SYMBOL group name unless TRANSFORM is non-nil."
(cond
(transform symbol)
((string-match-p "--" symbol) "Private")
(t "Public")))
(prot-minibuffer-missing-categories-mode 1)
;; NOTE 2025-12-02: The `eager-display' and `eager-update' are part of Emacs 31.
(setq completion-category-overrides
`((file . ((styles . (partial-completion))
(eager-display . nil)
(eager-update . t)
(group-function . ,#'prot/file-group)
(display-sort-function . ,#'prot/file-sort-directories-first)))
(bookmark . ((eager-display . nil)
(eager-update . nil)))
(symbol-help . ((eager-display . nil)
(eager-update . t)
(group-function . ,#'prot/symbol-group)))
(emoji . ((styles . (orderless))
(eager-display . t)
(eager-update . t)))
(unicode-name . ((styles . (orderless))
(eager-display . nil)
(eager-update . t)))
,@(mapcar
(lambda (category)
(cons category
'((styles . (basic orderless))
(eager-display . t)
(eager-update . t))))
'( buffer denote-file project-file eglot kill-ring
theme consult-location imenu embark-keybinding library)))))
(let* ((eager-update-properties '((styles . (basic substring flex))
(eager-display . nil)
(eager-update . t)))
(eager-update-properties-no-sort (append eager-update-properties (list (cons 'display-sort-function #'identity)))))
(setq completion-category-overrides
`((file . ((styles . (partial-completion))
(eager-display . nil)
(eager-update . t)
(group-function . ,#'prot-minibuffer-file-group)
(display-sort-function . ,#'prot-minibuffer-file-sort-directories-first)))
(bookmark . ((eager-display . nil)
(eager-update . nil)
(annotation-function . ,#'prot-minibuffer-bookmark-annotate)))
(project-file . (,@eager-update-properties
(group-function . ,#'prot-minibuffer-file-group)))
(library . (,@eager-update-properties
(annotation-function . ,#'prot-minibuffer-library-annotate)
(display-sort-function . ,#'prot-minibuffer-library-sort)))
(symbol-help . (,@eager-update-properties
(display-sort-function . ,#'prot-minibuffer-symbol-sort)))
(buffer . (,@eager-update-properties
(annotation-function . ,#'prot-minibuffer-buffer-annotate)))
(command . ((affixation-function . nil)
(annotation-function . ,#'prot-minibuffer-command-annotate)))
(emoji . ,eager-update-properties)
(theme . ,eager-update-properties)
(unicode-name . ,eager-update-properties)
(kill-ring . ,eager-update-properties-no-sort)
(imenu . ,eager-update-properties-no-sort)
(consult-location . ,eager-update-properties-no-sort)))))
;;; Orderless completion style (and prot-orderless.el)
(prot-emacs-configure
(prot-emacs-install orderless)
(require 'orderless)
;; Remember to check my `completion-styles' and the
;; `completion-category-overrides'.
(setq orderless-matching-styles '(orderless-prefixes orderless-regexp))
(setq orderless-smart-case nil)
(when prot-emacs-completion-extras
(prot-emacs-configure
(prot-emacs-install orderless)
(require 'orderless)
;; Remember to check my `completion-styles' and the
;; `completion-category-overrides'.
(setq orderless-matching-styles '(orderless-prefixes orderless-regexp))
(setq orderless-smart-case nil)
;; SPC should never complete: use it for `orderless' groups.
;; The `?' is a regexp construct.
(prot-emacs-keybind minibuffer-local-completion-map
"SPC" nil
"?" nil))
;; SPC should never complete: use it for `orderless' groups.
;; The `?' is a regexp construct.
(prot-emacs-keybind minibuffer-local-completion-map
"SPC" nil
"?" nil)))
(setq completion-ignore-case t)
(setq read-buffer-completion-ignore-case t)
@ -136,130 +98,18 @@
(file-name-shadow-mode 1))
(prot-emacs-configure
(add-hook 'minibuffer-setup-hook #'prot-common-truncate-lines-silently)
(prot-emacs-hook
(completion-list-mode-hook minibuffer-setup-hook)
prot-common-truncate-lines-silently)
(unless prot-emacs-completion-ui
(setq completion-show-help nil)
(setq completion-show-inline-help nil)
(setq completions-detailed t)
(setq completions-format 'one-column)
;; (setq completions-header-format (propertize "%s candidates:\n" 'face 'bold-italic))
(setq completions-header-format "")
(setq completions-highlight-face 'completions-highlight)
(setq completions-max-height 12)
(setq completions-sort 'historical)
(setq completion-auto-help 'always)
(setq completion-auto-select 'second-tab)
;; (setq minibuffer-visible-completions t) ; Emacs 30
;; These two are for Emacs 31. The value they have now means that
;; each completion category will have its own behaviour based on
;; what I am setting in the `completion-category-overrides'.
(setq completion-eager-display 'auto)
(setq completion-eager-update 'auto)
(defun prot/completions-tweak-style ()
"Tweak the style of the Completions buffer."
(setq-local mode-line-format nil)
(setq-local cursor-in-non-selected-windows nil)
(when (and completions-header-format
(not (string-blank-p completions-header-format)))
(setq-local display-line-numbers-offset -1))
(display-line-numbers-mode 1))
(prot-emacs-hook
completion-list-mode-hook
(prot/completions-tweak-style prot-common-truncate-lines-silently))
(defun prot/quit-completions ()
"Always quit the Completions window."
(when-let* ((window (get-buffer-window "*Completions*")))
(quit-window nil window)))
(add-hook 'minibuffer-exit-hook #'prot/quit-completions)
(defun prot/choose-completion-no-exit ()
"Call `choose-completion' without exiting the minibuffer.
Also see `prot/choose-completion-exit' and `prot/choose-completion-dwim'."
(interactive)
(choose-completion nil :no-exit :no-quit)
(switch-to-minibuffer))
(defun prot/choose-completion-exit ()
"Call `choose-completion' and exit the minibuffer.
Also see `prot/choose-completion-no-exit' and `prot/choose-completion-dwim'."
(interactive)
(choose-completion nil :no-exit)
(exit-minibuffer))
(defun prot/crm-p ()
"Return non-nil if `completing-read-multiple' is in use."
(when-let* ((_ (boundp crm-completion-table))
(window (active-minibuffer-window))
(buffer (window-buffer window)))
(buffer-local-value 'crm-completion-table buffer)))
(defun prot/choose-completion-dwim ()
"Call `choose-completion' that exits only on a unique match.
If the match is not unique, then complete up to the largest common
prefix or, anyhow, continue with the completion (e.g. in `find-file'
switch into the directory and then show the files therein).
Also see `prot/choose-completion-no-exit' and `prot/choose-completion-exit'."
(interactive)
(if (prot/crm-p)
(prot/choose-completion-no-exit)
(choose-completion nil :no-exit :no-quit)
(switch-to-minibuffer)
(minibuffer-completion-help)
(unless (get-buffer-window "*Completions*")
(exit-minibuffer))))
(define-advice minibuffer-completion-help (:around (&rest args) prot)
"Make `minibuffer-completion-help' display *Completions* in a side window.
Make the window be at slot 0, such that the *Help* buffer produced by
`prot/completions-describe-at-point' is to its right."
(let ((display-buffer-overriding-action
`((display-buffer-reuse-mode-window display-buffer-in-side-window)
(mode . completion-list-mode)
(side . bottom)
(slot . 0))))
(apply args)))
(defun prot/completions-describe-at-point (symbol)
"Describe SYMBOL at point inside the *Completions* buffer.
Place the *Help* buffer in a side window, situated to the right of the
*Completions* buffer. Make the window have the `prot-minibuffer-help'
property, such that it can be found by `prot/completions-close-help'."
(interactive (list (intern-soft (thing-at-point 'symbol))))
(unless (derived-mode-p 'completion-list-mode)
(user-error "Can only do this from the *Completions* buffer"))
(when symbol
(let ((help-window-select nil)
(display-buffer-overriding-action
`((display-buffer-reuse-mode-window display-buffer-in-side-window)
(mode . help-mode)
(side . bottom)
(slot . 1)
(window-height . fit-window-to-buffer)
(window-parameters . ((prot-minibuffer-help . t))))))
(describe-symbol symbol))))
(defun prot/completions-close-help ()
"Close the window that has a `'prot-minibuffer-help' parameter."
(when-let* ((help (seq-find
(lambda (window)
(window-parameter window 'prot-minibuffer-help))
(window-list))))
(delete-window help)))
(add-hook 'minibuffer-exit-hook #'prot/completions-close-help)
(prot-minibuffer-completions-mode 1)
(prot-emacs-keybind completion-list-mode-map
"h" #'prot/completions-describe-at-point ; "Help" mnemonic
"c" #'prot/choose-completion-no-exit ; "Choose" mnemonic
"TAB" #'prot/choose-completion-dwim
"RET" #'prot/choose-completion-exit)))
"h" #'prot-minibuffer-completions-describe-at-point ; "Help" mnemonic
"c" #'prot-minibuffer-choose-completion-no-exit ; "Choose" mnemonic
"TAB" #'prot-minibuffer-choose-completion-dwim
"RET" #'prot-minibuffer-choose-completion-exit)))
;;;; `savehist' (minibuffer and related histories)
(prot-emacs-configure

View file

@ -4046,28 +4046,29 @@ interpret user input and match candidates accordingly.
and even styles that are the same as the aforementioned ~flex~ and
~initials~.
Now that you know about the completion styles I use, take a look at
the value of my ~completion-styles~. You will notice that ~orderless~,
which is the most powerful/flexible is placed last. I do this because
Emacs tries the styles in the given order from left to right, moving
the next one until it finds a match. As such, I usually want to start
with tight matches (e.g. =li-fa-di= for ~list-faces-display~) and only
widen the scope of the search as I need to. This is easy to do because
none of the built-in completion styles parses the empty space, so as
soon as I type a space after some characters I am using ~orderless~.
Now that you know about the completion styles I use or have
experimented with, take a look at the value of my ~completion-styles~.
You will notice that when ~orderless~ is loaded, it is placed at the
end of the list. I do this because Emacs tries the styles in the given
order from left to right, moving to the next one until it finds a match.
As such, I usually want to start with tight matches (e.g. =li-fa-di=
for ~list-faces-display~) and only widen the scope of the search as I
need to. This is easy to do because as soon as I type =word1 word2= I
am likely switching to ~orderless~ because nothing else will
match---and if something else does yield results, than that is
probably what I am looking for, anyway.
Notice that this is not all, as we still have to consider what happens
when the minibuffer prompt we are using defines a specific completion
category whose pattern matching styles differ from what we have in the
~completion-styles~. To that end, we also set up a more fine-grained
set of completion styles on a per-category basis using overrides
([[#h:0d49218d-4199-4847-9765-60a3f94e515d][The =prot-emacs-completion.el= settings for completion category overrides]]).
This is not all though, as we still have to consider what happens when
the minibuffer prompt we are using defines a specific completion
category. We can arrange to have different ~completion-styles~ per
category and many more customisations which, of course, I do take care
of ([[#h:0d49218d-4199-4847-9765-60a3f94e515d][The =prot-emacs-completion.el= settings for completion category overrides]]).
#+begin_src emacs-lisp :tangle "prot-emacs-modules/prot-emacs-completion.el" :mkdirp yes
;;; General minibuffer settings
(prot-emacs-configure
;;;; Completion styles
(setq completion-styles '(basic)) ; also see `completion-category-overrides'
(setq completion-styles '(basic flex)) ; also see `completion-category-overrides'
(setq completion-pcm-leading-wildcard nil) ; Emacs 31
(with-eval-after-load 'orderless
(setq completion-styles (append completion-styles '(orderless)))))
@ -4100,12 +4101,18 @@ overrides where those make sense to me.
We can opt for per-category styles by configuring the user option
~completion-category-overrides~. Notice, for example, how I arrange
for ~partial-completion~ to be set only for the =file= completion
category, as I only ever need it there. Also bear in mind what I
described above about why ~orderless~ is placed last on the list:
Emacs uses the completion styles from left to right until something
matches the given input. So I do not need to have ~partial-completion~
first as ~basic~ will never match something like =~/.l/s/fo= for
=~/.local/share/fonts=.
category, as I only ever need it there: it expands something like
=~/.l/s/fo= into =~/.local/share/fonts=.
Overrides are not limited to completion styles. We can also set our
own metadata, as described in the documentation of the function
~completion-metadata~. Concretely, we can set our own functions for
annotating, sorting, and grouping candidates. My =prot-minibuffer.el=
defines several such functions ([[#h:761861c9-9a0e-4c97-b98d-20f13a6b3b79][The =prot-minibuffer= library]]). It
also sets things up for certain prompts without a completion category
to get one and thus to be subject to the customisations I define
herein. The ~prot-minibuffer-missing-categories-mode~ is responsible
for this.
#+begin_src emacs-lisp :tangle "prot-emacs-modules/prot-emacs-completion.el" :mkdirp yes
;;;; Completion category overrides
@ -4116,78 +4123,39 @@ first as ~basic~ will never match something like =~/.l/s/fo= for
;; explicitly override everything.
(setq completion-category-defaults nil)
;; Add some missing completion categories to let me configure the
;; relevant prompts via the `completion-category-overrides'.
(define-advice read-from-kill-ring (:around (&rest args) prot)
(let ((completion-extra-properties (list :category 'kill-ring)))
(apply args)))
(define-advice read-library-name (:around (&rest args) prot)
(let ((completion-extra-properties (list :category 'library)))
(apply args)))
(define-advice emoji--read-emoji (:around (&rest args) prot)
(let ((completion-extra-properties (list :category 'emoji)))
(apply args)))
(defun prot/file-sort-directories-first (files)
"Sort FILES to have directories first and then `completions-sort' sorting."
(setq files (delete "../" files))
(setq files
(pcase completions-sort
('nil files)
('alphabetical (minibuffer-sort-alphabetically files))
('historical (minibuffer-sort-by-history files))
(_ (funcall completions-sort files))))
(let ((directory-p (lambda (file) (string-suffix-p "/" file))))
(nconc (seq-filter directory-p files)
(seq-remove directory-p files))))
(defun prot/file-group (file transform)
"Return FILE group name unless TRANSFORM is non-nil."
(cond
(transform file)
((string-suffix-p "/" file) "Directories")
((string-match-p "\\.\\(tar\\|rar\\|zip\\)\\'" file) "Archives")
((string-match-p "\\.\\(txt\\|md\\|org\\)\\'" file) "Text")
((string-match-p "\\.\\(jpg\\|jpeg\\|png\\)\\'" file) "Images")
((string-match-p "\\.\\(mkv\\|webm\\|mp4\\|mp3\\|ogg\\|flac\\|wav\\)\\'" file) "Media")
((string-match-p "\\.\\(pdf\\|epub\\|texi\\|info\\)\\'" file) "Documents")
(t "Other")))
(defun prot/symbol-group (symbol transform)
"Return SYMBOL group name unless TRANSFORM is non-nil."
(cond
(transform symbol)
((string-match-p "--" symbol) "Private")
(t "Public")))
(prot-minibuffer-missing-categories-mode 1)
;; NOTE 2025-12-02: The `eager-display' and `eager-update' are part of Emacs 31.
(setq completion-category-overrides
`((file . ((styles . (partial-completion))
(eager-display . nil)
(eager-update . t)
(group-function . ,#'prot/file-group)
(display-sort-function . ,#'prot/file-sort-directories-first)))
(bookmark . ((eager-display . nil)
(eager-update . nil)))
(symbol-help . ((eager-display . nil)
(eager-update . t)
(group-function . ,#'prot/symbol-group)))
(emoji . ((styles . (orderless))
(eager-display . t)
(eager-update . t)))
(unicode-name . ((styles . (orderless))
(eager-display . nil)
(eager-update . t)))
,@(mapcar
(lambda (category)
(cons category
'((styles . (basic orderless))
(eager-display . t)
(eager-update . t))))
'( buffer denote-file project-file eglot kill-ring
theme consult-location imenu embark-keybinding library)))))
(let* ((eager-update-properties '((styles . (basic substring flex))
(eager-display . nil)
(eager-update . t)))
(eager-update-properties-no-sort (append eager-update-properties (list (cons 'display-sort-function #'identity)))))
(setq completion-category-overrides
`((file . ((styles . (partial-completion))
(eager-display . nil)
(eager-update . t)
(group-function . ,#'prot-minibuffer-file-group)
(display-sort-function . ,#'prot-minibuffer-file-sort-directories-first)))
(bookmark . ((eager-display . nil)
(eager-update . nil)
(annotation-function . ,#'prot-minibuffer-bookmark-annotate)))
(project-file . (,@eager-update-properties
(group-function . ,#'prot-minibuffer-file-group)))
(library . (,@eager-update-properties
(annotation-function . ,#'prot-minibuffer-library-annotate)
(display-sort-function . ,#'prot-minibuffer-library-sort)))
(symbol-help . (,@eager-update-properties
(display-sort-function . ,#'prot-minibuffer-symbol-sort)))
(buffer . (,@eager-update-properties
(annotation-function . ,#'prot-minibuffer-buffer-annotate)))
(command . ((affixation-function . nil)
(annotation-function . ,#'prot-minibuffer-command-annotate)))
(emoji . ,eager-update-properties)
(theme . ,eager-update-properties)
(unicode-name . ,eager-update-properties)
(kill-ring . ,eager-update-properties-no-sort)
(imenu . ,eager-update-properties-no-sort)
(consult-location . ,eager-update-properties-no-sort)))))
#+end_src
*** The =prot-emacs-completion.el= for the ~orderless~ completion style
@ -4396,48 +4364,14 @@ experience.
These are some settings for the default completion user interface.
These do not come into effect unless ~prot-emacs-completion-ui~ is set
to a ~nil~ value or when we are not using any package for in-buffer
completion (such as the ~corfu~ package).
to a ~nil~ value.
For a few days around early December 2025 I tried to use the following
setup exclusively on the Emacs 31 development target. It mostly works
and is better than it used to be, though there are several important
limitations:
- The eager update (recalculate the results as you type) is noticeably
choppy. I could improve it by making the function
~completions--background-update~ do a ~sit-for~ a few miliseconds,
but was not happy with this.
- The built-in =minibuffer.el= package lacks focus. There are options
that support conflicting workflows. One is a =TAB=-intensive
approach, where =TAB= triggers the display of completions, switches
to that buffer, and cycles through the results. Another way is to
set the user option ~minibuffer-visible-completions~ to a non-~nil~
value, which makes the cursor stay in the minibuffer and you select
a completion from there without switching to the =*Completions*=
buffer. Compounding the problem are confusingly named variables like
~completion-show-help~ and ~completion-auto-help~, which mean
completely different things. Even after setting all this up and
trying it full-time for several days (plus me maintaining ~mct~ for
years), I still do not know exactly what I need to configure to get
a consistent experience.
- The ~find-file~ navigation makes it harder to select a directory.
Whether with the =TAB=-centric workflow of its alternative, when I
do something like =~/G/P/= followed by =TAB= and then =RET= I expect
to navigate to a ~dired~ buffer that shows =~/Git/Projects/= (this
is possible thanks to the ~partial-completion~ style ([[#h:14b09958-279e-4069-81e3-5a16c9b69892][The =prot-emacs-completion.el= settings for completion styles]])).
Instead it takes me to =~/Git/= or, more precisely, to whatever the
top candidate is in the completions buffer. That candidate is not
even highlighted when I do all this!
Of course, I can keep adding functions to make things work the way I
want, but it ultimately is not worth it. Vertico is robust, super
fast, focused, and still highly configurable. My ~mct~ package is
there just because I started it all those years ago, before ~vertico~
was a thing and before the generic completions got most of what ~mct~
offers.
Because I do not like how the default minibuffer user interface works,
I maintain some customisations as part of my =prot-minibuffer.el= ([[#h:761861c9-9a0e-4c97-b98d-20f13a6b3b79][The =prot-minibuffer= library]]).
Some of those are about the completion category overrides to add
grouping and sorting facilities ([[#h:0d49218d-4199-4847-9765-60a3f94e515d][The =prot-emacs-completion.el= settings for completion category overrides]]).
The changes that are specific to the minibuffer interface for
completion are collectively enabled through the minor mode ~prot-minibuffer-completions-mode~.
- [[#h:f012a254-2716-4c29-a64b-c2b3df34f57f][The =init.el= option to load a minibuffer user interface]]
- [[#h:cff33514-d3ac-4c16-a889-ea39d7346dc5][The =prot-emacs-completion.el= submodule for ~vertico~ (=prot-emacs-vertico.el=)]]
@ -4445,130 +4379,18 @@ offers.
#+begin_src emacs-lisp :tangle "prot-emacs-modules/prot-emacs-completion.el"
(prot-emacs-configure
(add-hook 'minibuffer-setup-hook #'prot-common-truncate-lines-silently)
(prot-emacs-hook
(completion-list-mode-hook minibuffer-setup-hook)
prot-common-truncate-lines-silently)
(unless prot-emacs-completion-ui
(setq completion-show-help nil)
(setq completion-show-inline-help nil)
(setq completions-detailed t)
(setq completions-format 'one-column)
;; (setq completions-header-format (propertize "%s candidates:\n" 'face 'bold-italic))
(setq completions-header-format "")
(setq completions-highlight-face 'completions-highlight)
(setq completions-max-height 12)
(setq completions-sort 'historical)
(setq completion-auto-help 'always)
(setq completion-auto-select 'second-tab)
;; (setq minibuffer-visible-completions t) ; Emacs 30
;; These two are for Emacs 31. The value they have now means that
;; each completion category will have its own behaviour based on
;; what I am setting in the `completion-category-overrides'.
(setq completion-eager-display 'auto)
(setq completion-eager-update 'auto)
(defun prot/completions-tweak-style ()
"Tweak the style of the Completions buffer."
(setq-local mode-line-format nil)
(setq-local cursor-in-non-selected-windows nil)
(when (and completions-header-format
(not (string-blank-p completions-header-format)))
(setq-local display-line-numbers-offset -1))
(display-line-numbers-mode 1))
(prot-emacs-hook
completion-list-mode-hook
(prot/completions-tweak-style prot-common-truncate-lines-silently))
(defun prot/quit-completions ()
"Always quit the Completions window."
(when-let* ((window (get-buffer-window "*Completions*")))
(quit-window nil window)))
(add-hook 'minibuffer-exit-hook #'prot/quit-completions)
(defun prot/choose-completion-no-exit ()
"Call `choose-completion' without exiting the minibuffer.
Also see `prot/choose-completion-exit' and `prot/choose-completion-dwim'."
(interactive)
(choose-completion nil :no-exit :no-quit)
(switch-to-minibuffer))
(defun prot/choose-completion-exit ()
"Call `choose-completion' and exit the minibuffer.
Also see `prot/choose-completion-no-exit' and `prot/choose-completion-dwim'."
(interactive)
(choose-completion nil :no-exit)
(exit-minibuffer))
(defun prot/crm-p ()
"Return non-nil if `completing-read-multiple' is in use."
(when-let* ((_ (boundp crm-completion-table))
(window (active-minibuffer-window))
(buffer (window-buffer window)))
(buffer-local-value 'crm-completion-table buffer)))
(defun prot/choose-completion-dwim ()
"Call `choose-completion' that exits only on a unique match.
If the match is not unique, then complete up to the largest common
prefix or, anyhow, continue with the completion (e.g. in `find-file'
switch into the directory and then show the files therein).
Also see `prot/choose-completion-no-exit' and `prot/choose-completion-exit'."
(interactive)
(if (prot/crm-p)
(prot/choose-completion-no-exit)
(choose-completion nil :no-exit :no-quit)
(switch-to-minibuffer)
(minibuffer-completion-help)
(unless (get-buffer-window "*Completions*")
(exit-minibuffer))))
(define-advice minibuffer-completion-help (:around (&rest args) prot)
"Make `minibuffer-completion-help' display *Completions* in a side window.
Make the window be at slot 0, such that the *Help* buffer produced by
`prot/completions-describe-at-point' is to its right."
(let ((display-buffer-overriding-action
`((display-buffer-reuse-mode-window display-buffer-in-side-window)
(mode . completion-list-mode)
(side . bottom)
(slot . 0))))
(apply args)))
(defun prot/completions-describe-at-point (symbol)
"Describe SYMBOL at point inside the *Completions* buffer.
Place the *Help* buffer in a side window, situated to the right of the
,*Completions* buffer. Make the window have the `prot-minibuffer-help'
property, such that it can be found by `prot/completions-close-help'."
(interactive (list (intern-soft (thing-at-point 'symbol))))
(unless (derived-mode-p 'completion-list-mode)
(user-error "Can only do this from the *Completions* buffer"))
(when symbol
(let ((help-window-select nil)
(display-buffer-overriding-action
`((display-buffer-reuse-mode-window display-buffer-in-side-window)
(mode . help-mode)
(side . bottom)
(slot . 1)
(window-height . fit-window-to-buffer)
(window-parameters . ((prot-minibuffer-help . t))))))
(describe-symbol symbol))))
(defun prot/completions-close-help ()
"Close the window that has a `'prot-minibuffer-help' parameter."
(when-let* ((help (seq-find
(lambda (window)
(window-parameter window 'prot-minibuffer-help))
(window-list))))
(delete-window help)))
(add-hook 'minibuffer-exit-hook #'prot/completions-close-help)
(prot-minibuffer-completions-mode 1)
(prot-emacs-keybind completion-list-mode-map
"h" #'prot/completions-describe-at-point ; "Help" mnemonic
"c" #'prot/choose-completion-no-exit ; "Choose" mnemonic
"TAB" #'prot/choose-completion-dwim
"RET" #'prot/choose-completion-exit)))
"h" #'prot-minibuffer-completions-describe-at-point ; "Help" mnemonic
"c" #'prot-minibuffer-choose-completion-no-exit ; "Choose" mnemonic
"TAB" #'prot-minibuffer-choose-completion-dwim
"RET" #'prot-minibuffer-choose-completion-exit)))
#+end_src
*** The =prot-emacs-completion.el= settings for saving the history (~savehist-mode~)
@ -12985,6 +12807,316 @@ buffer instead of the current one."
;;; prot-eww.el ends here
#+end_src
** The =prot-minibuffer= library
:PROPERTIES:
:CUSTOM_ID: h:761861c9-9a0e-4c97-b98d-20f13a6b3b79
:END:
#+begin_src emacs-lisp :tangle "prot-lisp/prot-minibuffer.el" :mkdirp yes
;;; prot-minibuffer.el --- Extensions for the minibuffer and completions -*- lexical-binding: t -*-
;; Copyright (C) 2025 Protesilaos Stavrou
;; Author: Protesilaos Stavrou <info@protesilaos.com>
;; Maintainer: Protesilaos Stavrou <info@protesilaos.com>
;; URL: https://protesilaos.com/emacs/dotemacs
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; Extensions for the minibuffer and completions, intended for my
;; Emacs setup: <https://protesilaos.com/emacs/dotemacs/>.
;;; Code:
(defgroup prot-minibuffer nil
"Extensions for the minibuffer and completions."
:group 'minibuffer)
;;;; Completion category grouping, sorting, and affixating
;; Add some missing completion categories to let me configure the
;; relevant prompts via the `completion-category-overrides'.
(defun prot-minibuffer@read-from-kill-ring (&rest args)
(let ((completion-extra-properties (list :category 'kill-ring)))
(apply args)))
(defun prot-minibuffer@read-library-name (&rest args)
(let ((completion-extra-properties (list :category 'library)))
(apply args)))
(defun prot-minibuffer@emoji--read-emoji (&rest args)
(let ((completion-extra-properties (list :category 'emoji)))
(apply args)))
;;;###autoload
(define-minor-mode prot-minibuffer-missing-categories-mode
"When enabled, add missing compleiton categories to relevant prompts."
:global t
(if prot-minibuffer-missing-categories-mode
(dolist (original (list #'read-from-kill-ring #'read-library-name #'emoji--read-emoji))
(when-let* ((my-function-name (format "prot-minibuffer@%s" original))
(my-function-symbol (intern-soft my-function-name)))
(advice-add original :around my-function-symbol)))
(dolist (original (list #'read-from-kill-ring #'read-library-name #'emoji--read-emoji))
(when-let* ((my-function-name (format "prot-minibuffer@%s" original))
(my-function-symbol (intern-soft my-function-name)))
(advice-remove original my-function-symbol)))))
(defun prot-minibuffer--set-default-sort (candidates)
"Sort CANDIDATES according to `completions-sort' and return the sorted list."
(setq candidates
(pcase completions-sort
('nil candidates)
('alphabetical (minibuffer-sort-alphabetically candidates))
('historical (minibuffer-sort-by-history candidates))
(_ (funcall completions-sort candidates)))))
(defun prot-minibuffer-file-sort-directories-first (files)
"Sort FILES to have directories first and then `completions-sort' sorting.
Omit the .. directory from FILES."
(setq files (delete "../" files))
(setq files (prot-minibuffer--set-default-sort files))
(let ((directory-p (lambda (file) (string-suffix-p "/" file))))
(nconc (seq-filter directory-p files)
(seq-remove directory-p files))))
(defvar prot-minibuffer-file-extensions
'((archive "tar" "rar" "zip")
(image "jpg" "png" "jpeg")
(audio "mp3" "flac" "ogg" "wav")
(video "mkv" "webm" "mp4")
(text "org" "txt" "md")
(document "pdf" "epub" "info" "texi")
(program "sh" "el" "c" "py" "yaml" "toml" "conf" "js" "html"))
"Some common file extensions grouped by type.")
(defun prot-minibuffer--get-file-type-regexp (type)
"Return regular expression for file TYPE.
TYPE is a `car' among the elements of `prot-minibuffer-file-extensions'."
(when-let* ((extensions (alist-get type prot-minibuffer-file-extensions)))
(format "\\.%s\\'" (regexp-opt extensions))))
(defun prot-minibuffer-file-group (file transform)
"Return FILE group name unless TRANSFORM is non-nil."
(cond
(transform file)
((string-suffix-p "/" file) "Directory")
((string-match-p (prot-minibuffer--get-file-type-regexp 'archive) file) "Archive")
((string-match-p (prot-minibuffer--get-file-type-regexp 'text) file) "Text")
((string-match-p (prot-minibuffer--get-file-type-regexp 'image) file) "Image")
((string-match-p (prot-minibuffer--get-file-type-regexp 'audio) file) "Audio")
((string-match-p (prot-minibuffer--get-file-type-regexp 'video) file) "Video")
((string-match-p (prot-minibuffer--get-file-type-regexp 'document) file) "Document")
((string-match-p (prot-minibuffer--get-file-type-regexp 'program) file) "Program")
(t "Other")))
(defun prot-minibuffer-symbol-sort (symbols)
"Sort SYMBOLS so that public ones come first."
(setq symbols (prot-minibuffer--set-default-sort symbols))
(let ((private-p (lambda (symbol) (string-suffix-p "--" symbol))))
(nconc (seq-remove private-p symbols)
(seq-filter private-p symbols))))
(defun prot-minibuffer--propertize-suffix-with-space (string)
"Propertize STRING with spacing before it."
(format " %s%s"
(if (eq completions-format 'one-column)
(propertize " " 'display '(space :align-to 60))
" ")
(propertize string 'face 'completions-annotations)))
(defun prot-minibuffer-buffer-annotate (buffer)
"Return the major mode of BUFFER or nil."
(with-current-buffer (get-buffer buffer)
(prot-minibuffer--propertize-suffix-with-space (format "%s" major-mode))))
(defun prot-minibuffer-bookmark-annotate (bookmark)
"Return the filename data of BOOKMARK or nil."
(when-let* ((data (bookmark-get-bookmark bookmark))
(filename (bookmark-prop-get data 'filename)))
(prot-minibuffer--propertize-suffix-with-space filename)))
(defun prot-minibuffer-library-sort (libraries)
"Sort LIBRARIES, omitting autoloads and bytecode files."
(setq libraries (seq-remove
(lambda (library)
(string-match-p "\\(-autoload\\|\\.elc\\)" library))
libraries))
(prot-minibuffer--set-default-sort libraries))
;; NOTE 2025-12-19: Maybe there is a better way, but this is okay to start with.
(defun prot-minibuffer-library-annotate (library)
"Return the group documentation of LIBRARY."
(when-let* ((group-documentation (get (intern-soft library) 'group-documentation)))
(prot-minibuffer--propertize-suffix-with-space group-documentation)))
(defun prot-minibuffer-command-annotate (command)
"Annotate COMMAND with its key binding and shortened documentation string."
(let ((symbol (intern-soft command)))
(format "%s%s%s"
(if-let* ((binding (where-is-internal symbol overriding-local-map t))
(description (key-description binding))
(key (when (and binding (not (stringp binding)))
(format " %s " description))))
(propertize key 'face 'help-key-binding)
"")
(if (eq completions-format 'one-column)
(propertize " " 'display '(space :align-to 60))
" ")
(if-let* ((doc (condition-case nil (documentation symbol) (error nil)))
(first-line (substring doc 0 (string-search "\n" doc))))
(propertize first-line 'face 'completions-annotations)
""))))
;;;; Completions
(defun prot-minibuffer-completions-tweak-style ()
"Tweak the style of the Completions buffer."
(setq-local mode-line-format nil)
(setq-local cursor-in-non-selected-windows nil)
(when (and completions-header-format
(not (string-blank-p completions-header-format)))
(setq-local display-line-numbers-offset -1))
(display-line-numbers-mode 1))
(defun prot-minibuffer-quit-completions ()
"Always quit the Completions window."
(when-let* ((window (get-buffer-window "*Completions*")))
(quit-window nil window)))
(defun prot-minibuffer-choose-completion-no-exit ()
"Call `choose-completion' without exiting the minibuffer.
Also see `prot-minibuffer-choose-completion-exit' and `prot-minibuffer-choose-completion-dwim'."
(interactive)
(choose-completion nil :no-exit :no-quit)
(switch-to-minibuffer))
(defun prot-minibuffer-choose-completion-exit ()
"Call `choose-completion' and exit the minibuffer.
Also see `prot-minibuffer-choose-completion-no-exit' and `prot-minibuffer-choose-completion-dwim'."
(interactive)
(choose-completion nil :no-exit)
(exit-minibuffer))
(defun prot-minibuffer-crm-p ()
"Return non-nil if `completing-read-multiple' is in use."
(when-let* ((_ (featurep 'crm))
(window (active-minibuffer-window))
(buffer (window-buffer window)))
(buffer-local-value 'crm-completion-table buffer)))
(defun prot-minibuffer-choose-completion-dwim ()
"Call `choose-completion' that exits only on a unique match.
If the match is not unique, then complete up to the largest common
prefix or, anyhow, continue with the completion (e.g. in `find-file'
switch into the directory and then show the files therein).
Also see `prot-minibuffer-choose-completion-no-exit' and `prot-minibuffer-choose-completion-exit'."
(interactive)
(if (prot-minibuffer-crm-p)
(prot-minibuffer-choose-completion-no-exit)
(choose-completion nil :no-exit :no-quit)
(switch-to-minibuffer)
(minibuffer-completion-help)
(unless (get-buffer-window "*Completions*")
(exit-minibuffer))))
(define-advice minibuffer-completion-help (:around (&rest args) prot)
"Make `minibuffer-completion-help' display *Completions* in a side window.
Make the window be at slot 0, such that the *Help* buffer produced by
`prot-minibuffer-completions-describe-at-point' is to its right."
(let ((display-buffer-overriding-action
`((display-buffer-reuse-mode-window display-buffer-in-side-window)
(mode . completion-list-mode)
(side . bottom)
(slot . 0))))
(apply args)))
(defun prot-minibuffer-completions-describe-at-point (symbol)
"Describe SYMBOL at point inside the *Completions* buffer.
Place the *Help* buffer in a side window, situated to the right of the
,*Completions* buffer. Make the window have the `prot-minibuffer-help'
property, such that it can be found by `prot-minibuffer-completions-close-help'."
(interactive (list (intern-soft (thing-at-point 'symbol))))
(unless (derived-mode-p 'completion-list-mode)
(user-error "Can only do this from the *Completions* buffer"))
(when symbol
(let ((help-window-select nil)
(display-buffer-overriding-action
`((display-buffer-reuse-mode-window display-buffer-in-side-window)
(mode . help-mode)
(side . bottom)
(slot . 1)
(window-height . fit-window-to-buffer)
(window-parameters . ((prot-minibuffer-help . t))))))
(describe-symbol symbol))))
(defun prot-minibuffer-completions-close-help ()
"Close the window that has a `prot-minibuffer-help' parameter."
(when-let* ((help (seq-find
(lambda (window)
(window-parameter window 'prot-minibuffer-help))
(window-list))))
(delete-window help)))
;;;###autoload
(define-minor-mode prot-minibuffer-completions-mode
"Tweak the interface of the minibuffer and the *Completions*."
:global t
(if prot-minibuffer-completions-mode
(progn
(setq completion-show-help nil)
(setq completion-show-inline-help nil)
(setq completions-detailed t)
(setq completions-format 'one-column)
(setq completions-header-format "")
(setq completions-highlight-face 'button)
(setq completions-max-height 12)
(setq completions-sort 'historical)
(setq completion-auto-help t)
(setq completion-auto-select t)
(setq completion-eager-display 'auto)
(setq completion-eager-update 'auto)
(add-hook 'completion-list-mode-hook #'prot-minibuffer-completions-tweak-style)
(add-hook 'minibuffer-exit-hook #'prot-minibuffer-quit-completions)
(add-hook 'minibuffer-exit-hook #'prot-minibuffer-completions-close-help))
(setq completion-show-help t)
(setq completion-show-inline-help nil)
(setq completions-detailed nil)
(setq completions-format 'horizontal)
(setq completions-header-format (propertize "%s possible completions:\n" 'face 'shadow))
(setq completions-highlight-face 'completions-highlight)
(setq completions-max-height nil)
(setq completions-sort 'alphabetical)
(setq completion-auto-help t)
(setq completion-auto-select nil)
(setq completion-eager-display 'auto)
(setq completion-eager-update 'auto)
(remove-hook 'completion-list-mode-hook #'prot-minibuffer-completions-tweak-style)
(remove-hook 'minibuffer-exit-hook #'prot-minibuffer-quit-completions)
(remove-hook 'minibuffer-exit-hook #'prot-minibuffer-completions-close-help)))
(provide 'prot-minibuffer)
;;; prot-minibuffer.el ends here
#+end_src
** The =prot-modeline.el= library
:PROPERTIES:
:CUSTOM_ID: h:935adc09-abaa-4413-a5ab-a7a562081c20

View file

@ -0,0 +1,302 @@
;;; prot-minibuffer.el --- Extensions for the minibuffer and completions -*- lexical-binding: t -*-
;; Copyright (C) 2025 Protesilaos Stavrou
;; Author: Protesilaos Stavrou <info@protesilaos.com>
;; Maintainer: Protesilaos Stavrou <info@protesilaos.com>
;; URL: https://protesilaos.com/emacs/dotemacs
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; Extensions for the minibuffer and completions, intended for my
;; Emacs setup: <https://protesilaos.com/emacs/dotemacs/>.
;;; Code:
(defgroup prot-minibuffer nil
"Extensions for the minibuffer and completions."
:group 'minibuffer)
;;;; Completion category grouping, sorting, and affixating
;; Add some missing completion categories to let me configure the
;; relevant prompts via the `completion-category-overrides'.
(defun prot-minibuffer@read-from-kill-ring (&rest args)
(let ((completion-extra-properties (list :category 'kill-ring)))
(apply args)))
(defun prot-minibuffer@read-library-name (&rest args)
(let ((completion-extra-properties (list :category 'library)))
(apply args)))
(defun prot-minibuffer@emoji--read-emoji (&rest args)
(let ((completion-extra-properties (list :category 'emoji)))
(apply args)))
;;;###autoload
(define-minor-mode prot-minibuffer-missing-categories-mode
"When enabled, add missing compleiton categories to relevant prompts."
:global t
(if prot-minibuffer-missing-categories-mode
(dolist (original (list #'read-from-kill-ring #'read-library-name #'emoji--read-emoji))
(when-let* ((my-function-name (format "prot-minibuffer@%s" original))
(my-function-symbol (intern-soft my-function-name)))
(advice-add original :around my-function-symbol)))
(dolist (original (list #'read-from-kill-ring #'read-library-name #'emoji--read-emoji))
(when-let* ((my-function-name (format "prot-minibuffer@%s" original))
(my-function-symbol (intern-soft my-function-name)))
(advice-remove original my-function-symbol)))))
(defun prot-minibuffer--set-default-sort (candidates)
"Sort CANDIDATES according to `completions-sort' and return the sorted list."
(setq candidates
(pcase completions-sort
('nil candidates)
('alphabetical (minibuffer-sort-alphabetically candidates))
('historical (minibuffer-sort-by-history candidates))
(_ (funcall completions-sort candidates)))))
(defun prot-minibuffer-file-sort-directories-first (files)
"Sort FILES to have directories first and then `completions-sort' sorting.
Omit the .. directory from FILES."
(setq files (delete "../" files))
(setq files (prot-minibuffer--set-default-sort files))
(let ((directory-p (lambda (file) (string-suffix-p "/" file))))
(nconc (seq-filter directory-p files)
(seq-remove directory-p files))))
(defvar prot-minibuffer-file-extensions
'((archive "tar" "rar" "zip")
(image "jpg" "png" "jpeg")
(audio "mp3" "flac" "ogg" "wav")
(video "mkv" "webm" "mp4")
(text "org" "txt" "md")
(document "pdf" "epub" "info" "texi")
(program "sh" "el" "c" "py" "yaml" "toml" "conf" "js" "html"))
"Some common file extensions grouped by type.")
(defun prot-minibuffer--get-file-type-regexp (type)
"Return regular expression for file TYPE.
TYPE is a `car' among the elements of `prot-minibuffer-file-extensions'."
(when-let* ((extensions (alist-get type prot-minibuffer-file-extensions)))
(format "\\.%s\\'" (regexp-opt extensions))))
(defun prot-minibuffer-file-group (file transform)
"Return FILE group name unless TRANSFORM is non-nil."
(cond
(transform file)
((string-suffix-p "/" file) "Directory")
((string-match-p (prot-minibuffer--get-file-type-regexp 'archive) file) "Archive")
((string-match-p (prot-minibuffer--get-file-type-regexp 'text) file) "Text")
((string-match-p (prot-minibuffer--get-file-type-regexp 'image) file) "Image")
((string-match-p (prot-minibuffer--get-file-type-regexp 'audio) file) "Audio")
((string-match-p (prot-minibuffer--get-file-type-regexp 'video) file) "Video")
((string-match-p (prot-minibuffer--get-file-type-regexp 'document) file) "Document")
((string-match-p (prot-minibuffer--get-file-type-regexp 'program) file) "Program")
(t "Other")))
(defun prot-minibuffer-symbol-sort (symbols)
"Sort SYMBOLS so that public ones come first."
(setq symbols (prot-minibuffer--set-default-sort symbols))
(let ((private-p (lambda (symbol) (string-suffix-p "--" symbol))))
(nconc (seq-remove private-p symbols)
(seq-filter private-p symbols))))
(defun prot-minibuffer--propertize-suffix-with-space (string)
"Propertize STRING with spacing before it."
(format " %s%s"
(if (eq completions-format 'one-column)
(propertize " " 'display '(space :align-to 60))
" ")
(propertize string 'face 'completions-annotations)))
(defun prot-minibuffer-buffer-annotate (buffer)
"Return the major mode of BUFFER or nil."
(with-current-buffer (get-buffer buffer)
(prot-minibuffer--propertize-suffix-with-space (format "%s" major-mode))))
(defun prot-minibuffer-bookmark-annotate (bookmark)
"Return the filename data of BOOKMARK or nil."
(when-let* ((data (bookmark-get-bookmark bookmark))
(filename (bookmark-prop-get data 'filename)))
(prot-minibuffer--propertize-suffix-with-space filename)))
(defun prot-minibuffer-library-sort (libraries)
"Sort LIBRARIES, omitting autoloads and bytecode files."
(setq libraries (seq-remove
(lambda (library)
(string-match-p "\\(-autoload\\|\\.elc\\)" library))
libraries))
(prot-minibuffer--set-default-sort libraries))
;; NOTE 2025-12-19: Maybe there is a better way, but this is okay to start with.
(defun prot-minibuffer-library-annotate (library)
"Return the group documentation of LIBRARY."
(when-let* ((group-documentation (get (intern-soft library) 'group-documentation)))
(prot-minibuffer--propertize-suffix-with-space group-documentation)))
(defun prot-minibuffer-command-annotate (command)
"Annotate COMMAND with its key binding and shortened documentation string."
(let ((symbol (intern-soft command)))
(format "%s%s%s"
(if-let* ((binding (where-is-internal symbol overriding-local-map t))
(description (key-description binding))
(key (when (and binding (not (stringp binding)))
(format " %s " description))))
(propertize key 'face 'help-key-binding)
"")
(if (eq completions-format 'one-column)
(propertize " " 'display '(space :align-to 60))
" ")
(if-let* ((doc (condition-case nil (documentation symbol) (error nil)))
(first-line (substring doc 0 (string-search "\n" doc))))
(propertize first-line 'face 'completions-annotations)
""))))
;;;; Completions
(defun prot-minibuffer-completions-tweak-style ()
"Tweak the style of the Completions buffer."
(setq-local mode-line-format nil)
(setq-local cursor-in-non-selected-windows nil)
(when (and completions-header-format
(not (string-blank-p completions-header-format)))
(setq-local display-line-numbers-offset -1))
(display-line-numbers-mode 1))
(defun prot-minibuffer-quit-completions ()
"Always quit the Completions window."
(when-let* ((window (get-buffer-window "*Completions*")))
(quit-window nil window)))
(defun prot-minibuffer-choose-completion-no-exit ()
"Call `choose-completion' without exiting the minibuffer.
Also see `prot-minibuffer-choose-completion-exit' and `prot-minibuffer-choose-completion-dwim'."
(interactive)
(choose-completion nil :no-exit :no-quit)
(switch-to-minibuffer))
(defun prot-minibuffer-choose-completion-exit ()
"Call `choose-completion' and exit the minibuffer.
Also see `prot-minibuffer-choose-completion-no-exit' and `prot-minibuffer-choose-completion-dwim'."
(interactive)
(choose-completion nil :no-exit)
(exit-minibuffer))
(defun prot-minibuffer-crm-p ()
"Return non-nil if `completing-read-multiple' is in use."
(when-let* ((_ (featurep 'crm))
(window (active-minibuffer-window))
(buffer (window-buffer window)))
(buffer-local-value 'crm-completion-table buffer)))
(defun prot-minibuffer-choose-completion-dwim ()
"Call `choose-completion' that exits only on a unique match.
If the match is not unique, then complete up to the largest common
prefix or, anyhow, continue with the completion (e.g. in `find-file'
switch into the directory and then show the files therein).
Also see `prot-minibuffer-choose-completion-no-exit' and `prot-minibuffer-choose-completion-exit'."
(interactive)
(if (prot-minibuffer-crm-p)
(prot-minibuffer-choose-completion-no-exit)
(choose-completion nil :no-exit :no-quit)
(switch-to-minibuffer)
(minibuffer-completion-help)
(unless (get-buffer-window "*Completions*")
(exit-minibuffer))))
(define-advice minibuffer-completion-help (:around (&rest args) prot)
"Make `minibuffer-completion-help' display *Completions* in a side window.
Make the window be at slot 0, such that the *Help* buffer produced by
`prot-minibuffer-completions-describe-at-point' is to its right."
(let ((display-buffer-overriding-action
`((display-buffer-reuse-mode-window display-buffer-in-side-window)
(mode . completion-list-mode)
(side . bottom)
(slot . 0))))
(apply args)))
(defun prot-minibuffer-completions-describe-at-point (symbol)
"Describe SYMBOL at point inside the *Completions* buffer.
Place the *Help* buffer in a side window, situated to the right of the
*Completions* buffer. Make the window have the `prot-minibuffer-help'
property, such that it can be found by `prot-minibuffer-completions-close-help'."
(interactive (list (intern-soft (thing-at-point 'symbol))))
(unless (derived-mode-p 'completion-list-mode)
(user-error "Can only do this from the *Completions* buffer"))
(when symbol
(let ((help-window-select nil)
(display-buffer-overriding-action
`((display-buffer-reuse-mode-window display-buffer-in-side-window)
(mode . help-mode)
(side . bottom)
(slot . 1)
(window-height . fit-window-to-buffer)
(window-parameters . ((prot-minibuffer-help . t))))))
(describe-symbol symbol))))
(defun prot-minibuffer-completions-close-help ()
"Close the window that has a `prot-minibuffer-help' parameter."
(when-let* ((help (seq-find
(lambda (window)
(window-parameter window 'prot-minibuffer-help))
(window-list))))
(delete-window help)))
;;;###autoload
(define-minor-mode prot-minibuffer-completions-mode
"Tweak the interface of the minibuffer and the *Completions*."
:global t
(if prot-minibuffer-completions-mode
(progn
(setq completion-show-help nil)
(setq completion-show-inline-help nil)
(setq completions-detailed t)
(setq completions-format 'one-column)
(setq completions-header-format "")
(setq completions-highlight-face 'button)
(setq completions-max-height 12)
(setq completions-sort 'historical)
(setq completion-auto-help t)
(setq completion-auto-select t)
(setq completion-eager-display 'auto)
(setq completion-eager-update 'auto)
(add-hook 'completion-list-mode-hook #'prot-minibuffer-completions-tweak-style)
(add-hook 'minibuffer-exit-hook #'prot-minibuffer-quit-completions)
(add-hook 'minibuffer-exit-hook #'prot-minibuffer-completions-close-help))
(setq completion-show-help t)
(setq completion-show-inline-help nil)
(setq completions-detailed nil)
(setq completions-format 'horizontal)
(setq completions-header-format (propertize "%s possible completions:\n" 'face 'shadow))
(setq completions-highlight-face 'completions-highlight)
(setq completions-max-height nil)
(setq completions-sort 'alphabetical)
(setq completion-auto-help t)
(setq completion-auto-select nil)
(setq completion-eager-display 'auto)
(setq completion-eager-update 'auto)
(remove-hook 'completion-list-mode-hook #'prot-minibuffer-completions-tweak-style)
(remove-hook 'minibuffer-exit-hook #'prot-minibuffer-quit-completions)
(remove-hook 'minibuffer-exit-hook #'prot-minibuffer-completions-close-help)))
(provide 'prot-minibuffer)
;;; prot-minibuffer.el ends here