mirror of
https://github.com/protesilaos/dotfiles.git
synced 2026-09-10 07:16:20 -04:00
emacs: write prot-minibuffer.el and make relevant changes to the configuration
This commit is contained in:
parent
357c29ba34
commit
4a94b3a81d
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
302
emacs/.emacs.d/prot-lisp/prot-minibuffer.el
Normal file
302
emacs/.emacs.d/prot-lisp/prot-minibuffer.el
Normal 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
|
||||
Loading…
Reference in a new issue