mirror of
https://github.com/protesilaos/dotfiles.git
synced 2026-09-10 07:16:20 -04:00
446 lines
18 KiB
EmacsLisp
446 lines
18 KiB
EmacsLisp
;;; General minibuffer settings
|
|
(prot-emacs-configure
|
|
;;;; Completion styles
|
|
(setq completion-styles '(basic substring initials partial-completion flex)) ; also see `completion-category-overrides'
|
|
(setq completion-flex-nospace t)
|
|
(setq completion-pcm-leading-wildcard nil) ; Emacs 31
|
|
(with-eval-after-load 'orderless
|
|
(setq completion-styles (append completion-styles '(orderless)))))
|
|
|
|
;;;; Completion category overrides
|
|
(prot-emacs-configure
|
|
;; Reset all the per-category defaults so that (i) we use the
|
|
;; standard `completion-styles' and (ii) can specify our own styles
|
|
;; in the `completion-category-overrides' without having to
|
|
;; explicitly override everything.
|
|
(setq completion-category-defaults nil)
|
|
|
|
(prot-minibuffer-missing-categories-mode 1)
|
|
|
|
;; NOTE 2025-12-02: The `eager-display' and `eager-update' are part of Emacs 31.
|
|
(let* ((eager-update-properties '((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)
|
|
(affixation-function . ,#'prot-minibuffer-file-affixate)
|
|
(display-sort-function . ,#'prot-minibuffer-file-sort)))
|
|
(bookmark . (,@eager-update-properties
|
|
(affixation-function . ,#'prot-minibuffer-bookmark-affixate)))
|
|
(project-file . (,@eager-update-properties
|
|
(group-function . ,#'prot-minibuffer-file-group)
|
|
(affixation-function . ,#'prot-minibuffer-file-affixate)))
|
|
(prot-minibuffer-library . (,@eager-update-properties
|
|
(annotation-function . ,#'prot-minibuffer-library-annotate)
|
|
(display-sort-function . ,#'prot-minibuffer-library-sort)))
|
|
(symbol-help . (,@eager-update-properties
|
|
(group-function . ,#'prot-minibuffer-symbol-group)
|
|
(display-sort-function . ,#'prot-minibuffer-symbol-sort)))
|
|
(buffer . (,@eager-update-properties
|
|
;; NOTE 2026-07-25: I am disabling this because it messes up with the sorting.
|
|
;; (group-function . ,#'prot-minibuffer-buffer-group)
|
|
(affixation-function . ,#'prot-minibuffer-buffer-affixate)
|
|
(display-sort-function . ,#'prot-minibuffer-buffer-sort)))
|
|
(command . ((affixation-function . nil)
|
|
(annotation-function . ,#'prot-minibuffer-command-annotate)))
|
|
(denote-file . ,eager-update-properties)
|
|
(prot-minibuffer-emoji . ,eager-update-properties)
|
|
(theme . ,eager-update-properties)
|
|
(unicode-name . ,eager-update-properties)
|
|
(imenu . ,eager-update-properties-no-sort)
|
|
(consult-location . ,eager-update-properties-no-sort)
|
|
(prot-minibuffer-kill-ring . ((eager-display . t)
|
|
(eager-update . t)
|
|
(display-sort-function . identity)))))))
|
|
|
|
;;; Orderless completion style (and prot-orderless.el)
|
|
(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)))
|
|
|
|
(setq completion-ignore-case t)
|
|
(setq read-buffer-completion-ignore-case t)
|
|
(setq-default case-fold-search t) ; For general regexp
|
|
(setq read-file-name-completion-ignore-case t)
|
|
(setq minibuffer-history-case-insensitive-variables t)
|
|
|
|
(prot-emacs-configure
|
|
(setq read-minibuffer-restore-windows nil)
|
|
(setq enable-recursive-minibuffers t) ; Emacs 28
|
|
(minibuffer-depth-indicate-mode 1))
|
|
|
|
(prot-emacs-configure
|
|
(setq minibuffer-default-prompt-format " [%s]") ; Emacs 29
|
|
(minibuffer-electric-default-mode 1))
|
|
|
|
(prot-emacs-configure
|
|
(setq resize-mini-windows t)
|
|
(setq read-answer-short t) ; also check `use-short-answers' for Emacs28
|
|
(setq echo-keystrokes 0.25)
|
|
(setq kill-ring-max 60) ; Keep it small
|
|
|
|
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
|
|
;; Do not allow the cursor to move inside the minibuffer prompt. I
|
|
;; got this from the documentation of Daniel Mendler's Vertico
|
|
;; package: <https://github.com/minad/vertico>.
|
|
(setq minibuffer-prompt-properties
|
|
'(read-only t cursor-intangible t face minibuffer-prompt))
|
|
|
|
(setq crm-prompt (format "%s %%p" (propertize "[%d]" 'face 'shadow))) ; Emacs 31
|
|
|
|
(file-name-shadow-mode 1))
|
|
|
|
(prot-emacs-configure
|
|
(prot-emacs-hook
|
|
(completion-list-mode-hook minibuffer-setup-hook)
|
|
prot-common-truncate-lines-silently)
|
|
|
|
(setq completions-group-format
|
|
(concat
|
|
(propertize (make-string 20 ? ) 'face 'completions-group-separator)
|
|
(propertize " %s " 'face 'completions-group-title)
|
|
(propertize " " 'face 'completions-group-separator 'display '(space :align-to right))))
|
|
|
|
(unless prot-emacs-completion-ui
|
|
(prot-minibuffer-completions-mode 1)
|
|
|
|
(prot-emacs-keybind completion-list-mode-map
|
|
"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
|
|
(setq savehist-file (locate-user-emacs-file "savehist"))
|
|
(setq history-length 100)
|
|
(setq history-delete-duplicates t)
|
|
(setq savehist-save-minibuffer-history t)
|
|
(with-eval-after-load 'savehist
|
|
(add-to-list 'savehist-additional-variables 'kill-ring))
|
|
(savehist-mode 1))
|
|
|
|
(prot-emacs-configure
|
|
;;;; `dabbrev' (dynamic word completion (dynamic abbreviations))
|
|
(setq dabbrev-abbrev-char-regexp "\\sw\\|\\s_")
|
|
(setq dabbrev-abbrev-skip-leading-regexp "[$*/=~']")
|
|
(setq dabbrev-backward-only nil)
|
|
(setq dabbrev-case-distinction 'case-replace)
|
|
(setq dabbrev-case-fold-search nil)
|
|
(setq dabbrev-case-replace 'case-replace)
|
|
(setq dabbrev-check-other-buffers t)
|
|
(setq dabbrev-eliminate-newlines t)
|
|
(setq dabbrev-upcase-means-case-search t)
|
|
(setq dabbrev-ignored-buffer-modes '(archive-mode image-mode docview-mode pdf-view-mode)))
|
|
|
|
;;;; `abbrev' (Abbreviations, else Abbrevs)
|
|
(prot-emacs-configure
|
|
;; message-mode derives from text-mode, so we don't need a separate
|
|
;; hook for it.
|
|
(prot-emacs-hook
|
|
(text-mode-hook prog-mode-hook git-commit-mode-hook)
|
|
abbrev-mode)
|
|
|
|
(setq only-global-abbrevs nil)
|
|
|
|
(prot-emacs-abbrev global-abbrev-table
|
|
"meweb" "https://protesilaos.com"
|
|
"megit" "https://github.com/protesilaos"
|
|
"mehub" "https://github.com/protesilaos"
|
|
"meclone" "git@github.com/protesilaos/"
|
|
"melab" "https://gitlab.com/protesilaos"
|
|
"medrive" "hyper://5cr7mxac8o8aymun698736tayrh1h4kbqf359cfk57swjke716gy/"
|
|
";web" "https://protesilaos.com"
|
|
";git" "https://github.com/protesilaos"
|
|
";hub" "https://github.com/protesilaos"
|
|
";clone" "git@github.com/protesilaos/"
|
|
";lab" "https://gitlab.com/protesilaos"
|
|
";drive" "hyper://5cr7mxac8o8aymun698736tayrh1h4kbqf359cfk57swjke716gy/")
|
|
|
|
(prot-emacs-abbrev text-mode-abbrev-table
|
|
"asciidoc" "AsciiDoc"
|
|
"auctex" "AUCTeX"
|
|
"cafe" "café"
|
|
"cliche" "cliché"
|
|
"clojurescript" "ClojureScript"
|
|
"emacsconf" "EmacsConf"
|
|
"github" "GitHub"
|
|
"gitlab" "GitLab"
|
|
"javascript" "JavaScript"
|
|
"latex" "LaTeX"
|
|
"libreplanet" "LibrePlanet"
|
|
"linkedin" "LinkedIn"
|
|
"paypal" "PayPal"
|
|
"sourcehut" "SourceHut"
|
|
"texmacs" "TeXmacs"
|
|
"typescript" "TypeScript"
|
|
"visavis" "vis-à-vis"
|
|
"deja" "déjà"
|
|
"voila" "voilà"
|
|
"youtube" "YouTube"
|
|
";up" "🙃"
|
|
";uni" "🦄"
|
|
";laugh" "🤣"
|
|
";smile" "😀"
|
|
";sun" "☀️")
|
|
|
|
;; Allow abbrevs with a prefix colon, semicolon, or underscore. I demonstrated
|
|
;; this here: <https://protesilaos.com/codelog/2024-02-03-emacs-abbrev-mode/>.
|
|
(abbrev-table-put global-abbrev-table :regexp "\\(?:^\\|[\t\s]+\\)\\(?1:[:;_].*\\|.*\\)")
|
|
|
|
(with-eval-after-load 'text-mode
|
|
(abbrev-table-put text-mode-abbrev-table :regexp "\\(?:^\\|[\t\s]+\\)\\(?1:[:;_].*\\|.*\\)"))
|
|
|
|
(with-eval-after-load 'org
|
|
(prot-emacs-abbrev org-mode-abbrev-table
|
|
";dev" "{{{development-version}}}"
|
|
";key" #'prot-abbrev-org-macro-key
|
|
";cmd" #'prot-abbrev-org-macro-key-command)
|
|
(abbrev-table-put org-mode-abbrev-table :regexp "\\(?:^\\|[\t\s]+\\)\\(?1:[:;_].*\\|.*\\)"))
|
|
|
|
(with-eval-after-load 'message
|
|
(prot-emacs-abbrev message-mode-abbrev-table
|
|
"bestregards" "Best regards,\nProtesilaos (or simply \"Prot\")"
|
|
"allthebest" "All the best,\nProtesilaos (or simply \"Prot\")"
|
|
"niceday" "Have a nice day,\nProtesilaos (or simply \"Prot\")"
|
|
"abest" "All the best,\nProt"
|
|
"bregards" "Best regards,\nProt"
|
|
"nday" "Have a nice day,\nProt"
|
|
"nosrht" "P.S. I am phasing out SourceHut: <https://protesilaos.com/codelog/2024-01-27-sourcehut-no-more/>.
|
|
Development continues on GitHub with GitLab as a mirror."))
|
|
|
|
;; The `prot-emacs-abbrev' macro, which simplifies how we use
|
|
;; `define-abbrev', does not only expand a static text. It can take
|
|
;; a pair of string and function to trigger the latter when the
|
|
;; former is inserted. Think of it like the basis of a simplistic
|
|
;; templating system.
|
|
(require 'prot-abbrev)
|
|
(prot-emacs-abbrev global-abbrev-table
|
|
"metime" #'prot-abbrev-current-time
|
|
"medate" #'prot-abbrev-current-date
|
|
"mejitsi" #'prot-abbrev-jitsi-link
|
|
";time" #'prot-abbrev-current-time
|
|
";date" #'prot-abbrev-current-date
|
|
";jitsi" #'prot-abbrev-jitsi-link)
|
|
|
|
(prot-emacs-abbrev text-mode-abbrev-table
|
|
";update" #'prot-abbrev-update-html)
|
|
|
|
;; Because the *scratch* buffer is produced before we load this, we
|
|
;; have to explicitly activate the mode there.
|
|
(when-let* ((scratch (get-buffer "*scratch*")))
|
|
(with-current-buffer scratch
|
|
(abbrev-mode 1)))
|
|
|
|
;; By default, abbrev asks for confirmation on whether to use
|
|
;; `abbrev-file-name' to save abbrevations. I do not need that, nor
|
|
;; do I want it.
|
|
(remove-hook 'save-some-buffers-functions #'abbrev--possibly-save))
|
|
|
|
;;; Corfu (in-buffer completion popup)
|
|
(when (eq prot-emacs-completion-in-buffer 'corfu)
|
|
(prot-emacs-configure
|
|
(prot-emacs-install corfu)
|
|
|
|
(setq corfu-preview-current nil)
|
|
(setq corfu-min-width 20)
|
|
|
|
(setq corfu-popupinfo-delay '(1.25 . 0.5))
|
|
(corfu-popupinfo-mode 1) ; shows documentation after `corfu-popupinfo-delay'
|
|
|
|
(global-corfu-mode 1)
|
|
|
|
;; I also have (setq tab-always-indent 'complete) for TAB to complete
|
|
;; when it does not need to perform an indentation change.
|
|
(define-key corfu-map (kbd "<tab>") #'corfu-complete)
|
|
|
|
;; Sort by input history (no need to modify `corfu-sort-function').
|
|
(with-eval-after-load 'savehist
|
|
(corfu-history-mode 1)
|
|
(add-to-list 'savehist-additional-variables 'corfu-history))))
|
|
|
|
(when (eq prot-emacs-completion-in-buffer 'corfu)
|
|
(prot-emacs-configure
|
|
(prot-emacs-install cape)
|
|
|
|
(defun prot/cape-super-set-local (capfs &optional individual-capfs)
|
|
"Set `completion-at-point-functions' to current value plus CAPFS.
|
|
Treat CAPFS and the default value as a super CAPF. Then append the
|
|
INDIVIDUAL-CAPFS to the list."
|
|
(let* ((all-for-super (append completion-at-point-functions capfs))
|
|
(all-minus-global (delq t all-for-super))
|
|
(cape-super (apply #'cape-capf-super all-minus-global)))
|
|
(setq-local completion-at-point-functions (append (list cape-super) individual-capfs (list t)))))
|
|
|
|
(defun prot/cape-prog-setup ()
|
|
"Set up Cape for programming."
|
|
(prot/cape-super-set-local '(cape-dabbrev) '(cape-file)))
|
|
|
|
(add-hook 'prog-mode-hook #'prot/cape-prog-setup)
|
|
|
|
(defun prot/cape-text-setup ()
|
|
"Set up Cape for prose."
|
|
(prot/cape-super-set-local '(cape-dict cape-dabbrev cape-emoji) '(cape-file)))
|
|
|
|
(setq completion-at-point-functions '(cape-dabbrev cape-file))
|
|
|
|
(add-hook 'text-mode-hook #'prot/cape-text-setup)))
|
|
|
|
(when (eq prot-emacs-completion-in-buffer 'company)
|
|
(prot-emacs-configure
|
|
(prot-emacs-install company)
|
|
;; NOTE 2026-07-10: with the latest version of Company, (setq
|
|
;; tab-always-indent 'complete) is supported out-of-the-box, which
|
|
;; means that I can finally disable the automatic popup feature.
|
|
(setq company-idle-delay nil)
|
|
|
|
(setq company-tooltip-limit 6)
|
|
(setq company-tooltip-minimum-width 25)
|
|
(setq company-tooltip-align-annotations t)
|
|
(setq company-dabbrev-code-completion-styles t) ; use the `completion-styles'
|
|
|
|
(setq company-transformers '(company-sort-by-occurrence
|
|
company-sort-by-backend-importance
|
|
company-sort-prefer-same-case-prefix))
|
|
|
|
(setq company-frontends '(company-childframe-unless-just-one-frontend
|
|
company-preview-if-just-one-frontend
|
|
company-echo-metadata-frontend))
|
|
|
|
(setq company-backends '((company-capf company-dabbrev) company-files))
|
|
|
|
(define-advice company-show-doc-buffer (:around (&rest args) prot)
|
|
(let ((help-window-select nil)
|
|
(display-buffer-alist nil)
|
|
(display-buffer-overriding-action
|
|
'((display-buffer-reuse-mode-window display-buffer-below-selected)
|
|
(window-height . 0.3)
|
|
(post-command-select-window . nil))))
|
|
(apply args)))
|
|
|
|
(defun prot/company-prog-backends ()
|
|
"Set `company-backends' locally to my preferred value for `prog-mode' and derivatives."
|
|
(setq-local company-backends '((company-capf company-dabbrev-code) company-files)))
|
|
|
|
(add-hook 'prog-mode-hook #'prot/company-prog-backends)
|
|
|
|
(defun prot/company-text-backends ()
|
|
"Set `company-backends' locally to my preferred value for `text-mode' and derivatives."
|
|
(setq-local company-backends '((company-dabbrev company-ispell company-capf) company-files)))
|
|
|
|
(add-hook 'text-mode-hook #'prot/company-text-backends)
|
|
|
|
(global-company-mode 1)
|
|
|
|
(prot-emacs-install company-statistics)
|
|
|
|
(company-statistics-mode 1)))
|
|
|
|
(when (eq prot-emacs-completion-in-buffer 'completion-preview)
|
|
(prot-emacs-configure
|
|
(setq completion-preview-exact-match-only nil)
|
|
(setq completion-preview-commands '(self-insert-command
|
|
insert-char
|
|
analyze-text-conversion
|
|
completion-preview-insert-word))
|
|
(setq completion-preview-minimum-symbol-length 4)
|
|
(setq completion-preview-idle-delay 0.3)
|
|
(setq completion-preview-ignore-case t)
|
|
(setq completion-preview-sort-function #'identity)
|
|
|
|
(add-hook 'prog-mode-hook #'completion-preview-mode)
|
|
|
|
(with-eval-after-load 'completion-preview
|
|
(prot-emacs-keybind completion-preview-active-mode-map
|
|
"M-n" #'completion-preview-next-candidate
|
|
"M-p" #'completion-preview-prev-candidate
|
|
"M-<return>" #'completion-preview-insert
|
|
;; With TAB we effectively defer to another frontend to show
|
|
;; more completion candidates at once.
|
|
"<tab>" #'completion-preview-complete))))
|
|
|
|
;;; Enhanced minibuffer commands (consult.el)
|
|
(when prot-emacs-completion-extras
|
|
(prot-emacs-configure
|
|
(prot-emacs-install consult)
|
|
|
|
(prot-emacs-keybind global-map
|
|
"M-g M-g" #'consult-goto-line
|
|
"M-s M-b" #'consult-buffer
|
|
"M-s M-f" #'consult-find
|
|
"M-s M-g" #'consult-grep
|
|
"M-s M-h" #'consult-history
|
|
"M-s M-i" #'consult-imenu
|
|
"M-s M-l" #'consult-line
|
|
"M-s M-m" #'consult-mark
|
|
"M-s M-y" #'consult-yank-pop
|
|
"M-s M-s" #'consult-outline)
|
|
|
|
(setq consult-line-numbers-widen t)
|
|
;; (setq completion-in-region-function #'consult-completion-in-region)
|
|
(setq consult-async-min-input 3)
|
|
(setq consult-async-input-debounce 0.5)
|
|
(setq consult-async-input-throttle 0.8)
|
|
(setq consult-narrow-key nil)
|
|
(setq consult-find-args
|
|
(concat "find . -not ( "
|
|
"-path */.git* -prune "
|
|
"-or -path */.cache* -prune )"))
|
|
(setq consult-preview-key 'any)
|
|
(setq consult-project-function nil) ; always work from the current directory (use `cd' to switch directory)
|
|
|
|
;; see my `pulsar' package: <https://protesilaos.com/emacs/pulsar>
|
|
(setq consult-after-jump-hook nil) ; reset it to avoid conflicts with my function
|
|
(prot-emacs-hook
|
|
consult-after-jump-hook
|
|
(pulsar-recenter-top pulsar-reveal-entry)
|
|
nil
|
|
pulsar)))
|
|
|
|
;;; Extended minibuffer actions and more (embark.el)
|
|
(when prot-emacs-completion-extras
|
|
(prot-emacs-configure
|
|
(prot-emacs-install embark)
|
|
|
|
(add-hook 'embark-collect-mode-hook #'prot-common-truncate-lines-silently)
|
|
|
|
(prot-emacs-keybind minibuffer-local-map
|
|
"C-c C-c" #'embark-collect
|
|
"C-c C-e" #'embark-export)
|
|
|
|
;; Needed for correct exporting while using Embark with Consult commands.
|
|
(prot-emacs-install embark-consult)
|
|
|
|
(with-eval-after-load 'consult
|
|
(require 'embark-consult))))
|
|
|
|
;;; Detailed completion annotations (marginalia.el)
|
|
(when prot-emacs-completion-extras
|
|
(prot-emacs-configure
|
|
(prot-emacs-install marginalia)
|
|
(setq marginalia-max-relative-age 0) ; absolute time
|
|
(marginalia-mode 1)))
|
|
|
|
;;; The minibuffer user interface (mct, vertico, or none)
|
|
(when prot-emacs-completion-ui
|
|
(require
|
|
(pcase prot-emacs-completion-ui
|
|
('mct 'prot-emacs-mct)
|
|
('vertico 'prot-emacs-vertico))))
|
|
|
|
(provide 'prot-emacs-completion)
|