aboutsummaryrefslogtreecommitdiff
path: root/.config/emacs/lisp/minadstack
diff options
context:
space:
mode:
authorJack Jamison <jackqjamison@gmail.com>2026-07-05 01:19:30 -0400
committerJack Jamison <jackqjamison@gmail.com>2026-07-05 01:19:30 -0400
commitbdf9a71ab7baa2b1de9abcfd5df1a9107a55d141 (patch)
treec4524e6c41aa5107211103401dabfaefc81aa882 /.config/emacs/lisp/minadstack
parentfe3984f541bd32bdfa418afb305b614176b55ca0 (diff)
add a bunch of emacs packages HELP
Diffstat (limited to '.config/emacs/lisp/minadstack')
-rw-r--r--.config/emacs/lisp/minadstack/cape.el1359
-rw-r--r--.config/emacs/lisp/minadstack/consult.el5739
-rw-r--r--.config/emacs/lisp/minadstack/corfu-history.el114
-rw-r--r--.config/emacs/lisp/minadstack/corfu.el1444
-rw-r--r--.config/emacs/lisp/minadstack/marginalia.el1461
-rw-r--r--.config/emacs/lisp/minadstack/orderless.el672
-rw-r--r--.config/emacs/lisp/minadstack/vertico-directory.el136
-rw-r--r--.config/emacs/lisp/minadstack/vertico.el736
8 files changed, 11661 insertions, 0 deletions
diff --git a/.config/emacs/lisp/minadstack/cape.el b/.config/emacs/lisp/minadstack/cape.el
new file mode 100644
index 0000000..c180044
--- /dev/null
+++ b/.config/emacs/lisp/minadstack/cape.el
@@ -0,0 +1,1359 @@
+;;; cape.el --- Completion At Point Extensions -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021-2026 Free Software Foundation, Inc.
+
+;; Author: Daniel Mendler <mail@daniel-mendler.de>
+;; Maintainer: Daniel Mendler <mail@daniel-mendler.de>
+;; Created: 2021
+;; Version: 2.7
+;; Package-Requires: ((emacs "29.1") (compat "31"))
+;; URL: https://github.com/minad/cape
+;; Keywords: abbrev, convenience, matching, completion, text
+
+;; This file is 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:
+
+;; Let your completions fly! This package provides additional completion
+;; backends in the form of Capfs, see `completion-at-point-functions'.
+;;
+;; `cape-abbrev': Complete abbreviation (`add-global-abbrev', `add-mode-abbrev').
+;; `cape-dabbrev': Complete word from current buffers.
+;; `cape-dict': Complete word from dictionary file.
+;; `cape-elisp-block': Complete Elisp in Org or Markdown code block.
+;; `cape-elisp-symbol': Complete Elisp symbol.
+;; `cape-emoji': Complete Emoji.
+;; `cape-file': Complete file name.
+;; `cape-history': Complete from Eshell, Comint or minibuffer history.
+;; `cape-keyword': Complete programming language keyword.
+;; `cape-line': Complete entire line from file.
+;; `cape-rfc1345': Complete Unicode char using RFC 1345 mnemonics.
+;; `cape-sgml': Complete Unicode char from SGML entity, e.g., &alpha.
+;; `cape-tex': Complete Unicode char from TeX command, e.g. \hbar.
+
+;;; Code:
+
+(require 'compat)
+(eval-when-compile
+ (require 'cl-lib)
+ (require 'subr-x))
+
+;;;; Customization
+
+(defgroup cape nil
+ "Completion At Point Extensions."
+ :link '(info-link :tag "Info Manual" "(cape)")
+ :link '(url-link :tag "Website" "https://github.com/minad/cape")
+ :link '(emacs-library-link :tag "Library Source" "cape.el")
+ :group 'convenience
+ :group 'tools
+ :group 'matching
+ :prefix "cape-")
+
+(defcustom cape-dict-limit 100
+ "Maximal number of completion candidates returned by `cape-dict'."
+ :type '(choice (const nil) natnum))
+
+;; TODO bug#80071 file-local language. Add mechanism to locate dictionary file
+;; based on file-local language variable.
+(defcustom cape-dict-file "/usr/share/dict/words"
+ "Path to dictionary word list file.
+This variable can also be a list of paths or
+a function returning a single or more paths."
+ :type '(choice string (repeat string) function))
+
+(defcustom cape-dict-case-replace 'case-replace
+ "Preserve case of input.
+See `dabbrev-case-replace' for details."
+ :type '(choice (const :tag "Disable" nil)
+ (const :tag "Use `case-replace'" case-replace)
+ (other :tag "Enable" t)))
+
+(defcustom cape-dict-case-fold 'case-fold-search
+ "Case fold search during search.
+See `dabbrev-case-fold-search' for details."
+ :type '(choice (const :tag "Disable" nil)
+ (const :tag "Use `case-fold-search'" case-fold-search)
+ (other :tag "Enable" t)))
+
+(defcustom cape-dabbrev-buffer-function #'cape-same-mode-buffers
+ "Function which returns list of buffers.
+The buffers are scanned for completion candidates by `cape-dabbrev'."
+ :type `(choice (const :tag "Current buffer" current-buffer)
+ (const :tag "Text buffers" ,#'cape-text-buffers)
+ (const :tag "Buffers with same mode" ,#'cape-same-mode-buffers)
+ (function :tag "Custom function")))
+
+(defcustom cape-file-directory nil
+ "Base directory used by `cape-file."
+ :type '(choice (const nil) string function))
+
+(defcustom cape-file-prefix "file:"
+ "File completion trigger prefixes.
+The value can be a string or a list of strings. The default
+`file:' is the prefix of Org file links which work in arbitrary
+buffers via `org-open-at-point-global'."
+ :type '(choice string (repeat string)))
+
+(defcustom cape-file-directory-must-exist t
+ "The parent directory must exist for file completion."
+ :type 'boolean)
+
+(defcustom cape-line-buffer-function #'cape-same-mode-buffers
+ "Function which returns list of buffers.
+The buffers are scanned for completion candidates by `cape-line'."
+ :type `(choice (const :tag "Current buffer" current-buffer)
+ (const :tag "Text buffers" ,#'cape-text-buffers)
+ (const :tag "Buffers with same mode" ,#'cape-same-mode-buffers)
+ (function :tag "Custom function")))
+
+(defcustom cape-elisp-symbol-wrapper
+ '((org-mode ?~ ?~)
+ (markdown-mode ?` ?`)
+ (emacs-lisp-mode ?` ?')
+ (rst-mode "``" "``")
+ (log-edit-mode "`" "'")
+ (change-log-mode "`" "'")
+ (message-mode "`" "'")
+ (rcirc-mode "`" "'"))
+ "Wrapper characters for symbols."
+ :type '(alist :key-type symbol :value-type (list (choice character string)
+ (choice character string))))
+
+;;;; Helpers
+
+(defun cape--buffer-list (pred)
+ "Return list of buffers satisfying PRED."
+ (let* ((cur (current-buffer))
+ (orig (and (minibufferp) (window-buffer (minibuffer-selected-window))))
+ (list (cl-loop for buf in (buffer-list)
+ if (and (not (eq buf cur)) (not (eq buf orig))
+ (funcall pred buf))
+ collect buf)))
+ `(,cur ,@(and orig (list orig)) ,@list)))
+
+(defun cape-same-mode-buffers ()
+ "Return buffers with same major mode as current buffer."
+ (cape--buffer-list
+ (lambda (buf) (eq major-mode (buffer-local-value 'major-mode buf)))))
+
+(defun cape-text-buffers ()
+ "Return `text-mode' and `prog-mode' buffers."
+ (cape--buffer-list
+ (lambda (buf)
+ (let ((mode (buffer-local-value 'major-mode buf)))
+ (or (provided-mode-derived-p mode #'text-mode)
+ (provided-mode-derived-p mode #'prog-mode))))))
+
+(defun cape--case-fold-p (fold)
+ "Return non-nil if case folding is enabled for FOLD."
+ (if (eq fold 'case-fold-search) case-fold-search fold))
+
+(defun cape--case-replace-list (flag input strs)
+ "Replace case of STRS depending on INPUT and FLAG."
+ (if (and (if (eq flag 'case-replace) case-replace flag)
+ (let (case-fold-search) (string-match-p "\\`[[:upper:]]" input)))
+ (mapcar (apply-partially #'cape--case-replace flag input) strs)
+ strs))
+
+(defun cape--case-replace (flag input str)
+ "Replace case of STR depending on INPUT and FLAG."
+ (or (and (if (eq flag 'case-replace) case-replace flag)
+ (string-prefix-p input str t)
+ (let (case-fold-search) (string-match-p "\\`[[:upper:]]" input))
+ (save-match-data
+ ;; Ensure that single character uppercase input does not lead to an
+ ;; all uppercase result.
+ (when (and (= (length input) 1) (> (length str) 1))
+ (setq input (concat input (substring str 1 2))))
+ (and (string-match input input)
+ (replace-match str nil nil input))))
+ str))
+
+(defun cape--separator-p (str)
+ "Return non-nil if input STR has a separator character.
+Separator characters are used by completion styles like Orderless
+to split filter words. In Corfu, the separator is configurable
+via the variable `corfu-separator'."
+ (string-search (string ;; Support `corfu-separator' and Orderless
+ (or (and (bound-and-true-p corfu-mode)
+ (bound-and-true-p corfu-separator))
+ ?\s))
+ str))
+
+(defmacro cape--silent (&rest body)
+ "Silence BODY."
+ (declare (indent 0))
+ `(cl-letf ((inhibit-message t)
+ (message-log-max nil)
+ ((symbol-function #'minibuffer-message) #'ignore))
+ (ignore-errors ,@body)))
+
+(defun cape--bounds (thing)
+ "Return bounds of THING."
+ (or (bounds-of-thing-at-point thing) (cons (point) (point))))
+
+(defmacro cape--wrapped-table (wrap body)
+ "Create wrapped completion table, handle `completion--unquote'.
+WRAP is the wrapper function.
+BODY is the wrapping expression."
+ (declare (indent 1))
+ `(lambda (str pred action)
+ (,@body
+ (let ((result (complete-with-action action table str pred)))
+ (when (and (eq action 'completion--unquote) (functionp (cadr result)))
+ (cl-callf ,wrap (cadr result)))
+ result))))
+
+(defun cape--accept-all-table (table)
+ "Create completion TABLE which accepts all input."
+ (cape--wrapped-table cape--accept-all-table
+ (or (eq action 'lambda))))
+
+(defun cape--passthrough-table (table)
+ "Create completion TABLE disabling any filtering."
+ (cape--wrapped-table cape--passthrough-table
+ (let (completion-ignore-case completion-regexp-list (_ (setq str ""))))))
+
+(defun cape--noninterruptible-table (table)
+ "Create non-interruptible completion TABLE."
+ (cape--wrapped-table cape--noninterruptible-table
+ (let (throw-on-input))))
+
+(defun cape--silent-table (table)
+ "Create a new completion TABLE which is silent (no messages, no errors)."
+ (cape--wrapped-table cape--silent-table
+ (cape--silent)))
+
+(defun cape--nonessential-table (table)
+ "Mark completion TABLE as `non-essential'."
+ (let ((dir default-directory))
+ (cape--wrapped-table cape--nonessential-table
+ (let ((default-directory dir)
+ (non-essential t))))))
+
+(defun cape--table-drop-metadata (table keys)
+ "Create completion TABLE without metadata KEYS."
+ (if (functionp table)
+ (lambda (str pred action)
+ (if (eq action 'metadata)
+ (when-let* ((md (copy-sequence (funcall table str pred action))))
+ (dolist (k keys) (setq md (assq-delete-all k md)))
+ md)
+ (complete-with-action action table str pred)))
+ table))
+
+(defvar cape--debug-length 5
+ "Length of printed lists in `cape--debug-print'.")
+
+(defvar cape--debug-id 0
+ "Completion table identifier.")
+
+(defun cape--debug-message (&rest msg)
+ "Print debug MSG."
+ (let ((inhibit-message t))
+ (apply #'message msg)))
+
+(defun cape--debug-print (obj &optional full)
+ "Print OBJ as string, truncate lists if FULL is nil."
+ (cond
+ ((symbolp obj) (symbol-name obj))
+ ((functionp obj) "#<function>")
+ ((proper-list-p obj)
+ (concat
+ "("
+ (string-join
+ (mapcar #'cape--debug-print
+ (if full obj (take cape--debug-length obj)))
+ " ")
+ (if (and (not full) (length> obj cape--debug-length)) " ...)" ")")))
+ (t (let ((print-level 2))
+ (prin1-to-string obj)))))
+
+(defun cape--debug-table (table name beg end)
+ "Create completion TABLE with debug messages.
+NAME is the name of the Capf, BEG and END are the input markers."
+ (lambda (str pred action)
+ (let ((result (complete-with-action action table str pred)))
+ (if (and (eq action 'completion--unquote) (functionp (cadr result)))
+ ;; See `cape--wrapped-table'
+ (cl-callf cape--debug-table (cadr result) name beg end)
+ (cape--debug-message
+ "%s(action=%S input=%s:%s:%S prefix=%S ignore-case=%S%s%s) => %s"
+ name
+ (pcase action
+ ('nil 'try)
+ ('t 'all)
+ ('lambda 'test)
+ (_ action))
+ (+ beg 0) (+ end 0) (buffer-substring-no-properties beg end)
+ str completion-ignore-case
+ (if completion-regexp-list
+ (concat " regexp=" (cape--debug-print completion-regexp-list t))
+ "")
+ (if pred
+ (concat " predicate=" (cape--debug-print pred))
+ "")
+ (cape--debug-print result)))
+ result)))
+
+(defun cape--dynamic-table (beg end fun)
+ "Create dynamic completion table from FUN with caching.
+BEG and END are the input bounds. FUN is the function which
+computes the candidates. FUN must return a pair of a predicate
+function function and the list of candidates. The predicate is
+passed new input and must return non-nil if the candidates are
+still valid.
+
+It is only necessary to use this function if the set of
+candidates is computed dynamically based on the input and not
+statically determined. The behavior is similar but slightly
+different to `completion-table-dynamic'.
+
+The difference to the builtins `completion-table-dynamic' and
+`completion-table-with-cache' is that this function does not use
+the prefix argument of the completion table to compute the
+candidates. Instead it uses the input in the buffer between BEG
+and END to FUN to compute the candidates. This way the dynamic
+candidate computation is compatible with non-prefix completion
+styles like `substring' or `orderless', which pass the empty
+string as first argument to the completion table."
+ (let ((beg (copy-marker beg))
+ (end (copy-marker end t))
+ valid table)
+ (lambda (str pred action)
+ ;; Bail out early for `metadata' and `boundaries'. This is a pointless
+ ;; move because of caching, but we do it anyway in the hope that the
+ ;; profiler report looks less confusing, since the weight of the expensive
+ ;; FUN computation is moved to the `all-completions' action. Computing
+ ;; `all-completions' must surely be most expensive, so nobody will suspect
+ ;; a thing.
+ (unless (or (eq action 'metadata) (eq (car-safe action) 'boundaries))
+ (let ((input (buffer-substring-no-properties beg end)))
+ (unless (and valid
+ (or (cape--separator-p input)
+ (funcall valid input)))
+ (let* (;; Reset in case `all-completions' is used inside FUN
+ completion-ignore-case completion-regexp-list
+ ;; Retrieve new state by calling FUN
+ (new (and (< beg end) (funcall fun input)))
+ ;; No interrupt during state update
+ throw-on-input)
+ (setq valid (car new) table (cdr new)))))
+ (complete-with-action action table str pred)))))
+
+;;;; Capfs
+
+;;;;; cape-history
+
+(declare-function ring-elements "ring")
+(declare-function eshell-bol "eshell")
+(declare-function comint-line-beginning-position "comint")
+(defvar eshell-history-ring)
+(defvar comint-input-ring)
+
+(defvar cape--history-properties
+ (list :company-kind (lambda (_) 'text)
+ :exclusive 'no
+ :display-sort-function #'identity
+ :cycle-sort-function #'identity
+ :category 'cape-history)
+ "Completion extra properties for `cape-history'.")
+
+;;;###autoload
+(defun cape-history (&optional interactive)
+ "Complete from Eshell, Comint or minibuffer history.
+See also `consult-history' for a more flexible variant based on
+`completing-read'. If INTERACTIVE is nil the function acts like a Capf."
+ (interactive (list t))
+ (if interactive
+ (cape-interactive #'cape-history)
+ (let (history bol)
+ (cond
+ ((derived-mode-p 'eshell-mode)
+ (setq history eshell-history-ring
+ bol (static-if (< emacs-major-version 30)
+ (save-excursion (eshell-bol) (point))
+ (line-beginning-position))))
+ ((derived-mode-p 'comint-mode)
+ (setq history comint-input-ring
+ bol (comint-line-beginning-position)))
+ ((and (minibufferp) (not (eq minibuffer-history-variable t)))
+ (setq history (symbol-value minibuffer-history-variable)
+ bol (line-beginning-position))))
+ (when (ring-p history)
+ (setq history (ring-elements history)))
+ (when history
+ `(,bol ,(point) ,history ,@cape--history-properties)))))
+
+;;;;; cape-file
+
+(defvar comint-unquote-function)
+(defvar comint-requote-function)
+
+(defvar cape--file-properties
+ (list :annotation-function (lambda (s) (if (string-suffix-p "/" s) " Dir" " File"))
+ :company-kind (lambda (s) (if (string-suffix-p "/" s) 'folder 'file))
+ :exclusive 'no
+ :category 'file)
+ "Completion extra properties for `cape-file'.")
+
+;;;###autoload
+(defun cape-file (&optional interactive)
+ "Complete file name at point.
+See the user option `cape-file-directory-must-exist'.
+If INTERACTIVE is nil the function acts like a Capf."
+ (interactive (list t))
+ (if interactive
+ (cape-interactive '(cape-file-directory-must-exist) #'cape-file)
+ (pcase-let* ((default-directory (pcase cape-file-directory
+ ('nil default-directory)
+ ((pred stringp) cape-file-directory)
+ (_ (funcall cape-file-directory))))
+ (prefix (and cape-file-prefix
+ (looking-back
+ (concat
+ (regexp-opt (ensure-list cape-file-prefix) t)
+ "[^ \n\t]*")
+ (pos-bol))
+ (match-end 1)))
+ (`(,beg . ,end) (if prefix
+ (cons prefix (point))
+ (cape--bounds 'filename)))
+ (non-essential t)
+ (file (buffer-substring-no-properties beg end)))
+ (when (or prefix
+ (not cape-file-directory-must-exist)
+ (and (string-search "/" file)
+ (file-exists-p (file-name-directory
+ (substitute-in-file-name file)))))
+ (unless (boundp 'comint-unquote-function)
+ (require 'comint))
+ (let ((table (cape--nonessential-table
+ (completion-table-with-quoting
+ #'read-file-name-internal
+ comint-unquote-function
+ comint-requote-function))))
+ `( ,beg ,end ,table
+ :company-location
+ ,(lambda (file)
+ (let* ((str (buffer-substring-no-properties beg (point)))
+ (pre (car (completion-boundaries str table nil "")))
+ (file (file-name-concat (substring str 0 pre) file)))
+ (and (file-exists-p file) (list file))))
+ ,@(when (or prefix (string-match-p "./" file))
+ '(:company-prefix-length t))
+ ,@cape--file-properties))))))
+
+;;;;; cape-elisp-symbol
+
+(autoload 'elisp--company-kind "elisp-mode")
+(autoload 'elisp--company-doc-buffer "elisp-mode")
+(autoload 'elisp--company-doc-string "elisp-mode")
+(autoload 'elisp--company-location "elisp-mode")
+
+(defvar cape--elisp-symbol-properties
+ (list :annotation-function #'cape--elisp-symbol-annotation
+ :exit-function #'cape--elisp-symbol-exit
+ :predicate #'cape--elisp-symbol-predicate
+ :company-kind #'elisp--company-kind
+ :company-doc-buffer #'elisp--company-doc-buffer
+ :company-docsig #'elisp--company-doc-string
+ :company-location #'elisp--company-location
+ :exclusive 'no
+ :category 'symbol)
+ "Completion extra properties for `cape-elisp-symbol'.")
+
+(defun cape--elisp-symbol-predicate (sym)
+ "Return t if SYM is bound, fbound or propertized."
+ (or (fboundp sym) (boundp sym) (symbol-plist sym)))
+
+(defun cape--elisp-symbol-exit (sym status)
+ "Wrap symbol SYM with `cape-elisp-symbol-wrapper' buffers.
+STATUS is the exit status."
+ (when-let* (((not (eq status 'exact)))
+ (c (cl-loop for (m . c) in cape-elisp-symbol-wrapper
+ if (derived-mode-p m) return c))
+ ((or (not (derived-mode-p 'emacs-lisp-mode))
+ ;; Inside comment or string
+ (let ((s (syntax-ppss))) (or (nth 3 s) (nth 4 s)))))
+ (x (if (stringp (car c)) (car c) (string (car c))))
+ (y (if (stringp (cadr c)) (cadr c) (string (cadr c)))))
+ (save-excursion
+ (backward-char (length sym))
+ (unless (save-excursion
+ (and (ignore-errors (or (backward-char (length x)) t))
+ (looking-at-p (regexp-quote x))))
+ (insert x)))
+ (unless (looking-at-p (regexp-quote y))
+ (insert y))))
+
+(defun cape--elisp-symbol-annotation (sym)
+ "Return kind of SYM."
+ (setq sym (intern-soft sym))
+ (cond
+ ((special-form-p sym) " Special")
+ ((macrop sym) " Macro")
+ ((commandp sym) " Command")
+ ((fboundp sym) " Function")
+ ((custom-variable-p sym) " Custom")
+ ((boundp sym) " Variable")
+ ((featurep sym) " Feature")
+ ((facep sym) " Face")
+ (t " Symbol")))
+
+;;;###autoload
+(defun cape-elisp-symbol (&optional interactive)
+ "Complete Elisp symbol at point.
+If INTERACTIVE is nil the function acts like a Capf."
+ (interactive (list t))
+ (if interactive
+ ;; No cycling since it breaks the :exit-function.
+ (let (completion-cycle-threshold)
+ (cape-interactive #'cape-elisp-symbol))
+ (pcase-let ((`(,beg . ,end) (cape--bounds 'symbol)))
+ (when (eq (char-after beg) ?')
+ (setq beg (1+ beg) end (max beg end)))
+ `(,beg ,end ,obarray ,@cape--elisp-symbol-properties))))
+
+;;;;; cape-elisp-block
+
+(declare-function org-element-context "org-element")
+(declare-function markdown-code-block-lang "ext:markdown-mode")
+
+(defun cape--inside-block-p (&rest langs)
+ "Return non-nil if inside LANGS code block."
+ (when-let* ((face (get-text-property (point) 'face))
+ (lang (or (and (if (listp face)
+ (memq 'org-block face)
+ (eq 'org-block face))
+ (plist-get (cadr (org-element-context)) :language))
+ (and (if (listp face)
+ (memq 'markdown-code-face face)
+ (eq 'markdown-code-face face))
+ (save-excursion
+ (markdown-code-block-lang))))))
+ (member lang langs)))
+
+;;;###autoload
+(defun cape-elisp-block (&optional interactive)
+ "Complete Elisp in Org or Markdown code block.
+This Capf is particularly useful for literate Emacs configurations.
+If INTERACTIVE is nil the function acts like a Capf."
+ (interactive (list t))
+ (cond
+ (interactive
+ ;; No code block check. Always complete Elisp when command was
+ ;; explicitly invoked interactively.
+ (cape-interactive #'elisp-completion-at-point))
+ ((cape--inside-block-p "elisp" "emacs-lisp")
+ (elisp-completion-at-point))))
+
+;;;;; cape-dabbrev
+
+(defvar cape--dabbrev-properties
+ (list :annotation-function (lambda (_) " Dabbrev")
+ :company-kind (lambda (_) 'text)
+ :exclusive 'no
+ :category 'cape-dabbrev)
+ "Completion extra properties for `cape-dabbrev'.")
+
+(defvar dabbrev-case-replace)
+(defvar dabbrev-case-fold-search)
+(defvar dabbrev-abbrev-char-regexp)
+(defvar dabbrev-abbrev-skip-leading-regexp)
+(declare-function dabbrev--find-all-expansions "dabbrev")
+(declare-function dabbrev--reset-global-variables "dabbrev")
+
+(defun cape--dabbrev-list (input)
+ "Find all Dabbrev expansions for INPUT."
+ (cape--silent
+ (dlet ((dabbrev-check-other-buffers nil)
+ (dabbrev-check-all-buffers nil)
+ (dabbrev-backward-only nil)
+ (dabbrev-limit nil)
+ (dabbrev-search-these-buffers-only
+ (ensure-list (funcall cape-dabbrev-buffer-function))))
+ (dabbrev--reset-global-variables)
+ (cons
+ (apply-partially #'string-prefix-p input)
+ (cl-loop
+ with ic = (cape--case-fold-p dabbrev-case-fold-search)
+ for w in (dabbrev--find-all-expansions input ic)
+ collect (cape--case-replace (and ic dabbrev-case-replace) input w))))))
+
+(defun cape--dabbrev-bounds ()
+ "Return bounds of abbreviation."
+ (unless (boundp 'dabbrev-abbrev-char-regexp)
+ (require 'dabbrev))
+ (let ((re (or dabbrev-abbrev-char-regexp "\\sw\\|\\s_"))
+ (limit (minibuffer-prompt-end)))
+ (if (or (looking-at re)
+ (and (> (point) limit)
+ (save-excursion (forward-char -1) (looking-at re))))
+ (cons (save-excursion
+ (while (and (> (point) limit)
+ (save-excursion (forward-char -1) (looking-at re)))
+ (forward-char -1))
+ (when dabbrev-abbrev-skip-leading-regexp
+ (while (looking-at dabbrev-abbrev-skip-leading-regexp)
+ (forward-char 1)))
+ (point))
+ (save-excursion
+ (while (looking-at re)
+ (forward-char 1))
+ (point)))
+ (cons (point) (point)))))
+
+;;;###autoload
+(defun cape-dabbrev (&optional interactive)
+ "Complete with Dabbrev at point.
+
+If INTERACTIVE is nil the function acts like a Capf. In case you
+observe a performance issue with auto-completion and `cape-dabbrev'
+it is strongly recommended to disable scanning in other buffers.
+See the user option `cape-dabbrev-buffer-function'."
+ (interactive (list t))
+ (if interactive
+ (cape-interactive #'cape-dabbrev)
+ (pcase-let ((`(,beg . ,end) (cape--dabbrev-bounds)))
+ `(,beg ,end
+ ,(completion-table-case-fold
+ (cape--dynamic-table beg end #'cape--dabbrev-list)
+ (not (cape--case-fold-p dabbrev-case-fold-search)))
+ ,@cape--dabbrev-properties))))
+
+;;;;; cape-dict
+
+(defvar cape--dict-properties
+ (list :annotation-function (lambda (_) " Dict")
+ :company-kind (lambda (_) 'text)
+ :display-sort-function #'identity
+ :cycle-sort-function #'identity
+ :exclusive 'no
+ :category 'cape-dict)
+ "Completion extra properties for `cape-dict'.")
+
+(defun cape--dict-list (input)
+ "Return all words from `cape-dict-file' matching INPUT."
+ (let* ((inhibit-message t)
+ (message-log-max nil)
+ (default-directory
+ (if (and (not (file-remote-p default-directory))
+ (file-directory-p default-directory))
+ default-directory
+ user-emacs-directory))
+ (files (mapcar #'expand-file-name
+ (ensure-list
+ (if (functionp cape-dict-file)
+ (funcall cape-dict-file)
+ cape-dict-file))))
+ (words
+ (apply #'process-lines-ignore-status
+ "grep"
+ (concat "-Fh"
+ (and (cape--case-fold-p cape-dict-case-fold) "i")
+ (and cape-dict-limit (format "m%d" cape-dict-limit)))
+ input files)))
+ (cons
+ (apply-partially
+ (if (and cape-dict-limit (length= words cape-dict-limit))
+ #'equal #'string-search)
+ input)
+ (cape--case-replace-list cape-dict-case-replace input words))))
+
+;;;###autoload
+(defun cape-dict (&optional interactive)
+ "Complete word from dictionary at point.
+This completion function works best if the dictionary is sorted
+by frequency. See the custom option `cape-dict-file'. If
+INTERACTIVE is nil the function acts like a Capf."
+ (interactive (list t))
+ (if interactive
+ (cape-interactive #'cape-dict)
+ (pcase-let ((`(,beg . ,end) (cape--bounds 'word)))
+ `( ,beg ,end
+ ,(completion-table-case-fold
+ (cape--dynamic-table beg end #'cape--dict-list)
+ (not (cape--case-fold-p cape-dict-case-fold)))
+ ,@cape--dict-properties))))
+
+;;;;; cape-abbrev
+
+(defun cape--abbrev-list ()
+ "Abbreviation list."
+ (delete "" (cl-loop for x in (abbrev--suggest-get-active-tables-including-parents)
+ nconc (all-completions "" x))))
+
+(defun cape--abbrev-annotation (abbrev)
+ "Annotate ABBREV with expansion."
+ (concat " "
+ (truncate-string-to-width
+ (format
+ "%s"
+ (symbol-value
+ (cl-loop for x in (abbrev--suggest-get-active-tables-including-parents)
+ thereis (abbrev--symbol abbrev x))))
+ 30 0 nil t)))
+
+(defun cape--abbrev-exit (_str status)
+ "Expand expansion if STATUS is not exact."
+ (unless (eq status 'exact)
+ (expand-abbrev)))
+
+(defvar cape--abbrev-properties
+ (list :annotation-function #'cape--abbrev-annotation
+ :exit-function #'cape--abbrev-exit
+ :company-kind (lambda (_) 'snippet)
+ :exclusive 'no
+ :category 'cape-abbrev)
+ "Completion extra properties for `cape-abbrev'.")
+
+;;;###autoload
+(defun cape-abbrev (&optional interactive)
+ "Complete abbreviation at point.
+If INTERACTIVE is nil the function acts like a Capf."
+ (interactive (list t))
+ (if interactive
+ ;; No cycling since it breaks the :exit-function.
+ (let (completion-cycle-threshold)
+ (cape-interactive #'cape-abbrev))
+ (when-let* ((abbrevs (cape--abbrev-list))
+ (bounds (cape--bounds 'symbol)))
+ `(,(car bounds) ,(cdr bounds) ,abbrevs ,@cape--abbrev-properties))))
+
+;;;;; cape-line
+
+(defvar cape--line-properties
+ (list :display-sort-function #'identity
+ :cycle-sort-function #'identity
+ :exclusive 'no
+ :category 'cape-line)
+ "Completion extra properties for `cape-line'.")
+
+(defun cape--line-list ()
+ "Return all lines from buffer."
+ (let ((ht (make-hash-table :test #'equal))
+ (curr-buf (current-buffer))
+ (buffers (funcall cape-line-buffer-function))
+ lines)
+ (dolist (buf (ensure-list buffers))
+ (with-current-buffer buf
+ (let ((beg (point-min))
+ (max (point-max))
+ (pt (if (eq curr-buf buf) (point) -1))
+ end)
+ (save-excursion
+ (while (< beg max)
+ (goto-char beg)
+ (setq end (pos-eol))
+ (unless (<= beg pt end)
+ (let ((line (buffer-substring-no-properties beg end)))
+ (unless (or (string-blank-p line) (gethash line ht))
+ (puthash line t ht)
+ (push line lines))))
+ (setq beg (1+ end)))))))
+ (nreverse lines)))
+
+;;;###autoload
+(defun cape-line (&optional interactive)
+ "Complete current line from other lines.
+The buffers returned by `cape-line-buffer-function' are scanned for lines.
+If INTERACTIVE is nil the function acts like a Capf."
+ (interactive (list t))
+ (if interactive
+ (cape-interactive #'cape-line)
+ `(,(pos-bol) ,(point) ,(cape--line-list) ,@cape--line-properties)))
+
+;;;; Capf combinators
+
+(defun cape--company-call (&rest app)
+ "Apply APP and handle future return values."
+ ;; Backends are non-interruptible. Disable interrupts!
+ (let ((toi throw-on-input)
+ (throw-on-input nil))
+ (pcase (apply app)
+ ;; Handle async future return values.
+ (`(:async . ,fetch)
+ (let ((res 'cape--waiting))
+ (if toi
+ (unwind-protect
+ (progn
+ (funcall fetch
+ (lambda (arg)
+ (when (eq res 'cape--waiting)
+ (push 'cape--done unread-command-events)
+ (setq res arg))))
+ (when (eq res 'cape--waiting)
+ (let ((ev (let ((input-method-function nil)
+ (echo-keystrokes 0))
+ (read-event nil t))))
+ (unless (eq ev 'cape--done)
+ (push (cons t ev) unread-command-events)
+ (setq res 'cape--cancelled)
+ (throw toi t)))))
+ (setq unread-command-events
+ (delq 'cape--done unread-command-events)))
+ (funcall fetch (lambda (arg) (setq res arg)))
+ ;; Force synchronization, not interruptible! We use polling
+ ;; here and ignore pending input since we don't use
+ ;; `sit-for'. This is the same method used by Company itself.
+ (while (eq res 'cape--waiting)
+ (sleep-for 0.01)))
+ res))
+ ;; Plain old synchronous return value.
+ (res res))))
+
+(defvar-local cape--company-init nil)
+
+;;;###autoload
+(defun cape-company-to-capf (backend &optional valid)
+ "Convert Company BACKEND function to Capf.
+VALID is a function taking the old and new input string. It should
+return nil if the cached candidates became invalid. The default value
+for VALID is `string-prefix-p' such that the candidates are only fetched
+again if the input prefix changed."
+ (lambda ()
+ (when (and (symbolp backend) (not (fboundp backend)))
+ (ignore-errors (require backend nil t)))
+ (when (bound-and-true-p company-mode)
+ (error "`cape-company-to-capf' should not be used with `company-mode', use the Company backend directly instead"))
+ (when (and (symbolp backend) (not (alist-get backend cape--company-init)))
+ (funcall backend 'init)
+ (put backend 'company-init t)
+ (setf (alist-get backend cape--company-init) t))
+ (when-let* ((pre (pcase (cape--company-call backend 'prefix)
+ ((or `(,p ,_s) (and (pred stringp) p)) (cons p (length p)))
+ ((or `(,p ,_s ,l) `(,p . ,l)) (cons p l)))))
+ (let* ((end (point)) (beg (- end (length (car pre))))
+ (valid (if (cape--company-call backend 'no-cache (car pre))
+ #'equal (or valid #'string-prefix-p)))
+ (sort-fun (and (cape--company-call backend 'sorted) #'identity))
+ restore-props)
+ (list beg end
+ (funcall
+ (if (cape--company-call backend 'ignore-case)
+ #'completion-table-case-fold
+ #'identity)
+ (cape--dynamic-table
+ beg end
+ (lambda (input)
+ (let ((cands (cape--company-call backend 'candidates input)))
+ ;; The candidate string including text properties should be
+ ;; restored in the :exit-function, unless the UI guarantees
+ ;; this itself, like Corfu.
+ (unless (bound-and-true-p corfu-mode)
+ (setq restore-props cands))
+ (cons (apply-partially valid input) cands)))))
+ :category backend
+ :exclusive 'no
+ :company-prefix-length (cdr pre)
+ :company-doc-buffer (lambda (x) (cape--company-call backend 'doc-buffer x))
+ :company-location (lambda (x) (cape--company-call backend 'location x))
+ :company-docsig (lambda (x) (cape--company-call backend 'meta x))
+ :company-deprecated (lambda (x) (cape--company-call backend 'deprecated x))
+ :company-kind (lambda (x) (cape--company-call backend 'kind x))
+ :display-sort-function sort-fun
+ :cycle-sort-function sort-fun
+ :annotation-function (lambda (x)
+ (when-let* ((ann (cape--company-call backend 'annotation x)))
+ (concat " " (string-trim ann))))
+ :exit-function (lambda (x _status)
+ ;; Restore the candidate string including
+ ;; properties if restore-props is non-nil. See
+ ;; the comment above.
+ (setq x (or (car (member x restore-props)) x))
+ (cape--company-call backend 'post-completion x)))))))
+
+;;;###autoload
+(defun cape-interactive (&rest capfs)
+ "Complete interactively with the given CAPFS."
+ (let* ((ctx (and (consp (car capfs)) (car capfs)))
+ (capfs (if ctx (cdr capfs) capfs))
+ (completion-at-point-functions
+ (if ctx
+ (mapcar (lambda (f) `(lambda () (let ,ctx (funcall ',f)))) capfs)
+ capfs)))
+ (unless (completion-at-point)
+ (user-error "%s: No completions"
+ (mapconcat (lambda (fun)
+ (if (symbolp fun)
+ (symbol-name fun)
+ "anonymous-capf"))
+ capfs ", ")))))
+
+;;;###autoload
+(defun cape-capf-interactive (capf)
+ "Create interactive completion function from CAPF."
+ (lambda (&optional interactive)
+ (interactive (list t))
+ (if interactive (cape-interactive capf) (funcall capf))))
+
+(defvar cape--super-functions
+ '( :company-docsig :company-location :company-kind
+ :company-doc-buffer :company-deprecated
+ :annotation-function :exit-function)
+ "List of extra functions which are handled by `cape-wrap-super'.")
+
+;;;###autoload
+(defun cape-wrap-super (&rest capfs)
+ "Call CAPFS and return merged completion result.
+The CAPFS list can contain the keyword `:with' to mark the Capfs
+afterwards as auxiliary. One of the non-auxiliary Capfs before `:with'
+must return non-nil for the super Capf to set in and return a non-nil
+result. Such behavior is useful when listing multiple super Capfs in
+the `completion-at-point-functions':
+
+ (setq completion-at-point-functions
+ (list (cape-capf-super \\='elisp-completion-at-point
+ :with \\='tempel-complete)
+ (cape-capf-super \\='cape-dabbrev
+ :with \\='tempel-complete)))
+
+See the dual `cape-wrap-choose' if you want to try multiple Capfs in
+turn."
+ (when-let* ((results (cl-loop for capf in capfs until (eq capf :with)
+ for res = (funcall capf)
+ if res collect (cons t res))))
+ (pcase-let* ((results (nconc results
+ (cl-loop for capf in (cdr (memq :with capfs))
+ for res = (funcall capf)
+ if res collect (cons nil res))))
+ (`((,_main ,beg ,end . ,_)) results)
+ (cand-ht nil)
+ (tables nil)
+ (exclusive nil)
+ (prefix-len nil))
+ (cl-loop for (main beg2 end2 table . plist) in results do
+ ;; Note: `cape-capf-super' currently cannot merge Capfs which
+ ;; trigger at different beginning positions. In order to support
+ ;; this, take the smallest BEG value and then normalize all
+ ;; candidates by prefixing them such that they all start at the
+ ;; smallest BEG position.
+ (when (= beg beg2)
+ (push (list main (plist-get plist :predicate) table
+ ;; Plist attached to the candidates
+ (mapcan (lambda (f)
+ (when-let* ((v (plist-get plist f)))
+ (list f v)))
+ cape--super-functions))
+ tables)
+ ;; The resulting merged Capf is exclusive if one of the main
+ ;; Capfs is exclusive.
+ (when (and main (not (eq (plist-get plist :exclusive) 'no)))
+ (setq exclusive t))
+ (setq end (max end end2))
+ (let ((plen (plist-get plist :company-prefix-length)))
+ (cond
+ ((eq plen t)
+ (setq prefix-len t))
+ ((and (not prefix-len) (integerp plen))
+ (setq prefix-len plen))
+ ((and (integerp prefix-len) (integerp plen))
+ (setq prefix-len (max prefix-len plen)))))))
+ (setq tables (nreverse tables))
+ `( ,beg ,end
+ ,(lambda (str pred action)
+ (pcase action
+ ((or `(boundaries . ,_) 'metadata) nil)
+ ('t ;; all-completions
+ (let ((ht (make-hash-table :test #'equal))
+ (candidates nil))
+ (cl-loop for (main table-pred table cand-plist) in tables do
+ (let* ((pr (if (and table-pred pred)
+ (lambda (x) (and (funcall table-pred x) (funcall pred x)))
+ (or table-pred pred)))
+ (md (completion-metadata "" table pr))
+ (sort (or (completion-metadata-get md 'display-sort-function)
+ #'identity))
+ ;; Always compute candidates of the main Capf
+ ;; tables, which come first in the tables
+ ;; list. For the :with Capfs only compute
+ ;; candidates if we've already determined that
+ ;; main candidates are available.
+ (cands (when (or main (or exclusive cand-ht candidates))
+ (funcall sort (all-completions str table pr)))))
+ ;; Handle duplicates with a hash table.
+ (cl-loop
+ for cand in-ref cands
+ for dup = (gethash cand ht t) do
+ (cond
+ ((eq dup t)
+ ;; Candidate does not yet exist.
+ (puthash cand cand-plist ht))
+ ((not (equal dup cand-plist))
+ ;; Duplicate candidate. Candidate plist is
+ ;; different, therefore disambiguate the
+ ;; candidates.
+ (setf cand (propertize cand 'cape-capf-super
+ (cons cand cand-plist))))))
+ (when cands (push cands candidates))))
+ (when (or cand-ht candidates)
+ (setq candidates (apply #'nconc (nreverse candidates))
+ cand-ht ht)
+ candidates)))
+ (_ ;; try-completion and test-completion
+ (cl-loop for (_main table-pred table _cand-plist) in tables thereis
+ (complete-with-action
+ action table str
+ (if (and table-pred pred)
+ (lambda (x) (and (funcall table-pred x) (funcall pred x)))
+ (or table-pred pred)))))))
+ :category cape-super
+ :company-prefix-length ,prefix-len
+ :display-sort-function ,#'identity
+ :cycle-sort-function ,#'identity
+ ,@(and (not exclusive) '(:exclusive no))
+ ,@(mapcan
+ (lambda (prop)
+ (list prop
+ (lambda (cand &rest args)
+ (if-let* ((ref (get-text-property 0 'cape-capf-super cand)))
+ (when-let* ((fun (plist-get (cdr ref) prop)))
+ (apply fun (car ref) args))
+ (when-let* ((plist (and cand-ht (gethash cand cand-ht)))
+ (fun (plist-get plist prop)))
+ (apply fun cand args))))))
+ cape--super-functions)))))
+
+;;;###autoload
+(defun cape-wrap-choose (&rest capfs)
+ "Call each of CAPFS in turn and return first non-nil result.
+Use `cape-wrap-choose' to create a single Capf from multiple Capfs.
+Usually you want to add multiple non-exclusive Capfs to the variable
+`completion-at-point-functions' directly instead. See the dual
+`cape-wrap-super' if you want to merge multiple Capf results."
+ (cl-loop
+ for capf in capfs thereis
+ (pcase (funcall capf)
+ ((and result `(,beg ,end ,table . ,plist))
+ (let* ((str (buffer-substring-no-properties beg end))
+ (pt (- (point) beg))
+ (pred (plist-get plist :predicate))
+ (md (completion-metadata (substring str 0 pt) table pred)))
+ ;; Treat the Capfs always as non-exclusive. Return the first which
+ ;; returns non-nil. See also the comment in `corfu--capf-wrapper'.
+ (and (completion-try-completion str table pred pt md)
+ result))))))
+
+;;;###autoload
+(defun cape-wrap-debug (capf &optional name)
+ "Call CAPF and return a completion table which prints trace messages.
+If CAPF is an anonymous lambda, pass the Capf NAME explicitly for
+meaningful debugging output."
+ (unless name
+ (setq name (if (symbolp capf) capf "capf")))
+ (setq name (format "%s@%s" name (incf cape--debug-id)))
+ (pcase (funcall capf)
+ (`(,beg ,end ,table . ,plist)
+ (let* ((limit (1+ cape--debug-length))
+ (pred (plist-get plist :predicate))
+ (cands
+ ;; Reset regexps for `all-completions'
+ (let (completion-ignore-case completion-regexp-list)
+ (all-completions
+ "" table
+ (lambda (&rest args)
+ (and (or (not pred) (apply pred args)) (>= (decf limit) 0))))))
+ (plist-str "")
+ (plist-elt plist))
+ (while (cdr plist-elt)
+ (setq plist-str (format "%s %s=%s" plist-str
+ (substring (symbol-name (car plist-elt)) 1)
+ (cape--debug-print (cadr plist-elt)))
+ plist-elt (cddr plist-elt)))
+ (cape--debug-message
+ "%s => input=%s:%s:%S table=%s%s"
+ name (+ beg 0) (+ end 0) (buffer-substring-no-properties beg end)
+ (cape--debug-print cands)
+ plist-str))
+ `( ,beg ,end
+ ,(cape--debug-table
+ table name (copy-marker beg) (copy-marker end t))
+ ,@(when-let* ((exit (plist-get plist :exit-function)))
+ (list :exit-function
+ (lambda (str status)
+ (cape--debug-message "%s:exit(status=%s string=%S)"
+ name status str)
+ (funcall exit str status))))
+ . ,plist))
+ (result
+ (cape--debug-message "%s() => %s (No completion)"
+ name (cape--debug-print result)))))
+
+;;;###autoload
+(defun cape-wrap-buster (capf &optional valid)
+ "Call CAPF and return a completion table with cache busting.
+This function can be used as an advice around an existing Capf.
+The cache is busted when the input changes. The argument VALID
+can be a function taking the old and new input string. It should
+return nil if the new input requires that the completion table is
+refreshed. The default value for VALID is `equal', such that the
+completion table is refreshed on every input change."
+ (setq valid (or valid #'equal))
+ (pcase (funcall capf)
+ (`(,beg ,end ,table . ,plist)
+ (setq plist `(:cape--buster t . ,plist))
+ `( ,beg ,end
+ ,(let* ((beg (copy-marker beg))
+ (end (copy-marker end t))
+ (input (buffer-substring-no-properties beg end)))
+ (lambda (str pred action)
+ (let ((new-input (buffer-substring-no-properties beg end)))
+ (unless (or (not (eq action t))
+ (cape--separator-p new-input)
+ (funcall valid input new-input))
+ (pcase
+ ;; Reset in case `all-completions' is used inside CAPF
+ (let (completion-ignore-case completion-regexp-list)
+ (funcall capf))
+ ((and `(,new-beg ,new-end ,new-table . ,new-plist)
+ (guard (and (= beg new-beg) (= end new-end))))
+ (let (throw-on-input) ;; No interrupt during state update
+ (setf table new-table
+ input new-input
+ (cddr plist) new-plist))))))
+ (complete-with-action action table str pred)))
+ ,@plist))))
+
+;;;###autoload
+(defun cape-wrap-passthrough (capf)
+ "Call CAPF and make sure that no completion style filtering takes place.
+This function can be used as an advice around an existing Capf."
+ (pcase (funcall capf)
+ (`(,beg ,end ,table . ,plist)
+ `(,beg ,end ,(cape--passthrough-table table) ,@plist))))
+
+;;;###autoload
+(defun cape-wrap-properties (capf &rest properties)
+ "Call CAPF and add completion PROPERTIES.
+Completion properties include :exclusive, :category,
+:annotation-function, :affixation-function, :display-sort-function,
+:company-kind, :company-doc-buffer, :company-docsig, :company-location,
+:company-deprecated and :company-prefix-length."
+ (let ((keys (cl-loop for (k _) on properties by #'cddr
+ collect (intern (substring (symbol-name k) 1)))))
+ (pcase (funcall capf)
+ (`(,beg ,end ,table . ,plist)
+ `( ,beg ,end ,(cape--table-drop-metadata table keys)
+ ,@properties ,@plist)))))
+
+;;;###autoload
+(defun cape-wrap-nonexclusive (capf)
+ "Call CAPF and ensure that it is marked as non-exclusive.
+This function can be used as an advice around an existing Capf."
+ (cape-wrap-properties capf :exclusive 'no))
+
+;;;###autoload
+(defun cape-wrap-sort (capf &optional sort)
+ "Call CAPF and add SORT function as completion metadata.
+If the SORT argument is nil or not given, the completion UI will use
+its own default sorting algorithm. This function can be used as an
+advice around an existing Capf."
+ (cape-wrap-properties
+ capf
+ :display-sort-function sort
+ :cycle-sort-function sort))
+
+;;;###autoload
+(defun cape-wrap-predicate (capf predicate)
+ "Call CAPF and add an additional candidate PREDICATE.
+The PREDICATE is passed the candidate symbol or string."
+ (pcase (funcall capf)
+ (`(,beg ,end ,table . ,plist)
+ `( ,beg ,end ,table
+ :predicate
+ ,(if-let* ((pred (plist-get plist :predicate)))
+ ;; First argument is key, second is value for hash tables.
+ ;; The first argument can be a cons cell for alists. Then
+ ;; the candidate itself is either a string or a symbol. We
+ ;; normalize the calling convention here such that PREDICATE
+ ;; always receives a string or a symbol.
+ (lambda (&rest args)
+ (when (apply pred args)
+ (setq args (car args))
+ (funcall predicate (if (consp args) (car args) args))))
+ (lambda (key &optional _val)
+ (funcall predicate (if (consp key) (car key) key))))
+ ,@plist))))
+
+;;;###autoload
+(defun cape-wrap-silent (capf)
+ "Call CAPF and silence it (no messages, no errors).
+This function can be used as an advice around an existing Capf."
+ (pcase (cape--silent (funcall capf))
+ (`(,beg ,end ,table . ,plist)
+ `(,beg ,end ,(cape--silent-table table) ,@plist))))
+
+;;;###autoload
+(defun cape-wrap-case-fold (capf &optional nofold)
+ "Call CAPF and return a case-insensitive completion table.
+If NOFOLD is non-nil return a case sensitive table instead. This
+function can be used as an advice around an existing Capf."
+ (pcase (funcall capf)
+ (`(,beg ,end ,table . ,plist)
+ `(,beg ,end ,(completion-table-case-fold table nofold) ,@plist))))
+
+;;;###autoload
+(defun cape-wrap-noninterruptible (capf)
+ "Call CAPF and return a non-interruptible completion table.
+This function can be used as an advice around an existing Capf."
+ (pcase (let (throw-on-input) (funcall capf))
+ (`(,beg ,end ,table . ,plist)
+ `(,beg ,end ,(cape--noninterruptible-table table) ,@plist))))
+
+;;;###autoload
+(defun cape-wrap-prefix-length (capf length)
+ "Call CAPF and ensure that prefix length is greater or equal than LENGTH.
+If the prefix is long enough, enforce auto completion."
+ (pcase (funcall capf)
+ (`(,beg ,end ,table . ,plist)
+ (when (>= (- end beg) length)
+ `(,beg ,end ,table :company-prefix-length t ,@plist)))))
+
+;;;###autoload
+(defun cape-wrap-inside-faces (capf &rest faces)
+ "Call CAPF only if inside FACES."
+ (when-let* (((> (point) (point-min)))
+ (fs (get-text-property (1- (point)) 'face))
+ ((if (listp fs)
+ (cl-loop for f in fs thereis (memq f faces))
+ (memq fs faces))))
+ (funcall capf)))
+
+;;;###autoload
+(defun cape-wrap-inside-code (capf)
+ "Call CAPF only if inside code, not inside a comment or string.
+This function can be used as an advice around an existing Capf."
+ (let ((s (syntax-ppss)))
+ (and (not (nth 3 s)) (not (nth 4 s)) (funcall capf))))
+
+;;;###autoload
+(defun cape-wrap-inside-comment (capf)
+ "Call CAPF only if inside comment.
+This function can be used as an advice around an existing Capf."
+ (and (nth 4 (syntax-ppss)) (funcall capf)))
+
+;;;###autoload
+(defun cape-wrap-inside-string (capf)
+ "Call CAPF only if inside string.
+This function can be used as an advice around an existing Capf."
+ (and (nth 3 (syntax-ppss)) (funcall capf)))
+
+;;;###autoload
+(defun cape-wrap-accept-all (capf)
+ "Call CAPF and return a completion table which accepts every input.
+This function can be used as an advice around an existing Capf."
+ (pcase (funcall capf)
+ (`(,beg ,end ,table . ,plist)
+ `(,beg ,end ,(cape--accept-all-table table) . ,plist))))
+
+(defvar cape--trigger-syntax-table (make-syntax-table (syntax-table))
+ "Syntax table used for the trigger character.")
+
+;;;###autoload
+(defun cape-wrap-trigger (capf trigger)
+ "Ensure that TRIGGER character occurs before point and then call CAPF.
+See also `corfu-auto-trigger'.
+Example:
+ (setq corfu-auto-trigger \"/\"
+ completion-at-point-functions
+ (list (cape-capf-trigger \\='cape-abbrev ?/)))"
+ (when-let* ((pos (save-excursion (search-backward (char-to-string trigger) (pos-bol) 'noerror)))
+ ((save-excursion (not (re-search-backward "\\s-" pos 'noerror)))))
+ (pcase
+ ;; Treat the trigger character as punctuation.
+ (with-syntax-table cape--trigger-syntax-table
+ (unless (eq (char-syntax trigger) ?.)
+ (modify-syntax-entry trigger "."))
+ (funcall capf))
+ (`(,beg ,end ,table . ,plist)
+ (when (<= pos beg (1+ pos))
+ `( ,(1+ pos) ,end ,table
+ :company-prefix-length t
+ :exit-function
+ ,(let ((pos (copy-marker pos))
+ (end (copy-marker (1+ pos))))
+ (lambda (str status)
+ (delete-region pos end)
+ (when-let* ((exit (plist-get plist :exit-function)))
+ (funcall exit str status))))
+ . ,plist))))))
+
+(dolist (wrapper (list #'cape-wrap-accept-all #'cape-wrap-buster
+ #'cape-wrap-case-fold #'cape-wrap-choose
+ #'cape-wrap-debug #'cape-wrap-inside-code
+ #'cape-wrap-inside-comment #'cape-wrap-inside-faces
+ #'cape-wrap-inside-string #'cape-wrap-nonexclusive
+ #'cape-wrap-noninterruptible #'cape-wrap-passthrough
+ #'cape-wrap-predicate #'cape-wrap-prefix-length
+ #'cape-wrap-properties #'cape-wrap-silent
+ #'cape-wrap-sort #'cape-wrap-super #'cape-wrap-trigger))
+ (let ((name (string-remove-prefix "cape-wrap-" (symbol-name wrapper))))
+ (defalias (intern (format "cape-capf-%s" name))
+ (lambda (capf &rest args) (lambda () (apply wrapper capf args)))
+ (format "Create a %s Capf from CAPF.
+The Capf calls `%s' with CAPF and ARGS as arguments.
+See `%s' for documentation." name wrapper wrapper))))
+
+;;;###autoload (autoload 'cape-capf-accept-all "cape")
+;;;###autoload (autoload 'cape-capf-buster "cape")
+;;;###autoload (autoload 'cape-capf-case-fold "cape")
+;;;###autoload (autoload 'cape-capf-choose "cape")
+;;;###autoload (autoload 'cape-capf-debug "cape")
+;;;###autoload (autoload 'cape-capf-inside-code "cape")
+;;;###autoload (autoload 'cape-capf-inside-comment "cape")
+;;;###autoload (autoload 'cape-capf-inside-faces "cape")
+;;;###autoload (autoload 'cape-capf-inside-string "cape")
+;;;###autoload (autoload 'cape-capf-nonexclusive "cape")
+;;;###autoload (autoload 'cape-capf-noninterruptible "cape")
+;;;###autoload (autoload 'cape-capf-passthrough "cape")
+;;;###autoload (autoload 'cape-capf-predicate "cape")
+;;;###autoload (autoload 'cape-capf-prefix-length "cape")
+;;;###autoload (autoload 'cape-capf-properties "cape")
+;;;###autoload (autoload 'cape-capf-silent "cape")
+;;;###autoload (autoload 'cape-capf-sort "cape")
+;;;###autoload (autoload 'cape-capf-super "cape")
+;;;###autoload (autoload 'cape-capf-trigger "cape")
+
+(defvar-keymap cape-prefix-map
+ :doc "Keymap used as completion entry point.
+The keymap should be installed globally under a prefix."
+ "TAB" #'completion-at-point
+ "M-TAB" #'completion-at-point
+ "p" #'completion-at-point
+ "t" #'complete-tag
+ "d" #'cape-dabbrev
+ "h" #'cape-history
+ "f" #'cape-file
+ "s" #'cape-elisp-symbol
+ "e" #'cape-elisp-block
+ "a" #'cape-abbrev
+ "l" #'cape-line
+ "w" #'cape-dict
+ "k" 'cape-keyword
+ ":" 'cape-emoji
+ "\\" 'cape-tex
+ "_" 'cape-tex
+ "^" 'cape-tex
+ "&" 'cape-sgml
+ "r" 'cape-rfc1345)
+
+;;;###autoload (autoload 'cape-prefix-map "cape" nil t 'keymap)
+(defalias 'cape-prefix-map cape-prefix-map)
+
+(provide 'cape)
+;;; cape.el ends here
diff --git a/.config/emacs/lisp/minadstack/consult.el b/.config/emacs/lisp/minadstack/consult.el
new file mode 100644
index 0000000..d500c9c
--- /dev/null
+++ b/.config/emacs/lisp/minadstack/consult.el
@@ -0,0 +1,5739 @@
+;;; consult.el --- Search and navigate via completing-read -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021-2026 Free Software Foundation, Inc.
+
+;; Author: Daniel Mendler and Consult contributors
+;; Maintainer: Daniel Mendler <mail@daniel-mendler.de>
+;; Created: 2020
+;; Version: 3.6
+;; Package-Requires: ((emacs "29.1") (compat "31"))
+;; URL: https://github.com/minad/consult
+;; Keywords: matching, files, completion
+
+;; This file is 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:
+
+;; Consult implements a set of `consult-<thing>' commands, which aim to
+;; improve the way you use Emacs. The commands are founded on
+;; `completing-read', which selects from a list of candidate strings.
+;; Consult provides an enhanced buffer switcher `consult-buffer' and
+;; search and navigation commands like `consult-imenu' and
+;; `consult-line'. Searching through multiple files is supported by the
+;; asynchronous `consult-grep' command. Many Consult commands support
+;; previewing candidates. If a candidate is selected in the completion
+;; view, the buffer shows the candidate immediately.
+
+;; The Consult commands are compatible with multiple completion systems
+;; based on the Emacs `completing-read' API, including the default
+;; completion system, Vertico, Mct and Icomplete.
+
+;; See the README for an overview of the available Consult commands and
+;; the documentation of the configuration and installation of the
+;; package.
+
+;; The full list of contributors can be found in the acknowledgments
+;; section of the README.
+
+;;; Code:
+
+(eval-when-compile
+ (require 'cl-lib)
+ (require 'subr-x))
+(require 'compat)
+(require 'bookmark)
+
+(defgroup consult nil
+ "Search and navigate via `completing-read'."
+ :link '(info-link :tag "Info Manual" "(consult)")
+ :link '(url-link :tag "Website" "https://github.com/minad/consult")
+ :link '(url-link :tag "Wiki" "https://github.com/minad/consult/wiki")
+ :link '(emacs-library-link :tag "Library Source" "consult.el")
+ :group 'files
+ :group 'outlines
+ :group 'minibuffer
+ :prefix "consult-")
+
+;;;; Customization
+
+(defcustom consult-narrow-key nil
+ "Prefix key for narrowing during completion.
+
+Good choices for this key are \"<\" and \"C-+\" for example. The
+key must be a string accepted by `key-valid-p'."
+ :type '(choice key (const :tag "None" nil)))
+
+(defcustom consult-widen-key nil
+ "Key used for widening during completion.
+
+If this key is unset, defaults to twice the `consult-narrow-key'.
+The key must be a string accepted by `key-valid-p'."
+ :type '(choice key (const :tag "None" nil)))
+
+(defcustom consult-project-function
+ #'consult--default-project-function
+ "Function which returns project root directory.
+The function takes one boolean argument MAY-PROMPT. If
+MAY-PROMPT is non-nil, the function may ask the prompt the user
+for a project directory. The root directory is used by
+`consult-buffer' and `consult-grep'."
+ :type `(choice
+ (const :tag "Default project function" ,#'consult--default-project-function)
+ (function :tag "Custom function")
+ (const :tag "No project integration" nil)))
+
+(defcustom consult-async-refresh-delay 0.2
+ "Refreshing delay of the completion UI for asynchronous commands.
+
+The completion UI is only updated every
+`consult-async-refresh-delay' seconds. This applies to
+asynchronous commands like for example `consult-grep'."
+ :type '(float :tag "Delay in seconds"))
+
+(defcustom consult-async-input-throttle 0.5
+ "Input throttle for asynchronous commands.
+
+The asynchronous process is started only every
+`consult-async-input-throttle' seconds. This applies to asynchronous
+commands, e.g., `consult-grep'."
+ :type '(float :tag "Delay in seconds"))
+
+(defcustom consult-async-input-debounce 0.2
+ "Input debounce for asynchronous commands.
+
+The asynchronous process is started only when there has not been new
+input for `consult-async-input-debounce' seconds. This applies to
+asynchronous commands, e.g., `consult-grep'."
+ :type '(float :tag "Delay in seconds"))
+
+(defcustom consult-async-min-input 3
+ "Minimum number of characters needed, before asynchronous process is called.
+
+This applies to asynchronous commands, e.g., `consult-grep'."
+ :type '(natnum :tag "Number of characters"))
+
+(defcustom consult-async-split-style 'perl
+ "Async splitting style, see `consult-async-split-styles-alist'."
+ :type '(choice (const :tag "No splitting" nil)
+ (const :tag "Comma" comma)
+ (const :tag "Semicolon" semicolon)
+ (const :tag "Perl" perl)))
+
+(defcustom consult-async-split-styles-alist
+ `((none :function ,#'consult--split-none)
+ (comma :separator ?, :function ,#'consult--split-separator)
+ (semicolon :separator ?\; :function ,#'consult--split-separator)
+ (perl :initial ?# :function ,#'consult--split-perl))
+ "Async splitting styles."
+ :type '(alist :key-type symbol :value-type plist))
+
+(defcustom consult-async-indicator
+ '((running ?* consult-async-running)
+ (finished ?: consult-async-finished)
+ (killed ?\; consult-async-failed)
+ (failed ?! consult-async-failed))
+ "Async indicator characters and faces.
+Set to nil to disable."
+ :type '(alist :key-type symbol :value-type (list character face)))
+
+(defcustom consult-mode-histories
+ '((eshell-mode eshell-history-ring eshell-history-index eshell-bol)
+ (comint-mode comint-input-ring comint-input-ring-index comint-bol)
+ (term-mode term-input-ring term-input-ring-index term-bol))
+ "Alist of mode histories (mode history index bol).
+The histories can be rings or lists. Index, if provided, is a
+variable to set to the index of the selection within the ring or
+list. Bol, if provided is a function which jumps to the beginning
+of the line after the prompt."
+ :type '(alist :key-type symbol
+ :value-type (group :tag "Include Index"
+ (symbol :tag "List/Ring")
+ (symbol :tag "Index Variable")
+ (symbol :tag "Bol Function"))))
+
+(defcustom consult-themes nil
+ "List of themes (symbols or regexps) to be presented for selection.
+nil shows all `custom-available-themes'."
+ :type '(repeat (choice symbol regexp)))
+
+(defcustom consult-after-jump-hook (list #'recenter)
+ "Function called after jumping to a location.
+
+Commonly used functions for this hook are `recenter' and
+`reposition-window'. You may want to add a function which pulses the
+current line, e.g., `pulse-momentary-highlight-one-line'. The hook
+called during preview and for the jump after selection."
+ :type 'hook)
+
+(defcustom consult-line-start-from-top nil
+ "Start search from the top if non-nil.
+Otherwise start the search at the current line and wrap around."
+ :type 'boolean)
+
+(defcustom consult-point-placement 'match-beginning
+ "Where to leave point when jumping to a match.
+This setting affects the command `consult-line' and the `consult-grep' variants."
+ :type '(choice (const :tag "Beginning of the line" line-beginning)
+ (const :tag "Beginning of the match" match-beginning)
+ (const :tag "End of the match" match-end)))
+
+(defcustom consult-line-numbers-widen t
+ "Show absolute line numbers when narrowing is active.
+
+See also `display-line-numbers-widen'."
+ :type 'boolean)
+
+(defcustom consult-goto-line-numbers t
+ "Show line numbers for `consult-goto-line'."
+ :type 'boolean)
+
+(defcustom consult-fontify-preserve t
+ "Preserve fontification for line-based commands."
+ :type 'boolean)
+
+(defcustom consult-fontify-max-size (* 1024 1024)
+ "Avoid whole-buffer fontification for buffers larger than this character limit.
+This setting affects the command `consult-keep-lines'."
+ :type '(natnum :tag "Buffer size in characters"))
+
+(defcustom consult-buffer-filter
+ '("\\` "
+ "\\`\\*Completions\\*\\'"
+ "\\`\\*Multiple Choice Help\\*\\'"
+ "\\`\\*Flymake log\\*\\'"
+ "\\`\\*Semantic SymRef\\*\\'"
+ "\\`\\*vc\\*\\'"
+ "\\`newsrc-dribble\\'" ;; Gnus
+ "\\`\\*tramp/.*\\*\\'")
+ "Filter regexps for `consult-buffer'.
+
+The default setting is to filter ephemeral buffer names beginning
+with a space character, the *Completions* buffer and a few log
+buffers. The regular expressions are matched case sensitively."
+ :type '(repeat regexp))
+
+(defcustom consult-buffer-list-function #'buffer-list
+ "List of buffers to use for selection.
+By default, the variable is set to the function `buffer-list', which
+returns all buffers from all frames. Set it to
+`consult--frame-buffer-list' to only use buffers belonging to the
+current frame (or tab-bar tab). Alternatively use a custom function for
+custom buffer isolation."
+ :type `(choice (const :tag "All buffers" ,#'buffer-list)
+ (const :tag "Frame/Tab buffers" ,#'consult--frame-buffer-list)
+ (function :tag "Custom function")))
+
+(defcustom consult-buffer-sources
+ '(consult-source-buffer
+ consult-source-hidden-buffer
+ consult-source-modified-buffer
+ consult-source-other-buffer
+ consult-source-recent-file
+ consult-source-buffer-register
+ consult-source-file-register
+ consult-source-bookmark
+ consult-source-project-buffer-hidden
+ consult-source-project-recent-file-hidden
+ consult-source-project-root-hidden)
+ "Sources used by `consult-buffer'.
+See also `consult-project-buffer-sources'.
+See `consult--multi' for a description of the source data structure."
+ :type '(repeat symbol))
+
+(defcustom consult-project-buffer-sources
+ '(consult-source-project-buffer
+ consult-source-project-recent-file
+ consult-source-project-root)
+ "Sources used by `consult-project-buffer'.
+See also `consult-buffer-sources'.
+See `consult--multi' for a description of the source data structure."
+ :type '(repeat symbol))
+
+(defcustom consult-mode-command-filter
+ '(;; Filter commands
+ "-mode\\'" "--"
+ ;; Filter whole features
+ simple mwheel time so-long recentf tab-bar tab-line)
+ "Filter commands for `consult-mode-command'."
+ :type '(repeat (choice symbol regexp)))
+
+(defcustom consult-grep-max-columns 300
+ "Maximal number of columns of grep output.
+If set to nil, do not truncate candidates. This can have negative
+performance implications but helps if you want to export long lines via
+`embark-export'."
+ :type '(choice natnum (const nil)))
+
+(defconst consult--grep-match-regexp
+ "\\`\\(?:\\./\\)?\\([^\n\0]+\\)\0\\([0-9]+\\)\\([-:\0]\\)"
+ "Regexp used to match file and line of grep output.")
+
+(defcustom consult-grep-args
+ '("grep" (consult--grep-exclude-args)
+ "--null --line-buffered --color=never --ignore-case\
+ --with-filename --line-number -I -r")
+ "Command line arguments for grep, see `consult-grep'.
+The dynamically computed arguments are appended.
+Can be either a string, or a list of strings or expressions."
+ :type '(choice string (repeat (choice string sexp))))
+
+(defcustom consult-git-grep-args
+ "git --no-pager grep --null --color=never --ignore-case\
+ --extended-regexp --line-number -I"
+ "Command line arguments for git-grep, see `consult-git-grep'.
+The dynamically computed arguments are appended.
+Can be either a string, or a list of strings or expressions."
+ :type '(choice string (repeat (choice string sexp))))
+
+(defcustom consult-ripgrep-args
+ "rg --null --line-buffered --color=never --max-columns=1000 --path-separator /\
+ --smart-case --no-heading --with-filename --line-number --search-zip"
+ "Command line arguments for ripgrep, see `consult-ripgrep'.
+The dynamically computed arguments are appended.
+Can be either a string, or a list of strings or expressions."
+ :type '(choice string (repeat (choice string sexp))))
+
+(defcustom consult-find-args
+ "find . -not ( -path */.[A-Za-z]* -prune )"
+ "Command line arguments for find, see `consult-find'.
+The dynamically computed arguments are appended.
+Can be either a string, or a list of strings or expressions."
+ :type '(choice string (repeat (choice string sexp))))
+
+(defcustom consult-fd-args
+ '((if (executable-find "fdfind" 'remote) "fdfind" "fd")
+ "--full-path --color=never")
+ "Command line arguments for fd, see `consult-fd'.
+The dynamically computed arguments are appended.
+Can be either a string, or a list of strings or expressions."
+ :type '(choice string (repeat (choice string sexp))))
+
+(defcustom consult-locate-args
+ "locate --ignore-case" ;; --existing not supported by Debian plocate
+ "Command line arguments for locate, see `consult-locate'.
+The dynamically computed arguments are appended.
+Can be either a string, or a list of strings or expressions."
+ :type '(choice string (repeat (choice string sexp))))
+
+(defcustom consult-man-args
+ "man -k"
+ "Command line arguments for man, see `consult-man'.
+The dynamically computed arguments are appended.
+Can be either a string, or a list of strings or expressions."
+ :type '(choice string (repeat (choice string sexp))))
+
+(defcustom consult-preview-key 'any
+ "Preview trigger keys, can be nil, `any', a single key or a list of keys.
+Debouncing can be specified via the `:debounce' attribute. The
+individual keys must be strings accepted by `key-valid-p'."
+ :type '(choice (const :tag "Any key" any)
+ (list :tag "Debounced"
+ (const :debounce)
+ (float :tag "Seconds" 0.1)
+ (const any))
+ (const :tag "No preview" nil)
+ (key :tag "Key")
+ (repeat :tag "List of keys" key)))
+
+(defcustom consult-preview-partial-size (* 1024 1024)
+ "Files larger than this byte limit are previewed partially."
+ :type '(natnum :tag "File size in bytes"))
+
+(defcustom consult-preview-partial-chunk (* 10 1024)
+ "Partial preview chunk size in bytes.
+If a file is larger than `consult-preview-partial-size' only the
+chunk from the beginning of the file is previewed."
+ :type '(natnum :tag "Chunk size in bytes"))
+
+(defcustom consult-preview-max-count 10
+ "Number of file buffers to keep open temporarily during preview."
+ :type '(natnum :tag "Number of buffers"))
+
+(defcustom consult-preview-excluded-buffers nil
+ "Buffers excluded from preview.
+The value should conform to the predicate format demanded by the
+function `buffer-match-p'."
+ :type 'sexp)
+
+(defcustom consult-preview-excluded-files
+ ;; Do not preview remote and gpg files
+ '("\\`/[^/|:]+:" "\\.gpg\\'")
+ "List of regexps matched against names of files, which are not previewed."
+ :type '(repeat regexp))
+
+(defcustom consult-preview-allowed-hooks
+ '(global-font-lock-mode
+ save-place-find-file-hook)
+ "List of hooks, which should be executed during file preview.
+This variable applies to `find-file-hook', `change-major-mode-hook' and
+mode hooks, e.g., `prog-mode-hook'."
+ :type '(repeat symbol))
+
+(defcustom consult-preview-variables
+ '((inhibit-message . t)
+ (enable-dir-local-variables . nil)
+ (enable-local-variables . :safe)
+ (non-essential . t)
+ (delay-mode-hooks . t))
+ "Variables which are bound for file preview."
+ :type '(alist :key-type symbol))
+
+(defcustom consult-bookmark-narrow
+ `((?f "File" bookmark-default-handler)
+ (?h "Help" help-bookmark-jump Info-bookmark-jump
+ Man-bookmark-jump woman-bookmark-jump)
+ (?p "Picture" image-bookmark-jump)
+ (?d "Docview" doc-view-bookmark-jump)
+ (?m "Mail" gnus-summary-bookmark-jump)
+ (?s "Shell" eshell-bookmark-jump shell-bookmark-jump)
+ (?w "Web" eww-bookmark-jump xwidget-webkit-bookmark-jump-handler)
+ (?v "VC Directory" vc-dir-bookmark-jump)
+ (nil "Other"))
+ "Bookmark narrowing configuration.
+
+Each element of the list must have the form (char name handlers...)."
+ :type '(alist :key-type character :value-type (cons string (repeat function))))
+
+;;;; Faces
+
+(defgroup consult-faces nil
+ "Faces used by Consult."
+ :group 'consult
+ :group 'faces)
+
+(defface consult-preview-line
+ '((t :inherit consult-preview-insertion :extend t))
+ "Face used for line previews.")
+
+(defface consult-highlight-match
+ '((t :inherit match))
+ "Face used to highlight matches in the completion candidates.
+Used for example by `consult-grep'.")
+
+(defface consult-highlight-mark
+ '((t :inherit consult-highlight-match))
+ "Face used for mark positions in completion candidates.
+Used for example by `consult-mark'. The face should be different
+than the `cursor' face to avoid confusion.")
+
+(defface consult-preview-match
+ '((t :inherit isearch))
+ "Face used for match previews, e.g., in `consult-line'.")
+
+(defface consult-preview-insertion
+ '((t :inherit region))
+ "Face used for previews of text to be inserted.
+Used by `consult-completion-in-region', `consult-yank' and `consult-history'.")
+
+(defface consult-narrow-indicator
+ '((t :inherit warning :weight normal))
+ "Face used for the narrowing indicator.")
+
+(defface consult-async-running
+ '((t :inherit consult-narrow-indicator))
+ "Face used if asynchronous process is running.")
+
+(defface consult-async-finished
+ '((t :inherit success))
+ "Face used if asynchronous process has finished.")
+
+(defface consult-async-failed
+ '((t :inherit error))
+ "Face used if asynchronous process has failed.")
+
+(defface consult-async-split
+ '((t :inherit font-lock-negation-char-face))
+ "Face used to highlight punctuation character.")
+
+(defface consult-async-option
+ '((t :inherit warning :weight normal))
+ "Face used to highlight asynchronous command options.")
+
+(defface consult-help
+ '((t :inherit shadow))
+ "Face used to highlight help, e.g., in `consult-register-store'.")
+
+(defface consult-key
+ '((t :inherit font-lock-keyword-face))
+ "Face used to highlight keys, e.g., in `consult-register'.")
+
+(defface consult-line-number
+ '((t :inherit consult-key))
+ "Face used to highlight location line in `consult-global-mark'.")
+
+(defface consult-file
+ '((t :inherit font-lock-function-name-face))
+ "Face used to highlight files in `consult-buffer'.")
+
+(defface consult-grep-context
+ '((t :inherit shadow))
+ "Face used to highlight grep context in `consult-grep'.")
+
+(defface consult-bookmark
+ '((t :inherit font-lock-constant-face))
+ "Face used to highlight bookmarks in `consult-buffer'.")
+
+(defface consult-buffer
+ '((t))
+ "Face used to highlight buffers in `consult-buffer'.")
+
+(defface consult-line-number-prefix
+ '((t :inherit line-number))
+ "Face used to highlight line number prefixes.")
+
+(defface consult-line-number-wrapped
+ '((t :inherit consult-line-number-prefix :inherit warning :weight normal))
+ "Face used to highlight line number prefixes after wrap around.")
+
+;;;; Input history variables
+
+(defvar consult--path-history nil)
+(defvar consult--grep-history nil)
+(defvar consult--find-history nil)
+(defvar consult--man-history nil)
+(defvar consult--line-history nil)
+(defvar consult--line-multi-history nil)
+(defvar consult--theme-history nil)
+(defvar consult--minor-mode-menu-history nil)
+(defvar consult--buffer-history nil)
+
+;;;; Internal variables
+
+(defvar consult--regexp-compiler
+ #'consult--default-regexp-compiler
+ "Regular expression compiler used by `consult-grep' and other commands.
+The function must return a list of regular expressions and a highlighter
+function.")
+
+(defvar consult--customize-alist
+ ;; Disable preview in frames, since `consult--jump-preview' does not properly
+ ;; clean up. See gh:minad/consult#593. This issue should better be fixed in
+ ;; `consult--jump-preview'.
+ `((,#'consult-buffer-other-frame :preview-key nil)
+ (,#'consult-buffer-other-tab :preview-key nil))
+ "Command configuration alist for fine-grained configuration.
+
+Each element of the list must have the form (command-name plist...). The
+options set here will be evaluated and passed to `consult--read', when
+called from the corresponding command. Note that the options depend on
+the private `consult--read' API and should not be considered as stable
+as the public API.")
+
+(defvar consult--buffer-display #'switch-to-buffer
+ "Buffer display function.")
+
+(defvar consult--completion-candidate-hook
+ (list #'consult--default-completion-list-candidate
+ #'consult--default-completion-minibuffer-candidate)
+ "Get candidate from completion system.")
+
+;; Redisplay such that the updated completion UI will be displayed, even when
+;; the update happened due to `accept-process-output' inside a loop of a dynamic
+;; collection. See `consult--async-dynamic'.
+(defvar consult--completion-refresh-hook
+ (list #'redisplay #'consult--default-completion-list-refresh)
+ "Refresh completion system.")
+
+(defvar-local consult--preview-function nil
+ "Minibuffer-local variable which exposes the current preview function.
+This function can be called by custom completion systems from
+outside the minibuffer.")
+
+(defvar consult--annotate-align-step 10
+ "Round candidate width.")
+
+(defvar consult--annotate-align-width 0
+ "Maximum candidate width used for annotation alignment.")
+
+(defconst consult--tofu-char #x100000
+ "Special character used to encode line suffixes for disambiguation.
+We use characters in the Unicode PUA-B.")
+
+(defconst consult--tofu-range #xFFFE
+ "Special character range.")
+
+(defconst consult--tofu-regexp
+ (format "[%c-%c]" consult--tofu-char
+ (+ consult--tofu-char consult--tofu-range -1))
+ "Special character regexp.")
+
+(defvar-local consult--narrow nil
+ "Current narrow key.")
+
+(defvar-local consult--narrow-config nil
+ "Narrowing config of the current completion.")
+
+(defvar-local consult--narrow-overlay nil
+ "Narrowing indicator overlay.")
+
+(defvar consult--gc-threshold (* 64 1024 1024)
+ "Large GC threshold for temporary increase.")
+
+(defvar consult--gc-percentage 0.2
+ "Large GC percentage for temporary increase.")
+
+(defvar consult--process-chunk (* 1024 1024)
+ "Increase process output chunk size.")
+
+(defvar consult--async-log
+ " *consult-async*"
+ "Buffer for async logging output used by `consult--async-process'.")
+
+(defvar-local consult--focus-lines-overlays nil
+ "Overlays used by `consult-focus-lines'.")
+
+(defvar consult--focus-lines-indicator
+ (propertize
+ "FOCUS" 'face 'highlight
+ 'help-echo
+ "`consult-focus-lines': \\`mouse-1' or \\[consult-focus-lines] \\`RET' to reveal."
+ 'local-map
+ (define-keymap "<mode-line> <down-mouse-1>"
+ (lambda () (interactive) (consult-focus-lines nil 'reveal))))
+ "Mode line indicator displayed if `consult-focus-lines' is active.")
+
+;;;; Miscellaneous helper functions
+
+(defun consult--plist-remove (keys plist)
+ "Remove list of KEYS from PLIST."
+ (let (result)
+ (while plist
+ (unless (memq (car plist) keys)
+ (push (car plist) result)
+ (push (cadr plist) result))
+ (setq plist (cddr plist)))
+ (nreverse result)))
+
+(defun consult--key-parse (key)
+ "Parse KEY or signal error if invalid."
+ (unless (key-valid-p key)
+ (error "%S is not a valid key definition; see `key-valid-p'" key))
+ (key-parse key))
+
+(defun consult--in-buffer (fun &optional buffer)
+ "Ensure that FUN is executed inside BUFFER."
+ (unless buffer (setq buffer (current-buffer)))
+ (lambda (&rest args)
+ (with-current-buffer buffer
+ (apply fun args))))
+
+(defun consult--completion-table-in-buffer (table &optional buffer)
+ "Ensure that completion TABLE is executed inside BUFFER."
+ (if (functionp table)
+ (consult--in-buffer
+ (lambda (str pred action)
+ (let ((result (funcall table str pred action)))
+ (pcase action
+ ('metadata
+ (setq result
+ (mapcar
+ (lambda (x)
+ (if (and (string-suffix-p "-function" (symbol-name (car-safe x))) (cdr x))
+ (cons (car x) (consult--in-buffer (cdr x)))
+ x))
+ result)))
+ ((and 'completion--unquote (guard (functionp (cadr result))))
+ (cl-callf consult--in-buffer (cadr result) buffer)
+ (cl-callf consult--in-buffer (cadddr result) buffer)))
+ result))
+ buffer)
+ table))
+
+(defun consult--build-args (arg)
+ "Return ARG as a flat list of split strings.
+
+Turn ARG into a list, and for each element either:
+- split it if it a string.
+- eval it if it is an expression."
+ (seq-mapcat (lambda (x)
+ (if (stringp x)
+ (split-string-and-unquote x)
+ (ensure-list (eval x 'lexical))))
+ (ensure-list arg)))
+
+(defun consult--command-split (str)
+ "Return command argument and options list given input STR."
+ (save-match-data
+ (let ((opts ""))
+ (setq str (substring-no-properties str))
+ ;; Find first option
+ (when (string-match "\\(?:\\`\\| \\)-" str)
+ (setq opts (substring str (1- (match-end 0)))
+ str (substring str 0 (match-beginning 0)))
+ (when (equal opts "-")
+ (setq opts "")))
+ ;; Replace backslash-escaped dashes
+ (setq str (replace-regexp-in-string "\\(\\`\\| \\)\\\\-" "\\1-" str))
+ ;; Options end with double dash
+ (when (string-match "\\(\\`\\| \\)--\\(?: \\|\\'\\)" opts)
+ (setq str (concat str " " (substring opts (match-end 0)))
+ opts (substring opts 0 (match-beginning 0))))
+ ;; Use `split-string-shell-command' here instead of
+ ;; `split-string-and-unquote' since it handles more flexible input -
+ ;; double quoted strings, single quoted strings and escaped spaces.
+ (cons str (split-string-shell-command (string-trim opts))))))
+
+(defmacro consult--keep! (list form)
+ "Evaluate FORM for every element of LIST and keep the non-nil results."
+ (declare (indent 1) (debug (gv-place body)))
+ (cl-with-gensyms (head prev result)
+ `(let* ((,head (cons nil ,list))
+ (,prev ,head))
+ (while (cdr ,prev)
+ (if-let* ((,result (let ((it (cadr ,prev))) ,form)))
+ (progn
+ (pop ,prev)
+ (setcar ,prev ,result))
+ (setcdr ,prev (cddr ,prev))))
+ (setf ,list (cdr ,head))
+ nil)))
+
+(defun consult--completion-filter (pattern cands category highlight)
+ "Filter CANDS with PATTERN.
+
+CATEGORY is the completion category, used to find the completion style via
+`completion-category-defaults' and `completion-category-overrides'.
+HIGHLIGHT must be non-nil if the resulting strings should be highlighted."
+ ;; Ensure that the global completion style settings are used for
+ ;; `consult-line', `consult-focus-lines' and `consult-keep-lines' filtering.
+ ;; This override is necessary since users may want to override the settings
+ ;; buffer-locally for in-buffer completion via Corfu.
+ (dlet ((completion-lazy-hilit (not highlight))
+ (completion-styles (default-value 'completion-styles))
+ (completion-category-defaults (default-value 'completion-category-defaults))
+ (completion-category-overrides (default-value 'completion-category-overrides)))
+ ;; `completion-all-completions' returns an improper list where the last link
+ ;; is not necessarily nil.
+ (nconc (completion-all-completions pattern cands nil (length pattern)
+ `(metadata (category . ,category)))
+ nil)))
+
+(defun consult--completion-filter-complement (pattern cands category)
+ "Filter CANDS with complement of PATTERN given completion CATEGORY."
+ (let ((ht (consult--string-hash (consult--completion-filter pattern cands category nil))))
+ (seq-remove (lambda (x) (gethash x ht)) cands)))
+
+(defun consult--completion-filter-dispatch (pattern cands category highlight)
+ "Filter CANDS with PATTERN with optional complement.
+Either using `consult--completion-filter' or
+`consult--completion-filter-complement', depending on if the pattern starts
+with a bang. See `consult--completion-filter' for the arguments CATEGORY and
+HIGHLIGHT."
+ (cond
+ ((string-match-p "\\`!? ?\\'" pattern) cands) ;; empty pattern
+ ((string-prefix-p "! " pattern) (consult--completion-filter-complement
+ (substring pattern 2) cands category))
+ (t (consult--completion-filter pattern cands category highlight))))
+
+(defmacro consult--each-line (beg end &rest body)
+ "Iterate over each line.
+
+The line beginning/ending BEG/END is bound in BODY."
+ (declare (indent 2) (debug (symbolp symbolp body)))
+ (cl-with-gensyms (max)
+ `(save-excursion
+ (let ((,beg (point-min)) (,max (point-max)) ,end)
+ (while (< ,beg ,max)
+ (goto-char ,beg)
+ (setq ,end (pos-eol))
+ ,@body
+ (setq ,beg (1+ ,end)))))))
+
+(defun consult--display-width (string)
+ "Compute width of STRING taking display and invisible properties into account."
+ (let ((pos 0) (width 0) (end (length string)))
+ (while (< pos end)
+ (let ((nextd (next-single-property-change pos 'display string end))
+ (display (get-text-property pos 'display string)))
+ (if (stringp display)
+ (setq width (+ width (string-width display))
+ pos nextd)
+ (while (< pos nextd)
+ (let ((nexti (next-single-property-change pos 'invisible string nextd)))
+ (unless (get-text-property pos 'invisible string)
+ (setq width (+ width (string-width string pos nexti))))
+ (setq pos nexti))))))
+ width))
+
+(defun consult--string-hash (strings)
+ "Create hash table from STRINGS."
+ (let ((ht (make-hash-table :test #'equal :size (length strings))))
+ (dolist (str strings)
+ (puthash str t ht))
+ ht))
+
+(defmacro consult--local-let (binds &rest body)
+ "Buffer local let BINDS of dynamic variables in BODY."
+ (declare (indent 1) (debug let))
+ (let ((buffer (gensym "buffer"))
+ (local (mapcar (lambda (x) (cons (gensym "local") (car x))) binds)))
+ `(let ((,buffer (current-buffer))
+ ,@(mapcar (lambda (x) `(,(car x) (local-variable-p ',(cdr x)))) local))
+ (unwind-protect
+ (progn
+ ,@(mapcar (lambda (x) `(make-local-variable ',(car x))) binds)
+ (let (,@binds)
+ ,@body))
+ (when (buffer-live-p ,buffer)
+ (with-current-buffer ,buffer
+ ,@(mapcar (lambda (x)
+ `(unless ,(car x)
+ (kill-local-variable ',(cdr x))))
+ local)))))))
+
+(defvar consult--fast-abbreviate-file-name nil)
+(defun consult--fast-abbreviate-file-name (name)
+ "Return abbreviate file NAME.
+This function is a pure variant of `abbreviate-file-name', which
+does not access the file system. This is important if we require
+that the operation is fast, even for remote paths or paths on
+network file systems."
+ (save-match-data
+ (let (case-fold-search) ;; Assume that file system is case sensitive.
+ (setq name (directory-abbrev-apply name))
+ (if (string-match (with-memoization consult--fast-abbreviate-file-name
+ (directory-abbrev-make-regexp (expand-file-name "~")))
+ name)
+ (concat "~" (substring name (match-beginning 1)))
+ name))))
+
+(defun consult--left-truncate-file (file)
+ "Return abbreviated file name of FILE for use in `completing-read' prompt."
+ (save-match-data
+ (let* ((file-name-handler-alist) ;; No Tramp interference please.
+ (file (directory-file-name (abbreviate-file-name file)))
+ (prefix nil))
+ (when (string-match "\\`/\\([^/|:]+:[^/|:]*:\\)" file)
+ (setq prefix (propertize (match-string 1 file) 'face 'error)
+ file (if (= (match-end 0) (length file)) "/" (substring file (match-end 0)))))
+ (when (string-match "/\\([^/]+\\)/\\([^/]+\\)\\'" file)
+ (let* ((fst (truncate-string-to-width (match-string 1 file) 20 nil nil "…"))
+ (snd (truncate-string-to-width (match-string 2 file) 20 nil nil "…"))
+ (trunc (format "…/%s/%s" fst snd)))
+ (setq file (if (< (length trunc) (length file)) trunc file))))
+ (concat prefix file))))
+
+(defun consult--directory-prompt (prompt dir)
+ "Return prompt, paths and default directory.
+
+PROMPT is the prompt prefix. The directory is appended to the
+prompt prefix. For projects only the project name is shown. The
+`default-directory' is not shown. Other directories are
+abbreviated and only the last two path components are shown.
+
+If DIR is a string, it is returned as default directory. If DIR
+is a list of strings, the list is returned as search paths. If
+DIR is nil the `consult-project-function' is tried to retrieve
+the default directory. If no project is found the
+`default-directory' is returned as is. Otherwise the user is
+asked for the directories or files to search via
+`completing-read-multiple'."
+ (let* ((paths nil)
+ (dir
+ (pcase dir
+ ((pred stringp) dir)
+ ((or 'nil '(16)) (or (consult--project-root dir) default-directory))
+ (_
+ (pcase (if (stringp (car-safe dir))
+ dir
+ ;; Preserve this-command across `completing-read-multiple' call,
+ ;; such that `consult-customize' continues to work.
+ (let ((this-command this-command)
+ (def (abbreviate-file-name default-directory))
+ ;; bug#75910: category instead of `minibuffer-completing-file-name'
+ (minibuffer-completing-file-name t)
+ (ignore-case read-file-name-completion-ignore-case))
+ (minibuffer-with-setup-hook
+ (lambda ()
+ (setq-local completion-ignore-case ignore-case)
+ (set-syntax-table minibuffer-local-filename-syntax))
+ (completing-read-multiple "Dirs or files: "
+ #'completion-file-name-table
+ nil t def 'consult--path-history def))))
+ ((and `(,p) (guard (file-directory-p p))) p)
+ (ps (setq paths (mapcar (lambda (p)
+ (file-relative-name (expand-file-name p)))
+ ps))
+ default-directory)))))
+ (edir (file-name-as-directory (expand-file-name dir)))
+ (pdir (let ((default-directory edir))
+ ;; Bind default-directory in order to find the project
+ (consult--project-root))))
+ (list
+ (format "%s (%s): " prompt
+ (pcase paths
+ ((guard (<= 1 (length paths) 2))
+ (string-join (mapcar #'consult--left-truncate-file paths) ", "))
+ (`(,p . ,_)
+ (format "%d paths, %s, …" (length paths) (consult--left-truncate-file p)))
+ ((guard (equal edir pdir)) (concat "Project " (consult--project-name pdir)))
+ (_ (consult--left-truncate-file edir))))
+ (or paths '("."))
+ edir)))
+
+(defun consult--default-project-function (may-prompt)
+ "Return project root directory.
+When no project is found and MAY-PROMPT is non-nil ask the user."
+ (declare-function project-root "project")
+ (when-let* ((proj (project-current may-prompt)))
+ (project-root proj)))
+
+(defun consult--project-root (&optional may-prompt)
+ "Return project root as absolute path.
+When no project is found and MAY-PROMPT is non-nil ask the user."
+ ;; Preserve this-command across project selection,
+ ;; such that `consult-customize' continues to work.
+ (let ((this-command this-command))
+ (when-let* ((root (and consult-project-function
+ (funcall consult-project-function may-prompt))))
+ (expand-file-name root))))
+
+(defun consult--project-known-roots ()
+ "Return list of known project roots."
+ (let ((root (consult--project-root))
+ (dirs (sort (project-known-project-roots) #'string<)))
+ (when root
+ (setq root (abbreviate-file-name root)
+ dirs (cons root (delete root dirs))))
+ dirs))
+
+(defun consult--project-name (dir)
+ "Return the project name for DIR."
+ (if (string-match "/\\([^/]+\\)/\\'" dir)
+ (propertize (match-string 1 dir) 'help-echo (abbreviate-file-name dir))
+ dir))
+
+(defun consult--format-file-line-match (file line match)
+ "Format string FILE:LINE:MATCH with faces."
+ (setq line (number-to-string line)
+ match (concat file ":" line ":" match)
+ file (length file))
+ (put-text-property 0 file 'face 'consult-file match)
+ (put-text-property (1+ file) (+ 1 file (length line)) 'face 'consult-line-number match)
+ match)
+
+(defun consult--make-overlay (beg end &rest props)
+ "Make consult overlay between BEG and END with PROPS."
+ (let ((ov (make-overlay beg end)))
+ (while props
+ (overlay-put ov (car props) (cadr props))
+ (setq props (cddr props)))
+ ov))
+
+(defun consult--remove-dups (list)
+ "Remove duplicate strings from LIST."
+ (delete-dups (copy-sequence list)))
+
+(defsubst consult--in-range-p (pos)
+ "Return t if position POS lies in range `point-min' to `point-max'."
+ (<= (point-min) pos (point-max)))
+
+(defun consult--completion-window-p ()
+ "Return non-nil if the selected window belongs to the completion UI."
+ (or (eq (selected-window) (active-minibuffer-window))
+ (eq #'completion-list-mode (buffer-local-value 'major-mode (window-buffer)))))
+
+(defun consult--original-window ()
+ "Return window which was just selected just before the minibuffer was entered.
+In contrast to `minibuffer-selected-window' never return nil and
+always return an appropriate non-minibuffer window."
+ (or (minibuffer-selected-window)
+ (if (window-minibuffer-p (selected-window))
+ (next-window)
+ (selected-window))))
+
+(defun consult--forbid-minibuffer ()
+ "Raise an error if executed from the minibuffer."
+ (when (minibufferp)
+ (user-error "`%s' called inside the minibuffer" this-command)))
+
+(defun consult--require-minibuffer ()
+ "Raise an error if executed outside the minibuffer."
+ (unless (minibufferp)
+ (user-error "`%s' must be called inside the minibuffer" this-command)))
+
+(defsubst consult--fontify-region (start end)
+ "Ensure that region between START and END is fontified."
+ (when (and consult-fontify-preserve jit-lock-mode)
+ (jit-lock-fontify-now start end)))
+
+(defmacro consult--with-increased-gc (&rest body)
+ "Temporarily increase the GC limit in BODY to optimize for throughput."
+ (declare (indent 0) (debug t))
+ (cl-with-gensyms (overwrite)
+ `(let* ((,overwrite (> consult--gc-threshold gc-cons-threshold))
+ (gc-cons-threshold (if ,overwrite consult--gc-threshold gc-cons-threshold))
+ (gc-cons-percentage (if ,overwrite consult--gc-percentage gc-cons-percentage)))
+ ,@body)))
+
+(defmacro consult--slow-operation (message &rest body)
+ "Show delayed MESSAGE if BODY takes too long.
+Also temporarily increase the GC limit via `consult--with-increased-gc'."
+ (declare (indent 1) (debug t))
+ `(with-delayed-message (1 ,message)
+ (consult--with-increased-gc ,@body)))
+
+(defun consult--count-lines (pos)
+ "Move to position POS and return number of lines."
+ (let ((line 1))
+ (while (< (point) pos)
+ (forward-line)
+ (when (<= (point) pos)
+ (incf line)))
+ (goto-char pos)
+ line))
+
+(defun consult--marker-from-line-column (buffer line column)
+ "Get marker in BUFFER from LINE and COLUMN."
+ (when (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (save-excursion
+ (without-restriction
+ (goto-char (point-min))
+ ;; Location data might be invalid by now!
+ (ignore-errors
+ (forward-line (1- line))
+ (goto-char (min (+ (point) column) (pos-eol))))
+ (point-marker))))))
+
+(defsubst consult--copy-property (beg end str prop)
+ "Copy PROP from buffer region BEG to END to STR.
+The string STR is modified."
+ (let ((pos beg))
+ (while (< pos end)
+ (let ((next (next-single-property-change pos prop nil end))
+ (val (get-text-property pos prop)))
+ (when val
+ (if (eq prop 'face)
+ (add-face-text-property (- pos beg) (- next beg) val t str)
+ (put-text-property (- pos beg) (- next beg) prop val str)))
+ (setq pos next)))))
+
+(defun consult--copy-faces (beg end str)
+ "Copy faces from buffer region BEG to END to STR.
+The string STR is modified."
+ (consult--copy-property beg end str 'face)
+ (consult--copy-property beg end str 'invisible)
+ (consult--copy-property beg end str 'display))
+
+(defun consult--line-fontify (&optional curr-line)
+ "Annotation function to fontify `consult-location' line and add line number.
+CURR-LINE is the current line number."
+ (setq curr-line (or curr-line -1))
+ (let* ((width (length (number-to-string (line-number-at-pos
+ (point-max)
+ consult-line-numbers-widen))))
+ (before (format #("%%%dd " 0 6 (face consult-line-number-wrapped)) width))
+ (after (propertize before 'face 'consult-line-number-prefix)))
+ (lambda (cand)
+ (pcase-let* ((`(,pos . ,line) (get-text-property 0 'consult-location cand))
+ (buf (when consult-fontify-preserve
+ (if (consp pos)
+ (car pos)
+ (and (markerp pos) (marker-buffer pos))))))
+ (when (buffer-live-p buf)
+ (with-current-buffer buf
+ (goto-char (if (markerp pos) pos (cdr pos)))
+ (let ((beg (pos-bol))
+ (end (pos-eol)))
+ ;; Only apply lazy highlighting if the buffer has not been changed.
+ (when (string-prefix-p (buffer-substring-no-properties beg end) cand)
+ (setq cand (copy-sequence cand))
+ (consult--fontify-region beg end)
+ (consult--copy-faces beg end cand)))))
+ (list cand (format (if (< line curr-line) before after) line) "")))))
+
+(defsubst consult--location-candidate (cand marker line tofu &rest props)
+ "Add MARKER and LINE as `consult-location' text property to CAND.
+Furthermore add the additional text properties PROPS, and append
+TOFU suffix for disambiguation."
+ (setq cand (concat cand (consult--tofu-encode tofu)))
+ (add-text-properties 0 1 `(consult-location (,marker . ,line) ,@props) cand)
+ cand)
+
+(defsubst consult--buffer-substring (beg end &optional fontify)
+ "Return buffer substring between BEG and END.
+If FONTIFY and `consult-fontify-preserve' are non-nil, first ensure that
+the region has been fontified."
+ (if consult-fontify-preserve
+ (let ((str (buffer-substring-no-properties beg end)))
+ (when fontify (consult--fontify-region beg end))
+ (consult--copy-faces beg end str)
+ str)
+ (buffer-substring-no-properties beg end)))
+
+(defun consult--line-with-mark (marker)
+ "Current line string where the MARKER position is highlighted."
+ (let* ((beg (pos-bol))
+ (end (pos-eol))
+ (str (consult--buffer-substring beg end 'fontify)))
+ (if (>= marker end)
+ (concat str #(" " 0 1 (face consult-highlight-mark)))
+ (put-text-property (- marker beg) (- (1+ marker) beg)
+ 'face 'consult-highlight-mark str)
+ str)))
+
+;;;; Tofu cooks
+
+(defsubst consult--tofu-p (char)
+ "Return non-nil if CHAR is a tofu."
+ (<= consult--tofu-char char (+ consult--tofu-char consult--tofu-range -1)))
+
+(defun consult--tofu-strip (str)
+ "Strip tofus from STR."
+ (replace-regexp-in-string consult--tofu-regexp "" (substring-no-properties str)))
+
+(defsubst consult--tofu-append (cand id)
+ "Append tofu-encoded ID to CAND.
+The ID must fit within a single character. It must be smaller
+than `consult--tofu-range'."
+ (setq id (char-to-string (+ consult--tofu-char id)))
+ (add-text-properties 0 1 '(invisible t consult-strip t) id)
+ (concat cand id))
+
+(defsubst consult--tofu-get (cand)
+ "Extract tofu-encoded ID from CAND.
+See `consult--tofu-append'."
+ (- (aref cand (1- (length cand))) consult--tofu-char))
+
+;; We must disambiguate the lines by adding a suffix such that two lines with
+;; the same text can be distinguished. In order to avoid matching the line
+;; number, such that the user can search for numbers with `consult-line', we
+;; encode the line number as Unicode PUA-B characters. This way accidental
+;; matching is unlikely.
+(defun consult--tofu-encode (n)
+ "Return tofu-encoded number N as a string.
+Large numbers are encoded as multiple tofu characters."
+ (let (str tofu)
+ (while (progn
+ (setq tofu (char-to-string
+ (+ consult--tofu-char (% n consult--tofu-range)))
+ str (if str (concat tofu str) tofu))
+ (and (>= n consult--tofu-range)
+ (setq n (/ n consult--tofu-range)))))
+ (add-text-properties 0 (length str) '(invisible t consult-strip t) str)
+ str))
+
+;;;; Regexp utilities
+
+(defun consult--find-highlights (str start &rest ignored-faces)
+ "Find highlighted regions in STR from position START.
+Highlighted regions have a non-nil face property.
+IGNORED-FACES are ignored when searching for matches."
+ (let (highlights
+ (end (length str))
+ (beg start))
+ (while (< beg end)
+ (let ((next (next-single-property-change beg 'face str end))
+ (val (get-text-property beg 'face str)))
+ (when (and val
+ (not (memq val ignored-faces))
+ (not (and (consp val)
+ (seq-some (lambda (x) (memq x ignored-faces)) val))))
+ (push (cons (- beg start) (- next start)) highlights))
+ (setq beg next)))
+ (nreverse highlights)))
+
+(defun consult--point-placement (str start &rest ignored-faces)
+ "Compute point placement from STR with START offset.
+IGNORED-FACES are ignored when searching for matches.
+Return cons of point position and a list of match begin/end pairs."
+ (let* ((matches (apply #'consult--find-highlights str start ignored-faces))
+ (pos (pcase-exhaustive consult-point-placement
+ ('match-beginning (or (caar matches) 0))
+ ('match-end (or (cdar (last matches)) 0))
+ ('line-beginning 0))))
+ (dolist (match matches)
+ (decf (car match) pos)
+ (decf (cdr match) pos))
+ (cons pos matches)))
+
+(defun consult--highlight-regexps (regexps ignore-case str)
+ "Highlight REGEXPS (or single regexp string) in STR.
+If a regular expression contains capturing groups, only these are highlighted.
+If no capturing groups are used highlight the whole match. Case is ignored
+if IGNORE-CASE is non-nil."
+ (dolist (re (ensure-list regexps))
+ (let ((i 0))
+ (while (and (let ((case-fold-search ignore-case))
+ (string-match re str i))
+ ;; Ensure that regexp search made progress (edge case for .*)
+ (> (match-end 0) i))
+ ;; Unfortunately there is no way to avoid the allocation of the match
+ ;; data, since the number of capturing groups is unknown.
+ (let ((m (match-data)))
+ (setq i (cadr m) m (or (cddr m) m))
+ (while m
+ (when (car m)
+ (add-face-text-property (car m) (cadr m)
+ 'consult-highlight-match nil str))
+ (setq m (cddr m)))))))
+ str)
+
+(defun consult--highlight-literals (literals ignore-case str)
+ "Highlight list of LITERALS or single literal string in STR.
+Case insensitive if IGNORE-CASE is non-nil."
+ (consult--highlight-regexps (mapcar #'regexp-quote (ensure-list literals))
+ ignore-case str))
+
+(defconst consult--convert-regexp-table
+ (append
+ ;; For simplicity, treat word beginning/end as word boundaries,
+ ;; since PCRE does not make this distinction. Usually the
+ ;; context determines if \b is the beginning or the end.
+ '(("\\<" . "\\b") ("\\>" . "\\b")
+ ("\\_<" . "\\b") ("\\_>" . "\\b")
+ ("\\s-" . "[ \\n\\t\\r]") ("\\S-" . "[^ \\n\\t\\r]")
+ ("\\sw" . "[a-zA-Z0-9]") ("\\Sw" . "[^a-zA-Z0-0]")
+ ("\\s_" . "[a-zA-Z0-9_-]") ("\\S_" . "[^a-zA-Z0-0_-]"))
+ ;; Treat \` and \' as beginning and end of line. This is more
+ ;; widely supported and makes sense for line-based commands.
+ '(("\\`" . "^") ("\\'" . "$"))
+ ;; Historical: Unescaped *, +, ? are supported at the beginning
+ (mapcan (lambda (x)
+ (mapcar (lambda (y)
+ (cons (concat x y)
+ (concat (string-remove-prefix "\\" x) "\\" y)))
+ '("*" "+" "?")))
+ '("" "\\(" "\\(?:" "\\|" "^"))
+ ;; Different escaping
+ (mapcan (lambda (x) `(,x (,(cdr x) . ,(car x))))
+ '(("\\|" . "|")
+ ("\\(" . "(") ("\\)" . ")")
+ ("\\{" . "{") ("\\}" . "}"))))
+ "Regexp conversion table.")
+
+(defun consult--convert-regexp (regexp type)
+ "Convert Emacs REGEXP to regexp syntax TYPE."
+ (if (memq type '(emacs basic))
+ regexp
+ ;; Support for Emacs regular expressions is fairly complete for basic
+ ;; usage. There are a few unsupported Emacs regexp features:
+ ;; - \= point matching
+ ;; - Most syntax classes \sx \Sx
+ ;; - Character classes \cx \Cx
+ ;; - Explicitly numbered groups (?3:group)
+ (replace-regexp-in-string
+ (rx (or "\\\\" "\\^" ;; Pass through
+ (seq (or "\\(?:" "\\|") (any "*+?")) ;; Historical: \|+ or \(?:* etc
+ (seq "\\(" (any "*+")) ;; Historical: \(* or \(+
+ (seq (or bos "^") (any "*+?")) ;; Historical: + or * at the beginning
+ (seq (opt "\\") (any "(){|}")) ;; Escape parens/braces/pipe
+ (seq "\\" (any "'<>`")) ;; Special escapes
+ (seq "\\" (any "Ss") (any "-w_")) ;; Whitespace, word, symbol syntax class
+ (seq "\\_" (any "<>")))) ;; Beginning or end of symbol
+ (lambda (x) (or (cdr (assoc x consult--convert-regexp-table)) x))
+ regexp 'fixedcase 'literal)))
+
+(defun consult--default-regexp-compiler (input type ignore-case)
+ "Compile a string to a list of regular expressions.
+See `consult--compile-regexp' for INPUT, TYPE and IGNORE-CASE."
+ (setq input (consult--split-escaped input))
+ (cons (mapcar (lambda (x) (consult--convert-regexp x type)) input)
+ (when-let* ((regexps (seq-filter #'consult--valid-regexp-p input)))
+ (apply-partially #'consult--highlight-regexps regexps ignore-case))))
+
+(defun consult--compile-regexp (input type ignore-case)
+ "Compile the INPUT string to a list of regular expressions.
+Return a pair, the list of regular expressions and a highlight function.
+The highlight function takes a single argument, the string to highlight
+given the INPUT. TYPE is the desired type of regular expression, which
+can be `basic', `extended', `emacs' or `pcre'. If IGNORE-CASE is
+non-nil the highlight function matches case insensitively."
+ (funcall consult--regexp-compiler input type ignore-case))
+
+(defun consult--split-escaped (str)
+ "Split STR at spaces, which can be escaped with backslash."
+ (mapcar
+ (lambda (x) (string-replace "\0" " " x))
+ (split-string (replace-regexp-in-string
+ "\\\\\\\\\\|\\\\ "
+ (lambda (x) (if (equal x "\\ ") "\0" x))
+ str 'fixedcase 'literal)
+ " +" t)))
+
+(defun consult--join-regexps (regexps type)
+ "Join REGEXPS of TYPE."
+ ;; Add look-ahead wrapper only if there is more than one regular expression
+ (cond
+ ((and (eq type 'pcre) (cdr regexps))
+ (concat "^" (mapconcat (lambda (x) (format "(?=.*%s)" x))
+ regexps "")))
+ ((eq type 'basic)
+ (string-join regexps ".*"))
+ (t
+ (when (length> regexps 3)
+ (consult--minibuffer-message
+ "Too many regexps, %S ignored. Use post-filtering!"
+ (string-join (seq-drop regexps 3) " "))
+ (setq regexps (seq-take regexps 3)))
+ (consult--join-regexps-permutations regexps (and (eq type 'emacs) "\\")))))
+
+(defun consult--join-regexps-permutations (regexps esc)
+ "Join all permutations of REGEXPS.
+ESC is the escaping string for choice and groups."
+ (pcase regexps
+ ('nil "")
+ (`(,r) r)
+ (_ (mapconcat
+ (lambda (r)
+ (concat esc "(" r esc ").*" esc "("
+ (consult--join-regexps-permutations (remove r regexps) esc)
+ esc ")"))
+ regexps (concat esc "|")))))
+
+(defun consult--valid-regexp-p (re)
+ "Return t if regexp RE is valid."
+ (condition-case nil
+ (progn (string-match-p re "") t)
+ (invalid-regexp nil)))
+
+(defun consult--regexp-filter (regexps)
+ "Create filter regexp from REGEXPS."
+ (if (stringp regexps)
+ regexps
+ (mapconcat (lambda (x) (concat "\\(?:" x "\\)")) regexps "\\|")))
+
+;;;; Lookup functions
+
+(defun consult--lookup-member (selected candidates &rest _)
+ "Lookup SELECTED in CANDIDATES list, return original element."
+ (car (member selected candidates)))
+
+(defun consult--lookup-cons (selected candidates &rest _)
+ "Lookup SELECTED in CANDIDATES alist, return cons."
+ (assoc selected candidates))
+
+(defun consult--lookup-cdr (selected candidates &rest _)
+ "Lookup SELECTED in CANDIDATES alist, return `cdr' of element."
+ (cdr (assoc selected candidates)))
+
+(defun consult--lookup-location (selected candidates &rest _)
+ "Lookup SELECTED in CANDIDATES list of `consult-location' category.
+Return the location marker."
+ (when-let* ((found (member selected candidates)))
+ (setq found (car (consult--get-location (car found))))
+ ;; Check that marker is alive
+ (and (or (not (markerp found)) (marker-buffer found)) found)))
+
+(defun consult--lookup-prop (prop selected candidates &rest _)
+ "Lookup SELECTED in CANDIDATES list and return PROP value."
+ (when-let* ((found (member selected candidates)))
+ (get-text-property 0 prop (car found))))
+
+(defun consult--lookup-candidate (selected candidates &rest _)
+ "Lookup SELECTED in CANDIDATES list and return property `consult--candidate'."
+ (consult--lookup-prop 'consult--candidate selected candidates))
+
+;;;; Preview support
+
+(defun consult--preview-rename-buffer (buf &optional name)
+ "Rename BUF to the preview buffer name convention.
+NAME defaults to `buffer-name'."
+ (with-current-buffer buf
+ (rename-buffer (concat " Preview:" (or name (buffer-name))) 'unique)))
+
+(defun consult--preview-add-buffer (list buf &optional name)
+ "Add BUF to LIST and rename BUF to the preview buffer name convention.
+NAME defaults to `buffer-name'. Kill old buffers if the list length
+exceeds `consult-preview-max-count'."
+ (consult--preview-rename-buffer (cdr buf) name)
+ (push buf list)
+ (while (length> list consult-preview-max-count)
+ (kill-buffer (cdar (last list)))
+ (setq list (nbutlast list)))
+ list)
+
+(defun consult--preview-allowed-p (fun)
+ "Return non-nil if FUN is an allowed preview mode hook."
+ (or (memq fun consult-preview-allowed-hooks)
+ (when-let* (((symbolp fun))
+ (name (symbol-name fun))
+ ;; Global modes in Emacs 29 are activated via a
+ ;; `find-file-hook' ending with `-check-buffers'. This has been
+ ;; changed in Emacs 30. Now a `change-major-mode-hook' is used
+ ;; instead with the suffix `-check-buffers'.
+ (suffix (static-if (>= emacs-major-version 30)
+ "-enable-in-buffer"
+ "-check-buffers"))
+ ((string-suffix-p suffix name)))
+ (memq (intern (string-remove-suffix suffix name))
+ consult-preview-allowed-hooks))))
+
+(defun consult--filter-find-file-hook (orig &rest hooks)
+ "Filter `find-file-hook' by `consult-preview-allowed-hooks'.
+This function is an advice for `run-hooks'.
+ORIG is the original function, HOOKS the arguments."
+ (if (memq 'find-file-hook hooks)
+ (cl-letf* (((default-value 'find-file-hook)
+ (seq-filter #'consult--preview-allowed-p
+ (default-value 'find-file-hook)))
+ (find-file-hook (default-value 'find-file-hook)))
+ (apply orig hooks))
+ (apply orig hooks)))
+
+(defun consult--minibuffer-message (&rest msg)
+ "Show MSG in the minibuffer without logging."
+ (with-selected-window (or (active-minibuffer-window) (selected-window))
+ (let (message-log-max minibuffer-message-timeout)
+ (apply #'minibuffer-message msg))))
+
+(defun consult--find-file-temporarily-1 (name)
+ "Open file NAME, helper function for `consult--find-file-temporarily'."
+ ;; file-attributes may throw permission denied error
+ (when-let* ((attrs (ignore-errors (file-attributes name)))
+ (size (file-attribute-size attrs)))
+ (let* ((partial (>= size consult-preview-partial-size))
+ (buffer (if partial
+ (generate-new-buffer (format "consult-partial-preview-%s" name))
+ (find-file-noselect name 'nowarn)))
+ (success nil))
+ (unwind-protect
+ (with-current-buffer buffer
+ (if (not partial)
+ (when (or (eq major-mode 'hexl-mode)
+ (and (eq major-mode 'fundamental-mode)
+ (save-excursion (search-forward "\0" nil 'noerror))))
+ (error "No preview of binary file"))
+ (with-silent-modifications
+ (setq buffer-read-only t)
+ (insert-file-contents name nil 0 consult-preview-partial-chunk)
+ (goto-char (point-max))
+ (insert "\nFile truncated. End of partial preview.\n")
+ (goto-char (point-min)))
+ (when (save-excursion (search-forward "\0" nil 'noerror))
+ (error "No partial preview of binary file"))
+ ;; Auto detect major mode and hope for the best, given that the
+ ;; file is only previewed partially. If an error is thrown the
+ ;; buffer will be killed and preview is aborted.
+ (set-auto-mode)
+ (font-lock-mode 1))
+ (when (bound-and-true-p so-long-detected-p)
+ (error "No preview of file with long lines"))
+ ;; Run delayed hooks listed in `consult-preview-allowed-hooks'.
+ (dolist (hook (reverse (cons 'after-change-major-mode-hook delayed-mode-hooks)))
+ (run-hook-wrapped hook (lambda (fun)
+ (when (consult--preview-allowed-p fun)
+ (funcall fun))
+ nil)))
+ (setq success (current-buffer)))
+ (unless success
+ (kill-buffer buffer))))))
+
+(defun consult--find-file-temporarily (name)
+ "Open file NAME temporarily for preview."
+ (let ((vars (delq nil
+ (mapcar
+ (pcase-lambda (`(,k . ,v))
+ (if (boundp k)
+ (list k v (default-value k) (symbol-value k))
+ (message "consult-preview-variables: The variable `%s' is not bound" k)
+ nil))
+ consult-preview-variables))))
+ (condition-case err
+ (unwind-protect
+ (progn
+ (advice-add #'run-hooks :around #'consult--filter-find-file-hook)
+ (pcase-dolist (`(,k ,v . ,_) vars)
+ (set-default k v)
+ (set k v))
+ (consult--find-file-temporarily-1 name))
+ (advice-remove #'run-hooks #'consult--filter-find-file-hook)
+ (pcase-dolist (`(,k ,_ ,d ,v) vars)
+ (set-default k d)
+ (set k v)))
+ (error
+ (consult--minibuffer-message "%s" (error-message-string err))
+ nil))))
+
+(defun consult--temporary-files ()
+ "Return a function to open files temporarily for preview."
+ (let ((dir default-directory)
+ (hook (make-symbol "consult--temporary-files-upgrade-hook"))
+ (orig-buffers (buffer-list))
+ temporary-buffers)
+ (fset hook
+ (lambda (_)
+ ;; Fully initialize previewed files and keep them alive.
+ (unless (consult--completion-window-p)
+ (let (live-files)
+ (pcase-dolist (`(,file . ,buf) temporary-buffers)
+ (when-let* ((wins (and (buffer-live-p buf)
+ (get-buffer-window-list buf))))
+ (push (cons file (mapcar
+ (lambda (win)
+ (cons win (window-state-get win t)))
+ wins))
+ live-files)))
+ (pcase-dolist (`(,_ . ,buf) temporary-buffers)
+ (kill-buffer buf))
+ (setq temporary-buffers nil)
+ (pcase-dolist (`(,file . ,wins) live-files)
+ (when-let* ((buf (consult--file-action file)))
+ (push buf orig-buffers)
+ (pcase-dolist (`(,win . ,state) wins)
+ (setf (car (alist-get 'buffer state)) buf)
+ (window-state-put state win))))))))
+ (lambda (&optional name)
+ (if name
+ (let ((default-directory dir))
+ (setq name (let (file-name-handler-alist)
+ (abbreviate-file-name (expand-file-name name))))
+ (or
+ ;; Find existing fully initialized buffer (non-previewed). We have
+ ;; to check for fully initialized buffer before accessing the
+ ;; previewed buffers, since `embark-act' can open a buffer which is
+ ;; currently previewed, such that we end up with two buffers for
+ ;; the same file - one previewed and only partially initialized and
+ ;; one fully initialized. In this case we prefer the fully
+ ;; initialized buffer. For directories `get-file-buffer' returns nil,
+ ;; therefore we have to special case Dired.
+ (let (file-name-handler-alist)
+ (if (and (fboundp 'dired-find-buffer-nocreate) (file-directory-p name))
+ (dired-find-buffer-nocreate name)
+ (get-file-buffer name)))
+ ;; Find existing previewed buffer. Previewed buffers are not fully
+ ;; initialized (hooks are delayed) in order to ensure fast preview.
+ (cdr (assoc name temporary-buffers))
+ ;; If no existing buffer has been found, open the file for preview.
+ (when-let* (((not (seq-find (lambda (x) (string-match-p x name))
+ consult-preview-excluded-files)))
+ (buf (consult--find-file-temporarily name)))
+ ;; Only add new buffer if not already in the list
+ (unless (or (rassq buf temporary-buffers) (memq buf orig-buffers))
+ (add-hook 'window-selection-change-functions hook)
+ (cl-callf consult--preview-add-buffer temporary-buffers
+ (cons name buf) (file-name-nondirectory (directory-file-name name)))
+ ;; Disassociate buffer from file by setting `buffer-file-name'
+ ;; and `dired-directory' to nil. This lets us open an already
+ ;; previewed buffer with the Embark default action C-. RET.
+ ;; The buffer disassociation is delayed to avoid breaking modes
+ ;; like `pdf-view-mode' or `doc-view-mode' which rely on
+ ;; `buffer-file-name'. Executing (set-visited-file-name nil)
+ ;; early also prevents the major mode initialization.
+ (let ((hook (make-symbol "consult--temporary-files-disassociate-hook")))
+ (fset hook (lambda ()
+ (when (buffer-live-p buf)
+ (with-current-buffer buf
+ (remove-hook 'pre-command-hook hook)
+ (setq-local buffer-read-only t
+ dired-directory nil
+ buffer-file-name nil)))))
+ (add-hook 'pre-command-hook hook)))
+ buf)))
+ (remove-hook 'window-selection-change-functions hook)
+ (pcase-dolist (`(,_ . ,buf) temporary-buffers)
+ (kill-buffer buf))
+ (setq temporary-buffers nil)))))
+
+(defun consult--invisible-open-permanently ()
+ "Open overlays which hide the current line.
+See `isearch-open-necessary-overlays' and `isearch-open-overlay-temporary'."
+ (dolist (ov (overlays-in (pos-bol) (pos-eol)))
+ (when-let* ((fun (overlay-get ov 'isearch-open-invisible))
+ ((invisible-p (overlay-get ov 'invisible))))
+ (funcall fun ov))))
+
+(defun consult--invisible-open-temporarily ()
+ "Temporarily open overlays which hide the current line.
+See `isearch-open-necessary-overlays' and `isearch-open-overlay-temporary'."
+ (let (restore)
+ (dolist (ov (overlays-in (pos-bol) (pos-eol)))
+ (let ((inv (overlay-get ov 'invisible)))
+ (when (and (invisible-p inv) (overlay-get ov 'isearch-open-invisible))
+ (push (if-let* ((fun (overlay-get ov 'isearch-open-invisible-temporary)))
+ (progn
+ (funcall fun ov nil)
+ (lambda () (funcall fun ov t)))
+ (overlay-put ov 'invisible nil)
+ (lambda () (overlay-put ov 'invisible inv)))
+ restore))))
+ restore))
+
+(defun consult--jump-ensure-buffer (pos)
+ "Ensure that buffer of marker POS is displayed, return t if successful."
+ (or (not (markerp pos))
+ ;; Switch to buffer if it is not visible
+ (when-let* ((buf (marker-buffer pos)))
+ (or (and (eq (current-buffer) buf) (eq (window-buffer) buf))
+ (if-let* ((win (get-buffer-window buf)))
+ (select-window win 'norecord)
+ (consult--buffer-action buf 'norecord))
+ t))))
+
+(defun consult--jump (pos)
+ "Jump to POS.
+First push current position to mark ring, then move to new
+position and run `consult-after-jump-hook'."
+ (when pos
+ ;; Extract marker from list with with overlay positions, see `consult--line-match'
+ (when (consp pos) (setq pos (car pos)))
+ ;; When the marker is in the same buffer, record previous location
+ ;; such that the user can jump back quickly.
+ (when (or (not (markerp pos)) (eq (current-buffer) (marker-buffer pos)))
+ ;; push-mark mutates markers in the mark-ring and the mark-marker.
+ ;; Therefore we transform the marker to a number to be safe.
+ ;; We all love side effects!
+ (setq pos (+ pos 0))
+ (push-mark (point) t))
+ (when (consult--jump-ensure-buffer pos)
+ (unless (= (goto-char pos) (point)) ;; Widen if jump failed
+ (widen)
+ (goto-char pos))
+ (consult--invisible-open-permanently)
+ (run-hooks 'consult-after-jump-hook)))
+ nil)
+
+(defun consult--jump-preview ()
+ "The preview function used if selecting from a list of candidate positions.
+The function can be used as the `:state' argument of `consult--read'."
+ (let (restore)
+ (lambda (action cand)
+ (when (eq action 'preview)
+ (mapc #'funcall restore)
+ (setq restore nil)
+ ;; TODO Better buffer preview support
+ ;; 1. Use consult--buffer-preview instead of consult--jump-ensure-buffer
+ ;; 2. Remove function consult--jump-ensure-buffer
+ ;; 3. Remove consult-buffer-other-* from consult-customize-alist
+ (when-let* ((pos (or (car-safe cand) cand)) ;; Candidate can be previewed
+ ((consult--jump-ensure-buffer pos)))
+ (let ((saved-min (point-min-marker))
+ (saved-max (point-max-marker))
+ (saved-pos (point-marker)))
+ (set-marker-insertion-type saved-max t) ;; Grow when text is inserted
+ (push (lambda ()
+ (when-let* ((buf (marker-buffer saved-pos)))
+ (with-current-buffer buf
+ (narrow-to-region saved-min saved-max)
+ (goto-char saved-pos)
+ (set-marker saved-pos nil)
+ (set-marker saved-min nil)
+ (set-marker saved-max nil))))
+ restore))
+ (unless (= (goto-char pos) (point)) ;; Widen if jump failed
+ (widen)
+ (goto-char pos))
+ (setq restore (nconc (consult--invisible-open-temporarily) restore))
+ ;; Ensure that cursor is properly previewed (gh:minad/consult#764)
+ (unless (eq cursor-in-non-selected-windows 'box)
+ (let ((orig cursor-in-non-selected-windows)
+ (buf (current-buffer)))
+ (push
+ (if (local-variable-p 'cursor-in-non-selected-windows)
+ (lambda ()
+ (when (buffer-live-p buf)
+ (with-current-buffer buf
+ (setq-local cursor-in-non-selected-windows orig))))
+ (lambda ()
+ (when (buffer-live-p buf)
+ (with-current-buffer buf
+ (kill-local-variable 'cursor-in-non-selected-windows)))))
+ restore)
+ (setq-local cursor-in-non-selected-windows 'box)))
+ ;; Match previews
+ (let ((overlays
+ (list (save-excursion
+ (let ((vbeg (progn (beginning-of-visual-line) (point)))
+ (vend (progn (end-of-visual-line) (point)))
+ (end (pos-eol)))
+ (consult--make-overlay vbeg (if (= vend end) (1+ end) vend)
+ 'category 'consult-preview-line-overlay
+ 'window (selected-window)))))))
+ (dolist (match (cdr-safe cand))
+ (push (consult--make-overlay (+ (point) (car match))
+ (+ (point) (cdr match))
+ 'category 'consult-preview-match-overlay
+ 'window (selected-window))
+ overlays))
+ (push (lambda () (mapc #'delete-overlay overlays)) restore))
+ (run-hooks 'consult-after-jump-hook))))))
+
+(put 'consult-preview-line-overlay 'face 'consult-preview-line)
+(put 'consult-preview-line-overlay 'priority 1)
+(put 'consult-preview-match-overlay 'face 'consult-preview-match)
+(put 'consult-preview-match-overlay 'priority 2)
+
+(defun consult--jump-state ()
+ "The state function used if selecting from a list of candidate positions."
+ (consult--state-with-return (consult--jump-preview) #'consult--jump))
+
+(defun consult--get-location (cand)
+ "Return location from CAND."
+ (let ((loc (get-text-property 0 'consult-location cand)))
+ (when (consp (car loc))
+ ;; Transform cheap marker to real marker
+ (setcar loc (set-marker (make-marker) (cdar loc) (caar loc))))
+ loc))
+
+(defun consult--location-state (candidates)
+ "Location state function.
+The cheap location markers from CANDIDATES are upgraded on window
+selection change to full Emacs markers."
+ (let ((jump (consult--jump-state))
+ (hook (make-symbol "consult--location-upgrade-hook")))
+ (fset hook
+ (lambda (_)
+ (unless (consult--completion-window-p)
+ (remove-hook 'window-selection-change-functions hook)
+ (mapc #'consult--get-location
+ (if (functionp candidates) (funcall candidates) candidates)))))
+ (lambda (action cand)
+ (pcase action
+ ('setup (add-hook 'window-selection-change-functions hook))
+ ('exit (remove-hook 'window-selection-change-functions hook)))
+ (funcall jump action cand))))
+
+(defun consult--state-with-return (state return)
+ "Compose STATE function with RETURN function."
+ (lambda (action cand)
+ (funcall state action cand)
+ (when (and cand (eq action 'return))
+ (funcall return cand))))
+
+(defmacro consult--define-state (type)
+ "Define state function for TYPE."
+ `(defun ,(intern (format "consult--%s-state" type)) ()
+ ,(format "State function for %ss with preview.
+The result can be passed as :state argument to `consult--read'." type)
+ (consult--state-with-return (,(intern (format "consult--%s-preview" type)))
+ #',(intern (format "consult--%s-action" type)))))
+
+(defun consult--preview-key-normalize (preview-key)
+ "Normalize PREVIEW-KEY, return alist of keys and debounce times."
+ (let ((keys)
+ (debounce 0))
+ (setq preview-key (ensure-list preview-key))
+ (while preview-key
+ (if (eq (car preview-key) :debounce)
+ (setq debounce (cadr preview-key)
+ preview-key (cddr preview-key))
+ (let ((key (car preview-key)))
+ (unless (eq key 'any)
+ (setq key (consult--key-parse key)))
+ (push (cons key debounce) keys))
+ (pop preview-key)))
+ keys))
+
+(defun consult--preview-key-debounce (preview-key cand)
+ "Return debounce value of PREVIEW-KEY given the current candidate CAND."
+ (when (and (consp preview-key) (memq :keys preview-key))
+ (setq preview-key (funcall (plist-get preview-key :predicate) cand)))
+ (let ((map (make-sparse-keymap))
+ (keys (this-single-command-keys))
+ any)
+ (pcase-dolist (`(,k . ,d) (consult--preview-key-normalize preview-key))
+ (if (eq k 'any)
+ (setq any d)
+ (define-key map k `(lambda () ,d))))
+ (setq keys (lookup-key map keys))
+ (if (functionp keys) (funcall keys) any)))
+
+(defun consult--preview-append-local-pch (fun)
+ "Append FUN to local `post-command-hook' list."
+ ;; Symbol indirection because of bug#46407.
+ (let ((hook (make-symbol "consult--preview-post-command-hook")))
+ (fset hook fun)
+ ;; TODO Emacs 28 has a bug, where the hook--depth-alist is not cleaned up properly
+ ;; Do not use the broken add-hook here.
+ ;;(add-hook 'post-command-hook hook 'append 'local)
+ (setq-local post-command-hook
+ (append
+ (remove t post-command-hook)
+ (list hook)
+ (and (memq t post-command-hook) '(t))))))
+
+(defun consult--with-preview-f (preview-key state transform candidate save-input body)
+ "See `consult--with-preview' for documentation."
+ (let ((mb-input "") (timer (timer-create)) mb-narrow selected previewed)
+ (minibuffer-with-setup-hook
+ (if (and state preview-key)
+ (lambda ()
+ (let ((hook (make-symbol "consult--preview-minibuffer-exit-hook"))
+ (depth (recursion-depth)))
+ (fset hook
+ (lambda ()
+ (when (= (recursion-depth) depth)
+ (remove-hook 'minibuffer-exit-hook hook)
+ (cancel-timer timer)
+ (with-selected-window (consult--original-window)
+ ;; STEP 3: Reset preview
+ (when previewed
+ (funcall state 'preview nil))
+ ;; STEP 4: Notify the preview function of the minibuffer exit
+ (funcall state 'exit nil)))))
+ (add-hook 'minibuffer-exit-hook hook))
+ ;; STEP 1: Setup the preview function
+ (with-selected-window (consult--original-window)
+ (funcall state 'setup nil))
+ (setq consult--preview-function
+ (lambda ()
+ (when-let* ((cand (funcall candidate)))
+ ;; Drop properties to prevent bugs regarding candidate
+ ;; lookup, which must handle candidates without
+ ;; properties. Otherwise the arguments passed to the
+ ;; lookup function are confusing, since during preview
+ ;; the candidate has properties but for the final lookup
+ ;; after completion it does not.
+ (setq cand (substring-no-properties cand))
+ (with-selected-window (active-minibuffer-window)
+ (let ((input (minibuffer-contents-no-properties))
+ (narrow consult--narrow)
+ (win (consult--original-window)))
+ (with-selected-window win
+ (when-let* ((transformed (funcall transform narrow input cand))
+ (debounce (consult--preview-key-debounce preview-key transformed)))
+ (cancel-timer timer)
+ ;; The transformed candidate may have text
+ ;; properties, which change the preview display.
+ ;; This matters for example for `consult-grep',
+ ;; where the current candidate and input may
+ ;; stay equal, but the highlighting of the
+ ;; candidate changes while the candidates list
+ ;; is lagging a bit behind and updates
+ ;; asynchronously.
+ ;;
+ ;; In older Consult versions we instead compared
+ ;; the input without properties, since I worried
+ ;; that comparing the transformed candidates
+ ;; could be potentially expensive. However
+ ;; comparing the transformed candidates is more
+ ;; correct. The transformed candidate is the
+ ;; thing which is actually previewed.
+ (unless (equal-including-properties previewed transformed)
+ (if (> debounce 0)
+ (progn
+ (timer-set-function
+ timer
+ (lambda ()
+ ;; Preview only when a completion
+ ;; window is selected and when
+ ;; the preview window is alive.
+ (when (and (consult--completion-window-p)
+ (window-live-p win))
+ (with-selected-window win
+ ;; STEP 2: Preview candidate
+ (funcall state 'preview (setq previewed transformed))))))
+ (timer-set-time timer (timer-relative-time nil debounce))
+ (timer-activate timer))
+ ;; STEP 2: Preview candidate
+ (funcall state 'preview (setq previewed transformed)))))))))))
+ (consult--preview-append-local-pch
+ (lambda ()
+ (setq mb-input (minibuffer-contents-no-properties)
+ mb-narrow consult--narrow)
+ (funcall consult--preview-function))))
+ (lambda ()
+ (consult--preview-append-local-pch
+ (lambda ()
+ (setq mb-input (minibuffer-contents-no-properties)
+ mb-narrow consult--narrow)))))
+ (unwind-protect
+ (setq selected (when-let* ((result (funcall body)))
+ (when-let* ((save-input)
+ (list (symbol-value save-input))
+ ((equal (car list) result)))
+ (set save-input (cdr list)))
+ (funcall transform mb-narrow mb-input result)))
+ (when save-input
+ (add-to-history save-input mb-input))
+ (when state
+ ;; STEP 5: The preview function should perform its final action
+ (funcall state 'return selected))))))
+
+(defmacro consult--with-preview (preview-key state transform candidate save-input &rest body)
+ "Add preview support to BODY.
+
+STATE is the state function.
+TRANSFORM is the transformation function.
+CANDIDATE is the function returning the current candidate.
+PREVIEW-KEY are the keys which triggers the preview.
+SAVE-INPUT can be a history variable symbol to save the input.
+
+The state function takes two arguments, an action argument and the
+selected candidate. The candidate argument can be nil if no candidate is
+selected or if the selection was aborted. The function is called in
+sequence with the following arguments:
+
+ 1. \\='setup nil After entering the mb (minibuffer-setup-hook).
+⎧ 2. \\='preview CAND/nil Preview candidate CAND or reset if CAND is nil.
+⎪ \\='preview CAND/nil
+⎪ \\='preview CAND/nil
+⎪ ...
+⎩ 3. \\='preview nil Reset preview.
+ 4. \\='exit nil Before exiting the mb (minibuffer-exit-hook).
+ 5. \\='return CAND/nil After leaving the mb, CAND has been selected.
+
+The state function is always executed with the original window selected,
+see `consult--original-window'. The state function is called once in
+the beginning of the minibuffer setup with the `setup' argument. This is
+useful in order to perform certain setup operations which require that
+the minibuffer is initialized. During completion candidates are
+previewed. Then the function is called with the `preview' argument and a
+candidate CAND or nil if no candidate is selected. Furthermore if nil is
+passed for CAND, then the preview must be undone and the original state
+must be restored. The call with the `exit' argument happens once at the
+end of the completion process, just before exiting the minibuffer. The
+minibuffer is still alive at that point. Both `setup' and `exit' are
+only useful for setup and cleanup operations. They don't receive a
+candidate as argument. After leaving the minibuffer, the selected
+candidate or nil is passed to the state function with the action
+argument `return'. At this point the state function can perform the
+actual action on the candidate. The state function with the `return'
+argument is the continuation of `consult--read'. Via `unwind-protect' it
+is guaranteed, that if the `setup' action of a state function is
+invoked, the state function will also be called with `exit' and
+`return'."
+ (declare (indent 5) (debug t))
+ `(consult--with-preview-f ,preview-key ,state ,transform ,candidate ,save-input (lambda () ,@body)))
+
+;;;; Narrowing and grouping
+
+(defun consult--prefix-group (cand transform)
+ "Return title for CAND or TRANSFORM the candidate.
+The candidate must have a `consult--prefix-group' property."
+ (if transform
+ (substring cand (1+ (length (get-text-property 0 'consult--prefix-group cand))))
+ (get-text-property 0 'consult--prefix-group cand)))
+
+(defun consult--type-group (types)
+ "Return group function for TYPES."
+ (lambda (cand transform)
+ (if transform cand
+ (alist-get (get-text-property 0 'consult--type cand) types))))
+
+(defun consult--type-narrow (types)
+ "Return narrowing configuration from TYPES."
+ (list :predicate
+ (lambda (cand) (eq (get-text-property 0 'consult--type cand) consult--narrow))
+ :keys types))
+
+(defun consult--widen-key ()
+ "Return widening key, if `consult-widen-key' is not set.
+The default is twice the `consult-narrow-key'."
+ (cond
+ (consult-widen-key
+ (consult--key-parse consult-widen-key))
+ (consult-narrow-key
+ (let ((key (consult--key-parse consult-narrow-key)))
+ (vconcat key key)))))
+
+(defun consult-narrow (key)
+ "Narrow current completion with KEY.
+
+This command is used internally by the narrowing system of `consult--read'."
+ (declare (completion ignore))
+ (interactive
+ (list (unless (equal (this-single-command-keys) (consult--widen-key))
+ last-command-event)))
+ (consult--require-minibuffer)
+ (setq consult--narrow key)
+ (when-let* ((pred (plist-get consult--narrow-config :predicate)))
+ (setq minibuffer-completion-predicate (and consult--narrow pred)))
+ (when consult--narrow-overlay
+ (delete-overlay consult--narrow-overlay))
+ (when consult--narrow
+ (setq consult--narrow-overlay
+ (consult--make-overlay
+ (1- (minibuffer-prompt-end)) (minibuffer-prompt-end)
+ 'before-string
+ (format #(" [%s]" 0 5 (face consult-narrow-indicator))
+ (alist-get consult--narrow
+ (plist-get consult--narrow-config :keys))))))
+ (run-hooks 'consult--completion-refresh-hook))
+
+(defconst consult--narrow-delete
+ `( menu-item "" nil :filter
+ ,(lambda (&optional _)
+ (when (equal (minibuffer-contents-no-properties) "")
+ (lambda ()
+ (interactive)
+ (consult-narrow nil))))))
+
+(defconst consult--narrow-space
+ `( menu-item "" nil :filter
+ ,(lambda (&optional _)
+ (let ((str (minibuffer-contents-no-properties)))
+ (when-let* ((keys (plist-get consult--narrow-config :keys))
+ (pair (or (and (length= str 1) (assoc (aref str 0) keys))
+ (and (equal str "") (assoc ?\s keys)))))
+ (lambda ()
+ (interactive)
+ (delete-minibuffer-contents)
+ (consult-narrow (car pair))))))))
+
+(defun consult-narrow-help ()
+ "Print narrowing help as a `minibuffer-message'.
+
+This command can be bound to a key in `consult-narrow-map',
+to make it available for commands with narrowing."
+ (declare (completion ignore))
+ (interactive)
+ (consult--require-minibuffer)
+ (consult--minibuffer-message
+ (mapconcat (lambda (x)
+ (concat
+ (propertize (key-description (list (car x))) 'face 'consult-key)
+ " "
+ (propertize (cdr x) 'face 'consult-help)))
+ (plist-get consult--narrow-config :keys)
+ " ")))
+
+(defun consult--narrow-setup (config map)
+ "Setup narrowing with CONFIG and keymap MAP."
+ (setq consult--narrow-config (if (memq :keys config)
+ config (list :keys config)))
+ (when-let* ((key consult-narrow-key))
+ (setq key (consult--key-parse key))
+ (dolist (pair (plist-get consult--narrow-config :keys))
+ (define-key map (vconcat key (vector (car pair)))
+ (cons (cdr pair) #'consult-narrow))))
+ (when-let* ((widen (consult--widen-key)))
+ (define-key map widen (cons "All" #'consult-narrow))))
+
+;;;; Splitting completion style
+
+(defun consult--split-perl (str &optional _plist)
+ "Split input STR in async input and filtering part.
+
+The function returns a list with three elements: The async
+string, the start position of the completion filter string and a
+force flag. If the first character is a punctuation character it
+determines the separator. Examples: \"/async/filter\",
+\"#async#filter\"."
+ (if (string-match-p "^[[:punct:]]" str)
+ (save-match-data
+ (let ((q (regexp-quote (substring str 0 1))))
+ (string-match (concat "^" q "\\([^" q "]*\\)\\(" q "\\)?") str)
+ ;; Force update if two punctuation characters are entered.
+ `(,(propertize (match-string 1 str) 'consult--force (match-end 2))
+ ,(match-end 0)
+ ;; List of highlights
+ (0 . ,(match-beginning 1))
+ ,@(and (match-end 2) `((,(match-beginning 2) . ,(match-end 2)))))))
+ `(,str ,(length str))))
+
+(defun consult--split-none (str &optional _plist)
+ "Treat the complete input STR as async input."
+ `(,str ,(length str)))
+
+(defun consult--split-separator (str plist)
+ "Split input STR in async input and filtering part at first separator.
+PLIST is the splitter configuration, including the separator."
+ (let ((sep (regexp-quote (char-to-string (plist-get plist :separator)))))
+ (save-match-data
+ (if (string-match (format "^\\([^%s]+\\)\\(%s\\)?" sep sep) str)
+ ;; Force update if separator is entered.
+ `(,(propertize (match-string 1 str) 'consult--force (match-end 2))
+ ,(match-end 0)
+ ;; List of highlights
+ ,@(and (match-end 2) `((,(match-beginning 2) . ,(match-end 2)))))
+ `(,str ,(length str))))))
+
+(defun consult--split-setup (split)
+ "Setup splitting completion style with splitter function SPLIT."
+ (when (equal completion-styles '(consult--split))
+ (error "`consult--async-split-input' initialized twice"))
+ (let* ((styles completion-styles)
+ (catdef completion-category-defaults)
+ (catovr completion-category-overrides)
+ (try (lambda (str table pred point)
+ (let ((completion-styles styles)
+ (completion-category-defaults catdef)
+ (completion-category-overrides catovr)
+ (pos (cadr (funcall split str))))
+ (pcase (completion-try-completion (substring str pos) table pred
+ (max 0 (- point pos)))
+ ('t t)
+ (`(,newstr . ,newpt)
+ (setq newstr (concat (substring str 0 pos) newstr))
+ (if (eq (cadr (funcall split newstr)) pos)
+ (cons newstr (+ pos newpt))
+ (cons str point)))))))
+ (all (lambda (str table pred point)
+ (let ((completion-styles styles)
+ (completion-category-defaults catdef)
+ (completion-category-overrides catovr)
+ (pos (cadr (funcall split str))))
+ (completion-all-completions (substring str pos) table pred
+ (max 0 (- point pos)))))))
+ (setq-local completion-styles-alist (cons `(consult--split ,try ,all "")
+ completion-styles-alist)
+ completion-styles '(consult--split)
+ completion-category-defaults nil
+ completion-category-overrides nil)))
+
+;;;; Asynchronous pipeline
+
+(defun consult--async-pipeline (&rest async)
+ "Compose ASYNC pipeline.
+
+An async function must accept a single SINK argument and return a
+function accepting a single ACTION argument. In functional programming
+terminology, an async function is curried.
+
+ (lambda (sink)
+ (lambda (action)
+ ...))
+
+Async functions are composed with `consult--async-pipeline' as in the
+following example. The data flows downwards starting with the input
+from the user.
+
+ (consult--async-pipeline
+ (consult--async-min-input)
+ (consult--async-throttle)
+ (consult--async-process #\\='consult--man-builder)
+ (consult--async-transform #\\='consult--man-format)
+ (consult--async-highlight #\\='consult--man-builder))
+
+Nil functions are ignored to ease building conditional pipelines.
+
+ (consult--async-pipeline
+ (consult--async-min-input min-input)
+ (consult--async-throttle throttle debounce)
+ (consult--async-dynamic fun)
+ transform
+ (and highlight (consult--async-highlight highlight)))
+
+Async functions or pipelines can be passed as completion function to
+`consult--read' or used as `:async' field of `consult--multi' sources as
+shown in these examples:
+
+ (consult--read (consult--async-pipeline ...))
+ (consult--read (consult--dynamic-collection (lambda (input) ...)))
+ (consult--read (consult--process-collection #\\='consult--man-builder))
+
+ (defvar async-source
+ (list :async (consult--async-pipeline ...)))
+ (defvar dynamic-source
+ (list :async (consult--dynamic-collection (lambda (input) ...))))
+ (defvar command-source
+ (list :async (consult--process-collection #\\='consult--man-builder)))
+
+Incoming candidates and the action argument should be passed to the
+sink. The action can take the following forms:
+
+\\='setup Setup the internal closure state. Return nil.
+\\='destroy Destroy the internal closure state. Return nil.
+\\='flush Flush the list of candidates. Return nil.
+\\='refresh Request UI refresh. Return nil.
+\\='cancel Cancel any running process. Return nil.
+nil Return the list of candidates.
+list Append to the existing candidates list and return the whole list.
+string Update with the current user input string. Return nil.
+
+For the \\='setup action it is guaranteed that the call originates from
+the minibuffer. For the other actions no assumption about the context
+can be made."
+ (lambda (sink)
+ (seq-reduce (lambda (s f) (funcall f s)) (delq nil (reverse async)) sink)))
+
+(defun consult--async-wrap (async)
+ "Wrap ASYNC function with the default pipeline.
+The default pipeline provides `consult--async-split',
+`consult--async-indicator' and `consult--async-refresh'."
+ (consult--async-pipeline
+ (consult--async-split)
+ async
+ (consult--async-indicator)
+ (consult--async-refresh)))
+
+(defun consult--async-p (fun)
+ "Return t if FUN is an asynchronous function."
+ (and (functionp fun) (equal (func-arity fun) '(1 . 1))))
+
+(defmacro consult--with-async (async &rest body)
+ "Setup asynchronous completion in BODY.
+ASYNC is the asynchronous function or completion table."
+ (declare (indent 1) (debug (symbolp body)))
+ `(consult--with-async-f ,async (lambda (,async) ,@body)))
+
+(defun consult--with-async-f (async body)
+ "See `consult--with-async' for documentation."
+ (let (new-chunk orig-chunk)
+ (minibuffer-with-setup-hook
+ ;; Append such that we overwrite the completion style setting of
+ ;; `fido-mode'. See `consult--async-split' and `consult--split-setup'.
+ (:append
+ (lambda ()
+ (when (consult--async-p async)
+ (setq new-chunk (max read-process-output-max consult--process-chunk)
+ orig-chunk read-process-output-max
+ read-process-output-max new-chunk)
+ (funcall async 'setup)
+ (let* ((mb (current-buffer))
+ (fun (lambda ()
+ (when-let* ((win (active-minibuffer-window)))
+ (when (eq (window-buffer win) mb)
+ (with-current-buffer mb
+ (let ((inhibit-modification-hooks t))
+ ;; Push input string to request refresh.
+ (funcall async (minibuffer-contents-no-properties))))))))
+ ;; We use a symbol in order to avoid adding lambdas to
+ ;; the hook variable. Symbol indirection because of
+ ;; bug#46407.
+ (hook (make-symbol "consult--async-after-change-hook"))
+ (timer (timer-create)))
+ (timer-set-function timer fun)
+ ;; Delay modification hook to ensure that minibuffer is still
+ ;; alive after the change, such that we don't restart a new
+ ;; asynchronous search right before exiting the minibuffer.
+ (fset hook (lambda (&rest _)
+ (unless (memq timer timer-list)
+ (timer-set-time timer (current-time))
+ (timer-activate timer))))
+ (add-hook 'after-change-functions hook nil 'local)
+ ;; Immediately start asynchronous computation. This may lead
+ ;; to problems unnecessary work if content is inserted shortly
+ ;; afterwards.
+ (funcall fun)))))
+ (let ((async (if (consult--async-p async) async (lambda (_) async))))
+ (unwind-protect
+ (funcall body async)
+ (funcall async 'destroy)
+ (when (and orig-chunk (eq read-process-output-max new-chunk))
+ (setq read-process-output-max orig-chunk)))))))
+
+(defun consult--async-sink ()
+ "Asynchronous sink function."
+ (let (candidates last buffer)
+ (lambda (action)
+ (pcase-exhaustive action
+ ('setup
+ (setq buffer (current-buffer))
+ nil)
+ ((or (pred stringp) 'destroy 'cancel) nil)
+ ('flush (setq candidates nil last nil))
+ ('refresh
+ ;; Refresh the UI when the current minibuffer window belongs
+ ;; to the current asynchronous completion session.
+ (when-let* ((win (active-minibuffer-window)))
+ (when (eq (window-buffer win) buffer)
+ (with-selected-window win
+ (run-hooks 'consult--completion-refresh-hook)
+ ;; Interaction between asynchronous completion functions and
+ ;; preview: We have to trigger preview immediately when
+ ;; candidates arrive (gh:minad/consult#436).
+ (when (and consult--preview-function candidates)
+ (funcall consult--preview-function)))))
+ nil)
+ ('nil candidates)
+ ((pred consp)
+ ;; Lazily initialize last link, such that it is only initialized when
+ ;; appending, and not for one-shot async functions like
+ ;; `consult--async-static'.
+ (if (not candidates)
+ (setq candidates action)
+ (setq last (last (setcdr (or last (last candidates)) action)))
+ candidates))))))
+
+(defun consult--async-dynamic (fun &optional restart)
+ "Dynamic computation of candidates.
+FUN computes the candidates. It takes either a single input argument or
+an input argument and a callback function, if computed candidates should
+be updated incrementally. The callback function must not be called
+after FUN has returned.
+RESTART is the time after which an interrupted computation should be
+restarted and defaults to `consult-async-input-debounce'."
+ (setq restart (or restart consult-async-input-debounce))
+ (when (equal (func-arity fun) '(1 . 1))
+ (let ((orig fun))
+ (setq fun (lambda (input callback)
+ (funcall callback (funcall orig input))))))
+ (lambda (sink)
+ (let ((timer (timer-create)) (current nil) (compute nil))
+ (setq compute
+ (lambda (input)
+ (cancel-timer timer)
+ (funcall sink [indicator running])
+ (redisplay)
+ (let* ((state 'init)
+ (killed
+ (while-no-input
+ (funcall
+ fun input
+ (lambda (response)
+ (when (eq state 'done)
+ (error "consult--async-dynamic: Callback called too late"))
+ (let (throw-on-input)
+ (when (eq state 'init)
+ (funcall sink 'flush)
+ (setq state 'running))
+ (when response
+ (funcall sink response)
+ ;; Accept process input such that timers
+ ;; trigger and refresh the completion UI.
+ (accept-process-output)))))
+ (setq current input
+ state 'done)
+ nil)))
+ (funcall sink `[indicator ,(if killed 'killed 'finished)])
+ (funcall sink 'refresh)
+ ;; If the computation was killed, restart it after a while.
+ ;; This happens when the point is moved. Then the input does
+ ;; not change and the computation is not restarted otherwise.
+ (when (and killed (not (memq timer timer-list)))
+ (timer-set-function timer compute (list input))
+ (timer-set-time timer (timer-relative-time nil restart))
+ (timer-activate timer)))))
+ (lambda (action)
+ (prog1 (funcall sink action)
+ (pcase action
+ ((or 'cancel 'destroy) (cancel-timer timer))
+ ((pred stringp)
+ (if (not (equal action current))
+ (funcall compute action)
+ (cancel-timer timer)
+ (funcall sink [indicator finished])))))))))
+
+(defun consult--async-static (items)
+ "Async function with static ITEMS."
+ (consult--async-dynamic
+ (lambda (input)
+ (pcase-let ((`(,re . ,hl) (consult--compile-regexp
+ input 'emacs completion-ignore-case)))
+ (if re
+ (let* ((completion-regexp-list re)
+ (all (all-completions "" items)))
+ (cl-loop for s in-ref all do
+ (funcall hl (setf s (copy-sequence s))))
+ all)
+ (copy-sequence items))))))
+
+(defun consult--async-merge-sink (sink indicator tail idx)
+ "Create sink for the async sub-functions which merges the sub-lists.
+SINK is the joined sink.
+INDICATOR is a vector of indicator symbols.
+TAIL is a vector of list tail links for each sub-list.
+IDX is the index of the corresponding link in TAIL."
+ (lambda (action)
+ (pcase action
+ (`[indicator ,state]
+ (aset indicator (1- idx) state)
+ (let* ((severity [nil finished running killed failed])
+ (state (aref severity (cl-loop for i across indicator maximize
+ (or (seq-position severity i) 0)))))
+ (funcall sink `[indicator ,state])))
+ ('flush
+ ;; Flush items if sub-list exists.
+ (when-let* ((tl (aref tail idx)) (pre t))
+ (let ((i idx)) (while (not (setq pre (aref tail (decf i))))))
+ (setcdr pre (cdr tl))
+ (aset tail idx nil)
+ (funcall sink 'flush)
+ (funcall sink (cdr (aref tail 0)))))
+ ((pred consp)
+ (let ((tl (aref tail idx))
+ (last (last action))
+ pre)
+ (aset tail idx last)
+ (if tl ;; Append items if sub-list exists.
+ (progn
+ (setcdr last (cdr tl))
+ (setcdr tl action))
+ ;; Otherwise insert new sub-list.
+ (let ((i idx)) (while (not (setq pre (aref tail (decf i))))))
+ (setcdr last (cdr pre))
+ (setcdr pre action))
+ (funcall sink 'flush)
+ (funcall sink (cdr (aref tail 0))))))))
+
+(defun consult--async-merge (asyncs)
+ "Create merged async function from multiple ASYNCS."
+ (lambda (sink)
+ (let* ((indicator (make-vector (length asyncs) nil))
+ (tail (make-vector (1+ (length indicator)) nil))
+ (asyncs
+ (seq-map-indexed
+ (lambda (fun idx)
+ (funcall fun (consult--async-merge-sink sink indicator tail (1+ idx))))
+ asyncs)))
+ (aset tail 0 (list nil)) ;; Guard element
+ (lambda (action)
+ (dolist (async asyncs)
+ (funcall async action))
+ (funcall sink action)))))
+
+(defun consult--async-debug (prefix)
+ "Async function with debug messages.
+The messages are prefixed with PREFIX."
+ (lambda (sink)
+ (lambda (action)
+ (consult--async-log "%s: %S\n" prefix action)
+ (funcall sink action))))
+
+(defun consult--async-predicate (pred)
+ "Async function running only if PRED is non-nil."
+ (lambda (sink)
+ (let (input)
+ (lambda (action)
+ (prog1 (and (not (stringp action))
+ (funcall sink action))
+ (pcase action
+ ('setup (setq pred (consult--in-buffer pred)))
+ ((or 'cancel 'destroy) (setq input nil))
+ ((pred stringp) (setq input action)))
+ (when (and input (funcall pred))
+ (funcall sink input)
+ (setq input nil)))))))
+
+(defun consult--async-min-input (&optional min-input)
+ "Async function enforcing a minimum input length.
+MIN-INPUT is the minimum input length and defaults to
+`consult-async-min-input'."
+ (setq min-input (or min-input consult-async-min-input))
+ (lambda (sink)
+ (lambda (action)
+ (if (stringp action)
+ ;; Input can be marked with the `consult--force' property such that it
+ ;; is passed through in any case.
+ (funcall sink (if (or (and (not (equal action ""))
+ (get-text-property 0 'consult--force action))
+ (>= (length action) min-input))
+ action 'cancel))
+ (funcall sink action)))))
+
+(defun consult--async-split (&optional style)
+ "Async function, which splits the input string.
+STYLE is the splitting style and defaults to the splitting style
+configured by `consult-async-split-style'."
+ (setq style (or style consult-async-split-style 'none)
+ style (or (alist-get style consult-async-split-styles-alist)
+ (user-error "Splitting style `%s' not found" style)))
+ (lambda (sink)
+ (lambda (action)
+ (pcase action
+ ('setup
+ (consult--split-setup (let ((fun (plist-get style :function)))
+ (lambda (str) (funcall fun str style))))
+ (when-let* ((initial (plist-get style :initial)))
+ (save-excursion
+ (goto-char (minibuffer-prompt-end))
+ (unless (equal initial (char-after))
+ (insert-before-markers initial))))
+ (funcall sink 'setup))
+ ((pred stringp)
+ (pcase-let ((`(,input ,_ . ,highlights)
+ (funcall (plist-get style :function) action style))
+ (end (minibuffer-prompt-end)))
+ ;; Highlight punctuation characters
+ (pcase-dolist (`(,x . ,y) highlights)
+ (add-text-properties (+ end x) (+ end y)
+ '(face consult-async-split consult--split t rear-nonsticky t)))
+ (funcall sink input)))
+ (_ (funcall sink action))))))
+
+(defun consult--async-options ()
+ "Async function, which highlights commands options in the input string."
+ (lambda (sink)
+ (lambda (action)
+ (when (stringp action)
+ (save-match-data
+ (when-let* ((iend (save-excursion
+ (goto-char (minibuffer-prompt-end))
+ (search-forward action nil t)))
+ (ibeg (- iend (length action))))
+ (remove-list-of-text-properties ibeg iend '(face rear-nonsticky))
+ (when-let* (((string-match "\\(?:\\`\\| \\)\\(-\\)" action))
+ (beg (match-beginning 1))
+ ((string-match "\\(?:\\`\\| \\)\\(--\\)\\(?: \\|\\'\\)\\|\\'" action))
+ (end (or (match-end 1) (match-end 0))))
+ (add-text-properties (+ ibeg beg) (+ ibeg end)
+ '( face consult-async-option
+ rear-nonsticky t))))))
+ (funcall sink action))))
+
+(defun consult--async-indicator ()
+ "Async function with a state indicator overlay."
+ (lambda (sink)
+ (let ((ind (cl-loop for (k c f) in consult-async-indicator
+ collect (cons k (propertize (string c) 'face f))))
+ ov)
+ (lambda (action)
+ (pcase action
+ ('setup
+ (dolist (ov (overlays-at (- (minibuffer-prompt-end) 2)))
+ (when (eq (overlay-get ov 'category) 'consult-async-indicator-overlay)
+ (error "`consult--async-indicator' initialized twice")))
+ (setq ov (consult--make-overlay
+ (- (minibuffer-prompt-end) 2)
+ (- (minibuffer-prompt-end) 1)
+ 'category 'consult-async-indicator-overlay))
+ (funcall sink 'setup))
+ ('destroy
+ (delete-overlay ov)
+ (funcall sink 'destroy))
+ (`[indicator ,state]
+ (overlay-put ov 'display (alist-get state ind)))
+ (_ (funcall sink action)))))))
+
+(defun consult--async-log (formatted &rest args)
+ "Log FORMATTED ARGS to variable `consult--async-log'."
+ (with-current-buffer (get-buffer-create consult--async-log)
+ (goto-char (point-max))
+ (insert (apply #'format formatted args))))
+
+(defun consult--async-process (builder &rest props)
+ "Async process function.
+BUILDER is the command line builder function.
+PROPS are optional properties passed to `make-process'."
+ (lambda (sink)
+ (let (proc proc-buf last-args count)
+ (lambda (action)
+ (pcase action
+ ((pred stringp)
+ (funcall sink action)
+ (let ((args (funcall builder action)))
+ (unless (stringp (car args))
+ (setq args (car args)))
+ (unless (equal args last-args)
+ (setq last-args args)
+ (when proc
+ (delete-process proc)
+ (kill-buffer proc-buf)
+ (setq proc nil proc-buf nil))
+ (when args
+ (let* ((flush t)
+ (rest "")
+ (proc-filter
+ (lambda (_ out)
+ (when flush
+ (setq flush nil)
+ (funcall sink 'flush))
+ (let ((lines (split-string out "[\r\n]+")))
+ (if (not (cdr lines))
+ (setq rest (concat rest (car lines)))
+ (setcar lines (concat rest (car lines)))
+ (let* ((len (length lines))
+ (last (nthcdr (- len 2) lines)))
+ (setq rest (cadr last)
+ count (+ count len -1))
+ (setcdr last nil)
+ (funcall sink lines))))))
+ (proc-sentinel
+ (lambda (_ event)
+ (cond
+ (flush
+ (setq flush nil)
+ (funcall sink 'flush))
+ ((and (string-prefix-p "finished" event) (not (equal rest "")))
+ (incf count)
+ (funcall sink (list rest))))
+ (funcall sink `[indicator
+ ,(cond
+ ((string-prefix-p "killed" event) 'killed)
+ ((string-prefix-p "finished" event) 'finished)
+ (t 'failed))])
+ (consult--async-log
+ "consult--async-process sentinel: event=%s lines=%d\n"
+ (string-trim event) count)
+ (when (> (buffer-size proc-buf) 0)
+ (with-current-buffer (get-buffer-create consult--async-log)
+ (goto-char (point-max))
+ (insert ">>>>> stderr >>>>>\n")
+ (let ((beg (point)))
+ (insert-buffer-substring proc-buf)
+ (save-excursion
+ (goto-char beg)
+ (message #("%s" 0 2 (face error))
+ (buffer-substring-no-properties (pos-bol) (pos-eol)))))
+ (insert "<<<<< stderr <<<<<\n")))))
+ (process-adaptive-read-buffering nil))
+ (funcall sink [indicator running])
+ (consult--async-log "consult--async-process started: args=%S default-directory=%S\n"
+ args default-directory)
+ (setq count 0
+ proc-buf (generate-new-buffer " *consult-async-stderr*")
+ proc (apply #'make-process
+ `(,@props
+ :connection-type pipe
+ :name ,(car args)
+ ;;; XXX tramp bug, the stderr buffer must be empty
+ :stderr ,proc-buf
+ :noquery t
+ :command ,args
+ :filter ,proc-filter
+ :sentinel ,proc-sentinel)))))))
+ nil)
+ ((or 'cancel 'destroy)
+ (when proc
+ (delete-process proc)
+ (kill-buffer proc-buf)
+ (setq proc nil proc-buf nil))
+ (setq last-args nil)
+ (funcall sink action))
+ (_ (funcall sink action)))))))
+
+(defun consult--async-highlight (&optional highlight)
+ "Async function with candidate highlighting.
+HIGHLIGHT is a function called with the input string. It should return
+a function which mutably adds highlighting to a candidate string.
+HIGHLIGHT can also return a pair where the second element is the actual
+highlight function. If not given, HIGHLIGHT defaults to a function
+which highlights words."
+ (unless (functionp highlight)
+ (setq highlight
+ (lambda (input)
+ (consult--compile-regexp input 'emacs completion-ignore-case))))
+ (consult--async-transform-by-input
+ (lambda (input)
+ (when-let* ((hl (funcall highlight input))
+ (hl (if (functionp hl) hl (cdr hl))))
+ (lambda (cands)
+ (dolist (x cands cands)
+ (funcall hl (if (consp x) (car x) x))))))))
+
+(defun consult--async-throttle (&optional throttle debounce)
+ "Async function which throttles input.
+The THROTTLE delay defaults to `consult-async-input-throttle'.
+The DEBOUNCE delay defaults to `consult-async-input-debounce'."
+ (setq throttle (or throttle consult-async-input-throttle)
+ debounce (or debounce consult-async-input-debounce))
+ (lambda (sink)
+ (let ((timer (timer-create)) (last 0) initial-p input)
+ (lambda (action)
+ (pcase action
+ ((pred stringp)
+ (unless (equal action input)
+ (cancel-timer timer)
+ (funcall sink 'cancel)
+ (timer-set-function timer (lambda ()
+ (setq last (float-time))
+ (funcall sink action)))
+ (timer-set-time
+ timer
+ (timer-relative-time
+ ;; Debounce only if the user entered new input. Start
+ ;; immediately if the minibuffer contains initial input.
+ nil (max (if (funcall initial-p) 0 debounce)
+ (- (+ last throttle) (float-time)))))
+ (setq input action)
+ (timer-activate timer))
+ nil)
+ ('setup
+ (setq initial-p
+ (consult--in-buffer
+ (let ((initial (minibuffer-contents-no-properties)))
+ (lambda ()
+ (equal initial (minibuffer-contents-no-properties))))))
+ (funcall sink action))
+ ((or 'cancel 'destroy)
+ (cancel-timer timer)
+ (funcall sink action))
+ (_ (funcall sink action)))))))
+
+(defun consult--async-refresh (&optional delay)
+ "Async function which refreshes the display with a timer.
+The refresh happens after a DELAY, defaulting to
+`consult-async-refresh-delay'."
+ (setq delay (or delay consult-async-refresh-delay))
+ (lambda (sink)
+ (if (<= delay 0)
+ (lambda (action)
+ (pcase action
+ ((or (pred consp) 'flush)
+ (prog1 (funcall sink action)
+ (funcall sink 'refresh)))
+ (_ (funcall sink action))))
+ (let ((timer (timer-create)))
+ (lambda (action)
+ (prog1 (funcall sink action)
+ (pcase action
+ ((or (pred consp) 'flush)
+ (unless (memq timer timer-list)
+ (timer-set-function timer sink '(refresh))
+ (timer-set-time timer (timer-relative-time nil delay))
+ (timer-activate timer)))
+ ((or 'destroy 'refresh) ;; 'refresh already forced a refresh
+ (cancel-timer timer)))))))))
+
+(defun consult--async-transform-by-input (fun)
+ "Transform candidates via FUN.
+FUN takes the input string and must return a transformation function."
+ (lambda (sink)
+ (let (transform)
+ (lambda (action)
+ (cond
+ ((stringp action)
+ (setq transform (funcall fun action))
+ (funcall sink action))
+ ((and (consp action) transform)
+ (funcall sink (funcall transform action)))
+ (t (funcall sink action)))))))
+
+(defun consult--async-transform (fun)
+ "Use FUN to transform candidates."
+ (lambda (sink)
+ (lambda (action)
+ (funcall sink (if (consp action) (funcall fun action) action)))))
+
+(defun consult--async-map (fun)
+ "Map candidates by FUN."
+ (consult--async-transform (apply-partially #'mapcar fun)))
+
+(defun consult--async-filter (fun)
+ "Filter candidates by FUN."
+ (consult--async-transform (apply-partially #'seq-filter fun)))
+
+;;;; Prebuilt async pipelines
+
+(cl-defun consult--dynamic-collection (fun &key min-input throttle debounce
+ transform highlight)
+ "Dynamic candidate computation pipeline.
+FUN computes the candidates. It takes either a single input argument or
+an input argument and a callback function, if computed candidates should
+be updated incrementally. The callback function must not be called
+after FUN has returned.
+MIN-INPUT is passed to `consult--async-min-input'.
+THROTTLE and DEBOUNCE are passed to `consult--async-throttle'.
+TRANSFORM is an optional async function transforming the candidate.
+HIGHLIGHT is an optional highlight function, can be t for the default
+highlighting function."
+ (declare (indent 1))
+ (consult--async-pipeline
+ (consult--async-min-input min-input)
+ (consult--async-throttle throttle debounce)
+ (consult--async-dynamic fun)
+ transform
+ (and highlight (consult--async-highlight highlight))))
+
+(cl-defun consult--process-collection (builder &rest props &key min-input
+ debounce throttle transform
+ highlight &allow-other-keys)
+ "Asynchronous process pipeline.
+BUILDER is the command line builder function, which takes the
+input string and must either return a list of command line
+arguments or a pair of the command line argument list and a
+highlighting function.
+TRANSFORM is an optional async function transforming the candidate.
+If HIGHLIGHT is non-nil, highlight the candidates.
+MIN-INPUT is passed to `consult--async-min-input'.
+THROTTLE and DEBOUNCE are passed to `consult--async-throttle'.
+Other PROPS are passed to `make-process'."
+ (declare (indent 1))
+ (consult--async-pipeline
+ (consult--async-options)
+ (consult--async-min-input min-input)
+ (consult--async-throttle throttle debounce)
+ (apply #'consult--async-process builder
+ (consult--plist-remove
+ '(:min-input :throttle :debounce :transform :highlight) props))
+ transform
+ (and highlight (consult--async-highlight
+ (if (functionp highlight) highlight builder)))))
+
+;;;; Special keymaps
+
+(defvar-keymap consult-async-map
+ :doc "Keymap added for commands with asynchronous candidates."
+ ;; Overwriting some unusable defaults of default minibuffer completion.
+ "<remap> <minibuffer-complete-word>" #'self-insert-command
+ ;; Remap Emacs 29 history and default completion for now
+ ;; (gh:minad/consult#613).
+ "<remap> <minibuffer-complete-defaults>" #'ignore
+ "<remap> <minibuffer-complete-history>" #'consult-history)
+
+(defvar-keymap consult-narrow-map
+ :doc "Narrowing keymap which is added to the local minibuffer map.
+Note that `consult-narrow-key' and `consult-widen-key' are bound dynamically."
+ "SPC" consult--narrow-space
+ "DEL" consult--narrow-delete)
+
+;;;; Internal API: consult--read
+
+(defun consult--annotate-align (cand ann)
+ "Align annotation ANN by computing the maximum CAND width."
+ (setq consult--annotate-align-width
+ (max consult--annotate-align-width
+ (* (ceiling (consult--display-width cand)
+ consult--annotate-align-step)
+ consult--annotate-align-step)))
+ (when ann
+ (concat
+ #(" " 0 1 (display (space :align-to (+ left consult--annotate-align-width))))
+ ann)))
+
+(defun consult--add-history (async items)
+ "Add ITEMS to the minibuffer future history.
+ASYNC must be non-nil for async completion functions."
+ (setq items
+ (delete-dups
+ (append
+ ;; Defaults are at the beginning of the future history
+ (ensure-list minibuffer-default)
+ ;; Custom items
+ (remove "" (remq nil (ensure-list items)))
+ ;; Add all completions for non-async commands. For async commands
+ ;; this feature is not useful, since if one selects a completion
+ ;; candidate, the async search is restarted using that candidate
+ ;; string. This usually does not yield a desired result since the
+ ;; async input uses a special format, e.g., `#grep#filter'.
+ (unless async
+ (all-completions "" minibuffer-completion-table
+ minibuffer-completion-predicate)))))
+ ;; Prefix all items with the initial input from the async split style.
+ (when (and async (get-text-property (minibuffer-prompt-end) 'consult--split))
+ (let* ((beg (minibuffer-prompt-end))
+ (end (or (text-property-any beg (point-max) 'consult--split nil)
+ (point-max)))
+ (pre (buffer-substring beg end)))
+ (cl-loop for item in-ref items do
+ (unless (string-prefix-p pre item)
+ (setf item (concat pre item))))))
+ items)
+
+(defun consult--setup-keymap (keymap async narrow preview-key)
+ "Setup minibuffer keymap.
+
+KEYMAP is a command-specific keymap.
+ASYNC must be non-nil for async completion functions.
+NARROW is the narrowing configuration.
+PREVIEW-KEY are the preview keys."
+ (let ((old-map (current-local-map))
+ (map (make-sparse-keymap)))
+
+ ;; Add narrow keys
+ (when narrow
+ (consult--narrow-setup narrow map))
+
+ ;; Preview trigger keys
+ (when (and (consp preview-key) (memq :keys preview-key))
+ (setq preview-key (plist-get preview-key :keys)))
+ (setq preview-key (mapcar #'car (consult--preview-key-normalize preview-key)))
+ (when preview-key
+ (dolist (key preview-key)
+ (unless (or (eq key 'any) (lookup-key old-map key))
+ (define-key map key #'ignore))))
+
+ ;; Put the keymap together
+ (use-local-map
+ (make-composed-keymap
+ (delq nil (list keymap
+ (and async consult-async-map)
+ (and narrow consult-narrow-map)
+ map))
+ old-map))))
+
+(defun consult--tofu-hide-in-minibuffer (&rest _)
+ "Hide the tofus in the minibuffer."
+ (let* ((min (minibuffer-prompt-end))
+ (max (point-max))
+ (pos max))
+ (while (and (> pos min) (consult--tofu-p (char-before pos)))
+ (decf pos))
+ (when (< pos max)
+ (add-text-properties pos max '(invisible t rear-nonsticky t cursor-intangible t)))))
+
+(defun consult--read-affixate (fun cands)
+ "Affixate CANDS with annotation function FUN."
+ (mapcar (lambda (cand)
+ (let ((ann (funcall fun cand)))
+ (if (consp ann)
+ ann
+ (setq ann (or ann ""))
+ (list cand ""
+ ;; The default completion UI adds the
+ ;; `completions-annotations' face if no other faces are
+ ;; present.
+ (if (text-property-not-all 0 (length ann) 'face nil ann)
+ ann
+ (propertize ann 'face 'completions-annotations))))))
+ cands))
+
+(cl-defun consult--read-1 ( table &key
+ prompt predicate require-match history default keymap category
+ initial narrow initial-narrow add-history annotate state
+ preview-key sort lookup group inherit-input-method async-wrap)
+ "See `consult--read' for documentation."
+ (when (and async-wrap (consult--async-p table))
+ (setq table (funcall (funcall async-wrap table) (consult--async-sink))))
+ (minibuffer-with-setup-hook
+ (:append (lambda ()
+ (add-hook 'after-change-functions #'consult--tofu-hide-in-minibuffer nil 'local)
+ (consult--setup-keymap keymap (consult--async-p table) narrow preview-key)
+ (when initial-narrow (consult-narrow initial-narrow))
+ (setq-local minibuffer-default-add-function
+ (apply-partially #'consult--add-history (consult--async-p table) add-history)
+ kill-transform-function #'consult--tofu-strip)))
+ (consult--with-async table
+ (consult--with-preview
+ preview-key state
+ (lambda (narrow input cand)
+ (funcall lookup cand (funcall table nil) input narrow))
+ (apply-partially #'run-hook-with-args-until-success
+ 'consult--completion-candidate-hook)
+ (pcase-exhaustive history
+ (`(:input ,var) var)
+ ((pred symbolp)))
+ ;; Do not unnecessarily let-bind the lambdas to avoid over-capturing in
+ ;; the interpreter. This will make closures and the lambda string
+ ;; representation larger, which makes debugging much worse. Fortunately
+ ;; the over-capturing problem does not affect the bytecode interpreter
+ ;; which does a proper scope analysis.
+ (let* ((metadata `(metadata
+ ,@(when category `((category . ,category)))
+ ,@(when group `((group-function . ,group)))
+ ,@(when annotate
+ `((affixation-function
+ . ,(apply-partially #'consult--read-affixate annotate))))
+ ,@(unless sort '((cycle-sort-function . identity)
+ (display-sort-function . identity)))))
+ (consult--annotate-align-width 0)
+ (selected
+ (completing-read
+ prompt
+ (lambda (str pred action)
+ (let ((result (complete-with-action action (funcall table nil) str pred)))
+ (if (eq action 'metadata)
+ (if (and (eq (car result) 'metadata) (cdr result))
+ ;; Merge metadata
+ `(metadata ,@(cdr metadata) ,@(cdr result))
+ metadata)
+ result)))
+ predicate require-match initial
+ (if (symbolp history) history (cadr history))
+ default
+ inherit-input-method)))
+ ;; Repair the null completion semantics. `completing-read' may return
+ ;; an empty string even if REQUIRE-MATCH is non-nil. One can always
+ ;; opt-in to null completion by passing the empty string for DEFAULT.
+ (when (and (eq require-match t) (not default) (equal selected ""))
+ (user-error "No selection"))
+ selected)))))
+
+(cl-defun consult--read ( table &rest options &key
+ prompt predicate require-match history default command
+ keymap category initial narrow initial-narrow annotate
+ add-history state preview-key sort lookup group
+ inherit-input-method async-wrap)
+ "Enhanced completing read function to select from TABLE.
+
+The function is a thin wrapper around `completing-read'. Keyword
+arguments are used instead of positional arguments for code
+clarity. On top of `completing-read' it additionally supports
+computing the candidate list asynchronously, candidate preview
+and narrowing. You should use `completing-read' instead of
+`consult--read' if you don't use asynchronous candidate
+computation or candidate preview.
+
+Keyword OPTIONS:
+
+PROMPT is the string which is shown as prompt in the minibuffer.
+PREDICATE is a filter function called for each candidate, returns
+nil or t.
+REQUIRE-MATCH equals t means that an exact match is required.
+HISTORY is the symbol of the history variable.
+DEFAULT is the default selected value.
+ADD-HISTORY is a list of items to add to the history.
+CATEGORY is the completion category symbol.
+COMMAND is used for customization, defaulting to `this-command.'
+SORT should be set to nil if the candidates are already sorted.
+This will disable sorting in the completion UI.
+LOOKUP is a lookup function passed the selected candidate string,
+the list of candidates, the current input string and the current
+narrowing value.
+ANNOTATE is a function passed a candidate string. The function
+should either return an annotation string or a list of three
+strings (candidate prefix postfix).
+INITIAL is the initial input string.
+STATE is the state function, see `consult--with-preview'.
+GROUP is a completion metadata `group-function' as documented in
+the Elisp manual.
+PREVIEW-KEY are the preview keys. Can be nil, `any', a single
+key or a list of keys.
+NARROW is an alist of narrowing prefix strings and description.
+INITIAL-NARROW is an initial narrow key.
+KEYMAP is a command-specific keymap.
+INHERIT-INPUT-METHOD, if non-nil the minibuffer inherits the
+input method.
+ASYNC-WRAP wraps asynchronous functions and defaults to
+`consult--async-wrap'."
+ (ignore prompt predicate require-match history default keymap category
+ initial narrow initial-narrow add-history annotate state command
+ preview-key sort lookup group inherit-input-method async-wrap)
+ (apply #'consult--read-1 table
+ (consult--customize-args
+ options
+ :prompt "Select: "
+ :preview-key consult-preview-key
+ :sort t
+ :async-wrap #'consult--async-wrap
+ :lookup (lambda (selected &rest _) selected))))
+
+;;;; Internal API: consult--prompt
+
+(cl-defun consult--prompt-1 ( &key prompt history add-history initial default
+ keymap state preview-key transform inherit-input-method)
+ "See `consult--prompt' for documentation."
+ (minibuffer-with-setup-hook
+ (:append (lambda ()
+ (consult--setup-keymap keymap nil nil preview-key)
+ (setq-local minibuffer-default-add-function
+ (apply-partially #'consult--add-history nil add-history))))
+ (consult--with-preview
+ preview-key state
+ (lambda (_narrow inp _cand) (funcall transform inp))
+ (lambda () "")
+ history
+ (read-from-minibuffer prompt initial nil nil history default inherit-input-method))))
+
+(cl-defun consult--prompt ( &rest options &key prompt history add-history initial default
+ keymap state preview-key transform inherit-input-method command)
+ "Read from minibuffer.
+
+Keyword OPTIONS:
+
+PROMPT is the string to prompt with.
+TRANSFORM is a function which is applied to the current input string.
+HISTORY is the symbol of the history variable.
+INITIAL is initial input.
+DEFAULT is the default selected value.
+ADD-HISTORY is a list of items to add to the history.
+STATE is the state function, see `consult--with-preview'.
+PREVIEW-KEY are the preview keys (nil, `any', a single key or a list of keys).
+KEYMAP is a command-specific keymap.
+COMMAND is used for customization, defaulting to `this-command.'"
+ (ignore prompt history add-history initial default command
+ keymap state preview-key transform inherit-input-method)
+ (apply #'consult--prompt-1
+ (consult--customize-args
+ options
+ :prompt "Input: "
+ :preview-key consult-preview-key
+ :transform #'identity)))
+
+;;;; Internal API: consult--multi
+
+(defsubst consult--multi-source (sources cand)
+ "Lookup source for CAND in SOURCES list."
+ (aref sources (consult--tofu-get cand)))
+
+(defsubst consult--multi-visible-p (src)
+ "Is SRC visible according to `consult--narrow'?"
+ (if-let* ((n consult--narrow))
+ (pcase (plist-get src :narrow)
+ ((and ks `((,_ . ,_) . ,_)) (assq n ks))
+ ((or `(,k . ,_) k) (eq n k)))
+ (not (plist-get src :hidden))))
+
+(defun consult--multi-predicate (sources cand)
+ "Predicate function called for each candidate CAND given SOURCES."
+ (consult--multi-visible-p (consult--multi-source sources cand)))
+
+(defun consult--multi-narrow (sources)
+ "Return narrow list from SOURCES."
+ (thread-last
+ sources
+ (mapcan (lambda (src)
+ (when-let* ((narrow (plist-get src :narrow)))
+ (if (consp narrow)
+ (if (consp (car narrow)) (append narrow nil) (list narrow))
+ (when-let* ((name (plist-get src :name)))
+ (list (cons narrow name)))))))
+ (delq nil)
+ (delete-dups)))
+
+(defun consult--multi-annotate (sources cand)
+ "Annotate candidate CAND from multi SOURCES."
+ (consult--annotate-align
+ cand
+ (let ((src (consult--multi-source sources cand)))
+ (if-let* ((fun (plist-get src :annotate)))
+ (funcall fun (cdr (get-text-property 0 'multi-category cand)))
+ (plist-get src :name)))))
+
+(defun consult--multi-group (sources cand transform)
+ "Return title of candidate CAND or TRANSFORM the candidate given SOURCES."
+ (if transform cand
+ (plist-get (consult--multi-source sources cand) :name)))
+
+(defun consult--multi-preview-key (sources)
+ "Return preview keys from SOURCES."
+ (list :predicate
+ (lambda (cand)
+ (if (plist-member (cdr cand) :preview-key)
+ (plist-get (cdr cand) :preview-key)
+ consult-preview-key))
+ :keys
+ (delete-dups
+ (seq-filter (lambda (k) (or (eq k 'any) (stringp k)))
+ (seq-mapcat (lambda (src)
+ (ensure-list
+ (if (plist-member src :preview-key)
+ (plist-get src :preview-key)
+ consult-preview-key)))
+ sources)))))
+
+(defun consult--multi-lookup (sources selected candidates _input narrow &rest _)
+ "Lookup SELECTED in CANDIDATES given SOURCES, with potential NARROW."
+ (if (or (string-blank-p selected)
+ (not (consult--tofu-p (aref selected (1- (length selected))))))
+ ;; Non-existing candidate without Tofu or default submitted (empty string)
+ (let* ((src (cond
+ (narrow (seq-find (lambda (src)
+ (let ((n (plist-get src :narrow)))
+ (eq (or (car-safe n) n -1) narrow)))
+ sources))
+ ((seq-find (lambda (src) (plist-get src :default)) sources))
+ ((seq-find (lambda (src) (not (plist-get src :hidden))) sources))
+ ((aref sources 0))))
+ (idx (seq-position sources src))
+ (def (and (string-blank-p selected) ;; default candidate
+ (seq-find (lambda (cand) (eq idx (consult--tofu-get cand))) candidates))))
+ (if def
+ (cons (cdr (get-text-property 0 'multi-category def)) src)
+ `(,selected :match nil ,@src)))
+ (if-let* ((found (member selected candidates)))
+ ;; Existing candidate submitted
+ (cons (cdr (get-text-property 0 'multi-category (car found)))
+ (consult--multi-source sources selected))
+ ;; Non-existing Tofu'ed candidate submitted, e.g., via Embark
+ `(,(substring selected 0 -1) :match nil ,@(consult--multi-source sources selected)))))
+
+(defun consult--multi-items (idx src items)
+ "Create completion candidate strings from ITEMS.
+Attach source IDX and SRC properties to each item."
+ (unless (listp items)
+ (setq items (plist-get src :items)
+ items (if (functionp items) (funcall items) items)))
+ (let ((face (plist-get src :face))
+ (cat (or (plist-get src :category) 'general)))
+ (cl-loop
+ for item in items collect
+ (let* ((str (or (car-safe item) item))
+ (len (length str))
+ (cand (consult--tofu-append str idx)))
+ ;; Preserve existing `multi-category' datum of the candidate.
+ (unless (and (eq str item) (get-text-property 0 'multi-category str))
+ (put-text-property 0 len 'multi-category (cons cat (or (cdr-safe item) item)) cand))
+ (when face
+ (add-face-text-property 0 len face t cand))
+ cand))))
+
+(defun consult--multi-async (sources)
+ "Create async function from multi SOURCES."
+ (consult--async-merge
+ (cl-loop
+ for idx from 0 for src across sources collect
+ (let ((idx idx) (src src))
+ (consult--async-pipeline
+ (consult--async-predicate (apply-partially #'consult--multi-visible-p src))
+ (if-let* ((async (plist-get src :async)))
+ (consult--async-pipeline
+ async
+ (consult--async-transform
+ (apply-partially #'consult--multi-items idx src)))
+ (consult--async-static (consult--multi-items idx src t))))))))
+
+(defun consult--multi-enabled-sources (sources)
+ "Return vector of enabled SOURCES."
+ (vconcat
+ (cl-loop
+ for src in sources
+ if (when (setq src (if (symbolp src) (symbol-value src) src))
+ (unless (xor (plist-member src :async) (plist-member src :items))
+ (error "Source must specify either :items or :async"))
+ (funcall (or (plist-get src :enabled) #'always)))
+ collect src)))
+
+(defun consult--multi-state (sources)
+ "State function given SOURCES."
+ (when-let* ((states (delq nil (mapcar (lambda (src)
+ (when-let* ((fun (plist-get src :state)))
+ (cons src (funcall fun))))
+ sources))))
+ (let (last-fun)
+ (pcase-lambda (action `(,cand . ,src))
+ (pcase action
+ ('setup
+ (pcase-dolist (`(,_ . ,fun) states)
+ (funcall fun 'setup nil)))
+ ('exit
+ (pcase-dolist (`(,_ . ,fun) states)
+ (funcall fun 'exit nil)))
+ ('preview
+ (let ((selected-fun (cdr (assq src states))))
+ ;; If the candidate source changed during preview communicate to
+ ;; the last source, that none of its candidates is previewed anymore.
+ (when (and last-fun (not (eq last-fun selected-fun)))
+ (funcall last-fun 'preview nil))
+ (setq last-fun selected-fun)
+ (when selected-fun
+ (funcall selected-fun 'preview cand))))
+ ('return
+ (let ((selected-fun (cdr (assq src states))))
+ ;; Finish all the sources, except the selected one.
+ (pcase-dolist (`(,_ . ,fun) states)
+ (unless (eq fun selected-fun)
+ (funcall fun 'return nil)))
+ ;; Finish the source with the selected candidate
+ (when selected-fun
+ (funcall selected-fun 'return cand)))))))))
+
+(defun consult--multi-collection (sources)
+ "Static or asynchronous completion function from SOURCES."
+ (consult--with-increased-gc
+ (if (cl-loop for src across sources thereis (plist-get src :async))
+ (consult--multi-async sources)
+ (cl-loop for idx from 0 for src across sources nconc
+ (consult--multi-items idx src t)))))
+
+(defun consult--multi (sources &rest options)
+ "Select from candidates taken from a list of SOURCES.
+
+OPTIONS is the plist of options passed to `consult--read'. The following
+options are supported: :require-match, :history, :keymap, :initial,
+:initial-narrow, :add-history, :sort and :inherit-input-method. The other
+options of `consult--read' are used by the `consult--multi' implementation
+and should not be overwritten, except in in special scenarios.
+
+The function returns the selected candidate in the form (cons candidate
+source-plist). The plist has the key :match with a value nil if the
+candidate does not exist, t if the candidate exists and `new' if the
+candidate has been created.
+
+The sources of the source list can either be symbols of source variables
+or source values. Sources which are nil are ignored. Source values
+must be plists with the following fields.
+
+Either the :items or the :async source field is required:
+* :items - List of strings to select from or function returning list of
+ strings. The strings can carry metadata in text properties, which is
+ then available to the :annotate, :action and :state functions. The
+ list can also consist of pairs, with the string in the `car' used for
+ display and the `cdr' the actual candidate.
+* :async - Alternative to :items for asynchronous sources. The function
+ receives an asynchronous sink and an action as argument as documented
+ by `consult--async-pipeline'.
+
+Optional source fields:
+* :name - Name of the source as a string, used for narrowing,
+ group titles and annotations.
+* :narrow - Narrowing character, (char . string) pair or list of pairs.
+* :category - Completion category symbol.
+* :enabled - Function which must return t if the source is enabled.
+* :hidden - When t candidates of this source are hidden by default.
+* :face - Face used for highlighting the candidates.
+* :annotate - Annotation function called for each candidate, returns string.
+* :history - Name of history variable to add selected candidate.
+* :default - Must be t if the first item of the source is the default value.
+* :action - Function called with the selected candidate.
+* :new - Function called with new candidate name, only if :require-match is nil.
+* :state - State constructor for the source, must return the
+ state function. The state function is informed about state
+ changes of the UI and can be used to implement preview.
+* Other custom source fields can be added depending on the use
+ case. Note that the source is returned by `consult--multi'
+ together with the selected candidate."
+ (let* ((sources (consult--multi-enabled-sources sources))
+ (collection (consult--multi-collection sources))
+ (selected
+ (apply #'consult--read
+ collection
+ (append
+ options
+ (list
+ :category 'multi-category
+ :predicate (apply-partially #'consult--multi-predicate sources)
+ :annotate (apply-partially #'consult--multi-annotate sources)
+ :group (apply-partially #'consult--multi-group sources)
+ :lookup (apply-partially #'consult--multi-lookup sources)
+ :preview-key (consult--multi-preview-key sources)
+ :narrow (consult--multi-narrow sources)
+ :state (consult--multi-state sources))))))
+ (when-let* ((history (plist-get (cdr selected) :history)))
+ (add-to-history history (car selected)))
+ (if (plist-member (cdr selected) :match)
+ (when-let* ((fun (plist-get (cdr selected) :new)))
+ (funcall fun (car selected))
+ (plist-put (cdr selected) :match 'new))
+ (when-let* ((fun (plist-get (cdr selected) :action)))
+ (funcall fun (car selected)))
+ (setq selected `(,(car selected) :match t ,@(cdr selected))))
+ selected))
+
+;;;; Customization macro
+
+(defun consult--customize-put (cmds prop form)
+ "Set property PROP to FORM of commands CMDS."
+ (dolist (cmd cmds)
+ (cond
+ ((and (boundp cmd) (consp (symbol-value cmd)))
+ (setf (plist-get (symbol-value cmd) prop) (eval form 'lexical)))
+ ((functionp cmd)
+ (setf (plist-get (alist-get cmd consult--customize-alist) prop) form))
+ (t (warn "consult-customize: %s is neither a command nor a source" cmd))))
+ nil)
+
+(defmacro consult-customize (&rest args)
+ "Set properties of commands or sources.
+ARGS is a list of commands or sources followed by the list of
+keyword-value pairs. For `consult-customize' to succeed, the customized
+sources and commands must exist. When a command is invoked, the value
+of `:command' or `this-command' is used to lookup the corresponding
+customization options."
+ (let (setter)
+ (while args
+ (let ((cmds (seq-take-while (lambda (x) (not (keywordp x))) args)))
+ (setq args (seq-drop-while (lambda (x) (not (keywordp x))) args))
+ (while (keywordp (car args))
+ (push `(consult--customize-put ',cmds ,(car args) ',(cadr args)) setter)
+ (setq args (cddr args)))))
+ (macroexp-progn setter)))
+
+(defun consult--customize-args (options &rest defaults)
+ "Get configuration from `consult--customize-alist' for the current command.
+OPTIONS is the option plist, and DEFAULTS are default options which are
+overridden by OPTIONS."
+ (append
+ (mapcar (lambda (x) (eval x 'lexical))
+ (alist-get (or (plist-get options :command) this-command)
+ consult--customize-alist))
+ (consult--plist-remove '(:command) options)
+ defaults))
+
+;;;; Commands
+
+;;;;; Command: consult-completion-in-region
+
+(defun consult--insertion-preview (start end)
+ "State function for previewing a candidate in a specific region.
+The candidates are previewed in the region from START to END. This function is
+used as the `:state' argument for `consult--read' in the `consult-yank' family
+of functions and in `consult-completion-in-region'."
+ (unless (or (minibufferp)
+ ;; Disable preview if anything odd is going on with the markers.
+ ;; Otherwise we get "Marker points into wrong buffer errors". See
+ ;; gh:minad/consult#375, where Org mode source blocks are
+ ;; completed in a different buffer than the original buffer. This
+ ;; completion is probably also problematic in my Corfu completion
+ ;; package.
+ (not (eq (window-buffer) (current-buffer)))
+ (and (markerp start) (not (eq (marker-buffer start) (current-buffer))))
+ (and (markerp end) (not (eq (marker-buffer end) (current-buffer)))))
+ (let (ov)
+ (lambda (action cand)
+ (cond
+ ((and (not cand) ov)
+ (delete-overlay ov)
+ (setq ov nil))
+ ((and (eq action 'preview) cand)
+ (unless ov
+ (setq ov (consult--make-overlay start end
+ 'invisible t
+ 'window (selected-window))))
+ ;; Use `add-face-text-property' on a copy of "cand in order to merge face properties
+ (setq cand (copy-sequence cand))
+ (add-face-text-property 0 (length cand) 'consult-preview-insertion t cand)
+ ;; Use the `before-string' property since the overlay might be empty.
+ (overlay-put ov 'before-string cand)))))))
+
+(defun consult--in-region (start end table predicate)
+ "Internal `completion-in-region-function'.
+The arguments START, END, TABLE and PREDICATE and
+expected return value are as specified for `completion-in-region'."
+ (barf-if-buffer-read-only)
+ (let* ((initial (buffer-substring-no-properties start end))
+ (metadata (completion-metadata initial table predicate))
+ ;; bug#75910: category instead of `minibuffer-completing-file-name'
+ (minibuffer-completing-file-name
+ (eq 'file (completion-metadata-get metadata 'category)))
+ (threshold (completion--cycle-threshold metadata))
+ (all (completion-all-completions initial table predicate
+ (if (<= start (point) end)
+ (- (point) start)
+ (length initial))
+ metadata)))
+ ;; Normalize improper list
+ (when-let* ((last (last all)))
+ (setcdr last nil))
+ (if (or (eq threshold t) (length< all (1+ (or threshold 1)))
+ (and completion-cycling completion-all-sorted-completions))
+ (let (completion-auto-help)
+ (completion--in-region start end table predicate))
+ ;; Wrap all annotation functions to ensure that they are executed
+ ;; in the original buffer.
+ (let* ((exit-fun (plist-get completion-extra-properties :exit-function))
+ (ann-fun (plist-get completion-extra-properties :annotation-function))
+ (aff-fun (plist-get completion-extra-properties :affixation-function))
+ (docsig-fun (plist-get completion-extra-properties :company-docsig))
+ (completion-extra-properties
+ `(,@(and ann-fun (list :annotation-function (consult--in-buffer ann-fun)))
+ ,@(and aff-fun (list :affixation-function (consult--in-buffer aff-fun)))
+ ;; Provide `:annotation-function' if `:company-docsig' is specified.
+ ,@(and docsig-fun (not ann-fun) (not aff-fun)
+ (list :annotation-function
+ (consult--in-buffer
+ (lambda (cand)
+ (concat (propertize " " 'display '(space :align-to center))
+ (funcall docsig-fun cand))))))))
+ (completion
+ (consult--local-let ((enable-recursive-minibuffers t))
+ ;; Evaluate completion table in the original buffer.
+ ;; This is a reasonable thing to do and required by
+ ;; some completion tables in particular by lsp-mode.
+ ;; See gh:minad/vertico#61.
+ (consult--read
+ (consult--completion-table-in-buffer table)
+ :command #'consult-completion-in-region
+ :prompt (if (minibufferp)
+ ;; Use existing minibuffer prompt and input
+ (let ((prompt (buffer-substring (point-min) start)))
+ (put-text-property
+ (max 0 (1- (minibuffer-prompt-end))) (length prompt)
+ 'face 'shadow prompt)
+ prompt)
+ "Complete: ")
+ :state (consult--insertion-preview start end)
+ :predicate predicate
+ :initial initial))))
+ (completion--replace start end completion)
+ (when exit-fun
+ (funcall exit-fun completion
+ ;; If completion is finished and cannot be further
+ ;; completed, return `finished'. Otherwise return
+ ;; `exact'.
+ (if (eq (try-completion completion table predicate) t)
+ 'finished 'exact)))
+ t))))
+
+;;;###autoload
+(defun consult-completion-in-region (start end table predicate)
+ "Use minibuffer completion as the UI for `completion-at-point'.
+
+The arguments START, END, TABLE and PREDICATE and expected return value
+are as specified for `completion-in-region'. Use this function as a
+value for `completion-in-region-function'."
+ (if (and (or (bound-and-true-p vertico-mode) (bound-and-true-p icomplete-mode))
+ (not (eq table minibuffer-completion-table)))
+ (consult--in-region start end table predicate)
+ (completion--in-region start end table predicate)))
+
+;;;;; Command: consult-outline
+
+(defun consult--outline-candidates ()
+ "Return list of outline heading strings with position attached."
+ (consult--forbid-minibuffer)
+ (let* ((line (line-number-at-pos (point-min) consult-line-numbers-widen))
+ (heading-regexp (concat "^\\(?:"
+ ;; default definition from outline.el
+ (or (bound-and-true-p outline-regexp) "[*\^L]+")
+ "\\)"))
+ (heading-alist (bound-and-true-p outline-heading-alist))
+ (level-fun (or (bound-and-true-p outline-level)
+ (lambda () ;; as in the default from outline.el
+ (or (cdr (assoc (match-string 0) heading-alist))
+ (- (match-end 0) (match-beginning 0))))))
+ (buffer (current-buffer))
+ candidates)
+ (save-excursion
+ (goto-char (point-min))
+ (while (save-excursion
+ (if-let* ((fun (bound-and-true-p outline-search-function)))
+ (funcall fun)
+ (re-search-forward heading-regexp nil t)))
+ (incf line (consult--count-lines (match-beginning 0)))
+ (push (consult--location-candidate
+ (buffer-substring-no-properties (pos-bol) (pos-eol))
+ (cons buffer (point)) (1- line) (1- line)
+ 'consult--outline-level (funcall level-fun))
+ candidates)
+ (goto-char (1+ (pos-eol)))))
+ (unless candidates
+ (user-error "No headings"))
+ (nreverse candidates)))
+
+;;;###autoload
+(defun consult-outline (&optional level)
+ "Jump to an outline heading, obtained by matching against `outline-regexp'.
+
+This command supports narrowing to a heading level and candidate
+preview. The initial narrowing LEVEL can be given as prefix
+argument. The symbol at point is added to the future history."
+ (interactive
+ (list (and current-prefix-arg (prefix-numeric-value current-prefix-arg))))
+ (let* ((candidates (consult--slow-operation
+ "Collecting headings..."
+ (consult--outline-candidates)))
+ (min-level (- (cl-loop for cand in candidates minimize
+ (get-text-property 0 'consult--outline-level cand))
+ ?1))
+ (narrow-pred (lambda (cand)
+ (<= (get-text-property 0 'consult--outline-level cand)
+ (+ consult--narrow min-level))))
+ (narrow-keys (mapcar (lambda (c) (cons c (format "Level %c" c)))
+ (number-sequence ?1 ?9)))
+ (narrow-init (and level (max ?1 (min ?9 (+ level ?0))))))
+ (consult--read
+ candidates
+ :prompt "Go to heading: "
+ :annotate (consult--line-fontify)
+ :category 'consult-location
+ :sort nil
+ :require-match t
+ :lookup #'consult--line-match
+ :initial-narrow narrow-init
+ :narrow (list :predicate narrow-pred :keys narrow-keys)
+ :history '(:input consult--line-history)
+ :add-history (thing-at-point 'symbol)
+ :state (consult--location-state candidates))))
+
+;;;;; Command: consult-mark
+
+(defun consult--mark-candidates (markers)
+ "Return list of candidates strings for MARKERS."
+ (consult--forbid-minibuffer)
+ (let* ((candidates)
+ (width (length (number-to-string (line-number-at-pos
+ (point-max)
+ consult-line-numbers-widen))))
+ (fmt (format #("%%%dd %%s%%s" 0 6 (face consult-line-number-prefix)) width)))
+ (save-excursion
+ (dolist (marker markers)
+ (when-let* ((pos (marker-position marker))
+ ((and (eq (marker-buffer marker) (current-buffer))
+ (consult--in-range-p pos))))
+ (goto-char pos)
+ ;; `line-number-at-pos' is a very slow function, which should be
+ ;; replaced everywhere. However in this case the slow
+ ;; line-number-at-pos does not hurt much, since the mark ring is
+ ;; usually small since it is limited by `mark-ring-max'.
+ (let* ((line (line-number-at-pos pos consult-line-numbers-widen))
+ (cand (format fmt line (consult--line-with-mark marker) (consult--tofu-encode marker))))
+ (put-text-property 0 width 'consult-strip t cand)
+ (put-text-property 0 (length cand) 'consult-location (cons marker line) cand)
+ (push cand candidates)))))
+ (unless candidates
+ (user-error "No marks"))
+ (nreverse (delete-dups candidates))))
+
+;;;###autoload
+(defun consult-mark (&optional markers)
+ "Jump to a marker in MARKERS list (defaults to buffer-local `mark-ring').
+
+The command supports preview of the currently selected marker position.
+The symbol at point is added to the future history."
+ (interactive)
+ (consult--read
+ (consult--mark-candidates
+ (or markers (cons (mark-marker) mark-ring)))
+ :prompt "Go to mark: "
+ :category 'consult-location
+ :sort nil
+ :require-match t
+ :lookup #'consult--lookup-location
+ :history '(:input consult--line-history)
+ :add-history (thing-at-point 'symbol)
+ :state (consult--jump-state)))
+
+;;;;; Command: consult-global-mark
+
+(defun consult--global-mark-candidates (markers)
+ "Return list of candidates strings for MARKERS."
+ (consult--forbid-minibuffer)
+ (let ((candidates))
+ (save-excursion
+ (dolist (marker markers)
+ (when-let* ((pos (marker-position marker))
+ (buf (marker-buffer marker))
+ ((not (minibufferp buf))))
+ (with-current-buffer buf
+ (when (consult--in-range-p pos)
+ (goto-char pos)
+ ;; `line-number-at-pos' is slow, see comment in `consult--mark-candidates'.
+ (let* ((line (line-number-at-pos pos consult-line-numbers-widen))
+ (prefix (consult--format-file-line-match (buffer-name buf) line ""))
+ (cand (concat prefix (consult--line-with-mark marker) (consult--tofu-encode marker))))
+ (put-text-property 0 (length prefix) 'consult-strip t cand)
+ (put-text-property 0 (length cand) 'consult-location (cons marker line) cand)
+ (push cand candidates)))))))
+ (unless candidates
+ (user-error "No global marks"))
+ (nreverse (delete-dups candidates))))
+
+;;;###autoload
+(defun consult-global-mark (&optional markers)
+ "Jump to a marker in MARKERS list (defaults to `global-mark-ring').
+
+The command supports preview of the currently selected marker position.
+The symbol at point is added to the future history."
+ (interactive)
+ (consult--read
+ (consult--global-mark-candidates
+ (or markers global-mark-ring))
+ :prompt "Go to global mark: "
+ ;; Despite `consult-global-mark' formatting the candidates in grep-like
+ ;; style, we are not using the `consult-grep' category, since the candidates
+ ;; have location markers attached.
+ :category 'consult-location
+ :sort nil
+ :require-match t
+ :lookup #'consult--lookup-location
+ :history '(:input consult--line-history)
+ :add-history (thing-at-point 'symbol)
+ :state (consult--jump-state)))
+
+;;;;; Command: consult-line
+
+(defun consult--line-candidates (top curr-line)
+ "Return list of line candidates.
+Start from top if TOP non-nil.
+CURR-LINE is the current line number."
+ (consult--forbid-minibuffer)
+ (let* ((buffer (current-buffer))
+ (line (line-number-at-pos (point-min) consult-line-numbers-widen))
+ default-cand candidates)
+ (consult--each-line beg end
+ (unless (looking-at-p "^\\s-*$")
+ (push (consult--location-candidate
+ (buffer-substring-no-properties beg end)
+ (cons buffer beg) line line)
+ candidates)
+ (when (and (not default-cand) (>= line curr-line))
+ (setq default-cand candidates)))
+ (incf line))
+ (unless candidates
+ (user-error "No lines"))
+ (nreverse
+ (if (or top (not default-cand))
+ candidates
+ (let ((before (cdr default-cand)))
+ (setcdr default-cand nil)
+ (nconc before candidates))))))
+
+(defun consult--line-point-placement (selected candidates highlighted &rest ignored-faces)
+ "Find point position on matching line.
+SELECTED is the currently selected candidate.
+CANDIDATES is the list of candidates.
+HIGHLIGHTED is the highlighted string to determine the match position.
+IGNORED-FACES are ignored when determining the match position."
+ (when-let* ((pos (consult--lookup-location selected candidates)))
+ (if highlighted
+ (let* ((matches (apply #'consult--point-placement highlighted 0 ignored-faces))
+ (dest (+ pos (car matches))))
+ ;; Only create a new marker when jumping across buffers (for example
+ ;; `consult-line-multi'). Avoid creating unnecessary markers, when
+ ;; scrolling through candidates, since creating markers is not free.
+ (when (and (markerp pos) (not (eq (marker-buffer pos) (current-buffer))))
+ (setq dest (move-marker (make-marker) dest (marker-buffer pos))))
+ (cons dest (cdr matches)))
+ pos)))
+
+(defun consult--line-match (selected candidates input &rest _)
+ "Lookup position of match.
+SELECTED is the currently selected candidate.
+CANDIDATES is the list of candidates.
+INPUT is the input string entered by the user."
+ (consult--line-point-placement selected candidates
+ (and (not (string-blank-p input))
+ (car (consult--completion-filter
+ input
+ (list (substring-no-properties selected))
+ 'consult-location 'highlight)))
+ 'completions-first-difference))
+
+;;;###autoload
+(defun consult-line (&optional initial start)
+ "Search for a matching line.
+
+Depending on the setting `consult-point-placement' the command
+jumps to the beginning or the end of the first match on the line
+or the line beginning. The default candidate is the non-empty
+line next to point. This command obeys narrowing. Optional
+INITIAL input can be provided. The search starting point is
+changed if the START prefix argument is set. The symbol at point
+and the last `isearch-string' is added to the future history."
+ (interactive (list nil (not (not current-prefix-arg))))
+ (let* ((curr-line (line-number-at-pos (point) consult-line-numbers-widen))
+ (top (not (eq start consult-line-start-from-top)))
+ (candidates (consult--slow-operation "Collecting lines..."
+ (consult--line-candidates top curr-line))))
+ (consult--read
+ candidates
+ :prompt (if top "Go to line from top: " "Go to line: ")
+ :annotate (consult--line-fontify curr-line)
+ :category 'consult-location
+ :sort nil
+ :require-match t
+ ;; Always add last `isearch-string' to future history
+ :add-history (list (thing-at-point 'symbol) isearch-string)
+ :history '(:input consult--line-history)
+ :lookup #'consult--line-match
+ :default (car candidates)
+ ;; Add `isearch-string' as initial input if starting from Isearch
+ :initial (or initial
+ (and isearch-mode
+ (prog1 isearch-string (isearch-done))))
+ :state (consult--location-state candidates))))
+
+;;;;; Command: consult-line-multi
+
+(defun consult--line-multi-match (selected candidates &rest _)
+ "Lookup position of match.
+SELECTED is the currently selected candidate.
+CANDIDATES is the list of candidates."
+ (consult--line-point-placement selected candidates
+ (car (member selected candidates))))
+
+(defun consult--line-multi-group (cand transform)
+ "Group function used by `consult-line-multi'.
+If TRANSFORM non-nil, return transformed CAND, otherwise return title."
+ (if transform cand
+ (let* ((marker (car (get-text-property 0 'consult-location cand)))
+ (buf (if (consp marker)
+ (car marker) ;; Handle cheap marker
+ (marker-buffer marker))))
+ (if buf (buffer-name buf) "Dead buffer"))))
+
+(defun consult--line-multi-candidates (buffers input callback)
+ "Collect matching candidates from multiple buffers.
+INPUT is the user input which should be matched.
+BUFFERS is the list of buffers.
+CALLBACK receives the candidates."
+ (pcase-let ((`(,regexps . ,hl) (consult--compile-regexp input 'emacs completion-ignore-case))
+ (candidates nil)
+ (cand-idx 0))
+ (when regexps
+ (dolist (buf buffers)
+ (with-current-buffer buf
+ (save-excursion
+ (let ((line (line-number-at-pos (point-min) consult-line-numbers-widen)))
+ (goto-char (point-min))
+ (while (and (not (eobp))
+ (save-excursion (re-search-forward (car regexps) nil t)))
+ (incf line (consult--count-lines (match-beginning 0)))
+ (let ((bol (pos-bol))
+ (eol (pos-eol)))
+ (goto-char bol)
+ (when (and (not (looking-at-p "^\\s-*$"))
+ (cl-loop for r in (cdr regexps) always
+ (progn
+ (goto-char bol)
+ (re-search-forward r eol t))))
+ (push (consult--location-candidate
+ (funcall hl (buffer-substring-no-properties bol eol))
+ (cons buf bol) (1- line) cand-idx)
+ candidates)
+ (incf cand-idx))
+ (goto-char (1+ eol)))))))
+ (funcall callback (nreverse candidates))
+ (setq candidates nil)))))
+
+;;;###autoload
+(defun consult-line-multi (query &optional initial)
+ "Search for a matching line in multiple buffers.
+
+By default search across all project buffers. If the prefix
+argument QUERY is non-nil, all buffers are searched. Optional
+INITIAL input can be provided. The symbol at point and the last
+`isearch-string' is added to the future history. In order to
+search a subset of buffers, QUERY can be set to a plist according
+to `consult--buffer-query'."
+ (interactive "P")
+ (unless (keywordp (car-safe query))
+ (setq query (list :sort 'alpha-current :directory (and (not query) 'project))))
+ (pcase-let* ((`(,prompt . ,buffers) (consult--buffer-query-prompt "Go to line" query))
+ (collection (consult--dynamic-collection
+ (apply-partially #'consult--line-multi-candidates
+ buffers))))
+ (consult--read
+ collection
+ :prompt prompt
+ :annotate (consult--line-fontify)
+ :category 'consult-location
+ :sort nil
+ :require-match t
+ ;; Always add last Isearch string to future history
+ :add-history (delq nil (list (thing-at-point 'symbol) isearch-string))
+ :history '(:input consult--line-multi-history)
+ :lookup #'consult--line-multi-match
+ ;; Add `isearch-string' as initial input if starting from Isearch
+ :initial (or initial
+ (and isearch-mode
+ (prog1 isearch-string (isearch-done))))
+ :state (consult--location-state (lambda () (funcall collection nil)))
+ :group #'consult--line-multi-group)))
+
+;;;;; Command: consult-keep-lines
+
+(defun consult--keep-lines-state (filter)
+ "State function for `consult-keep-lines' with FILTER function."
+ (let ((font-lock-orig font-lock-mode)
+ (whitespace-orig (bound-and-true-p whitespace-mode))
+ (hl-line-orig (bound-and-true-p hl-line-mode))
+ (point-orig (point))
+ lines content-orig replace last-input)
+ (if (use-region-p)
+ (save-restriction
+ ;; Use the same behavior as `keep-lines'.
+ (let ((rbeg (region-beginning))
+ (rend (save-excursion
+ (goto-char (region-end))
+ (unless (or (bolp) (eobp))
+ (forward-line 0))
+ (point))))
+ (consult--fontify-region rbeg rend)
+ (narrow-to-region rbeg rend)
+ (consult--each-line beg end
+ (push (consult--buffer-substring beg end) lines))
+ (setq content-orig (buffer-string)
+ replace (lambda (content &optional pos)
+ (delete-region rbeg rend)
+ (insert-before-markers content)
+ (goto-char (or pos rbeg))
+ (setq rend (+ rbeg (length content)))
+ (add-face-text-property rbeg rend 'region t)))))
+ ;; Font-locking is lazy, i.e., if a line has not been looked at yet, the
+ ;; line is not font-locked. Therefore we have to enforce slow font-locking
+ ;; now. In order to prevent is hang-up we check the region size against
+ ;; `consult-fontify-max-size'.
+ (when (< (- (point-max) (point-min)) consult-fontify-max-size)
+ (consult--fontify-region (point-min) (point-max)))
+ (setq content-orig (buffer-string)
+ replace (lambda (content &optional pos)
+ (delete-region (point-min) (point-max))
+ (insert content)
+ (goto-char (or pos (point-min)))))
+ (consult--each-line beg end
+ (push (consult--buffer-substring beg end) lines)))
+ (setq lines (nreverse lines))
+ (lambda (action input)
+ ;; Restoring content and point position
+ (when (and (eq action 'return) last-input)
+ ;; No undo recording, modification hooks, buffer modified-status
+ (with-silent-modifications (funcall replace content-orig point-orig)))
+ ;; Committing or new input provided -> Update
+ (when (and input ;; Input has been provided
+ (or
+ ;; Committing, but not with empty input
+ (and (eq action 'return) (not (string-match-p "\\`!? ?\\'" input)))
+ ;; Input has changed
+ (not (equal input last-input))))
+ (let ((filtered-content
+ (if (string-match-p "\\`!? ?\\'" input)
+ ;; Special case the empty input for performance.
+ ;; Otherwise it could happen that the minibuffer is empty,
+ ;; but the buffer has not been updated.
+ content-orig
+ (if (eq action 'return)
+ (apply #'concat (mapcan (lambda (x) (list x "\n"))
+ (funcall filter input lines)))
+ (while-no-input
+ ;; Heavy computation is interruptible if *not* committing!
+ ;; Allocate new string candidates since the matching function mutates!
+ (apply #'concat (mapcan (lambda (x) (list x "\n"))
+ (funcall filter input (mapcar #'copy-sequence lines)))))))))
+ (when (stringp filtered-content)
+ (when font-lock-mode (font-lock-mode -1))
+ (when (bound-and-true-p whitespace-mode) (whitespace-mode -1))
+ (when (bound-and-true-p hl-line-mode) (hl-line-mode -1))
+ (if (eq action 'return)
+ (atomic-change-group
+ ;; Disable modification hooks for performance
+ (let ((inhibit-modification-hooks t))
+ (funcall replace filtered-content)))
+ ;; No undo recording, modification hooks, buffer modified-status
+ (with-silent-modifications
+ (funcall replace filtered-content)
+ (setq last-input input))))))
+ ;; Restore modes
+ (when (eq action 'return)
+ (when hl-line-orig (hl-line-mode 1))
+ (when whitespace-orig (whitespace-mode 1))
+ (when font-lock-orig (font-lock-mode 1))))))
+
+;;;###autoload
+(defun consult-keep-lines (filter &optional initial)
+ "Filter a subset of the lines in the current buffer with live preview.
+
+The filtered lines are kept and the other lines are deleted. When
+called interactively, the lines selected are those that match the
+minibuffer input. In order to match the inverse of the input, prefix
+the input with `! '. When called from Elisp, the filtering is performed
+by a FILTER function. If the buffer is narrowed to a region, the
+command only acts on this region. See also `consult-focus-lines' which
+uses overlays to display only matching lines, but does not modify the
+buffer.
+
+FILTER is the filter function, called for each line.
+INITIAL is the initial input."
+ (interactive
+ (list (lambda (pattern cands)
+ ;; Use consult-location completion category when filtering lines
+ (consult--completion-filter-dispatch
+ pattern cands 'consult-location 'highlight))))
+ (consult--forbid-minibuffer)
+ (let ((ro buffer-read-only))
+ (unwind-protect
+ (minibuffer-with-setup-hook
+ (lambda ()
+ (when ro
+ (consult--minibuffer-message
+ (substitute-command-keys
+ " [Unlocked read-only buffer. \\[minibuffer-keyboard-quit] to quit.]"))))
+ (setq buffer-read-only nil)
+ (consult--with-increased-gc
+ (consult--prompt
+ :prompt "Keep lines: "
+ :initial initial
+ :history 'consult--line-history
+ :state (consult--keep-lines-state filter))))
+ (setq buffer-read-only ro))))
+
+;;;;; Command: consult-focus-lines
+
+(defun consult--focus-lines-state (filter)
+ "State function for `consult-focus-lines' with FILTER function."
+ (let (lines overlays last-input pt-orig pt-min pt-max)
+ (save-excursion
+ (save-restriction
+ (when (use-region-p)
+ (narrow-to-region
+ (region-beginning)
+ ;; Behave the same as `keep-lines'.
+ ;; Move to the next line.
+ (save-excursion
+ (goto-char (region-end))
+ (unless (or (bolp) (eobp))
+ (forward-line 0))
+ (point))))
+ (setq pt-orig (point) pt-min (point-min) pt-max (point-max))
+ (let ((i 0))
+ (consult--each-line beg end
+ ;; Use "\n" for empty lines, since we need a non-empty string to
+ ;; attach the text property to.
+ (let ((line (if (eq beg end) (char-to-string ?\n)
+ (buffer-substring-no-properties beg end))))
+ (put-text-property 0 1 'consult--focus-line (cons (incf i) beg) line)
+ (push line lines)))
+ (setq lines (nreverse lines)))))
+ (lambda (action input)
+ ;; New input provided -> Update
+ (when (and input (not (equal input last-input)))
+ (let (new-overlays)
+ (pcase (while-no-input
+ (unless (string-match-p "\\`!? ?\\'" input) ;; Empty input.
+ (let* ((inhibit-quit (eq action 'return)) ;; Non interruptible, when quitting!
+ (not (string-prefix-p "! " input))
+ (stripped (string-remove-prefix "! " input))
+ (matches (funcall filter stripped lines))
+ (old-ind 0)
+ (block-beg pt-min)
+ (block-end pt-min))
+ (while old-ind
+ (let ((match (pop matches)) (ind nil) (beg pt-max) (end pt-max) prop)
+ (when match
+ (setq prop (get-text-property 0 'consult--focus-line match)
+ ind (car prop)
+ beg (cdr prop)
+ ;; Check for empty lines, see above.
+ end (+ 1 beg (if (equal match "\n") 0 (length match)))))
+ (unless (eq ind (1+ old-ind))
+ (let ((a (if not block-beg block-end))
+ (b (if not block-end beg)))
+ (when (/= a b)
+ (push (consult--make-overlay a b 'invisible t) new-overlays)))
+ (setq block-beg beg))
+ (setq block-end end old-ind ind)))))
+ 'commit)
+ ('commit
+ (mapc #'delete-overlay overlays)
+ (setq last-input input overlays new-overlays))
+ (_ (mapc #'delete-overlay new-overlays)))))
+ (when (eq action 'return)
+ (cond
+ ((not input)
+ (mapc #'delete-overlay overlays)
+ (goto-char pt-orig))
+ ((equal input "")
+ (consult-focus-lines nil 'show)
+ (goto-char pt-orig))
+ (t
+ ;; Successfully terminated -> Remember invisible overlays
+ (cl-callf nconc consult--focus-lines-overlays overlays)
+ ;; move point past invisible
+ (goto-char (if-let* ((ov (and (invisible-p pt-orig)
+ (seq-find (lambda (ov) (overlay-get ov 'invisible))
+ (overlays-at pt-orig)))))
+ (overlay-end ov)
+ pt-orig))))))))
+
+;;;###autoload
+(defun consult-focus-lines (filter &optional show initial)
+ "Show only matching lines using overlays.
+
+In contrast to `consult-keep-lines' the buffer is not modified. The
+FILTER selects the lines which are shown. When called interactively,
+the lines selected are those that match the minibuffer input. In order
+to match the inverse of the input, prefix the input with `! '. With
+optional prefix argument SHOW reveal the hidden lines. Alternatively
+rerun the command and exit the minibuffer directly without input to
+reveal the lines. When called from Elisp, the filtering is performed by
+a FILTER function. If the buffer is narrowed to a region, the command
+only acts on this region.
+
+FILTER is the filter function, called for each line.
+SHOW is the prefix argument, if non-nil reveal all hidden lines.
+INITIAL is the initial input."
+ (interactive
+ (list (lambda (pattern cands)
+ ;; Use consult-location completion category when filtering lines
+ (consult--completion-filter-dispatch
+ pattern cands 'consult-location nil))
+ current-prefix-arg))
+ (if show
+ (progn
+ (mapc #'delete-overlay consult--focus-lines-overlays)
+ (setq consult--focus-lines-overlays nil)
+ (message "All lines revealed"))
+ (consult--forbid-minibuffer)
+ (consult--with-increased-gc
+ (consult--prompt
+ :prompt
+ (if consult--focus-lines-overlays
+ "Focus on lines (RET to reveal): "
+ "Focus on lines: ")
+ :initial initial
+ :history 'consult--line-history
+ :state (consult--focus-lines-state filter))))
+ (cl-callf2 assq-delete-all 'consult--focus-lines-overlays mode-line-misc-info)
+ (when (and consult--focus-lines-overlays consult--focus-lines-indicator)
+ (push `(consult--focus-lines-overlays ,consult--focus-lines-indicator)
+ mode-line-misc-info)))
+
+;;;;; Command: consult-goto-line
+
+(defun consult--goto-line-position (str msg)
+ "Transform input STR to line number.
+Print an error message with MSG function."
+ (save-match-data
+ (if (and str (string-match "\\`\\([[:digit:]]+\\):?\\([[:digit:]]*\\)\\'" str))
+ (let ((line (string-to-number (match-string 1 str)))
+ (col (string-to-number (match-string 2 str))))
+ (save-excursion
+ (save-restriction
+ (when consult-line-numbers-widen
+ (widen))
+ (goto-char (point-min))
+ (forward-line (1- line))
+ (goto-char (min (+ (point) col) (pos-eol)))
+ (point))))
+ (when (and str (not (equal str "")))
+ (funcall msg "Please enter a number."))
+ nil)))
+
+;;;###autoload
+(defun consult-goto-line (&optional arg)
+ "Read line number and jump to the line with preview.
+
+Enter either a line number to jump to the first column of the
+given line or line:column in order to jump to a specific column.
+Jump directly if a line number is given as prefix ARG. The
+command respects narrowing and the settings
+`consult-goto-line-numbers' and `consult-line-numbers-widen'."
+ (interactive "P")
+ (if arg
+ (call-interactively #'goto-line)
+ (consult--forbid-minibuffer)
+ (consult--local-let ((display-line-numbers consult-goto-line-numbers)
+ (display-line-numbers-widen consult-line-numbers-widen))
+ (while (if-let* ((pos (consult--goto-line-position
+ (consult--prompt
+ :prompt "Go to line: "
+ :history 'goto-line-history
+ :state
+ (let ((preview (consult--jump-preview)))
+ (lambda (action str)
+ (funcall preview action
+ (consult--goto-line-position str #'ignore)))))
+ #'consult--minibuffer-message)))
+ (consult--jump pos)
+ t)))))
+
+;;;;; Command: consult-recent-file
+
+(defun consult--file-preview ()
+ "Create preview function for files."
+ (let ((open (consult--temporary-files))
+ (preview (consult--buffer-preview)))
+ (lambda (action cand)
+ (unless cand
+ (funcall open))
+ (funcall preview action
+ (and cand
+ (eq action 'preview)
+ (funcall open cand))))))
+
+(defun consult--file-action (file)
+ "Open FILE via `consult--buffer-action'."
+ ;; Try to preserve the buffer as is, if it has already been opened, for
+ ;; example in literal or raw mode.
+ (setq file (abbreviate-file-name (expand-file-name file)))
+ (consult--buffer-action (or (get-file-buffer file) (find-file-noselect file))))
+
+(consult--define-state file)
+
+;;;###autoload
+(defun consult-recent-file ()
+ "Find recent file using `completing-read'."
+ (interactive)
+ (find-file
+ (consult--read
+ (or
+ (mapcar #'consult--fast-abbreviate-file-name (bound-and-true-p recentf-list))
+ (user-error "No recent files, `recentf-mode' is %s"
+ (if recentf-mode "enabled" "disabled")))
+ :prompt "Find recent file: "
+ :sort nil
+ :require-match t
+ :category 'file
+ :state (consult--file-preview)
+ :history 'file-name-history)))
+
+;;;;; Command: consult-mode-command
+
+(defun consult--mode-name (mode)
+ "Return name part of MODE."
+ (replace-regexp-in-string
+ "global-\\(.*\\)-mode" "\\1"
+ (replace-regexp-in-string
+ "\\(-global\\)?-mode\\'" ""
+ (if (eq mode 'c-mode)
+ "cc"
+ (symbol-name mode))
+ 'fixedcase)
+ 'fixedcase))
+
+(defun consult--mode-command-candidates (modes)
+ "Extract commands from MODES.
+
+The list of features is searched for files belonging to the modes.
+From these files, the commands are extracted."
+ (let* ((case-fold-search)
+ (buffer (current-buffer))
+ (command-filter (consult--regexp-filter (seq-filter #'stringp consult-mode-command-filter)))
+ (feature-filter (seq-filter #'symbolp consult-mode-command-filter))
+ (minor-hash (consult--string-hash minor-mode-list))
+ (minor-local-modes (seq-filter (lambda (m)
+ (and (gethash m minor-hash)
+ (local-variable-if-set-p m)))
+ modes))
+ (minor-global-modes (seq-filter (lambda (m)
+ (and (gethash m minor-hash)
+ (not (local-variable-if-set-p m))))
+ modes))
+ (major-modes (seq-remove (lambda (m)
+ (gethash m minor-hash))
+ modes))
+ (major-paths-hash (consult--string-hash (mapcar #'symbol-file major-modes)))
+ (minor-local-paths-hash (consult--string-hash (mapcar #'symbol-file minor-local-modes)))
+ (minor-global-paths-hash (consult--string-hash (mapcar #'symbol-file minor-global-modes)))
+ (major-name-regexp (regexp-opt (mapcar #'consult--mode-name major-modes)))
+ (minor-local-name-regexp (regexp-opt (mapcar #'consult--mode-name minor-local-modes)))
+ (minor-global-name-regexp (regexp-opt (mapcar #'consult--mode-name minor-global-modes)))
+ (commands))
+ (dolist (feature load-history commands)
+ (when-let* ((name (alist-get 'provide feature)))
+ (let* ((path (car feature))
+ (file (file-name-nondirectory path))
+ (key (cond
+ ((memq name feature-filter) nil)
+ ((or (gethash path major-paths-hash)
+ (string-match-p major-name-regexp file))
+ ?m)
+ ((or (gethash path minor-local-paths-hash)
+ (string-match-p minor-local-name-regexp file))
+ ?l)
+ ((or (gethash path minor-global-paths-hash)
+ (string-match-p minor-global-name-regexp file))
+ ?g))))
+ (when key
+ (dolist (cmd (cdr feature))
+ (let ((sym (cdr-safe cmd)))
+ (when (and (consp cmd)
+ (eq (car cmd) 'defun)
+ (commandp sym)
+ (not (get sym 'byte-obsolete-info))
+ (or (not read-extended-command-predicate)
+ (funcall read-extended-command-predicate sym buffer)))
+ (let ((name (symbol-name sym)))
+ (unless (string-match-p command-filter name)
+ (push (propertize name
+ 'consult--candidate sym
+ 'consult--type key)
+ commands))))))))))))
+
+;;;###autoload
+(defun consult-mode-command (&rest modes)
+ "Run a command from any of the given MODES.
+
+If no MODES are specified, use currently active major and minor modes."
+ (interactive)
+ (unless modes
+ (setq modes (cons major-mode
+ (seq-filter (lambda (m)
+ (and (boundp m) (symbol-value m)))
+ minor-mode-list))))
+ (let ((narrow `((?m . ,(format "Major: %s" major-mode))
+ (?l . "Local Minor")
+ (?g . "Global Minor"))))
+ (command-execute
+ (consult--read
+ (consult--mode-command-candidates modes)
+ :prompt "Mode command: "
+ :predicate
+ (lambda (cand)
+ (let ((key (get-text-property 0 'consult--type cand)))
+ (if consult--narrow
+ (= key consult--narrow)
+ (/= key ?g))))
+ :lookup #'consult--lookup-candidate
+ :group (consult--type-group narrow)
+ :narrow narrow
+ :require-match t
+ :history 'extended-command-history
+ :category 'command))))
+
+;;;;; Command: consult-yank
+
+(defun consult--read-from-kill-ring ()
+ "Open kill ring menu and return selected string."
+ ;; `current-kill' updates `kill-ring' with interprogram paste, see
+ ;; gh:minad/consult#443.
+ (current-kill 0)
+ ;; Do not specify a :lookup function in order to preserve completion-styles
+ ;; highlighting of the current candidate. We have to perform a final lookup to
+ ;; obtain the original candidate which may be propertized with yank-specific
+ ;; properties, like 'yank-handler.
+ (consult--lookup-member
+ (consult--read
+ (consult--remove-dups
+ (or (if yank-from-kill-ring-rotate
+ (append kill-ring-yank-pointer
+ (butlast kill-ring (length kill-ring-yank-pointer)))
+ kill-ring)
+ (user-error "Kill ring is empty")))
+ :prompt "Yank from kill-ring: "
+ :history t ;; disable history
+ :sort nil
+ :category 'kill-ring
+ :require-match t
+ :lookup #'consult--lookup-member
+ :state
+ (consult--insertion-preview
+ (point)
+ ;; If previous command is yank, hide previously yanked string
+ (or (and (eq last-command 'yank) (mark t)) (point))))
+ kill-ring))
+
+;; Adapted from the Emacs `yank-from-kill-ring' function.
+;;;###autoload
+(defun consult-yank-from-kill-ring (string &optional arg)
+ "Select STRING from the kill ring and insert it.
+With prefix ARG, put point at beginning, and mark at end, like `yank' does.
+
+This command behaves like `yank-from-kill-ring', which also offers a
+`completing-read' interface to the `kill-ring'. Additionally the
+Consult version supports preview of the selected string."
+ (interactive (list (consult--read-from-kill-ring) current-prefix-arg))
+ (when string
+ (setq yank-window-start (window-start))
+ (push-mark)
+ (insert-for-yank string)
+ (setq this-command 'yank)
+ (when yank-from-kill-ring-rotate
+ (if-let* ((pos (seq-position kill-ring string)))
+ (setq kill-ring-yank-pointer (nthcdr pos kill-ring))
+ (kill-new string)))
+ (when (consp arg)
+ ;; Swap point and mark like in `yank'.
+ (goto-char (prog1 (mark t)
+ (set-marker (mark-marker) (point) (current-buffer)))))))
+
+(put 'consult-yank-replace 'delete-selection 'yank)
+(put 'consult-yank-pop 'delete-selection 'yank)
+(put 'consult-yank-from-kill-ring 'delete-selection 'yank)
+
+;;;###autoload
+(defun consult-yank-pop (&optional arg)
+ "If there is a recent yank act like `yank-pop'.
+
+Otherwise select string from the kill ring and insert it.
+See `yank-pop' for the meaning of ARG.
+
+This command behaves like `yank-pop', which also offers a
+`completing-read' interface to the `kill-ring'. Additionally the
+Consult version supports preview of the selected string."
+ (interactive "*p")
+ (if (eq last-command 'yank)
+ (yank-pop (or arg 1))
+ (call-interactively #'consult-yank-from-kill-ring)))
+
+;; Adapted from the Emacs yank-pop function.
+;;;###autoload
+(defun consult-yank-replace (string)
+ "Select STRING from the kill ring.
+
+If there was no recent yank, insert the string.
+Otherwise replace the just-yanked string with the selected string."
+ (interactive (list (consult--read-from-kill-ring)))
+ (when string
+ (if (not (eq last-command 'yank))
+ (consult-yank-from-kill-ring string)
+ (let ((inhibit-read-only t)
+ (pt (point))
+ (mk (mark t)))
+ (setq this-command 'yank)
+ (funcall (or yank-undo-function 'delete-region) (min pt mk) (max pt mk))
+ (setq yank-undo-function nil)
+ (set-marker (mark-marker) pt (current-buffer))
+ (insert-for-yank string)
+ (set-window-start (selected-window) yank-window-start t)
+ (if (< pt mk)
+ (goto-char (prog1 (mark t)
+ (set-marker (mark-marker) (point) (current-buffer)))))))))
+
+;;;;; Command: consult-bookmark
+
+(defun consult--bookmark-preview ()
+ "Create preview function for bookmarks."
+ (let ((preview (consult--jump-preview))
+ (open (consult--temporary-files)))
+ (lambda (action cand)
+ (unless cand
+ (funcall open))
+ (funcall
+ preview action
+ ;; Only preview bookmarks with the default handler.
+ (when-let* ((bm (and cand (eq action 'preview) (assoc cand bookmark-alist)))
+ (handler (or (bookmark-get-handler bm) #'bookmark-default-handler))
+ ((eq handler #'bookmark-default-handler))
+ (file (bookmark-get-filename bm))
+ (pos (bookmark-get-position bm))
+ (buf (funcall open file)))
+ (set-marker (make-marker) pos buf))))))
+
+(defun consult--bookmark-action (bm)
+ "Open BM via `consult--buffer-action'."
+ (bookmark-jump bm consult--buffer-display))
+
+(consult--define-state bookmark)
+
+(defun consult--bookmark-candidates ()
+ "Return bookmark candidates."
+ (bookmark-maybe-load-default-file)
+ (let ((narrow (cl-loop for (y _ . xs) in consult-bookmark-narrow nconc
+ (cl-loop for x in xs collect (cons x y)))))
+ (cl-loop for bm in bookmark-alist collect
+ (propertize (car bm)
+ 'consult--type
+ (alist-get
+ (or (bookmark-get-handler bm) #'bookmark-default-handler)
+ narrow)))))
+
+;;;###autoload
+(defun consult-bookmark (name)
+ "If bookmark NAME exists, open it, otherwise create a new bookmark with NAME.
+
+The command supports preview of file bookmarks and narrowing. See the
+variable `consult-bookmark-narrow' for the narrowing configuration."
+ (interactive
+ (list
+ (let ((narrow (cl-loop for (x y . _) in consult-bookmark-narrow collect (cons x y))))
+ (consult--read
+ (consult--bookmark-candidates)
+ :prompt "Bookmark: "
+ :state (consult--bookmark-preview)
+ :category 'bookmark
+ :history 'bookmark-history
+ ;; Add default names to future history.
+ ;; Ignore errors such that `consult-bookmark' can be used in
+ ;; buffers which are not backed by a file.
+ :add-history (ignore-errors (bookmark-prop-get (bookmark-make-record) 'defaults))
+ :group (consult--type-group narrow)
+ :narrow (consult--type-narrow narrow)))))
+ (bookmark-maybe-load-default-file)
+ (if (assoc name bookmark-alist)
+ (bookmark-jump name)
+ (bookmark-set name)))
+
+;;;;; Command: consult-complex-command
+
+;;;###autoload
+(defun consult-complex-command ()
+ "Select and evaluate command from the command history.
+
+This command can act as a drop-in replacement for `repeat-complex-command'."
+ (interactive)
+ (let* ((history (or (delete-dups (mapcar #'prin1-to-string command-history))
+ (user-error "There are no previous complex commands")))
+ (cmd (read (consult--read
+ history
+ :prompt "Command: "
+ :default (car history)
+ :sort nil
+ :history t ;; disable history
+ :category 'expression))))
+ ;; Taken from `repeat-complex-command'
+ (add-to-history 'command-history cmd)
+ (apply #'funcall-interactively
+ (car cmd)
+ (mapcar (lambda (e) (eval e t)) (cdr cmd)))))
+
+;;;;; Command: consult-history
+
+(defun consult--current-history ()
+ "Return the history and index variable relevant to the current buffer.
+If the minibuffer is active, the minibuffer history is returned,
+otherwise the history corresponding to the mode. There is a
+special case for `repeat-complex-command', for which the command
+history is used."
+ (cond
+ ;; In the minibuffer we use the current minibuffer history,
+ ;; which can be configured by setting `minibuffer-history-variable'.
+ ((minibufferp)
+ (when (eq minibuffer-history-variable t)
+ (user-error "Minibuffer history is disabled for `%s'" this-command))
+ (list (mapcar #'consult--tofu-strip
+ (if (eq minibuffer-history-variable 'command-history)
+ ;; If pressing "C-x M-:", i.e., `repeat-complex-command',
+ ;; we are instead querying the `command-history' and get a
+ ;; full s-expression. Alternatively you might want to use
+ ;; `consult-complex-command', which can also be bound to
+ ;; "C-x M-:"!
+ (mapcar #'prin1-to-string command-history)
+ (symbol-value minibuffer-history-variable)))))
+ ;; Otherwise we use a mode-specific history, see `consult-mode-histories'.
+ (t (let ((found (seq-find (lambda (h)
+ (and (derived-mode-p (car h))
+ (boundp (if (consp (cdr h)) (cadr h) (cdr h)))))
+ consult-mode-histories)))
+ (unless found
+ (user-error "No history configured for `%s', see `consult-mode-histories'"
+ major-mode))
+ (cons (symbol-value (cadr found)) (cddr found))))))
+
+;;;###autoload
+(defun consult-history (&optional history index bol)
+ "Insert string from HISTORY of current buffer.
+In order to select from a specific HISTORY, pass the history
+variable as argument. INDEX is the name of the index variable to
+update, if any. BOL is the function which jumps to the beginning
+of the prompt. See also `cape-history' from the Cape package."
+ (interactive)
+ (declare-function ring-elements "ring")
+ (pcase-let* ((`(,history ,index ,bol) (if history
+ (list history index bol)
+ (consult--current-history)))
+ (history (if (ring-p history) (ring-elements history) history))
+ (`(,beg . ,end)
+ (if (minibufferp)
+ (cons (minibuffer-prompt-end) (point-max))
+ (if bol
+ (save-excursion
+ (funcall bol)
+ (cons (point) (pos-eol)))
+ (cons (point) (point)))))
+ (str (consult--local-let ((enable-recursive-minibuffers t))
+ (consult--read
+ (or (consult--remove-dups history)
+ (user-error "History is empty"))
+ :prompt "History: "
+ :history t ;; disable history
+ :category ;; Report category depending on history variable
+ (and (minibufferp)
+ (pcase minibuffer-history-variable
+ ('extended-command-history 'command)
+ ('buffer-name-history 'buffer)
+ ('face-name-history 'face)
+ ('read-envvar-name-history 'environment-variable)
+ ('bookmark-history 'bookmark)
+ ('file-name-history 'file)))
+ :sort nil
+ :initial (buffer-substring-no-properties beg end)
+ :lookup #'consult--lookup-member
+ :state (consult--insertion-preview beg end)))))
+ (delete-region beg end)
+ (when index
+ (set index (seq-position history str)))
+ (insert (substring-no-properties str))))
+
+;;;;; Command: consult-isearch-history
+
+(defun consult-isearch-forward (&optional reverse)
+ "Continue Isearch forward optionally in REVERSE."
+ (declare (completion ignore))
+ (interactive)
+ (consult--require-minibuffer)
+ (setq isearch-new-forward (not reverse) isearch-new-nonincremental nil)
+ (funcall (or (command-remapping #'exit-minibuffer) #'exit-minibuffer)))
+
+(defun consult-isearch-backward (&optional reverse)
+ "Continue Isearch backward optionally in REVERSE."
+ (declare (completion ignore))
+ (interactive)
+ (consult-isearch-forward (not reverse)))
+
+(defvar-keymap consult-isearch-history-map
+ :doc "Additional keymap used by `consult-isearch-history'."
+ "<remap> <isearch-forward>" #'consult-isearch-forward
+ "<remap> <isearch-backward>" #'consult-isearch-backward)
+
+(defun consult--isearch-history-candidates ()
+ "Return Isearch history candidates."
+ ;; Do not throw an error on empty history, in order to allow starting a
+ ;; search. We do not :require-match here.
+ (let ((history (if (eq t search-default-mode)
+ (append regexp-search-ring search-ring)
+ (append search-ring regexp-search-ring))))
+ (delete-dups
+ (mapcar
+ (lambda (cand)
+ ;; The search type can be distinguished via text properties.
+ (let* ((props (plist-member (text-properties-at 0 cand)
+ 'isearch-regexp-function))
+ (type (pcase (cadr props)
+ ((and 'nil (guard (not props))) ?r)
+ ('nil ?l)
+ ('word-search-regexp ?w)
+ ('isearch-symbol-regexp ?s)
+ ('char-fold-to-regexp ?c)
+ (_ ?u))))
+ ;; Disambiguate history items. The same string could
+ ;; occur with different search types.
+ (consult--tofu-append cand type)))
+ history))))
+
+(defconst consult--isearch-history-narrow
+ '((?c . "Char")
+ (?u . "Custom")
+ (?l . "Literal")
+ (?r . "Regexp")
+ (?s . "Symbol")
+ (?w . "Word")))
+
+;;;###autoload
+(defun consult-isearch-history ()
+ "Read a search string with completion from the Isearch history.
+
+This replaces the current search string if Isearch is active, and
+starts a new Isearch session otherwise."
+ (interactive)
+ (consult--forbid-minibuffer)
+ (let* ((isearch-message-function #'ignore)
+ (cursor-in-echo-area t) ;; Avoid cursor flickering
+ (candidates (consult--isearch-history-candidates)))
+ (unless isearch-mode (isearch-mode t))
+ (with-isearch-suspended
+ (setq isearch-new-string
+ (consult--read
+ candidates
+ :prompt "I-search: "
+ :category 'consult-isearch-history
+ :history t ;; disable history
+ :sort nil
+ :initial isearch-string
+ :keymap consult-isearch-history-map
+ :annotate
+ (lambda (cand)
+ (consult--annotate-align
+ cand
+ (alist-get (consult--tofu-get cand) consult--isearch-history-narrow)))
+ :group
+ (lambda (cand transform)
+ (if transform
+ cand
+ (alist-get (consult--tofu-get cand) consult--isearch-history-narrow)))
+ :lookup
+ (lambda (selected candidates &rest _)
+ (if-let* ((found (member selected candidates)))
+ (substring (car found) 0 -1)
+ selected))
+ :state
+ (lambda (action cand)
+ (when (and (eq action 'preview) cand)
+ (setq isearch-string cand)
+ (isearch-update-from-string-properties cand)
+ (isearch-update)))
+ :narrow
+ (list :predicate
+ (lambda (cand) (= (consult--tofu-get cand) consult--narrow))
+ :keys consult--isearch-history-narrow))
+ isearch-new-message
+ (mapconcat #'isearch-text-char-description isearch-new-string "")))
+ ;; Setting `isearch-regexp' etc only works outside of `with-isearch-suspended'.
+ (unless (plist-member (text-properties-at 0 isearch-string) 'isearch-regexp-function)
+ (setq isearch-regexp t
+ isearch-regexp-function nil))))
+
+;;;;; Command: consult-minor-mode-menu
+
+(defun consult--minor-mode-candidates ()
+ "Return list of minor-mode candidate strings."
+ (mapcar
+ (pcase-lambda (`(,name . ,sym))
+ (propertize
+ name
+ 'consult--candidate sym
+ 'consult--minor-mode-narrow
+ (logior
+ (ash (if (local-variable-if-set-p sym) ?l ?g) 8)
+ (if (and (boundp sym) (symbol-value sym)) ?i ?o))
+ 'consult--minor-mode-group
+ (concat
+ (if (local-variable-if-set-p sym) "Local " "Global ")
+ (if (and (boundp sym) (symbol-value sym)) "On" "Off"))))
+ (nconc
+ ;; according to describe-minor-mode-completion-table-for-symbol
+ ;; the minor-mode-list contains *all* minor modes
+ (mapcar (lambda (sym) (cons (symbol-name sym) sym)) minor-mode-list)
+ ;; take the lighters from minor-mode-alist
+ (delq nil
+ (mapcar (pcase-lambda (`(,sym ,lighter))
+ (when (and lighter (not (equal "" lighter)))
+ (let (message-log-max)
+ (setq lighter (string-trim (format-mode-line lighter)))
+ (unless (string-blank-p lighter)
+ (cons lighter sym)))))
+ minor-mode-alist)))))
+
+(defconst consult--minor-mode-menu-narrow
+ '((?l . "Local")
+ (?g . "Global")
+ (?i . "On")
+ (?o . "Off")))
+
+;;;###autoload
+(defun consult-minor-mode-menu ()
+ "Enable or disable minor mode.
+
+This is an alternative to `minor-mode-menu-from-indicator'."
+ (interactive)
+ (call-interactively
+ (consult--read
+ (consult--minor-mode-candidates)
+ :prompt "Minor mode: "
+ :require-match t
+ :category 'minor-mode
+ :group
+ (lambda (cand transform)
+ (if transform cand (get-text-property 0 'consult--minor-mode-group cand)))
+ :narrow
+ (list :predicate
+ (lambda (cand)
+ (let ((narrow (get-text-property 0 'consult--minor-mode-narrow cand)))
+ (or (= (logand narrow 255) consult--narrow)
+ (= (ash narrow -8) consult--narrow))))
+ :keys
+ consult--minor-mode-menu-narrow)
+ :lookup #'consult--lookup-candidate
+ :history 'consult--minor-mode-menu-history)))
+
+;;;;; Command: consult-theme
+
+;;;###autoload
+(defun consult-theme (theme)
+ "Disable current themes and enable THEME from `consult-themes'.
+
+The command supports previewing the currently selected theme."
+ (interactive
+ (list
+ (let* ((regexp (consult--regexp-filter
+ (mapcar (lambda (x) (if (stringp x) x (format "\\`%s\\'" x)))
+ consult-themes)))
+ (avail-themes (seq-filter
+ (lambda (x) (string-match-p regexp (symbol-name x)))
+ (cons 'default (custom-available-themes))))
+ (saved-theme (car custom-enabled-themes)))
+ (consult--read
+ (mapcar #'symbol-name avail-themes)
+ :prompt "Theme: "
+ :require-match t
+ :category 'theme
+ :history 'consult--theme-history
+ :lookup (lambda (selected &rest _)
+ (setq selected (and selected (intern-soft selected)))
+ (or (and selected (car (memq selected avail-themes)))
+ saved-theme))
+ :state (lambda (action theme)
+ (with-selected-window (or (active-minibuffer-window)
+ (selected-window))
+ (pcase action
+ ('return (consult-theme (or theme saved-theme)))
+ ((and 'preview (guard theme)) (consult-theme theme)))))
+ :default (symbol-name (or saved-theme 'default))))))
+ (when (eq theme 'default) (setq theme nil))
+ (unless (eq theme (car custom-enabled-themes))
+ (mapc #'disable-theme custom-enabled-themes)
+ (when theme
+ (unless (and (memq theme custom-known-themes) (get theme 'theme-settings))
+ (load-theme theme 'no-confirm 'no-enable))
+ (if (and (memq theme custom-known-themes) (get theme 'theme-settings))
+ (enable-theme theme)
+ (consult--minibuffer-message "%s is not a valid theme" theme)))))
+
+;;;;; Command: consult-buffer
+
+(defun consult--buffer-sort-alpha (buffers)
+ "Sort BUFFERS alphabetically, put starred buffers at the end."
+ (sort buffers
+ (lambda (x y)
+ (setq x (buffer-name x) y (buffer-name y))
+ (let ((a (and (length> x 0) (eq (aref x 0) ?*)))
+ (b (and (length> y 0) (eq (aref y 0) ?*))))
+ (if (eq a b)
+ (string< x y)
+ (not a))))))
+
+(defun consult--buffer-sort-alpha-current (buffers)
+ "Sort BUFFERS alphabetically, put current at the beginning."
+ (let ((buffers (consult--buffer-sort-alpha buffers))
+ (current (current-buffer)))
+ (if (memq current buffers)
+ (cons current (delq current buffers))
+ buffers)))
+
+(defun consult--buffer-sort-visibility (buffers)
+ "Sort BUFFERS by visibility."
+ (let ((current (car (memq (current-buffer) buffers))) visible)
+ (consult--keep! buffers
+ (unless (eq it current)
+ (if (get-buffer-window it 'visible)
+ (progn (push it visible) nil)
+ it)))
+ (nconc buffers (nreverse visible) (and current (list current)))))
+
+(defun consult--normalize-directory (dir)
+ "Normalize directory DIR.
+DIR can be project, nil or a path."
+ (cond
+ ((eq dir 'project) (consult--project-root))
+ (dir (expand-file-name dir))))
+
+(defun consult--buffer-query-prompt (prompt query)
+ "Return a list of buffers and create an appropriate prompt string.
+Return a pair of a prompt string and a list of buffers. PROMPT
+is the prefix of the prompt string. QUERY specifies the buffers
+to search and is passed to `consult--buffer-query'."
+ (let* ((dir (plist-get query :directory))
+ (ndir (consult--normalize-directory dir))
+ (buffers (apply #'consult--buffer-query :directory ndir query))
+ (count (length buffers)))
+ (cons (format "%s (%d buffer%s%s): " prompt count
+ (if (= count 1) "" "s")
+ (cond
+ ((and ndir (eq dir 'project))
+ (format ", Project %s" (consult--project-name ndir)))
+ (ndir (concat ", " (consult--left-truncate-file ndir)))
+ (t "")))
+ buffers)))
+
+(defun consult--frame-buffer-list ()
+ "List of buffers belonging to the current frame or tab."
+ (let ((buffers (append (frame-parameter nil 'buffer-list)
+ (reverse (frame-parameter nil 'buried-buffer-list)))))
+ ;; Sometimes visible buffers are not registered in the buffer-list.
+ (cl-loop for win in (window-list) for buf = (window-buffer win)
+ unless (memq buf buffers) do (push buf buffers))
+ buffers))
+
+(cl-defun consult--buffer-query ( &key sort directory mode as predicate (filter t)
+ include (exclude consult-buffer-filter)
+ (buffer-list consult-buffer-list-function))
+ "Query for a list of matching buffers.
+The function supports filtering by various criteria which are
+used throughout Consult. In particular it is the backbone of
+most `consult-buffer-sources'.
+DIRECTORY can either be the symbol project or a file name.
+SORT can be visibility, alpha or nil.
+FILTER can be either t, nil or invert.
+EXCLUDE is a list of regexps.
+INCLUDE is a list of regexps.
+MODE can be a mode or a list of modes to restrict the returned buffers.
+PREDICATE is a predicate function.
+BUFFER-LIST is a function or a list of buffers.
+AS is a conversion function."
+ (let ((root (consult--normalize-directory directory)))
+ (setq buffer-list (cond
+ ((functionp buffer-list) (funcall buffer-list))
+ ((listp buffer-list) (copy-sequence buffer-list))
+ (t (buffer-list))))
+ (when (or filter mode root)
+ (let ((exclude-re (consult--regexp-filter exclude))
+ (include-re (consult--regexp-filter include))
+ (case-fold-search))
+ (consult--keep! buffer-list
+ (and
+ (or (not mode)
+ (let ((mm (buffer-local-value 'major-mode it)))
+ (if (consp mode)
+ (seq-some (lambda (m) (provided-mode-derived-p mm m)) mode)
+ (provided-mode-derived-p mm mode))))
+ (pcase-exhaustive filter
+ ('nil t)
+ ((or 't 'invert)
+ (eq (eq filter t)
+ (and
+ (or (not exclude)
+ (not (string-match-p exclude-re (buffer-name it))))
+ (or (not include)
+ (not (not (string-match-p include-re (buffer-name it)))))))))
+ (or (not root)
+ (when-let* ((dir (buffer-local-value 'default-directory it)))
+ (string-prefix-p root
+ (if (and (/= 0 (length dir)) (eq (aref dir 0) ?/))
+ dir
+ (expand-file-name dir)))))
+ (or (not predicate) (funcall predicate it))
+ it))))
+ (when sort
+ (setq buffer-list (funcall (intern (format "consult--buffer-sort-%s" sort)) buffer-list)))
+ (when as
+ (cl-loop for it in-ref buffer-list do (setf it (funcall as it))))
+ buffer-list))
+
+(defun consult--buffer-file-hash ()
+ "Return hash table of all buffer file names."
+ (consult--string-hash (consult--buffer-query :as #'buffer-file-name)))
+
+(defun consult--buffer-pair (buffer)
+ "Return a pair of name of BUFFER and BUFFER."
+ (cons (buffer-name buffer) buffer))
+
+(defun consult--buffer-preview ()
+ "Buffer preview function."
+ (let ((orig-buf (window-buffer (consult--original-window)))
+ (orig-prev (copy-sequence (window-prev-buffers)))
+ (orig-next (copy-sequence (window-next-buffers)))
+ (orig-bl (copy-sequence (frame-parameter nil 'buffer-list)))
+ (orig-bbl (copy-sequence (frame-parameter nil 'buried-buffer-list)))
+ other-win)
+ (lambda (action cand)
+ (pcase action
+ ('return
+ ;; Restore buffer list for the current tab
+ (set-frame-parameter nil 'buffer-list orig-bl)
+ (set-frame-parameter nil 'buried-buffer-list orig-bbl))
+ ('exit
+ (set-window-prev-buffers other-win orig-prev)
+ (set-window-next-buffers other-win orig-next))
+ ('preview
+ ;; Prevent opening the preview in another tab, since restoring the tab
+ ;; status is difficult and also costly.
+ (cl-letf* (((symbol-function #'display-buffer-in-tab) #'ignore)
+ ((symbol-function #'display-buffer-in-new-tab) #'ignore))
+ (when (and (eq consult--buffer-display #'switch-to-buffer-other-window)
+ (not other-win))
+ (switch-to-buffer-other-window orig-buf 'norecord)
+ (setq other-win (selected-window)))
+ (let ((win (or other-win (selected-window)))
+ (buf (or (and cand (get-buffer cand)) orig-buf)))
+ (when (and (window-live-p win) (buffer-live-p buf)
+ (not (buffer-match-p consult-preview-excluded-buffers buf)))
+ (with-selected-window win
+ (unless (or orig-prev orig-next)
+ (setq orig-prev (copy-sequence (window-prev-buffers))
+ orig-next (copy-sequence (window-next-buffers))))
+ (switch-to-buffer buf 'norecord))))))))))
+
+(defun consult--buffer-action (buffer &optional norecord)
+ "Switch to BUFFER via `consult--buffer-display' function.
+If NORECORD is non-nil, do not record the buffer switch in the buffer list."
+ (funcall consult--buffer-display buffer norecord))
+
+(consult--define-state buffer)
+
+(defvar consult-source-bookmark
+ `( :name "Bookmark"
+ :narrow ?m
+ :category bookmark
+ :face consult-bookmark
+ :history bookmark-history
+ :items ,#'bookmark-all-names
+ :state ,#'consult--bookmark-state)
+ "Bookmark source for `consult-buffer'.")
+
+(defvar consult-source-project-buffer
+ `( :name "Project Buffer"
+ :narrow ?b
+ :category buffer
+ :face consult-buffer
+ :history buffer-name-history
+ :state ,#'consult--buffer-state
+ :enabled ,(lambda () consult-project-function)
+ :items
+ ,(lambda ()
+ (when-let* ((root (consult--project-root)))
+ (consult--buffer-query :sort 'visibility
+ :directory root
+ :as #'consult--buffer-pair))))
+ "Project buffer source for `consult-buffer'.")
+
+(defvar consult-source-project-recent-file
+ `( :name "Project File"
+ :narrow ?f
+ :category file
+ :face consult-file
+ :history file-name-history
+ :state ,#'consult--file-state
+ :new
+ ,(lambda (file)
+ (consult--file-action
+ (expand-file-name file (consult--project-root))))
+ :enabled
+ ,(lambda ()
+ (and consult-project-function
+ recentf-mode))
+ :items
+ ,(lambda ()
+ (when-let* ((root (consult--project-root)))
+ (let ((len (length root))
+ (ht (consult--buffer-file-hash))
+ items)
+ (dolist (file (bound-and-true-p recentf-list) (nreverse items))
+ ;; Emacs 29 abbreviates file paths by default, see
+ ;; `recentf-filename-handlers'. I recommend to set
+ ;; `recentf-filename-handlers' to nil to avoid any slow down.
+ (unless (eq (aref file 0) ?/)
+ (let (file-name-handler-alist) ;; No Tramp slowdown please.
+ (setq file (expand-file-name file))))
+ (when (and (not (gethash file ht)) (string-prefix-p root file))
+ (let ((part (substring file len)))
+ (when (equal part "") (setq part "./"))
+ (push (cons part file) items))))))))
+ "Project file source for `consult-buffer'.")
+
+(defvar consult-source-project-root
+ `( :name "Project Root"
+ :narrow ?r
+ :category file
+ :face consult-file
+ :history file-name-history
+ :action ,(lambda (root)
+ (let ((default-directory root))
+ (call-interactively #'find-file)))
+ :items ,#'consult--project-known-roots)
+ "Known project root source.")
+
+(defvar consult-source-project-buffer-hidden
+ `( :hidden t :narrow ((?p . "Project") (?B . "Project Buffer"))
+ ,@consult-source-project-buffer)
+ "Like `consult-source-project-buffer' but hidden by default.")
+
+(defvar consult-source-project-recent-file-hidden
+ `( :hidden t :narrow ((?p . "Project") (?F . "Project File"))
+ ,@consult-source-project-recent-file)
+ "Like `consult-source-project-recent-file' but hidden by default.")
+
+(defvar consult-source-project-root-hidden
+ `( :hidden t :narrow ((?p . "Project") (?R . "Project Root"))
+ ,@consult-source-project-root)
+ "Like `consult-source-project-root' but hidden by default.")
+
+(defvar consult-source-hidden-buffer
+ `( :name "Hidden Buffer"
+ :narrow ?\s
+ :hidden t
+ :category buffer
+ :face consult-buffer
+ :history buffer-name-history
+ :action ,#'consult--buffer-action
+ :items
+ ,(lambda () (consult--buffer-query :sort 'visibility
+ :filter 'invert
+ :as #'consult--buffer-pair
+ :buffer-list t)))
+ "Hidden buffer source for `consult-buffer'.
+The source is hidden by default and can be summoned via its narrow key.
+All buffers are taken into account, i.e., the entire `buffer-list' from
+all frames.")
+
+(defvar consult-source-modified-buffer
+ `( :name "Modified Buffer"
+ :narrow ?*
+ :hidden t
+ :category buffer
+ :face consult-buffer
+ :history buffer-name-history
+ :state ,#'consult--buffer-state
+ :items
+ ,(lambda () (consult--buffer-query :sort 'visibility
+ :as #'consult--buffer-pair
+ :predicate
+ (lambda (buf)
+ (and (buffer-modified-p buf)
+ (buffer-file-name buf))))))
+ "Modified buffer source for `consult-buffer'.
+The source is hidden by default and can be summoned via its narrow key.
+Only buffers returned by the `consult-buffer-list-function' are taken
+into account.")
+
+(defvar consult-source-buffer
+ `( :name "Buffer"
+ :narrow ?b
+ :category buffer
+ :face consult-buffer
+ :history buffer-name-history
+ :state ,#'consult--buffer-state
+ :default t
+ :items
+ ,(lambda () (consult--buffer-query :sort 'visibility
+ :as #'consult--buffer-pair)))
+ "Buffer source for `consult-buffer'.
+Only buffers returned by the `consult-buffer-list-function' are taken into
+account.")
+
+(defvar consult-source-other-buffer
+ `( :name "Other Buffer"
+ :narrow ?o
+ :hidden t
+ :category buffer
+ :face consult-buffer
+ :history buffer-name-history
+ :state ,#'consult--buffer-state
+ :enabled ,(lambda () (not (eq consult-buffer-list-function #'buffer-list)))
+ :items
+ ,(lambda ()
+ (let ((local (consult--string-hash (consult--buffer-query))))
+ (consult--buffer-query :sort 'visibility
+ :predicate (lambda (buf) (not (gethash buf local)))
+ :as #'consult--buffer-pair
+ :buffer-list t))))
+ "Source for `consult-buffer' for buffers from other frames or tabs.
+The source is hidden by default and can be summoned via its narrow key.
+Only buffers returned by the `consult-buffer-list-function' are taken
+into account.")
+
+(autoload 'consult-register--candidates "consult-register")
+
+(defun consult--buffer-register-p (reg)
+ "Return non-nil if REG is a buffer register."
+ (and (eq (car-safe reg) 'buffer) (buffer-live-p (get-buffer (cdr reg)))))
+
+(defvar consult-source-buffer-register
+ `( :name "Buffer Register"
+ :narrow (?r . "Register")
+ :category buffer
+ :state ,#'consult--buffer-state
+ :enabled ,(lambda () (cl-loop for (_ . reg) in register-alist
+ thereis (consult--buffer-register-p reg)))
+ :items ,(lambda () (consult-register--candidates #'consult--buffer-register-p)))
+ "Buffer register source.")
+
+(defun consult--file-register-p (reg)
+ "Return non-nil if REG is a file register."
+ (memq (car-safe reg) '(file-query file)))
+
+(defvar consult-source-file-register
+ `( :name "File Register"
+ :narrow (?r . "Register")
+ :category file
+ :state ,#'consult--file-state
+ :enabled ,(lambda () (cl-loop for (_ . reg) in register-alist
+ thereis (consult--file-register-p reg)))
+ :items ,(lambda () (consult-register--candidates #'consult--file-register-p)))
+ "File register source.")
+
+(defvar consult-source-recent-file
+ `( :name "File"
+ :narrow ?f
+ :category file
+ :face consult-file
+ :history file-name-history
+ :state ,#'consult--file-state
+ :new ,#'consult--file-action
+ :enabled ,(lambda () recentf-mode)
+ :items
+ ,(lambda ()
+ (let ((ht (consult--buffer-file-hash))
+ items)
+ (dolist (file (bound-and-true-p recentf-list) (nreverse items))
+ ;; Emacs 29 abbreviates file paths by default, see
+ ;; `recentf-filename-handlers'. I recommend to set
+ ;; `recentf-filename-handlers' to nil to avoid any slow down.
+ (unless (eq (aref file 0) ?/)
+ (let (file-name-handler-alist) ;; No Tramp slowdown please.
+ (setq file (expand-file-name file))))
+ (unless (gethash file ht)
+ (push (consult--fast-abbreviate-file-name file) items))))))
+ "Recent file source for `consult-buffer'.")
+
+;;;###autoload
+(defun consult-buffer (&optional sources)
+ "Enhanced `switch-to-buffer' command with support for virtual buffers.
+
+The command supports recent files, bookmarks, views and project files as
+virtual buffers. Buffers are previewed. Narrowing to buffers (b), files (f),
+bookmarks (m) and project files (p) is supported via the corresponding
+keys. In order to determine the project-specific files and buffers, the
+`consult-project-function' is used. The virtual buffer SOURCES
+default to `consult-buffer-sources'. See `consult--multi' for the
+configuration of the virtual buffer sources."
+ (interactive)
+ (let ((selected (consult--multi (or sources consult-buffer-sources)
+ :require-match
+ (confirm-nonexistent-file-or-buffer)
+ :prompt "Switch to: "
+ :history 'consult--buffer-history
+ :sort nil)))
+ ;; For non-matching candidates, fall back to buffer creation.
+ (unless (plist-get (cdr selected) :match)
+ (consult--buffer-action (car selected)))))
+
+(defmacro consult--with-project (&rest body)
+ "Ensure that BODY is executed with a project root."
+ (declare (indent 0) (debug t))
+ `(consult--with-project-f (lambda () ,@body)))
+
+(defun consult--with-project-f (body)
+ "See `consult--with-project' for documentation."
+ ;; We have to work quite hard here to ensure that the project root is only
+ ;; overridden at the current recursion level. When entering a recursive
+ ;; minibuffer session, we should be able to still switch the project.
+ (let ((consult-project-function
+ (let ((root (or (consult--project-root t) (user-error "No project found")))
+ (depth (recursion-depth))
+ (orig consult-project-function))
+ (lambda (may-prompt)
+ (if (= depth (recursion-depth))
+ root
+ (funcall orig may-prompt))))))
+ (funcall body)))
+
+;;;###autoload
+(defun consult-project-buffer ()
+ "Enhanced `project-switch-to-buffer' command with support for virtual buffers.
+The command may prompt you for a project directory if it is invoked from
+outside a project. See `consult-buffer' for more details."
+ (interactive)
+ (consult--with-project
+ (consult-buffer consult-project-buffer-sources)))
+
+;;;###autoload
+(defun consult-buffer-other-window ()
+ "Variant of `consult-buffer', switching to a buffer in another window."
+ (interactive)
+ (let ((consult--buffer-display #'switch-to-buffer-other-window))
+ (consult-buffer)))
+
+;;;###autoload
+(defun consult-buffer-other-frame ()
+ "Variant of `consult-buffer', switching to a buffer in another frame."
+ (interactive)
+ (let ((consult--buffer-display #'switch-to-buffer-other-frame))
+ (consult-buffer)))
+
+;;;###autoload
+(defun consult-buffer-other-tab ()
+ "Variant of `consult-buffer', switching to a buffer in another tab."
+ (interactive)
+ (let ((consult--buffer-display #'switch-to-buffer-other-tab))
+ (consult-buffer)))
+
+;;;;; Command: consult-grep
+
+(defun consult--grep-format (builder)
+ "Async function highlighting grep match results.
+BUILDER is the command line builder function."
+ (consult--async-transform-by-input
+ (lambda (input)
+ (let ((highlight (cdr (funcall builder input))))
+ (lambda (cands)
+ (let ((file "") (file-len 0) result)
+ (save-match-data
+ (dolist (str cands (nreverse result))
+ (when (string-match consult--grep-match-regexp str)
+ ;; We share the file name across candidates to reduce
+ ;; the amount of allocated memory.
+ (unless (and (= file-len (- (match-end 1) (match-beginning 1)))
+ (eq t (compare-strings
+ file 0 file-len
+ str (match-beginning 1) (match-end 1) nil)))
+ (setq file (match-string 1 str)
+ file-len (length file)))
+ (let* ((line (match-string 2 str))
+ (ctx (= (aref str (match-beginning 3)) ?-))
+ (sep (if ctx "-" ":"))
+ (content (substring str (match-end 0)))
+ (line-len (length line)))
+ (when (and consult-grep-max-columns
+ (length> content consult-grep-max-columns))
+ (setq content (substring content 0 consult-grep-max-columns)))
+ (when highlight
+ (funcall highlight content))
+ (setq str (concat file sep line sep content))
+ ;; Store file name in order to avoid allocations in `consult--prefix-group'
+ (add-text-properties 0 file-len `(face consult-file consult--prefix-group ,file) str)
+ (put-text-property (1+ file-len) (+ 1 file-len line-len) 'face 'consult-line-number str)
+ (when ctx
+ (add-face-text-property (+ 2 file-len line-len) (length str) 'consult-grep-context 'append str))
+ (push str result)))))))))))
+
+(defun consult--grep-position (cand &optional find-file)
+ "Return the grep position marker for CAND.
+FIND-FILE is the file open function, defaulting to `find-file-noselect'."
+ (when cand
+ (let* ((file-end (next-single-property-change 0 'face cand))
+ (line-end (next-single-property-change (1+ file-end) 'face cand))
+ (matches (consult--point-placement cand (1+ line-end) 'consult-grep-context))
+ (file (substring-no-properties cand 0 file-end))
+ (line (string-to-number (substring-no-properties cand (+ 1 file-end) line-end))))
+ (when-let* ((pos (consult--marker-from-line-column
+ (funcall (or find-file #'consult--file-action) file)
+ line (or (car matches) 0))))
+ (cons pos (cdr matches))))))
+
+(defun consult--grep-state ()
+ "Grep state function."
+ (let ((open (consult--temporary-files))
+ (jump (consult--jump-state)))
+ (lambda (action cand)
+ (unless cand
+ (funcall open))
+ (funcall jump action (consult--grep-position
+ cand
+ (and (not (eq action 'return)) open))))))
+
+(defun consult--grep-exclude-args ()
+ "Produce grep exclude arguments.
+Take the variable `grep-find-ignored-directories' and the variable
+`grep-find-ignored-files' into account."
+ (unless (boundp 'grep-find-ignored-files) (require 'grep))
+ (nconc (mapcar (lambda (s) (concat "--exclude=" s))
+ (bound-and-true-p grep-find-ignored-files))
+ (mapcar (lambda (s) (concat "--exclude-dir=" s))
+ (bound-and-true-p grep-find-ignored-directories))))
+
+(defun consult--grep (prompt make-builder dir initial)
+ "Run asynchronous grep.
+
+MAKE-BUILDER is the function that returns the command line
+builder function. DIR is a directory or a list of file or
+directories. PROMPT is the prompt string. INITIAL is initial
+input."
+ (pcase-let* ((`(,prompt ,paths ,dir) (consult--directory-prompt prompt dir))
+ (default-directory dir)
+ (builder (funcall make-builder paths)))
+ (consult--read
+ (consult--process-collection builder
+ :transform (consult--grep-format builder)
+ :file-handler t)
+ :prompt prompt
+ :lookup #'consult--lookup-member
+ :state (consult--grep-state)
+ :initial initial
+ :add-history (thing-at-point 'symbol)
+ :require-match t
+ :category 'consult-grep
+ :group #'consult--prefix-group
+ :history '(:input consult--grep-history)
+ :sort nil)))
+
+(defun consult--grep-lookahead-p (&rest cmd)
+ "Return t if grep CMD supports look-ahead."
+ (eq 0 (process-file-shell-command
+ (concat "echo xaxbx | "
+ (mapconcat #'shell-quote-argument `(,@cmd "^(?=.*b)(?=.*a)") " ")))))
+
+(defun consult--grep-make-builder (paths)
+ "Build grep command line and grep across PATHS."
+ (let* ((cmd (consult--build-args consult-grep-args))
+ (type (if (consult--grep-lookahead-p (car cmd) "-P") 'pcre 'extended)))
+ (lambda (input)
+ (pcase-let* ((`(,arg . ,opts) (consult--command-split input))
+ (flags (append cmd opts))
+ (ignore-case (or (member "-i" flags) (member "--ignore-case" flags))))
+ (if (or (member "-F" flags) (member "--fixed-strings" flags))
+ (cons (append cmd (list "-e" arg) opts paths)
+ (apply-partially #'consult--highlight-literals arg ignore-case))
+ (pcase-let ((`(,re . ,hl) (consult--compile-regexp arg type ignore-case)))
+ (when re
+ (cons (append cmd
+ (list (if (eq type 'pcre) "-P" "-E") ;; perl or extended
+ "-e" (consult--join-regexps re type))
+ opts paths)
+ hl))))))))
+
+(autoload 'consult-compile-error "consult-compile")
+
+;;;###autoload
+(defun consult-grep-match (&optional arg)
+ "Jump to grep matches related to the current project or file.
+
+This command collects entries from all related Grep buffers. The
+command supports preview of the currently selected match. With prefix
+ARG, jump to the match in the Grep buffer, instead of to the actual
+location of the match. This command is a thin wrapper around
+`consult-compile-error'."
+ (interactive "P")
+ (consult-compile-error arg t))
+
+;;;###autoload
+(defun consult-grep (&optional dir initial)
+ "Search with `grep' for files in DIR where the content matches a regexp.
+
+The initial input is given by the INITIAL argument. DIR can be nil, a
+directory string or a list of file/directory paths. If `consult-grep'
+is called interactively with a prefix argument, the user can specify the
+directories or files to search in. Multiple directories or files must
+be separated by comma in the minibuffer, since they are read via
+`completing-read-multiple'. By default the project directory is used if
+`consult-project-function' is defined and returns non-nil. Otherwise
+the `default-directory' is searched. If the command is invoked with a
+double prefix argument (twice `C-u') the user is asked for a project, if
+not yet inside a project, or the current project is searched.
+
+The input string is split, the first part of the string (grep input) is
+passed to the asynchronous grep process and the second part of the
+string is passed to the completion-style filtering.
+
+The input string is split at a punctuation character, which is given as
+the first character of the input string. The format is similar to
+Perl-style regular expressions, e.g., /regexp/. Furthermore command
+line options can be passed to grep, specified behind --. The overall
+prompt input has the form `#async-input --grep-opt#filter-string'.
+
+Note that the grep input string is transformed from Emacs regular
+expressions to Posix regular expressions. Always enter Emacs regular
+expressions at the prompt. `consult-grep' behaves like builtin Emacs
+search commands, e.g., Isearch, which take Emacs regular expressions.
+Furthermore the asynchronous input split into words, each word must
+match separately and in any order. See `consult--regexp-compiler' for
+the inner workings. In order to disable transformations of the grep
+input, adjust `consult--regexp-compiler' accordingly.
+
+Here we give a few example inputs:
+
+#alpha beta : Search for alpha and beta in any order.
+#alpha.*beta : Search for alpha before beta.
+#\\(alpha\\|beta\\) : Search for alpha or beta (Note Emacs syntax!)
+#word -C3 : Search for word, include 3 lines as context
+#first#second : Search for first, quick filter for second.
+
+The symbol at point is added to the future history."
+ (interactive "P")
+ (consult--grep "Grep" #'consult--grep-make-builder dir initial))
+
+;;;;; Command: consult-git-grep
+
+(defun consult--git-grep-make-builder (paths)
+ "Create grep command line builder given PATHS."
+ (let ((cmd (consult--build-args consult-git-grep-args)))
+ (lambda (input)
+ (pcase-let* ((`(,arg . ,opts) (consult--command-split input))
+ (flags (append cmd opts))
+ (ignore-case (or (member "-i" flags) (member "--ignore-case" flags))))
+ (if (or (member "-F" flags) (member "--fixed-strings" flags))
+ (cons (append cmd (list "-e" arg) opts paths)
+ (apply-partially #'consult--highlight-literals arg ignore-case))
+ (pcase-let ((`(,re . ,hl) (consult--compile-regexp arg 'extended ignore-case)))
+ (when re
+ (cons (append cmd
+ (cdr (mapcan (lambda (x) (list "--and" "-e" x)) re))
+ opts paths)
+ hl))))))))
+
+;;;###autoload
+(defun consult-git-grep (&optional dir initial)
+ "Search with `git grep' for files in DIR with INITIAL input.
+See `consult-grep' for details."
+ (interactive "P")
+ (consult--grep "Git-grep" #'consult--git-grep-make-builder dir initial))
+
+;;;;; Command: consult-ripgrep
+
+(defun consult--ripgrep-make-builder (paths)
+ "Create ripgrep command line builder given PATHS."
+ (let* ((cmd (consult--build-args consult-ripgrep-args))
+ (type (if (consult--grep-lookahead-p (car cmd) "-P") 'pcre 'extended)))
+ (lambda (input)
+ (pcase-let* ((`(,arg . ,opts) (consult--command-split input))
+ (flags (append cmd opts))
+ (ignore-case
+ (and (not (or (member "-s" flags) (member "--case-sensitive" flags)))
+ (or (member "-i" flags) (member "--ignore-case" flags)
+ (and (or (member "-S" flags) (member "--smart-case" flags))
+ (let (case-fold-search)
+ ;; Case insensitive if there are no uppercase letters
+ (not (string-match-p "[[:upper:]]" arg))))))))
+ (if (or (member "-F" flags) (member "--fixed-strings" flags))
+ (cons (append cmd (list "-e" arg) opts paths)
+ (apply-partially #'consult--highlight-literals arg ignore-case))
+ (pcase-let ((`(,re . ,hl) (consult--compile-regexp arg type ignore-case)))
+ (when re
+ (cons (append cmd (and (eq type 'pcre) '("-P"))
+ (list "-e" (consult--join-regexps re type))
+ opts paths)
+ hl))))))))
+
+;;;###autoload
+(defun consult-ripgrep (&optional dir initial)
+ "Search with `rg' for files in DIR with INITIAL input.
+See `consult-grep' for details."
+ (interactive "P")
+ (consult--grep "Ripgrep" #'consult--ripgrep-make-builder dir initial))
+
+;;;;; Command: consult-find
+
+(defun consult--find (prompt builder initial)
+ "Run find command in current directory.
+
+The function returns the selected file.
+The filename at point is added to the future history.
+
+BUILDER is the command line builder function.
+PROMPT is the prompt.
+INITIAL is initial input."
+ (consult--read
+ (consult--process-collection builder
+ :transform (consult--async-map (lambda (x) (string-remove-prefix "./" x)))
+ :highlight t :file-handler t) ;; allow tramp
+ :prompt prompt
+ :sort nil
+ :require-match t
+ :initial initial
+ :add-history (thing-at-point 'filename)
+ :category 'file
+ :history '(:input consult--find-history)))
+
+(defun consult--find-make-builder (paths)
+ "Build find command line, finding across PATHS."
+ (let* ((cmd (seq-mapcat (lambda (x)
+ (if (equal x ".") paths (list x)))
+ (consult--build-args consult-find-args)))
+ (type (if (eq 0 (process-file-shell-command
+ (concat (car cmd) " -regextype emacs -version")))
+ 'emacs 'basic)))
+ (lambda (input)
+ (pcase-let* ((`(,arg . ,opts) (consult--command-split input))
+ (method (or (seq-find (lambda (o)
+ (member o '("-name" "-path" "-regex"
+ "-iname" "-ipath" "-iregex")))
+ opts)
+ "-iregex"))
+ (opts (remove method opts))
+ (ignore-case (string-prefix-p "-i" method)))
+ (if (not (string-suffix-p "regex" method))
+ (when-let* ((args (consult--split-escaped arg)))
+ (cons (append cmd
+ (cdr (mapcan
+ (lambda (x) `("-and" ,method ,(format "*%s*" x)))
+ args))
+ opts)
+ (apply-partially #'consult--highlight-literals args ignore-case)))
+ (pcase-let ((`(,re . ,hl) (consult--compile-regexp arg type ignore-case)))
+ (when (or re opts) ;; Either option or regexp must be provided
+ (cons (append cmd
+ (cdr (mapcan
+ (lambda (x)
+ `("-and" ,method
+ ,(format
+ ".*%s.*"
+ ;; Replace non-capturing groups with capturing groups.
+ ;; GNU find does not support non-capturing groups.
+ (replace-regexp-in-string
+ "\\\\(\\?:" "\\(" x 'fixedcase 'literal))))
+ re))
+ opts)
+ hl))))))))
+
+;;;###autoload
+(defun consult-find (&optional dir initial)
+ "Search for files with `find' in DIR.
+The file names must match the input regexp. INITIAL is the
+initial minibuffer input. See `consult-grep' for details
+regarding the asynchronous search and the arguments."
+ (interactive "P")
+ (pcase-let* ((`(,prompt ,paths ,dir) (consult--directory-prompt "Find" dir))
+ (default-directory dir)
+ (builder (consult--find-make-builder paths)))
+ (find-file (consult--find prompt builder initial))))
+
+;;;;; Command: consult-fd
+
+(defun consult--fd-make-builder (paths)
+ "Build find command line, finding across PATHS."
+ (let ((cmd (consult--build-args consult-fd-args)))
+ (lambda (input)
+ (pcase-let* ((`(,arg . ,opts) (consult--command-split input))
+ (flags (append cmd opts))
+ (ignore-case
+ (and (not (or (member "-s" flags) (member "--case-sensitive" flags)))
+ (or (member "-i" flags) (member "--ignore-case" flags)
+ (let (case-fold-search)
+ ;; Case insensitive if there are no uppercase letters
+ (not (string-match-p "[[:upper:]]" arg)))))))
+ (if (or (member "-F" flags) (member "--fixed-strings" flags)
+ (member "-g" flags) (member "--glob" flags))
+ (when-let* ((args (consult--split-escaped arg)))
+ (cons (append cmd opts
+ (mapcan (lambda (x) `("--and" ,x))
+ (if (or (member "-g" flags) (member "--glob" flags))
+ (mapcar (lambda (x) (concat "**/" x)) args)
+ args))
+ (mapcan (lambda (x) `("--search-path" ,x)) paths))
+ (apply-partially #'consult--highlight-literals args ignore-case)))
+ (pcase-let ((`(,re . ,hl) (consult--compile-regexp arg 'pcre ignore-case)))
+ (when (or re opts) ;; Either option or regexp must be provided
+ (cons (append cmd opts
+ (mapcan (lambda (x) `("--and" ,x)) re)
+ (mapcan (lambda (x) `("--search-path" ,x)) paths))
+ hl))))))))
+
+;;;###autoload
+(defun consult-fd (&optional dir initial)
+ "Search for files with `fd' in DIR.
+The file names must match the input regexp. INITIAL is the
+initial minibuffer input. See `consult-grep' for details
+regarding the asynchronous search and the arguments."
+ (interactive "P")
+ (pcase-let* ((`(,prompt ,paths ,dir) (consult--directory-prompt "Fd" dir))
+ (default-directory dir)
+ (builder (consult--fd-make-builder paths)))
+ (find-file (consult--find prompt builder initial))))
+
+;;;;; Command: consult-locate
+
+(defun consult--locate-builder (input)
+ "Build command line from INPUT."
+ (pcase-let ((`(,arg . ,opts) (consult--command-split input)))
+ (unless (string-blank-p arg)
+ (cons (append (consult--build-args consult-locate-args)
+ (consult--split-escaped arg) opts)
+ (cdr (consult--default-regexp-compiler arg 'basic t))))))
+
+;;;###autoload
+(defun consult-locate (&optional initial)
+ "Search with `locate' for files which match input given INITIAL input.
+
+The input is treated literally such that locate can take advantage of
+the locate database index. Regular expressions would often force a slow
+linear search through the entire database. The locate process is started
+asynchronously, similar to `consult-grep'. See `consult-grep' for more
+details regarding the asynchronous search."
+ (interactive)
+ (find-file (consult--find "Locate: " #'consult--locate-builder initial)))
+
+;;;;; Command: consult-man
+
+(defun consult--man-builder (input)
+ "Build command line from INPUT."
+ (pcase-let* ((`(,arg . ,opts) (consult--command-split input))
+ (`(,re . ,hl) (consult--compile-regexp arg 'extended t)))
+ (when re
+ (cons (append (consult--build-args consult-man-args)
+ (list (consult--join-regexps re 'extended))
+ opts)
+ hl))))
+
+(defun consult--man-format (lines)
+ "Format man candidates from LINES."
+ (let ((candidates))
+ (save-match-data
+ (dolist (str lines)
+ (when (string-match "\\`\\(.*?\\([^ ]+\\) *(\\([^,)]+\\)[^)]*).*?\\) +- +\\(.*\\)\\'" str)
+ (let* ((names (match-string 1 str))
+ (name (match-string 2 str))
+ (section (match-string 3 str))
+ (desc (match-string 4 str))
+ (cand (format "%s - %s" names desc)))
+ (add-text-properties 0 (length names)
+ (list 'face 'consult-file
+ 'consult-man (concat section " " name))
+ cand)
+ (push cand candidates)))))
+ (nreverse candidates)))
+
+(defun consult--man-preview ()
+ "Create preview function for man pages."
+ (let ((preview (consult--buffer-preview))
+ (orig (buffer-list))
+ buffers)
+ (lambda (action cand)
+ (unless cand
+ (pcase-dolist (`(,_ . ,buf) buffers)
+ (kill-buffer buf))
+ (setq buffers nil))
+ (let ((consult--buffer-display #'switch-to-buffer-other-window))
+ (funcall preview action
+ (and cand
+ (eq action 'preview)
+ (or (cdr (assoc cand buffers))
+ (when-let* ((buf (consult--man-action cand t)))
+ (unless (memq buf orig)
+ (cl-callf consult--preview-add-buffer
+ buffers (cons cand buf)))
+ buf))))))))
+
+(defun consult--man-action (page &optional nodisplay)
+ "Create man PAGE buffer, do not display if NODISPLAY is non-nil."
+ (dlet ((Man-prefer-synchronous-call t)
+ (Man-notify-method (and (not nodisplay) 'aggressive))
+ (inhibit-message t)
+ (message-log-max nil))
+ (when-let* ((buf (man page))
+ ((buffer-live-p buf)))
+ (with-current-buffer buf
+ (goto-char (point-min))
+ (current-buffer)))))
+
+(consult--define-state man)
+
+;;;###autoload
+(defun consult-man (&optional initial)
+ "Search for man page given INITIAL input.
+
+The input string is not preprocessed and passed literally to the
+underlying man commands. The man process is started asynchronously,
+similar to `consult-grep'. See `consult-grep' for more details regarding
+the asynchronous search."
+ (interactive)
+ (consult--read
+ (consult--process-collection #'consult--man-builder
+ :transform (consult--async-transform #'consult--man-format)
+ :highlight t)
+ :prompt "Manual entry: "
+ :require-match t
+ :category 'consult-man
+ :state (consult--man-state)
+ :lookup (apply-partially #'consult--lookup-prop 'consult-man)
+ :initial initial
+ :add-history (thing-at-point 'symbol)
+ :history '(:input consult--man-history)))
+
+;;;; Integration with completion systems
+
+;;;;; Integration: Default *Completions*
+
+(defun consult--default-completion-list-preview ()
+ "Preview candidate at point in *Completions* buffer."
+ (when-let* ((win (active-minibuffer-window))
+ (buf (window-buffer win))
+ (fun (buffer-local-value 'consult--preview-function buf)))
+ (funcall fun)))
+
+(defun consult--default-completion-list-preview-setup ()
+ "Setup preview at point in *Completions* buffer."
+ (add-hook 'post-command-hook #'consult--default-completion-list-preview nil 'local))
+(add-hook 'completion-list-mode-hook #'consult--default-completion-list-preview-setup)
+
+(defun consult--default-completion-minibuffer-candidate ()
+ "Return current minibuffer candidate from default completion system or Icomplete."
+ (when (minibufferp)
+ (let ((content (minibuffer-contents-no-properties)))
+ ;; When the current minibuffer content matches a candidate, return it!
+ (if (test-completion content
+ minibuffer-completion-table
+ minibuffer-completion-predicate)
+ content
+ ;; Return the full first candidate of the sorted completion list.
+ (when-let* ((completions (completion-all-sorted-completions)))
+ (concat
+ (substring content 0 (or (cdr (last completions)) 0))
+ (car completions)))))))
+
+(defun consult--default-completion-list-candidate ()
+ "Return current candidate at point from completions buffer."
+ (when-let* ((buffer
+ (if (derived-mode-p #'completion-list-mode)
+ ;; Use current buffer if already inside *Completions* buffer
+ (current-buffer)
+ ;; Otherwise check if there is an active *Completions* buffer
+ ;; which can be controlled remotely from the minibuffer. See
+ ;; the setting `minibuffer-visible-completions'.
+ (when-let* ((bound-and-true-p minibuffer-visible-completions)
+ (window (get-buffer-window "*Completions*" 'visible))
+ (buffer (window-buffer window))
+ ((eq (buffer-local-value 'completion-reference-buffer buffer)
+ (window-buffer (active-minibuffer-window)))))
+ buffer))))
+ (with-current-buffer buffer
+ ;; TODO Use `completion-list-candidate-at-point' on Emacs 31
+ (let (beg)
+ (when (cond
+ ((and (not (eobp)) (get-text-property (point) 'completion--string))
+ (setq beg (1+ (point))))
+ ((and (not (bobp)) (get-text-property (1- (point)) 'completion--string))
+ (setq beg (point))))
+ (get-text-property (previous-single-property-change beg 'completion--string)
+ 'completion--string))))))
+
+(defun consult--default-completion-list-refresh ()
+ "Refresh default completion UI."
+ (when (and (bound-and-true-p completion-eager-update)
+ (bound-and-true-p completion-eager-display)
+ (not (bound-and-true-p vertico-mode))
+ (not (bound-and-true-p icomplete-mode)))
+ (minibuffer-completion-help)))
+
+;;;;; Integration: Vertico
+
+(defvar vertico--input)
+
+(defun consult--vertico-candidate ()
+ "Return current candidate for Consult preview."
+ (declare-function vertico--candidate "ext:vertico")
+ (and vertico--input (vertico--candidate 'highlight)))
+
+(defun consult--vertico-refresh ()
+ "Refresh completion UI."
+ (declare-function vertico--exhibit "ext:vertico")
+ (when vertico--input
+ (setq vertico--input t)
+ (vertico--exhibit)))
+
+(with-eval-after-load 'vertico
+ (add-hook 'consult--completion-candidate-hook #'consult--vertico-candidate)
+ (add-hook 'consult--completion-refresh-hook #'consult--vertico-refresh)
+ (define-key consult-async-map [remap vertico-insert] 'vertico-next-group))
+
+;;;;; Integration: Mct
+
+(with-eval-after-load 'mct
+ (add-hook 'consult--completion-refresh-hook 'mct--live-completions-refresh))
+
+;;;;; Integration: Icomplete
+
+(defun consult--icomplete-refresh ()
+ "Refresh icomplete view."
+ (defvar icomplete-mode)
+ (declare-function icomplete-exhibit "icomplete")
+ (when icomplete-mode
+ (let ((top (car completion-all-sorted-completions)))
+ (completion--flush-all-sorted-completions)
+ ;; force flushing, otherwise narrowing is broken!
+ (setq completion-all-sorted-completions nil)
+ (when top
+ (let* ((completions (completion-all-sorted-completions))
+ (last (last completions))
+ (before)) ;; completions before top
+ ;; warning: completions is an improper list
+ (while (consp completions)
+ (if (equal (car completions) top)
+ (progn
+ (setcdr last (append (nreverse before) (cdr last)))
+ (setq completion-all-sorted-completions completions
+ completions nil))
+ (push (car completions) before)
+ (setq completions (cdr completions)))))))
+ (icomplete-exhibit)))
+
+(with-eval-after-load 'icomplete
+ (add-hook 'consult--completion-refresh-hook #'consult--icomplete-refresh))
+
+(provide 'consult)
+;;; consult.el ends here
diff --git a/.config/emacs/lisp/minadstack/corfu-history.el b/.config/emacs/lisp/minadstack/corfu-history.el
new file mode 100644
index 0000000..ba7af47
--- /dev/null
+++ b/.config/emacs/lisp/minadstack/corfu-history.el
@@ -0,0 +1,114 @@
+;;; corfu-history.el --- Sorting by history for Corfu -*- lexical-binding: t -*-
+
+;; Copyright (C) 2022-2026 Free Software Foundation, Inc.
+
+;; Author: Daniel Mendler <mail@daniel-mendler.de>
+;; Maintainer: Daniel Mendler <mail@daniel-mendler.de>
+;; Created: 2022
+;; Version: 2.10
+;; Package-Requires: ((emacs "29.1") (compat "31") (corfu "2.10"))
+;; URL: https://github.com/minad/corfu
+
+;; This file is 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:
+
+;; Enable `corfu-history-mode' to sort candidates by their history position.
+;; The recently selected candidates are stored in the `corfu-history' variable.
+;; If `history-delete-duplicates' is nil, duplicate elements are ranked higher
+;; with exponential decay. In order to save the history across Emacs sessions,
+;; enable `savehist-mode'.
+;;
+;; (corfu-history-mode)
+;; (savehist-mode)
+
+;;; Code:
+
+(require 'corfu)
+(eval-when-compile
+ (require 'cl-lib))
+
+(defvar corfu-history nil
+ "History of Corfu candidates.
+The maximum length is determined by the variable `history-length'
+or the property `history-length' of `corfu-history'.")
+
+(defvar corfu-history--hash nil
+ "Hash table of Corfu candidates.")
+
+(defcustom corfu-history-duplicate 10
+ "History position shift for duplicate history elements.
+The more often a duplicate element occurs in the history, the earlier it
+appears in the completion list. The shift decays exponentially with
+`corfu-history-decay'. Note that duplicates occur only if
+`history-delete-duplicates' is disabled."
+ :type 'number
+ :group 'corfu)
+
+(defcustom corfu-history-decay 10
+ "Exponential decay for the position shift of duplicate elements.
+The shift will decay away after `corfu-history-duplicate' times
+`corfu-history-decay' history elements."
+ :type 'number
+ :group 'corfu)
+
+(defun corfu-history--sort-predicate (x y)
+ "Sorting predicate which compares X and Y."
+ (or (< (cdr x) (cdr y))
+ (and (= (cdr x) (cdr y))
+ (corfu--length-string< (car x) (car y)))))
+
+(defun corfu-history--sort (cands)
+ "Sort CANDS by history."
+ (unless corfu-history--hash
+ (let ((ht (make-hash-table :test #'equal :size (length corfu-history)))
+ (decay (/ -1.0 (* corfu-history-duplicate corfu-history-decay))))
+ (cl-loop for elem in corfu-history for idx from 0
+ for r = (if-let* ((r (gethash elem ht)))
+ ;; Reduce duplicate rank with exponential decay.
+ (- r (round (* corfu-history-duplicate (exp (* decay idx)))))
+ ;; Never outrank the most recent element.
+ (if (= idx 0) (/ most-negative-fixnum 2) idx))
+ do (puthash elem r ht))
+ (setq corfu-history--hash ht)))
+ (cl-loop for ht = corfu-history--hash for max = most-positive-fixnum
+ for cand on cands do
+ (setcar cand (cons (car cand) (gethash (car cand) ht max))))
+ (setq cands (sort cands #'corfu-history--sort-predicate))
+ (cl-loop for cand on cands do (setcar cand (caar cand)))
+ cands)
+
+;;;###autoload
+(define-minor-mode corfu-history-mode
+ "Update Corfu history and sort completions by history."
+ :global t :group 'corfu
+ (if corfu-history-mode
+ (add-function :override corfu-sort-function #'corfu-history--sort)
+ (remove-function corfu-sort-function #'corfu-history--sort)))
+
+(cl-defmethod corfu--insert :before (_status &context (corfu-history-mode (eql t)))
+ (when (>= corfu--index 0)
+ (unless (or (not (bound-and-true-p savehist-mode))
+ (memq 'corfu-history (bound-and-true-p savehist-ignored-variables)))
+ (defvar savehist-minibuffer-history-variables)
+ (add-to-list 'savehist-minibuffer-history-variables 'corfu-history))
+ (add-to-history 'corfu-history
+ (substring-no-properties
+ (nth corfu--index corfu--candidates)))
+ (setq corfu-history--hash nil)))
+
+(provide 'corfu-history)
+;;; corfu-history.el ends here
diff --git a/.config/emacs/lisp/minadstack/corfu.el b/.config/emacs/lisp/minadstack/corfu.el
new file mode 100644
index 0000000..bd3b314
--- /dev/null
+++ b/.config/emacs/lisp/minadstack/corfu.el
@@ -0,0 +1,1444 @@
+;;; corfu.el --- COmpletion in Region FUnction -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021-2026 Free Software Foundation, Inc.
+
+;; Author: Daniel Mendler <mail@daniel-mendler.de>
+;; Maintainer: Daniel Mendler <mail@daniel-mendler.de>
+;; Created: 2021
+;; Version: 2.10
+;; Package-Requires: ((emacs "29.1") (compat "31"))
+;; URL: https://github.com/minad/corfu
+;; Keywords: abbrev, convenience, matching, completion, text
+
+;; This file is 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:
+
+;; Corfu enhances in-buffer completion with a small completion popup.
+;; The current candidates are shown in a popup below or above the
+;; point. The candidates can be selected by moving up and down.
+;; Corfu is the minimalistic in-buffer completion counterpart of the
+;; Vertico minibuffer UI.
+
+;;; Code:
+
+(require 'compat)
+(eval-when-compile
+ (require 'cl-lib)
+ (require 'subr-x))
+
+(defgroup corfu nil
+ "COmpletion in Region FUnction."
+ :link '(info-link :tag "Info Manual" "(corfu)")
+ :link '(url-link :tag "Website" "https://github.com/minad/corfu")
+ :link '(url-link :tag "Wiki" "https://github.com/minad/corfu/wiki")
+ :link '(emacs-library-link :tag "Library Source" "corfu.el")
+ :group 'convenience
+ :group 'tools
+ :group 'matching
+ :prefix "corfu-")
+
+(defcustom corfu-count 10
+ "Maximal number of candidates to show."
+ :type 'natnum)
+
+(defcustom corfu-scroll-margin 2
+ "Number of lines at the top and bottom when scrolling.
+The value should lie between 0 and corfu-count/2."
+ :type 'natnum)
+
+(defcustom corfu-min-width 15
+ "Popup minimum width in characters."
+ :type 'natnum)
+
+(defcustom corfu-max-width 100
+ "Popup maximum width in characters."
+ :type 'natnum)
+
+(defcustom corfu-cycle nil
+ "Enable cycling for `corfu-next' and `corfu-previous'."
+ :type 'boolean)
+
+(defcustom corfu-on-exact-match nil
+ "Configure how a single exact match should be handled.
+- nil: No special handling, continue completion.
+- insert: Insert candidate, quit and call the `:exit-function'.
+- quit: Quit completion without further action.
+- show: Initiate completion even for a single match only."
+ :type '(choice (const insert) (const show) (const quit) (const nil)))
+
+(defcustom corfu-continue-commands
+ '(ignore universal-argument universal-argument-more digit-argument
+ "\\`corfu-" "\\`scroll-other-window")
+ "Continue Corfu completion after executing these commands.
+The list can contain either command symbols or regular expressions."
+ :type '(repeat (choice regexp symbol)))
+
+(defcustom corfu-preview-current 'insert
+ "Preview currently selected candidate.
+If the variable has the value `insert', the candidate is automatically
+inserted on further input."
+ :type '(choice boolean (const insert)))
+
+(defcustom corfu-preselect 'valid
+ "Configure if the prompt or first candidate is preselected.
+- prompt: Always select the prompt.
+- first: Always select the first candidate.
+- valid: Only select the prompt if valid and not equal to the first candidate.
+- directory: Like first, but select the prompt if it is a directory."
+ :type '(choice (const prompt) (const valid) (const first) (const directory)))
+
+(defcustom corfu-separator ?\s
+ "Component separator character.
+The character used for separating components in the input. The presence
+of this separator character will inhibit quitting at completion
+boundaries, so that any further characters can be entered. To enter the
+first separator character, call `corfu-insert-separator' (bound to M-SPC
+by default). Useful for multi-component completion styles such as
+Orderless."
+ :type 'character)
+
+(defcustom corfu-quit-at-boundary 'separator
+ "Automatically quit at completion boundary.
+nil: Never quit at completion boundary.
+t: Always quit at completion boundary.
+separator: Quit at boundary if no `corfu-separator' has been inserted."
+ :type '(choice boolean (const separator)))
+
+(defcustom corfu-quit-no-match 'separator
+ "Automatically quit if no matching candidate is found.
+When staying alive even if there is no match a warning message is
+shown in the popup.
+nil: Stay alive even if there is no match.
+t: Quit if there is no match.
+separator: Only stay alive if there is no match and
+`corfu-separator' has been inserted."
+ :type '(choice boolean (const separator)))
+
+(defcustom corfu-left-margin-width 0.5
+ "Width of the left margin in units of the character width."
+ :type 'float)
+
+(defcustom corfu-right-margin-width 0.5
+ "Width of the right margin in units of the character width."
+ :type 'float)
+
+(defcustom corfu-bar-width 0.2
+ "Width of the bar in units of the character width."
+ :type 'float)
+
+(defcustom corfu-border-width 1
+ "Width of the border in pixels, only applies to GUI Emacs."
+ :type 'natnum)
+
+(defcustom corfu-margin-formatters nil
+ "Registry for margin formatter functions.
+Each function of the list is called with the completion metadata as
+argument until an appropriate formatter is found. The function should
+return a formatter function, which takes the candidate string and must
+return a string, possibly an icon. In order to preserve correct popup
+alignment, the length and display width of the returned string must
+precisely span the same number of characters of the fixed-width popup
+font. For example the kind-icon package returns a string of length 3
+with a display width of 3 characters."
+ :type 'hook)
+
+(defcustom corfu-sort-function #'corfu-sort-length-alpha
+ "Default sorting function.
+This function is used if the completion table does not specify a
+`display-sort-function'."
+ :type `(choice
+ (const :tag "No sorting" nil)
+ (const :tag "By length and alpha" ,#'corfu-sort-length-alpha)
+ (function :tag "Custom function")))
+
+(defcustom corfu-sort-override-function nil
+ "Override sort function which overrides the `display-sort-function'.
+This function is used even if a completion table specifies its
+own sort function."
+ :type '(choice (const nil) function))
+
+(defcustom corfu-auto nil
+ "Enable auto completion.
+Auto completion is disabled by default for safety and unobtrusiveness.
+Note that auto completion is particularly dangerous in untrusted files
+since some completion functions may perform arbitrary code execution,
+notably the Emacs built-in `elisp-completion-at-point'. See also the
+settings `corfu-auto-delay', `corfu-auto-prefix' and
+`corfu-auto-commands'."
+ :type 'boolean)
+
+(defgroup corfu-faces nil
+ "Faces used by Corfu."
+ :group 'corfu
+ :group 'faces)
+
+(defface corfu-default
+ '((((class color) (min-colors 88) (background dark)) :background "#191a1b")
+ (((class color) (min-colors 88) (background light)) :background "#f0f0f0")
+ (((background dark)) :background "gray" :foreground "black")
+ (t :background "gray"))
+ "Default face, foreground and background colors used for the popup.")
+
+(defface corfu-current
+ '((((class color) (min-colors 88) (background dark))
+ :background "#00415e" :foreground "white" :extend t)
+ (((class color) (min-colors 88) (background light))
+ :background "#c0efff" :foreground "black" :extend t)
+ (t :background "magenta" :foreground "white" :extend t))
+ "Face used to highlight the currently selected candidate.")
+
+(defface corfu-bar
+ '((((class color) (min-colors 88) (background dark)) :background "#a8a8a8")
+ (((class color) (min-colors 88) (background light)) :background "#505050")
+ (t :background "black"))
+ "The background color is used for the scrollbar indicator.")
+
+(defface corfu-border
+ '((((class color) (min-colors 88) (background dark)) :background "#323232")
+ (((class color) (min-colors 88) (background light)) :background "#d7d7d7")
+ (t :background "gray"))
+ "The background color used for the thin border.")
+
+(defface corfu-annotations
+ '((t :inherit completions-annotations))
+ "Face used for annotations.")
+
+(defface corfu-deprecated
+ '((t :inherit shadow :strike-through t))
+ "Face used for deprecated candidates.")
+
+(defvar-keymap corfu-mode-map
+ :doc "Keymap used when `corfu-mode' is active.")
+
+(defvar-keymap corfu-map
+ :doc "Keymap used when popup is shown."
+ "<remap> <move-beginning-of-line>" #'corfu-prompt-beginning
+ "<remap> <move-end-of-line>" #'corfu-prompt-end
+ "<remap> <beginning-of-buffer>" #'corfu-first
+ "<remap> <end-of-buffer>" #'corfu-last
+ "<remap> <scroll-down-command>" #'corfu-scroll-down
+ "<remap> <scroll-up-command>" #'corfu-scroll-up
+ "<remap> <next-line>" #'corfu-next
+ "<remap> <previous-line>" #'corfu-previous
+ "<remap> <completion-at-point>" #'corfu-complete
+ "<remap> <keyboard-escape-quit>" #'corfu-reset
+ "<down>" #'corfu-next
+ "<up>" #'corfu-previous
+ "M-n" #'corfu-next
+ "M-p" #'corfu-previous
+ "C-g" #'corfu-quit
+ "RET" #'corfu-insert
+ "TAB" #'corfu-complete
+ "M-TAB" #'corfu-expand
+ "M-g" 'corfu-info-location
+ "M-h" 'corfu-info-documentation
+ "M-SPC" #'corfu-insert-separator)
+
+(defvar corfu--candidates nil
+ "List of candidates.")
+
+(defvar corfu--metadata nil
+ "Completion metadata.")
+
+(defvar corfu--base ""
+ "Base string, which is concatenated with the candidate.")
+
+(defvar corfu--total 0
+ "Length of the candidate list `corfu--candidates'.")
+
+(defvar corfu--hilit #'identity
+ "Lazy candidate highlighting function.")
+
+(defvar corfu--index -1
+ "Index of current candidate or negative for prompt selection.")
+
+(defvar corfu--preselect -1
+ "Index of preselected candidate, negative for prompt selection.")
+
+(defvar corfu--scroll 0
+ "Scroll position.")
+
+(defvar corfu--input nil
+ "Cons of last prompt contents and point.")
+
+(defvar corfu--preview-ov nil
+ "Current candidate overlay.")
+
+(defvar corfu--change-group nil
+ "Undo change group.")
+
+(defvar corfu--frame nil
+ "Popup frame.")
+
+(defvar corfu--width 0
+ "Popup width of current completion to reduce width fluctuations.")
+
+(defconst corfu--initial-state
+ (mapcar
+ (lambda (k) (cons k (symbol-value k)))
+ '(corfu--base
+ corfu--candidates
+ corfu--hilit
+ corfu--index
+ corfu--preselect
+ corfu--scroll
+ corfu--input
+ corfu--total
+ corfu--preview-ov
+ corfu--change-group
+ corfu--metadata
+ corfu--width))
+ "Initial Corfu state.")
+
+(defvar corfu--frame-parameters
+ '((no-accept-focus . t)
+ (no-focus-on-map . t)
+ (min-width . t)
+ (min-height . t)
+ (border-width . 0)
+ (outer-border-width . 0)
+ (vertical-scroll-bars . nil)
+ (horizontal-scroll-bars . nil)
+ (menu-bar-lines . 0)
+ (tool-bar-lines . 0)
+ (tab-bar-lines . 0)
+ (tab-bar-lines-keep-state . t)
+ (no-other-frame . t)
+ (unsplittable . t)
+ (undecorated . t)
+ (fullscreen . nil)
+ (cursor-type . nil)
+ (no-special-glyphs . t)
+ (desktop-dont-save . t)
+ (inhibit-double-buffering . t)) ;; Avoid display artifacts on X/Gtk builds
+ "Default child frame parameters.
+It is recommended to avoid changing these parameters.")
+
+(defvar corfu--buffer-parameters
+ '((mode-line-format . nil)
+ (header-line-format . nil)
+ (tab-line-format . nil)
+ (tab-bar-format . nil)
+ (frame-title-format . "")
+ (truncate-lines . t)
+ (cursor-in-non-selected-windows . nil)
+ (cursor-type . nil)
+ (show-trailing-whitespace . nil)
+ (display-line-numbers . nil)
+ (left-fringe-width . 0)
+ (right-fringe-width . 0)
+ (left-margin-width . 0)
+ (right-margin-width . 0)
+ (fringes-outside-margins . 0)
+ (fringe-indicator-alist (continuation) (truncation))
+ (indicate-empty-lines . nil)
+ (indicate-buffer-boundaries . nil)
+ (buffer-read-only . t)
+ (pixel-scroll-precision-mode . nil))
+ "Default child frame buffer parameters.
+It is recommended to avoid changing these parameters.")
+
+(defvar corfu--mouse-ignore-map
+ (let ((map (define-keymap "<touchscreen-begin>" #'ignore)))
+ (dotimes (i 7)
+ (dolist (k '(mouse down-mouse drag-mouse double-mouse triple-mouse))
+ (keymap-set map (format "<%s-%s>" k (1+ i)) #'ignore)))
+ map)
+ "Ignore all mouse clicks.")
+
+(defun corfu--replace (beg end str)
+ "Replace range between BEG and END with STR."
+ (unless (equal str (buffer-substring-no-properties beg end))
+ (completion--replace beg end str)))
+
+(defun corfu--capf-wrapper (fun &optional prefix)
+ "Wrapper for `completion-at-point' FUN.
+The wrapper determines if the Capf is applicable at the current
+position, performs sanity checking on the returned result and computes
+the initial completion state. PREFIX is the minimum prefix length."
+ (pcase (funcall fun)
+ (`(,beg ,end ,table . ,plist)
+ (and (integer-or-marker-p beg) ;; Valid Capf result
+ (<= beg (point) end) ;; Sanity checking
+ ;; Check minimal prefix length if given.
+ (or (not prefix)
+ (let ((len (or (plist-get plist :company-prefix-length)
+ (- (point) beg))))
+ (or (eq len t) (>= len prefix))))
+ (let* ((str (buffer-substring-no-properties beg end))
+ (pt (- (point) beg))
+ (pred (plist-get plist :predicate))
+ (state (corfu--compute (cons str pt) table pred)))
+ (cond ((alist-get 'corfu--candidates state)
+ `(,fun ,beg ,end ,table :corfu--state ,state ,@plist))
+ ;; Stop with empty result for exclusive Capf.
+ ((not (eq 'no (plist-get plist :exclusive)))
+ '(nil))))))))
+
+(defun corfu--make-buffer (name)
+ "Create buffer with NAME."
+ (let ((fr face-remapping-alist)
+ (ls line-spacing)
+ (buffer (get-buffer-create name)))
+ (with-current-buffer buffer
+ ;;; XXX HACK install mouse ignore map
+ (use-local-map corfu--mouse-ignore-map)
+ (dolist (var corfu--buffer-parameters)
+ (set-local (car var) (cdr var)))
+ (setq-local face-remapping-alist (copy-tree fr)
+ line-spacing ls)
+ (cl-pushnew 'corfu-default (alist-get 'default face-remapping-alist))
+ buffer)))
+
+(defvar corfu--gtk-resize-child-frames
+ (let ((case-fold-search t))
+ ;; XXX HACK to fix resizing on gtk3/gnome taken from posframe.el
+ ;; More information:
+ ;; * https://github.com/minad/corfu/issues/17
+ ;; * https://gitlab.gnome.org/GNOME/mutter/-/issues/840
+ ;; * https://lists.gnu.org/archive/html/emacs-devel/2020-02/msg00001.html
+ (and (string-match-p "gtk3" system-configuration-features)
+ (string-match-p "gnome\\|cinnamon"
+ (or (getenv "XDG_CURRENT_DESKTOP")
+ (getenv "DESKTOP_SESSION") ""))
+ 'resize-mode)))
+
+;; Not present on non-gtk/non-x builds
+(defvar x-gtk-resize-child-frames)
+(defvar x-fast-protocol-requests)
+
+;; Function adapted from posframe.el by tumashu
+(defun corfu--make-frame (frame x y width height)
+ "Show current buffer in child frame at X/Y with WIDTH/HEIGHT.
+FRAME is the existing frame."
+ (when-let* (((frame-live-p frame))
+ (timer (frame-parameter frame 'corfu--hide-timer)))
+ (cancel-timer timer)
+ (set-frame-parameter frame 'corfu--hide-timer nil))
+ (let* ((window-min-height 1)
+ (window-min-width 1)
+ (inhibit-redisplay t)
+ (x-fast-protocol-requests t)
+ (x-gtk-resize-child-frames corfu--gtk-resize-child-frames)
+ (before-make-frame-hook)
+ (after-make-frame-functions)
+ (parent (window-frame))
+ (graphic (display-graphic-p parent))
+ (params `((background-color
+ . ,(face-attribute 'corfu-default :background nil 'default))
+ (font . ,(frame-parameter parent 'font))
+ (right-fringe . ,right-fringe-width)
+ (left-fringe . ,left-fringe-width)
+ (internal-border-width . ,corfu-border-width)
+ (child-frame-border-width . ,corfu-border-width)
+ ,@corfu--frame-parameters)))
+ (unless (and (frame-live-p frame)
+ (eq (frame-parent frame)
+ (and (not (and graphic (bound-and-true-p exwm--connection)))
+ parent))
+ ;; Handle mixed tty/graphical sessions
+ (eq graphic (display-graphic-p frame))
+ ;; If there is more than one window, `frame-root-window' may
+ ;; return nil. Recreate the frame in this case.
+ (window-live-p (frame-root-window frame)))
+ (when frame (delete-frame frame))
+ (setq frame (make-frame
+ `((name . ,(if graphic "EmacsCorfuGUI" "EmacsCorfuTTY"))
+ (parent-frame . ,parent)
+ (minibuffer . ,(minibuffer-window parent))
+ (width . 0) (height . 0) (visibility . nil)
+ ,@params))))
+ ;; XXX HACK Setting the same frame-parameter/face-background is not a nop.
+ ;; Check before applying the setting. Without the check, the frame flickers
+ ;; on Mac. We have to apply the face background before adjusting the frame
+ ;; parameter, otherwise the border is not updated.
+ (let ((new (face-attribute 'corfu-border :background nil 'default)))
+ (unless (equal (face-attribute 'internal-border :background frame 'default) new)
+ (set-face-background 'internal-border new frame))
+ ;; XXX The Emacs Mac Port does not support `internal-border', we also have
+ ;; to set `child-frame-border'.
+ (unless (equal (face-attribute 'child-frame-border :background frame 'default) new)
+ (set-face-background 'child-frame-border new frame)))
+ ;; Reset frame parameters if they changed. For example `tool-bar-mode'
+ ;; overrides the parameter `tool-bar-lines' for every frame, including child
+ ;; frames. The child frame API is a pleasure to work with. It is full of
+ ;; lovely surprises.
+ (let* ((win (frame-root-window frame))
+ (is (frame-parameters frame))
+ (diff (cl-loop for p in params for (k . v) = p
+ unless (equal (alist-get k is) v) collect p)))
+ (when diff (modify-frame-parameters frame diff))
+ ;; XXX HACK: `set-window-buffer' must be called to force fringe update.
+ (when (or diff (not (eq (window-buffer win) (current-buffer))))
+ (set-window-buffer win (current-buffer)))
+ ;; Disallow selection of root window (gh:minad/corfu#63)
+ (set-window-parameter win 'no-delete-other-windows t)
+ (set-window-parameter win 'no-other-window t)
+ ;; Mark window as dedicated to prevent frame reuse (gh:minad/corfu#60)
+ (set-window-dedicated-p win t))
+ (redirect-frame-focus frame parent)
+ (pcase-let* ((`(,ox ,oy ,right ,bottom) (frame-edges frame 'outer-edges))
+ (border (* 2 corfu-border-width))
+ (ow (- (- right ox) left-fringe-width right-fringe-width border))
+ (oh (- (- bottom oy) border))
+ (pos-change (or (/= x ox) (/= y oy)))
+ (size-change (or (/= ow width) (/= oh height))))
+ (cond
+ ((and pos-change size-change)
+ ;; TODO: New Emacs 31 function for faster resizing/movement in one go.
+ ;; Add this function to Compat 31 as backport.
+ (static-if (fboundp 'set-frame-size-and-position-pixelwise)
+ (set-frame-size-and-position-pixelwise frame width height x y)
+ (set-frame-size frame width height t)
+ (set-frame-position frame x y)))
+ (pos-change (set-frame-position frame x y))
+ (size-change (set-frame-size frame width height t)))))
+ (make-frame-visible frame)
+ ;; Unparent child frame if EXWM is used, otherwise EXWM buffers are drawn on
+ ;; top of the Corfu child frame.
+ (when (and (bound-and-true-p exwm--connection)
+ (display-graphic-p frame) (frame-parent frame))
+ (redisplay t)
+ (set-frame-parameter frame 'parent-frame nil))
+ frame)
+
+(defun corfu--hide-frame-deferred (frame)
+ "Deferred hiding of child FRAME."
+ (when (and (frame-live-p frame) (frame-visible-p frame))
+ (set-frame-parameter frame 'corfu--hide-timer nil)
+ (make-frame-invisible frame)
+ (with-current-buffer (window-buffer (frame-root-window frame))
+ (with-silent-modifications
+ (delete-region (point-min) (point-max))))))
+
+(defun corfu--hide-frame (frame)
+ "Hide child FRAME."
+ (when (and (frame-live-p frame) (frame-visible-p frame))
+ (cond
+ ((not (display-graphic-p frame))
+ (corfu--hide-frame-deferred frame))
+ ((not (frame-parameter frame 'corfu--hide-timer))
+ (set-frame-parameter
+ frame 'corfu--hide-timer
+ (run-at-time 0 nil #'corfu--hide-frame-deferred frame))))))
+
+(defun corfu--move-to-front (elem list)
+ "Move all ELEM (also duplicates) to front of LIST."
+ (if (member elem list)
+ (nconc (cl-loop for x in list if (equal x elem) collect x)
+ (delete elem list))
+ list))
+
+(defun corfu--filter-completions (&rest args)
+ "Compute all completions for ARGS with lazy highlighting."
+ (dlet ((completion-lazy-hilit t) (completion-lazy-hilit-fn nil))
+ (static-if (>= emacs-major-version 30)
+ (cons (apply #'completion-all-completions args) completion-lazy-hilit-fn)
+ (cl-letf* ((orig-pcm (symbol-function #'completion-pcm--hilit-commonality))
+ (orig-flex (symbol-function #'completion-flex-all-completions))
+ ((symbol-function #'completion-flex-all-completions)
+ (lambda (&rest args)
+ ;; Unfortunately for flex we have to undo the lazy highlighting, since flex uses
+ ;; the completion-score for sorting, which is applied during highlighting.
+ (cl-letf (((symbol-function #'completion-pcm--hilit-commonality) orig-pcm))
+ (apply orig-flex args))))
+ ((symbol-function #'completion-pcm--hilit-commonality)
+ (lambda (pattern cands)
+ (setq completion-lazy-hilit-fn
+ (lambda (x)
+ ;; `completion-pcm--hilit-commonality' sometimes throws an internal error
+ ;; for example when entering "/sudo:://u".
+ (condition-case nil
+ (car (completion-pcm--hilit-commonality pattern (list x)))
+ (t x))))
+ cands))
+ ((symbol-function #'completion-hilit-commonality)
+ (lambda (cands prefix &optional base)
+ (setq completion-lazy-hilit-fn
+ (lambda (x) (car (completion-hilit-commonality (list x) prefix base))))
+ (and cands (nconc cands base)))))
+ (cons (apply #'completion-all-completions args) completion-lazy-hilit-fn)))))
+
+(defun corfu--try-completion (str table pred pt &optional md)
+ "Complete STR given TABLE, predicate PRED, point PT and optional metadata MD."
+ (setq md (or md (completion-metadata (substring str 0 pt) table pred)))
+ (completion-try-completion str table pred pt md))
+
+(defsubst corfu--length-string< (x y)
+ "Sorting predicate which compares X and Y first by length then by `string<'."
+ (or (< (length x) (length y)) (and (= (length x) (length y)) (string< x y))))
+
+(defmacro corfu--partition! (list form)
+ "Evaluate FORM for every element and partition LIST."
+ (cl-with-gensyms (head1 head2 tail1 tail2)
+ `(let* ((,head1 (cons nil nil))
+ (,head2 (cons nil nil))
+ (,tail1 ,head1)
+ (,tail2 ,head2))
+ (while ,list
+ (if (let ((it (car ,list))) ,form)
+ (progn
+ (setcdr ,tail1 ,list)
+ (pop ,tail1))
+ (setcdr ,tail2 ,list)
+ (pop ,tail2))
+ (pop ,list))
+ (setcdr ,tail1 (cdr ,head2))
+ (setcdr ,tail2 nil)
+ (setq ,list (cdr ,head1)))))
+
+(defun corfu--move-prefix-candidates-to-front (field cands)
+ "Move CANDS which match prefix of FIELD to the beginning."
+ (let* ((word (substring field 0
+ (seq-position field corfu-separator)))
+ (len (length word)))
+ (corfu--partition!
+ cands
+ (and (>= (length it) len)
+ (eq t (compare-strings word 0 len it 0 len
+ completion-ignore-case))))))
+
+(defun corfu--delete-dups (list)
+ "Delete `equal-including-properties' consecutive duplicates from LIST."
+ (let ((beg list))
+ (while (cdr beg)
+ (let ((end (cdr beg)))
+ (while (equal (car beg) (car end)) (pop end))
+ ;; The deduplication is quadratic in the number of duplicates. We could
+ ;; avoid this via a hash table taking properties into account.
+ (while (not (eq beg end))
+ (let ((dup beg))
+ (while (not (eq (cdr dup) end))
+ (if (equal-including-properties (car beg) (cadr dup))
+ (setcdr dup (cddr dup))
+ (pop dup))))
+ (pop beg)))))
+ list)
+
+(defun corfu--sort-function ()
+ "Return the sorting function."
+ (or corfu-sort-override-function
+ (corfu--metadata-get 'display-sort-function)
+ corfu-sort-function))
+
+(defun corfu--compute (input table pred)
+ "Compute state from INPUT, TABLE and PRED."
+ (pcase-let* ((`(,str . ,pt) input)
+ (before (substring str 0 pt))
+ (after (substring str pt))
+ (corfu--metadata (completion-metadata before table pred))
+ ;; bug#47678: `completion-boundaries' fails for `partial-completion'
+ ;; if the cursor is moved before the slashes of "~//".
+ ;; See also vertico.el which has the same issue.
+ (bounds (condition-case nil
+ (completion-boundaries before table pred after)
+ (t (cons 0 (length after)))))
+ (field (substring str (car bounds) (+ pt (cdr bounds))))
+ (completing-file (eq (corfu--metadata-get 'category) 'file))
+ (`(,all . ,hl) (corfu--filter-completions str table pred pt corfu--metadata))
+ (base (or (when-let* ((z (last all))) (prog1 (cdr z) (setcdr z nil))) 0))
+ (corfu--base (substring str 0 base))
+ (pre nil))
+ ;; Filter the ignored file extensions. We cannot use modified predicate for
+ ;; this filtering, since this breaks the special casing in the
+ ;; `completion-file-name-table' for `file-exists-p' and `file-directory-p'.
+ (when completing-file (setq all (completion-pcm--filename-try-filter all)))
+ ;; Sort using the `display-sort-function' or the Corfu sort functions, and
+ ;; delete duplicates with respect to `equal-including-properties'. This is
+ ;; a deviation from the Vertico completion UI with more aggressive
+ ;; deduplication, where candidates are compared with `equal'. Corfu
+ ;; preserves candidates which differ in their text properties. Corfu tries
+ ;; to preserve text properties as much as possible, when calling the
+ ;; `:exit-function' to help Capfs with candidate disambiguation. This
+ ;; matters in particular for Lsp backends, which produce duplicates for
+ ;; overloaded methods.
+ (setq all (funcall (or (corfu--sort-function) #'identity) all)
+ all (corfu--move-prefix-candidates-to-front field all))
+ (when (and completing-file (not (string-suffix-p "/" field)))
+ (setq all (corfu--move-to-front (concat field "/") all)))
+ (setq all (corfu--delete-dups (corfu--move-to-front field all))
+ pre (if (or (eq corfu-preselect 'prompt) (not all)
+ (and completing-file (eq corfu-preselect 'directory)
+ (= (length corfu--base) (length str))
+ (test-completion str table pred))
+ (and (eq corfu-preselect 'valid)
+ (not (equal field (car all)))
+ (not (and completing-file (equal (concat field "/") (car all))))
+ (test-completion str table pred)))
+ -1 0))
+ `((corfu--input . ,input)
+ (corfu--base . ,corfu--base)
+ (corfu--metadata . ,corfu--metadata)
+ (corfu--candidates . ,all)
+ (corfu--total . ,(length all))
+ (corfu--hilit . ,(or hl #'identity))
+ (corfu--preselect . ,pre)
+ (corfu--index . ,(or (and (>= corfu--index 0) (/= corfu--index corfu--preselect)
+ (seq-position all (nth corfu--index corfu--candidates)))
+ pre)))))
+
+(defun corfu--update (&optional interruptible)
+ "Update state, optionally INTERRUPTIBLE."
+ (pcase-let* ((`(,beg ,end ,table ,pred . ,_) completion-in-region--data)
+ (pt (- (point) beg))
+ (str (buffer-substring-no-properties beg end))
+ (input (cons str pt)))
+ (unless (equal corfu--input input)
+ ;; Redisplay such that the input is immediately shown before the expensive
+ ;; candidate recomputation (gh:minad/corfu#48). See also corresponding
+ ;; issue gh:minad/vertico#89.
+ (when interruptible (redisplay))
+ ;; Bind non-essential=t to prevent Tramp from opening new connections,
+ ;; without the user explicitly requesting it via M-TAB.
+ (pcase (let ((non-essential t))
+ (if interruptible
+ (while-no-input (corfu--compute input table pred))
+ (corfu--compute input table pred)))
+ ('nil (keyboard-quit))
+ ((and state (pred consp))
+ (dolist (s state) (set (car s) (cdr s))))))
+ input))
+
+(defun corfu--match-symbol-p (pattern sym)
+ "Return non-nil if SYM is matching an element of the PATTERN list."
+ (cl-loop with case-fold-search = nil
+ for x in (and (symbolp sym) pattern)
+ thereis (if (symbolp x)
+ (eq sym x)
+ (string-match-p x (symbol-name sym)))))
+
+(defun corfu--metadata-get (prop)
+ "Return PROP from completion metadata."
+ ;; Marginalia and various icon packages advise `completion-metadata-get' to
+ ;; inject their annotations, but are meant only for minibuffer completion.
+ ;; Therefore call `completion-metadata-get' without advices here.
+ (let ((completion-extra-properties (nth 4 completion-in-region--data)))
+ (funcall (advice--cd*r (symbol-function (compat-function completion-metadata-get)))
+ corfu--metadata prop)))
+
+(defun corfu--format-candidates (cands)
+ "Format annotated CANDS."
+ (cl-loop for c in cands do
+ (cl-loop for s in-ref c do
+ (setf s (replace-regexp-in-string "[ \t]*\n[ \t]*" " " s))))
+ (let* ((cw (cl-loop for x in cands maximize (string-width (car x))))
+ (pw (cl-loop for x in cands maximize (string-width (cadr x))))
+ (sw (cl-loop for x in cands maximize (string-width (caddr x))))
+ (width (min (max corfu--width corfu-min-width (+ pw cw sw))
+ ;; -4 because of margins and some additional safety
+ corfu-max-width (- (frame-width) 4)))
+ (trunc (not (display-graphic-p))))
+ (setq corfu--width width)
+ (list pw width
+ (cl-loop
+ for (cand prefix suffix) in cands collect
+ (let ((s (concat
+ prefix (make-string (- pw (string-width prefix)) ?\s) cand
+ (when (> sw 0)
+ (make-string (max 0 (- width pw (string-width cand)
+ (string-width suffix)))
+ ?\s))
+ suffix)))
+ (if trunc (truncate-string-to-width s width) s))))))
+
+(defun corfu--compute-scroll ()
+ "Compute new scroll position."
+ (let ((off (max (min corfu-scroll-margin (/ corfu-count 2)) 0))
+ (corr (if (= corfu-scroll-margin (/ corfu-count 2)) (1- (mod corfu-count 2)) 0)))
+ (setq corfu--scroll (min (max 0 (- corfu--total corfu-count))
+ (max 0 (+ corfu--index off 1 (- corfu-count))
+ (min (- corfu--index off corr) corfu--scroll))))))
+
+(defun corfu--candidates-popup (pos)
+ "Show candidates popup at POS."
+ (corfu--compute-scroll)
+ (pcase-let* ((last (min (+ corfu--scroll corfu-count) corfu--total))
+ (bar (ceiling (* corfu-count corfu-count) corfu--total))
+ (lo (min (- corfu-count bar 1) (floor (* corfu-count corfu--scroll) corfu--total)))
+ (`(,mf . ,acands)
+ (corfu--affixate
+ (cl-loop
+ repeat corfu-count for c in (nthcdr corfu--scroll corfu--candidates)
+ collect (funcall corfu--hilit
+ ;; bug#77754: Highlight unquoted string.
+ (substring (or (get-text-property
+ 0 'completion--unquoted c) c))))))
+ (`(,pw ,width ,fcands) (corfu--format-candidates acands))
+ ;; Disable the left margin if a margin formatter is active.
+ (corfu-left-margin-width (if mf 0 corfu-left-margin-width)))
+ ;; Nonlinearity at the end and the beginning
+ (when (/= corfu--scroll 0)
+ (setq lo (max 1 lo)))
+ (when (/= last corfu--total)
+ (setq lo (min (- corfu-count bar 2) lo)))
+ (corfu--popup-show pos pw width fcands (- corfu--index corfu--scroll)
+ (and (> corfu--total corfu-count) lo) bar)))
+
+(defun corfu--range-valid-p ()
+ "Check the completion range, return non-nil if valid."
+ (pcase-let ((buf (current-buffer))
+ (pt (point))
+ (`(,beg ,end . ,_) completion-in-region--data))
+ (and beg end
+ (eq buf (marker-buffer end)) (eq buf (window-buffer))
+ (<= beg pt end)
+ (save-excursion (goto-char beg) (<= (pos-bol) pt (pos-eol))))))
+
+(defun corfu--continue-p ()
+ "Check if completion should continue after a command.
+Corfu bails out if the current buffer changed unexpectedly or if
+point moved out of range, see `corfu--range-valid-p'. Also the
+input must satisfy the `completion-in-region-mode--predicate' and
+the last command must be listed in `corfu-continue-commands'."
+ (and (corfu--range-valid-p)
+ ;; We keep Corfu alive if a `overriding-terminal-local-map' is
+ ;; installed, e.g., the `universal-argument-map'. It would be good to
+ ;; think about a better criterion instead. Unfortunately relying on
+ ;; `this-command' alone is insufficient, since the value of
+ ;; `this-command' gets clobbered in the case of transient keymaps.
+ (or overriding-terminal-local-map
+ ;; Check if it is an explicitly listed continue command
+ (corfu--match-symbol-p corfu-continue-commands this-command)
+ (pcase-let ((`(,beg ,end . ,_) completion-in-region--data))
+ (and (or (equal (or (car corfu--input) "") "") (< beg end)) ;; Check for empty input
+ (or (not corfu-quit-at-boundary) ;; Check separator or predicate
+ (and (eq corfu-quit-at-boundary 'separator)
+ (or (eq this-command #'corfu-insert-separator)
+ ;; with separator, any further chars allowed
+ (seq-contains-p (car corfu--input) corfu-separator)))
+ (funcall completion-in-region-mode--predicate)))))))
+
+(defun corfu--preview-current-p ()
+ "Return t if the selected candidate is previewed."
+ (and corfu-preview-current (>= corfu--index 0) (/= corfu--index corfu--preselect)))
+
+(defun corfu--preview-current (beg end)
+ "Show current candidate as overlay given BEG and END."
+ (when (corfu--preview-current-p)
+ (corfu--preview-delete)
+ (setq beg (+ beg (length corfu--base))
+ corfu--preview-ov (make-overlay beg end nil))
+ (overlay-put corfu--preview-ov 'priority 1000)
+ (overlay-put corfu--preview-ov 'window (selected-window))
+ (overlay-put corfu--preview-ov (if (= beg end) 'after-string 'display)
+ (substring-no-properties (nth corfu--index corfu--candidates)))))
+
+(defun corfu--preview-delete ()
+ "Delete the preview overlay."
+ (when corfu--preview-ov
+ (delete-overlay corfu--preview-ov)
+ (setq corfu--preview-ov nil)))
+
+(defun corfu--window-change (_)
+ "Window and buffer change hook which quits Corfu."
+ (unless (corfu--range-valid-p)
+ (corfu-quit)))
+
+(defun corfu--debug (&rest _)
+ "Debugger used by `corfu--protect'."
+ (let ((inhibit-message t))
+ (require 'backtrace)
+ (declare-function backtrace-to-string "backtrace")
+ (message "Corfu detected an error:\n%s" (backtrace-to-string)))
+ (let (message-log-max)
+ (message "%s %s"
+ (propertize "Corfu detected an error:" 'face 'error)
+ (substitute-command-keys "Press \\[view-echo-area-messages] to see the stack trace")))
+ nil)
+
+(defun corfu--protect (fun)
+ "Protect FUN such that errors are caught.
+If an error occurs, the FUN is retried with `debug-on-error' enabled and
+the stack trace is shown in the *Messages* buffer."
+ (static-if (fboundp 'handler-bind) ;; Available on Emacs 30
+ (ignore-errors
+ (handler-bind ((error #'corfu--debug))
+ (funcall fun)))
+ (when (or debug-on-error (condition-case nil
+ (progn (funcall fun) nil)
+ (error t)))
+ (let ((debug-on-error t)
+ (debugger #'corfu--debug))
+ (condition-case nil
+ (funcall fun)
+ ((debug error) nil))))))
+
+(defun corfu--post-command ()
+ "Refresh Corfu after last command."
+ (corfu--protect
+ (lambda ()
+ (if (corfu--continue-p)
+ (corfu--exhibit)
+ (corfu-quit)))))
+
+(defun corfu--goto (index)
+ "Go to candidate with INDEX."
+ (setq corfu--index (max corfu--preselect (min index (1- corfu--total)))))
+
+(defun corfu--exit-function (str status cands)
+ "Call the `:exit-function' with STR and STATUS.
+Lookup STR in CANDS to restore text properties."
+ (when-let* ((exit (plist-get completion-extra-properties :exit-function)))
+ (funcall exit (or (car (member str cands)) str) status)))
+
+(defun corfu--done (str status cands)
+ "Exit completion and call the exit function with STR and STATUS.
+Lookup STR in CANDS to restore text properties."
+ (let ((completion-extra-properties (nth 4 completion-in-region--data)))
+ ;; For successful completions, amalgamate undo operations,
+ ;; such that completion can be undone in a single step.
+ (undo-amalgamate-change-group corfu--change-group)
+ (corfu-quit)
+ (corfu--exit-function str status cands)))
+
+(defun corfu--setup (beg end table pred)
+ "Setup Corfu completion state.
+See `completion-in-region' for the arguments BEG, END, TABLE, PRED."
+ (let ((props completion-extra-properties))
+ (when (eq (car props) :corfu--state)
+ (dolist (s (cadr props)) (set (car s) (cdr s)))
+ (setq props (cddr props)))
+ (setq end (if (and (markerp end) (marker-insertion-type end)) end (copy-marker end t))
+ completion-in-region--data (list (+ 0 beg) end table pred props)))
+ (completion-in-region-mode)
+ (activate-change-group (setq corfu--change-group (prepare-change-group)))
+ (setcdr (assq #'completion-in-region-mode minor-mode-overriding-map-alist) corfu-map)
+ (add-hook 'pre-command-hook #'corfu--prepare nil 'local)
+ (add-hook 'window-selection-change-functions #'corfu--window-change nil 'local)
+ (add-hook 'window-buffer-change-functions #'corfu--window-change nil 'local)
+ (add-hook 'post-command-hook #'corfu--post-command)
+ ;; Disable default post-command handling, since we have our own
+ ;; checks in `corfu--post-command'.
+ (remove-hook 'post-command-hook #'completion-in-region--postch)
+ (let ((sym (make-symbol "corfu--teardown"))
+ (buf (current-buffer)))
+ (fset sym (lambda ()
+ ;; Ensure that the tear-down runs in the correct buffer, if still alive.
+ (unless completion-in-region-mode
+ (remove-hook 'completion-in-region-mode-hook sym)
+ (corfu--teardown buf))))
+ (add-hook 'completion-in-region-mode-hook sym)))
+
+(defun corfu--in-region (&rest args)
+ "Corfu completion in region function called with ARGS."
+ ;; XXX We can get an endless loop when `completion-in-region-function' is set
+ ;; globally to `corfu--in-region'. This should never happen.
+ (apply (if (corfu--popup-support-p) #'corfu--in-region-1
+ (default-value 'completion-in-region-function))
+ args))
+
+(defun corfu--in-region-1 (beg end table pred)
+ "Complete in region, see `completion-in-region' for BEG, END, TABLE, PRED."
+ (barf-if-buffer-read-only)
+ ;; Restart the completion. This can happen for example if C-M-/
+ ;; (`dabbrev-completion') is pressed while the Corfu popup is already open.
+ (when completion-in-region-mode (corfu-quit))
+ (let* ((pt (max 0 (- (point) beg)))
+ (str (buffer-substring-no-properties beg end))
+ (input (cons str pt))
+ (md (completion-metadata (substring str 0 pt) table pred))
+ (threshold (completion--cycle-threshold md))
+ (completion-in-region-mode-predicate
+ (or completion-in-region-mode-predicate #'always)))
+ (pcase (corfu--try-completion str table pred pt md)
+ ('nil (corfu--message "No match") nil)
+ ('t (goto-char end)
+ (corfu--message "Sole match")
+ (if (eq corfu-on-exact-match 'show)
+ (corfu--setup beg end table pred)
+ (corfu--exit-function
+ str 'finished
+ (alist-get 'corfu--candidates (corfu--compute input table pred))))
+ t)
+ ((and newinp `(,newstr . ,newpt))
+ (setq end (copy-marker end t))
+ (corfu--replace beg end newstr)
+ (goto-char (+ beg newpt))
+ (let* ((state (corfu--compute newinp table pred))
+ (base (alist-get 'corfu--base state))
+ (total (alist-get 'corfu--total state))
+ (cands (alist-get 'corfu--candidates state)))
+ (cond
+ ((= total 0)
+ (when (test-completion newstr table pred)
+ (corfu--exit-function newstr 'finished nil)))
+ ((= total 1)
+ ;; Setup popup if `corfu-on-exact-match' is `show' or if completion
+ ;; can continue.
+ (if (or (eq corfu-on-exact-match 'show)
+ (consp (corfu--try-completion newstr table pred newpt)))
+ (corfu--setup beg end table pred)
+ (corfu--exit-function (car cands) 'finished nil)))
+ ;; Too many candidates for cycling -> Setup popup.
+ ((or (not threshold) (and (not (eq threshold t)) (< threshold total)))
+ (corfu--setup beg end table pred))
+ (t
+ ;; Cycle through candidates.
+ (corfu--cycle-candidates total cands (+ (length base) beg) end)
+ ;; Do not show Corfu when completion is finished after the candidate.
+ (unless (equal (completion-boundaries (car cands) table pred "") '(0 . 0))
+ (corfu--setup beg end table pred)))))
+ t))))
+
+(defun corfu--message (&rest msg)
+ "Show completion MSG."
+ (let (message-log-max) (apply #'message msg)))
+
+(defun corfu--cycle-candidates (total cands beg end)
+ "Cycle between TOTAL number of CANDS.
+See `completion-in-region' for the arguments BEG, END, TABLE, PRED."
+ (let* ((idx 0)
+ (map (make-sparse-keymap))
+ (replace (lambda ()
+ (interactive)
+ (corfu--replace beg end (nth idx cands))
+ (corfu--message "Cycling %d/%d..." (1+ idx) total)
+ (setq idx (mod (1+ idx) total))
+ (set-transient-map map))))
+ (define-key map [remap completion-at-point] replace)
+ (define-key map [remap corfu-complete] replace)
+ (define-key map (vector last-command-event) replace)
+ (funcall replace)))
+
+(cl-defgeneric corfu--popup-show (pos off width lines &optional curr lo bar)
+ "Show LINES as popup at POS - OFF.
+WIDTH is the width of the popup.
+The current candidate CURR is highlighted.
+A scroll bar is displayed from LO to LO+BAR."
+ (let ((lh (max (default-line-height) (cdr (posn-object-width-height pos)))))
+ (with-current-buffer (corfu--make-buffer " *corfu*")
+ (let* ((ch (default-line-height))
+ (cw (default-font-width))
+ ;; bug#74214, bug#37755, bug#37689: Even for larger fringes, fringe
+ ;; bitmaps can only have a width between 1 and 16. Therefore we
+ ;; restrict the fringe width to 16 pixel. This restriction may
+ ;; cause problem on HDPi systems. Hopefully Emacs will adopt
+ ;; larger fringe bitmaps in the future and lift the restriction.
+ (ml (min 16 (ceiling (* cw corfu-left-margin-width))))
+ (mr (min 16 (ceiling (* cw corfu-right-margin-width))))
+ (bw (min mr (ceiling (* cw corfu-bar-width))))
+ (graphic (display-graphic-p))
+ (marginl (and (not graphic) (propertize " " 'display `(space :width (,ml)))))
+ (sbar (if graphic
+ #(" " 0 1 (display (right-fringe corfu--bar corfu--bar)))
+ (concat
+ (propertize " " 'display `(space :align-to (- right (,bw))))
+ (propertize " " 'face 'corfu-bar 'display `(space :width (,bw))))))
+ (cbar (if graphic
+ #(" " 0 1 (display (left-fringe corfu--nil corfu-current))
+ 1 2 (display (right-fringe corfu--bar corfu--cbar)))
+ sbar))
+ (cmargin (and graphic
+ #(" " 0 1 (display (left-fringe corfu--nil corfu-current))
+ 1 2 (display (right-fringe corfu--nil corfu-current)))))
+ (pos (posn-x-y pos))
+ (width (+ (* width cw) (if graphic 0 (+ ml mr))))
+ ;; XXX HACK: Minimum popup height must be at least 1 line of the
+ ;; parent frame (gh:minad/corfu#261).
+ (height (max lh (* (length lines) ch)))
+ (edge (window-inside-pixel-edges))
+ (border (if graphic corfu-border-width 0))
+ (x (max 0 (min (+ (car edge) (- (or (car pos) 0) ml (* cw off) border))
+ (- (frame-pixel-width) width
+ (if graphic (+ ml mr (* 2 border)) 0)))))
+ (yb (+ (cadr edge) (or (cdr pos) 0) lh
+ (static-if (< emacs-major-version 31) (window-tab-line-height) 0)))
+ (y (if (> (+ yb (* corfu-count ch) lh lh) (frame-pixel-height))
+ (- yb height lh border border)
+ yb))
+ (bmp (logxor (1- (ash 1 mr)) (1- (ash 1 bw)))))
+ (setq left-fringe-width (if graphic ml 0) right-fringe-width (if graphic mr 0))
+ ;; Define an inverted corfu--bar face
+ (unless (equal (and (facep 'corfu--bar) (face-attribute 'corfu--bar :foreground))
+ (face-attribute 'corfu-bar :background))
+ (set-face-attribute (make-face 'corfu--bar) nil
+ :foreground (face-attribute 'corfu-bar :background)))
+ (unless (or (= right-fringe-width 0) (eq (get 'corfu--bar 'corfu--bmp) bmp))
+ (put 'corfu--bar 'corfu--bmp bmp)
+ (define-fringe-bitmap 'corfu--bar (vector (lognot bmp)) 1 mr '(top periodic))
+ (define-fringe-bitmap 'corfu--nil [0] 1 1)
+ ;; Fringe bitmaps require symbol face specification, define internal face.
+ (set-face-attribute (make-face 'corfu--cbar) nil
+ :inherit '(corfu--bar corfu-current)))
+ (with-silent-modifications
+ (delete-region (point-min) (point-max))
+ (apply #'insert
+ (cl-loop for row from 0 for line in lines collect
+ (let ((str (concat marginl line
+ (if (and lo (<= lo row (+ lo bar)))
+ (if (eq row curr) cbar sbar)
+ (and (eq row curr) cmargin))
+ "\n")))
+ (when (eq row curr)
+ (add-face-text-property
+ 0 (length str) 'corfu-current 'append str))
+ str)))
+ (goto-char (point-min)))
+ (setq corfu--frame (corfu--make-frame corfu--frame x y width height))))))
+
+(cl-defgeneric corfu--popup-hide ()
+ "Hide Corfu popup."
+ (corfu--hide-frame corfu--frame))
+
+(cl-defgeneric corfu--popup-support-p ()
+ "Return non-nil if child frames are supported."
+ (or (display-graphic-p) (featurep 'tty-child-frames)))
+
+(cl-defgeneric corfu--insert (status)
+ "Insert current candidate, exit with STATUS if non-nil."
+ ;; XXX There is a small bug here, depending on interpretation.
+ ;; When completing "~/emacs/master/li|/calc" where "|" is the
+ ;; cursor, then the candidate only includes the prefix
+ ;; "~/emacs/master/lisp/", but not the suffix "/calc". Default
+ ;; completion has the same problem when selecting in the
+ ;; *Completions* buffer. See bug#48356.
+ (pcase-let* ((`(,beg ,end . ,_) completion-in-region--data)
+ (str (concat corfu--base (nth corfu--index corfu--candidates))))
+ (corfu--replace beg end str)
+ (corfu--goto -1) ;; Reset selection, completion may continue.
+ (when status (corfu--done str status nil))
+ str))
+
+(cl-defgeneric corfu--affixate (cands)
+ "Annotate CANDS with annotation function."
+ (let* ((dep (corfu--metadata-get 'company-deprecated))
+ (mf (let ((completion-extra-properties (nth 4 completion-in-region--data)))
+ (run-hook-with-args-until-success 'corfu-margin-formatters corfu--metadata))))
+ (setq cands
+ (if-let* ((aff (corfu--metadata-get 'affixation-function)))
+ (funcall aff cands)
+ (if-let* ((ann (corfu--metadata-get 'annotation-function)))
+ (cl-loop for cand in cands collect
+ (let ((suff (or (funcall ann cand) "")))
+ ;; The default completion UI adds the
+ ;; `completions-annotations' face if no other faces are
+ ;; present. We use a custom `corfu-annotations' face to
+ ;; allow further styling which fits better for popups.
+ (unless (text-property-not-all 0 (length suff) 'face nil suff)
+ (setq suff (propertize suff 'face 'corfu-annotations)))
+ (list cand "" suff)))
+ (cl-loop for cand in cands collect (list cand "" "")))))
+ (cl-loop for x in cands for (c . _) = x do
+ (when mf
+ (setf (cadr x) (funcall mf c)))
+ (when (and dep (funcall dep c))
+ (setcar x (setq c (substring c)))
+ (add-face-text-property 0 (length c) 'corfu-deprecated 'append c)))
+ (cons mf cands)))
+
+(cl-defgeneric corfu--prepare ()
+ "Insert selected candidate unless command is marked to continue completion."
+ (corfu--preview-delete)
+ ;; Ensure that state is initialized before next Corfu command
+ (when (and (symbolp this-command) (string-prefix-p "corfu-" (symbol-name this-command)))
+ (corfu--update))
+ ;; If the next command is not listed in `corfu-continue-commands', insert the
+ ;; currently selected candidate and bail out of completion. This way you can
+ ;; continue typing after selecting a candidate. The candidate will be inserted
+ ;; and your new input will be appended.
+ (and (corfu--preview-current-p) (eq corfu-preview-current 'insert)
+ ;; See the comment about `overriding-local-map' in `corfu--post-command'.
+ (not (or overriding-terminal-local-map
+ (corfu--match-symbol-p corfu-continue-commands this-command)))
+ (corfu--insert 'exact)))
+
+(cl-defgeneric corfu--exhibit ()
+ "Exhibit Corfu UI."
+ (pcase-let ((`(,beg ,end ,table ,pred . ,_) completion-in-region--data)
+ (`(,str . ,pt) (corfu--update 'interruptible)))
+ (cond
+ ;; 1) Single exactly matching candidate and no further completion is possible.
+ ((and corfu-on-exact-match
+ (not (eq corfu-on-exact-match 'show))
+ (equal corfu--candidates (list str))
+ (not (consp (corfu--try-completion str table pred pt))))
+ (if (eq corfu-on-exact-match 'quit)
+ (corfu-quit)
+ (corfu--done (car corfu--candidates) 'finished nil)))
+ ;; 2) There exist candidates => Show candidates popup.
+ (corfu--candidates
+ (let ((pos (posn-at-point (min (point-max) (+ beg (length corfu--base))))))
+ (corfu--preview-current beg end)
+ (corfu--candidates-popup pos)))
+ ;; 3) No candidates & `corfu-quit-no-match' & initialized => Confirmation popup.
+ ((pcase-exhaustive corfu-quit-no-match
+ ('t nil)
+ ('nil corfu--input)
+ ('separator (seq-contains-p (car corfu--input) corfu-separator)))
+ (corfu--popup-show (posn-at-point beg) 0 8 '(#("No match" 0 8 (face italic)))))
+ ;; 4) No candidates & initialized => Quit.
+ (corfu--input (corfu-quit)))))
+
+(cl-defgeneric corfu--teardown (buffer)
+ "Tear-down Corfu in BUFFER, which might be dead at this point."
+ (corfu--popup-hide)
+ (corfu--preview-delete)
+ (remove-hook 'post-command-hook #'corfu--post-command)
+ (when (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (remove-hook 'window-selection-change-functions #'corfu--window-change 'local)
+ (remove-hook 'window-buffer-change-functions #'corfu--window-change 'local)
+ (remove-hook 'pre-command-hook #'corfu--prepare 'local)
+ (accept-change-group corfu--change-group)))
+ (cl-loop for (k . v) in corfu--initial-state do (set k v)))
+
+(defun corfu-sort-length-alpha (list)
+ "Sort LIST by length and alphabetically."
+ (sort list #'corfu--length-string<))
+
+(defun corfu-quit ()
+ "Quit Corfu completion."
+ (interactive)
+ (completion-in-region-mode -1))
+
+(defun corfu-reset ()
+ "Reset Corfu completion.
+This command can be executed multiple times by hammering the ESC key. If a
+candidate is selected, unselect the candidate. Otherwise reset the input. If
+there hasn't been any input, then quit."
+ (interactive)
+ (if (/= corfu--index corfu--preselect)
+ (progn
+ (corfu--goto -1)
+ (setq this-command #'corfu-first))
+ ;; Cancel all changes and start new change group.
+ (pcase-let* ((`(,beg ,end . ,_) completion-in-region--data)
+ (str (buffer-substring-no-properties beg end)))
+ (cancel-change-group corfu--change-group)
+ (goto-char end)
+ (activate-change-group (setq corfu--change-group (prepare-change-group)))
+ ;; Quit when resetting, when input did not change.
+ (when (equal str (buffer-substring-no-properties beg end))
+ (corfu-quit)))))
+
+(defun corfu-insert-separator ()
+ "Insert a separator character, inhibiting quit on completion boundary.
+If the currently selected candidate is previewed, jump to the input
+prompt instead. See `corfu-separator' for more details."
+ (interactive)
+ (if (not (corfu--preview-current-p))
+ (insert corfu-separator)
+ (corfu--goto -1)
+ (unless (or (= (car completion-in-region--data) (point))
+ (= (char-before) corfu-separator))
+ (insert corfu-separator))))
+
+(defun corfu-next (&optional n)
+ "Go forward N candidates."
+ (interactive "p")
+ (let ((index (+ corfu--index (or n 1))))
+ (corfu--goto
+ (cond
+ ((not corfu-cycle) index)
+ ((= corfu--total 0) -1)
+ ((< corfu--preselect 0) (1- (mod (1+ index) (1+ corfu--total))))
+ (t (mod index corfu--total))))))
+
+(defun corfu-previous (&optional n)
+ "Go backward N candidates."
+ (interactive "p")
+ (corfu-next (- (or n 1))))
+
+(defun corfu-scroll-down (&optional n)
+ "Go back by N pages."
+ (interactive "p")
+ (corfu--goto (max 0 (- corfu--index (* (or n 1) corfu-count)))))
+
+(defun corfu-scroll-up (&optional n)
+ "Go forward by N pages."
+ (interactive "p")
+ (corfu-scroll-down (- (or n 1))))
+
+(defun corfu-first ()
+ "Go to first candidate.
+If the first candidate is already selected, go to the prompt."
+ (interactive)
+ (corfu--goto (if (> corfu--index 0) 0 -1)))
+
+(defun corfu-last ()
+ "Go to last candidate."
+ (interactive)
+ (corfu--goto (1- corfu--total)))
+
+(defun corfu-prompt-beginning (arg)
+ "Move to beginning of the prompt line.
+If the point is already the beginning of the prompt move to the
+beginning of the line. If ARG is not 1 or nil, move backward ARG - 1
+lines first."
+ (interactive "^p")
+ (let ((beg (car completion-in-region--data)))
+ (if (or (not (eq arg 1))
+ (and (= corfu--preselect corfu--index) (= (point) beg)))
+ (move-beginning-of-line arg)
+ (corfu--goto -1)
+ (goto-char beg))))
+
+(defun corfu-prompt-end (arg)
+ "Move to end of the prompt line.
+If the point is already the end of the prompt move to the end of
+the line. If ARG is not 1 or nil, move forward ARG - 1 lines
+first."
+ (interactive "^p")
+ (let ((end (cadr completion-in-region--data)))
+ (if (or (not (eq arg 1))
+ (and (= corfu--preselect corfu--index) (= (point) end)))
+ (move-end-of-line arg)
+ (corfu--goto -1)
+ (goto-char end))))
+
+(defun corfu-complete ()
+ "Complete current input.
+If a candidate is selected, insert it. Otherwise invoke
+`corfu-expand'. Return non-nil if the input has been expanded."
+ (interactive)
+ (if (< corfu--index 0)
+ (corfu-expand)
+ ;; Continue completion with selected candidate. Exit with status 'finished
+ ;; if input is a valid match and no further completion is possible.
+ (pcase-let ((`(,_beg ,_end ,table ,pred . ,_) completion-in-region--data)
+ (newstr (corfu--insert nil)))
+ (and (test-completion newstr table pred)
+ (or (not (consp (corfu--try-completion newstr table pred (length newstr))))
+ ;; Additionally finish completion if at the end of a boundary,
+ ;; even if other longer candidates match, since the user invoked
+ ;; `corfu-complete' with an explicitly selected candidate!
+ (equal (completion-boundaries newstr table pred "") '(0 . 0)))
+ (corfu--done newstr 'finished nil))
+ t)))
+
+(defun corfu-expand ()
+ "Expands the common prefix of all candidates.
+If the currently selected candidate is previewed, invoke
+`corfu-complete' instead. Expansion relies on the completion
+styles via `completion-try-completion'. Return non-nil if the
+input has been expanded."
+ (interactive)
+ (if (corfu--preview-current-p)
+ (corfu-complete)
+ (pcase-let* ((`(,beg ,end ,table ,pred . ,_) completion-in-region--data)
+ (pt (max 0 (- (point) beg)))
+ (str (buffer-substring-no-properties beg end)))
+ (pcase (corfu--try-completion str table pred pt)
+ ('t
+ (goto-char end)
+ (corfu--done str 'finished corfu--candidates)
+ t)
+ ((and `(,newstr . ,newpt) (guard (not (and (= pt newpt) (equal newstr str)))))
+ (corfu--replace beg end newstr)
+ (goto-char (+ beg newpt))
+ ;; Exit with status 'finished if input is a valid match
+ ;; and no further completion is possible.
+ (and (test-completion newstr table pred)
+ (not (consp (corfu--try-completion newstr table pred newpt)))
+ (corfu--done newstr 'finished corfu--candidates))
+ t)))))
+
+(defun corfu-insert ()
+ "Insert current candidate.
+Quit if no candidate is selected."
+ (interactive)
+ (if (>= corfu--index 0)
+ (corfu--insert 'finished)
+ (corfu-quit)))
+
+(defun corfu-send ()
+ "Insert current candidate and send it when inside comint or eshell."
+ (interactive)
+ (corfu-insert)
+ (cond
+ ((and (derived-mode-p 'eshell-mode) (fboundp 'eshell-send-input))
+ (eshell-send-input))
+ ((and (derived-mode-p 'comint-mode) (fboundp 'comint-send-input))
+ (comint-send-input))))
+
+;;;###autoload
+(define-minor-mode corfu-mode
+ "COmpletion in Region FUnction."
+ :group 'corfu :keymap corfu-mode-map
+ (cond
+ (corfu-mode
+ (when corfu-auto
+ (require 'corfu-auto)
+ (add-hook 'post-command-hook 'corfu-auto--post-command 10 'local))
+ (setq-local completion-in-region-function #'corfu--in-region))
+ (t
+ (remove-hook 'post-command-hook 'corfu-auto--post-command 'local)
+ (kill-local-variable 'completion-in-region-function))))
+
+(defcustom global-corfu-minibuffer t
+ "Corfu should be enabled in the minibuffer by `global-corfu-mode'.
+The variable can either be t, nil or a custom predicate function. If
+the variable is set to t, Corfu is only enabled if the minibuffer has
+local `completion-at-point-functions'."
+ :type '(choice (const t) (const nil) function)
+ :group 'corfu)
+
+;;;###autoload
+(define-globalized-minor-mode global-corfu-mode
+ corfu-mode corfu--on
+ :group 'corfu
+ :predicate t
+ (remove-hook 'minibuffer-setup-hook #'corfu--minibuffer-on)
+ (when (and global-corfu-mode global-corfu-minibuffer)
+ (add-hook 'minibuffer-setup-hook #'corfu--minibuffer-on 100)))
+
+(defun corfu--on ()
+ "Enable `corfu-mode' in the current buffer respecting `global-corfu-modes'."
+ (unless (or noninteractive buffer-read-only (eq (aref (buffer-name) 0) ?\s))
+ (corfu-mode)))
+
+(defun corfu--minibuffer-on ()
+ "Enable `corfu-mode' in the minibuffer respecting `global-corfu-minibuffer'."
+ (when (and global-corfu-minibuffer (not noninteractive)
+ (if (functionp global-corfu-minibuffer)
+ (funcall global-corfu-minibuffer)
+ (local-variable-p 'completion-at-point-functions)))
+ (corfu-mode)))
+
+;; Do not show Corfu commands with M-X
+(dolist (sym '( corfu-next corfu-previous corfu-first corfu-last corfu-quit corfu-reset
+ corfu-complete corfu-insert corfu-scroll-up corfu-scroll-down corfu-expand
+ corfu-send corfu-insert-separator corfu-prompt-beginning corfu-prompt-end
+ corfu-info-location corfu-info-documentation ;; autoloads in corfu-info.el
+ corfu-quick-jump corfu-quick-insert corfu-quick-complete)) ;; autoloads in corfu-quick.el
+ (put sym 'completion-predicate #'ignore))
+
+(defun corfu--capf-wrapper-advice (orig fun which)
+ "Around advice for `completion--capf-wrapper'.
+The ORIG function takes the FUN and WHICH arguments."
+ (if corfu-mode (corfu--capf-wrapper fun) (funcall orig fun which)))
+
+(defun corfu--eldoc-advice ()
+ "Return non-nil if Corfu is currently not active."
+ (not (and corfu-mode completion-in-region-mode)))
+
+;; Install advice which fixes `completion--capf-wrapper', such that it respects
+;; the completion styles for non-exclusive Capfs. See also the fixme comment in
+;; the `completion--capf-wrapper' function in minibuffer.el.
+(advice-add #'completion--capf-wrapper :around #'corfu--capf-wrapper-advice)
+
+;; Register Corfu with ElDoc
+(advice-add #'eldoc-display-message-no-interference-p
+ :before-while #'corfu--eldoc-advice)
+(eldoc-add-command #'corfu-complete #'corfu-insert #'corfu-expand #'corfu-send)
+
+(with-eval-after-load 'corfu-terminal
+ (when (featurep 'tty-child-frames)
+ (display-warning 'corfu "`corfu-terminal' is not needed on Emacs 31")))
+
+(provide 'corfu)
+;;; corfu.el ends here
diff --git a/.config/emacs/lisp/minadstack/marginalia.el b/.config/emacs/lisp/minadstack/marginalia.el
new file mode 100644
index 0000000..3c49eed
--- /dev/null
+++ b/.config/emacs/lisp/minadstack/marginalia.el
@@ -0,0 +1,1461 @@
+;;; marginalia.el --- Enrich existing commands with completion annotations -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021-2026 Free Software Foundation, Inc.
+
+;; Author: Omar Antolín Camarena <omar@matem.unam.mx>, Daniel Mendler <mail@daniel-mendler.de>
+;; Maintainer: Omar Antolín Camarena <omar@matem.unam.mx>, Daniel Mendler <mail@daniel-mendler.de>
+;; Created: 2020
+;; Version: 2.10
+;; Package-Requires: ((emacs "29.1") (compat "30"))
+;; URL: https://github.com/minad/marginalia
+;; Keywords: docs, help, matching, completion
+
+;; This file is 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:
+
+;; Enrich existing commands with completion annotations. The information
+;; associated with the completion candidates is shown in the minibuffer or the
+;; *Completions* buffer. For files the owner and permissions are shown, for
+;; buffers the major mode and modification status and for functions or commands
+;; the docstring is shown.
+
+;;; Code:
+
+(require 'compat)
+(eval-when-compile
+ (require 'subr-x)
+ (require 'cl-lib))
+
+;;;; Customization
+
+(defgroup marginalia nil
+ "Enrich existing commands with completion annotations."
+ :link '(info-link :tag "Info Manual" "(marginalia)")
+ :link '(url-link :tag "Website" "https://github.com/minad/marginalia")
+ :link '(emacs-library-link :tag "Library Source" "marginalia.el")
+ :group 'help
+ :group 'docs
+ :group 'minibuffer
+ :prefix "marginalia-")
+
+(defcustom marginalia-field-width 80
+ "Maximum truncation width of annotation fields.
+
+This value is adjusted depending on the `window-width'."
+ :type 'natnum)
+
+(defcustom marginalia-separator " "
+ "Annotation field separator."
+ :type 'string)
+
+(defcustom marginalia-align 'left
+ "Alignment of the annotations."
+ :type '(choice (const :tag "Left" left)
+ (const :tag "Center" center)
+ (const :tag "Right" right)))
+
+(defcustom marginalia-align-offset 0
+ "Additional offset added to the alignment."
+ :type 'natnum)
+
+(defcustom marginalia-max-relative-age (* 60 60 24 14)
+ "Maximum relative age in seconds displayed by the file annotator.
+
+Set to `most-positive-fixnum' to always use a relative age, or 0 to never show
+a relative age."
+ :type 'natnum)
+
+(defcustom marginalia-remote-file-regexps
+ '("\\`/\\([^/|:]+\\):") ;; Tramp path
+ "List of remote file regexps where the files should not be annotated.
+
+The first match group is displayed instead of the detailed file
+attribute information. For Tramp paths, the protocol is
+displayed instead."
+ :type '(repeat regexp))
+
+(defcustom marginalia-annotators
+ (mapcar
+ (lambda (x) (append x (list 'builtin 'none)))
+ `((command ,#'marginalia-annotate-command ,#'marginalia-annotate-binding)
+ (embark-keybinding ,#'marginalia-annotate-embark-keybinding)
+ (customize-group ,#'marginalia-annotate-customize-group)
+ (variable ,#'marginalia-annotate-variable)
+ (function ,#'marginalia-annotate-function)
+ (face ,#'marginalia-annotate-face)
+ (color ,#'marginalia-annotate-color)
+ (unicode-name ,#'marginalia-annotate-char)
+ (minor-mode ,#'marginalia-annotate-minor-mode)
+ (symbol ,#'marginalia-annotate-symbol)
+ (environment-variable ,#'marginalia-annotate-environment-variable)
+ (input-method ,#'marginalia-annotate-input-method)
+ (coding-system ,#'marginalia-annotate-coding-system)
+ (charset ,#'marginalia-annotate-charset)
+ (package ,#'marginalia-annotate-package)
+ (imenu ,#'marginalia-annotate-imenu)
+ (bookmark ,#'marginalia-annotate-bookmark)
+ (file ,#'marginalia-annotate-file)
+ (project-file ,#'marginalia-annotate-project-file)
+ (project-buffer ,#'marginalia-annotate-buffer)
+ (buffer ,#'marginalia-annotate-buffer)
+ (library ,#'marginalia-annotate-library)
+ (theme ,#'marginalia-annotate-theme)
+ (tab ,#'marginalia-annotate-tab)
+ (frame ,#'marginalia-annotate-frame)
+ (multi-category ,#'marginalia-annotate-multi-category)))
+ "Annotator function registry.
+Associates completion categories with annotation functions. Each
+annotation function must return a string, which is appended to the
+completion candidate. The annotation functions are executed in the
+original window and the original buffer, if still alive."
+ :type '(alist :key-type symbol :value-type (repeat symbol)))
+
+(defcustom marginalia-classifiers
+ (list #'marginalia-classify-by-command-name
+ #'marginalia-classify-original-category
+ #'marginalia-classify-by-prompt
+ #'marginalia-classify-symbol)
+ "List of functions to determine current completion category.
+Each function should take no arguments and return a symbol
+indicating the category, or nil to indicate it could not
+determine it."
+ :type 'hook)
+
+(defcustom marginalia-prompt-categories
+ '(("\\<customize group\\>" . customize-group)
+ ("\\<M-x\\>" . command)
+ ("\\<package\\>" . package)
+ ("\\<bookmark\\>" . bookmark)
+ ("\\<color\\>" . color)
+ ("\\<face\\>" . face)
+ ("\\<environment variable\\>" . environment-variable)
+ ("\\<function\\|\\(?:hook\\|advice\\) to remove\\>" . function)
+ ("\\<variable\\>" . variable)
+ ("\\<input method\\>" . input-method)
+ ("\\<charset\\>" . charset)
+ ("\\<coding system\\>" . coding-system)
+ ("\\<minor mode\\>" . minor-mode)
+ ("\\<kill-ring\\>" . kill-ring)
+ ("\\<tab by name\\>" . tab)
+ ("\\<frame\\>" . frame)
+ ("\\<library\\>" . library)
+ ("\\<theme\\>" . theme))
+ "Associates regexps to match against minibuffer prompts with categories.
+The prompts are matched case-insensitively."
+ :type '(alist :key-type regexp :value-type symbol))
+
+(defcustom marginalia-censor-variables
+ '("pass\\|auth-source-netrc-cache\\|auth-source-.*-nonce\\|api-?key")
+ "The value of variables matching any of these regular expressions is not shown.
+This configuration variable is useful to hide variables which may
+hold sensitive data, e.g., passwords. The variable names are
+matched case-sensitively."
+ :type '(repeat (choice symbol regexp)))
+
+(defcustom marginalia-command-categories
+ `((,#'imenu . imenu)
+ (,#'recentf-open . file)
+ (,#'where-is . command))
+ "Associate commands with a completion category.
+The value of `this-command' is used as key for the lookup."
+ :type '(alist :key-type symbol :value-type symbol))
+
+(defgroup marginalia-faces nil
+ "Faces used by `marginalia-mode'."
+ :group 'marginalia
+ :group 'faces)
+
+(defface marginalia-key
+ '((t :inherit font-lock-keyword-face))
+ "Face used to highlight keys.")
+
+(defface marginalia-type
+ '((t :inherit marginalia-key))
+ "Face used to highlight types.")
+
+(defface marginalia-char
+ '((t :inherit marginalia-key))
+ "Face used to highlight character annotations.")
+
+(defface marginalia-lighter
+ '((t :inherit marginalia-size))
+ "Face used to highlight minor mode lighters.")
+
+(defface marginalia-on
+ '((t :inherit success))
+ "Face used to signal enabled modes.")
+
+(defface marginalia-off
+ '((t :inherit error))
+ "Face used to signal disabled modes.")
+
+(defface marginalia-documentation
+ '((t :inherit completions-annotations))
+ "Face used to highlight documentation strings.")
+
+(defface marginalia-value
+ '((t :inherit marginalia-key))
+ "Face used to highlight general variable values.")
+
+(defface marginalia-null
+ '((t :inherit font-lock-comment-face))
+ "Face used to highlight null or unbound variable values.")
+
+(defface marginalia-true
+ '((t :inherit font-lock-builtin-face))
+ "Face used to highlight true variable values.")
+
+(defface marginalia-function
+ '((t :inherit font-lock-function-name-face))
+ "Face used to highlight function symbols.")
+
+(defface marginalia-symbol
+ '((t :inherit font-lock-type-face))
+ "Face used to highlight general symbols.")
+
+(defface marginalia-list
+ '((t :inherit font-lock-constant-face))
+ "Face used to highlight list expressions.")
+
+(defface marginalia-mode
+ '((t :inherit marginalia-key))
+ "Face used to highlight buffer major modes.")
+
+(defface marginalia-date
+ '((t :inherit marginalia-key))
+ "Face used to highlight dates.")
+
+(defface marginalia-version
+ '((t :inherit marginalia-number))
+ "Face used to highlight package versions.")
+
+(defface marginalia-archive
+ '((t :inherit warning))
+ "Face used to highlight package archives.")
+
+(defface marginalia-installed
+ '((t :inherit success))
+ "Face used to highlight the status of packages.")
+
+(defface marginalia-size
+ '((t :inherit marginalia-number))
+ "Face used to highlight sizes.")
+
+(defface marginalia-number
+ '((t :inherit font-lock-constant-face))
+ "Face used to highlight numeric values.")
+
+(defface marginalia-string
+ '((t :inherit font-lock-string-face))
+ "Face used to highlight string values.")
+
+(defface marginalia-modified
+ '((t :inherit font-lock-negation-char-face))
+ "Face used to highlight buffer modification indicators.")
+
+(defface marginalia-file-name
+ '((t :inherit marginalia-documentation))
+ "Face used to highlight file names.")
+
+(defface marginalia-file-owner
+ '((t :inherit font-lock-preprocessor-face))
+ "Face used to highlight file owner and group names.")
+
+(defface marginalia-file-priv-no
+ '((t :inherit shadow))
+ "Face used to highlight the no file privilege attribute.")
+
+(defface marginalia-file-priv-dir
+ '((t :inherit font-lock-keyword-face))
+ "Face used to highlight the dir file privilege attribute.")
+
+(defface marginalia-file-priv-link
+ '((t :inherit font-lock-keyword-face))
+ "Face used to highlight the link file privilege attribute.")
+
+(defface marginalia-file-priv-read
+ '((t :inherit font-lock-type-face))
+ "Face used to highlight the read file privilege attribute.")
+
+(defface marginalia-file-priv-write
+ '((t :inherit font-lock-builtin-face))
+ "Face used to highlight the write file privilege attribute.")
+
+(defface marginalia-file-priv-exec
+ '((t :inherit font-lock-function-name-face))
+ "Face used to highlight the exec file privilege attribute.")
+
+(defface marginalia-file-priv-other
+ '((t :inherit font-lock-constant-face))
+ "Face used to highlight some other file privilege attribute.")
+
+(defface marginalia-file-priv-rare
+ '((t :inherit font-lock-variable-name-face))
+ "Face used to highlight a rare file privilege attribute.")
+
+;;;; Pre-declarations for external packages
+
+(declare-function bookmark-prop-get "bookmark")
+
+(declare-function project-current "project")
+(declare-function project-root "project")
+
+(defvar package--builtins)
+(defvar package-archive-contents)
+(declare-function package--from-builtin "package")
+(declare-function package-desc-archive "package")
+(declare-function package-desc-status "package")
+(declare-function package-desc-summary "package")
+(declare-function package-desc-version "package")
+(declare-function package-version-join "package")
+
+(declare-function color-rgb-to-hex "color")
+(declare-function color-rgb-to-hsl "color")
+(declare-function color-hsl-to-rgb "color")
+
+;;;; Marginalia mode
+
+(defalias 'marginalia--orig-completion-metadata-get
+ (symbol-function
+ (if (fboundp 'marginalia--orig-completion-metadata-get)
+ 'marginalia--orig-completion-metadata-get
+ (compat-function completion-metadata-get)))
+ "Original `completion-metadata-get' function.")
+
+(defvar marginalia--pangram "Cwm fjord bank glyphs vext quiz.")
+
+(defvar marginalia--bookmark-type-transforms
+ (let ((words (regexp-opt '("handle" "handler" "jump" "bookmark"))))
+ `((,(format "-+%s-+" words) . "-")
+ (,(format "\\`%s-+" words) . "")
+ (,(format "-%s\\'" words) . "")
+ ("\\`default\\'" . "File")
+ (".*" . ,#'capitalize)))
+ "List of bookmark type transformers.
+Relying on this mechanism is discouraged in favor of the
+`bookmark-handler-type' property. The function names are matched
+case-sensitively.")
+
+(defvar marginalia--cand-width-step 10
+ "Round candidate width.")
+
+(defvar-local marginalia--cand-width-max 20
+ "Maximum width of candidates.")
+
+(defvar marginalia--fontified-file-modes nil
+ "List of fontified file modes.")
+
+(defvar-local marginalia--cache nil
+ "The cache, pair of list and hashtable.")
+
+(defvar marginalia--cache-size 100
+ "Size of the cache, set to 0 to disable the cache.
+Disabling the cache is useful on non-incremental UIs like default completion or
+for performance profiling of the annotators.")
+
+(defvar-local marginalia--command nil
+ "Last command symbol saved in order to allow annotations.")
+
+(defvar-local marginalia--base-position 0
+ "Last completion base position saved to get full file paths.")
+
+(defvar marginalia--metadata nil
+ "Completion metadata from the current completion.")
+
+(defvar marginalia--ellipsis nil)
+(defun marginalia--ellipsis ()
+ "Return ellipsis."
+ (with-memoization marginalia--ellipsis
+ (cond
+ ((bound-and-true-p truncate-string-ellipsis))
+ ((char-displayable-p ?…) "…")
+ ("..."))))
+
+(defun marginalia--abbreviate-file-name (file)
+ "Abbreviate FILE name without Tramp slowdown."
+ (let (file-name-handler-alist)
+ (abbreviate-file-name file)))
+
+(defun marginalia--truncate (str width)
+ "Truncate string STR to WIDTH."
+ (when (floatp width) (setq width (round (* width marginalia-field-width))))
+ (when-let* ((pos (string-search "\n" str)))
+ (setq str (substring str 0 pos)))
+ (let* ((face (and (not (equal str ""))
+ (get-text-property (1- (length str)) 'face str)))
+ (ell (if face
+ (propertize (marginalia--ellipsis) 'face face)
+ (marginalia--ellipsis)))
+ (trunc
+ (if (< width 0)
+ (nreverse (truncate-string-to-width (reverse str) (- width) 0 ?\s ell))
+ (truncate-string-to-width str width 0 ?\s ell))))
+ (unless (string-prefix-p str trunc)
+ (put-text-property 0 (length trunc) 'help-echo str trunc))
+ trunc))
+
+(cl-defmacro marginalia--field (field &key truncate face width format)
+ "Format FIELD as a string according to some options.
+TRUNCATE is the truncation width.
+WIDTH is the field width.
+FORMAT is a format string.
+FACE is the name of the face, with which the field should be propertized."
+ (setq field (if format `(format ,format ,field) `(or ,field "")))
+ (when width (setq field `(format ,(format "%%%ds" (- width)) ,field)))
+ (when truncate (setq field `(marginalia--truncate ,field ,truncate)))
+ (when face
+ (setq field (if (or format width truncate)
+ (cl-with-gensyms (f)
+ `(let ((,f ,field))
+ (put-text-property 0 (length ,f) 'face ,face ,f)
+ ,f))
+ `(propertize ,field 'face ,face))))
+ field)
+
+(defmacro marginalia--fields (&rest fields)
+ "Format annotation FIELDS as a string with separators in between."
+ (let ((left t))
+ (cons 'concat
+ (mapcan
+ (lambda (field)
+ (if (not (eq (car field) :left))
+ `(,@(when left (setq left nil) `(#(" " 0 1 (marginalia--align t))))
+ marginalia-separator (marginalia--field ,@field))
+ (unless left (error "Left fields must come first"))
+ `((marginalia--field ,@(cdr field)))))
+ fields))))
+
+(defmacro marginalia--in-minibuffer (&rest body)
+ "Run BODY inside minibuffer if minibuffer is active.
+Otherwise stay within current buffer."
+ (declare (indent 0))
+ `(with-current-buffer (if-let* ((win (active-minibuffer-window)))
+ (window-buffer win)
+ (current-buffer))
+ ,@body))
+
+(defun marginalia--documentation (str)
+ "Format documentation string STR."
+ (when str
+ (marginalia--fields
+ (str :truncate 1.0 :face 'marginalia-documentation))))
+
+(defun marginalia-annotate-binding (cand)
+ "Annotate command CAND with keybinding."
+ (when-let* ((sym (intern-soft cand))
+ (key (and (commandp sym) (where-is-internal sym nil 'first-only))))
+ (format #(" (%s)" 1 5 (face marginalia-key)) (key-description key))))
+
+(defun marginalia--annotator (cat)
+ "Return annotation function for category CAT."
+ (pcase (car (alist-get cat marginalia-annotators))
+ ('none #'ignore)
+ ('builtin nil)
+ (fun fun)))
+
+(defun marginalia-annotate-multi-category (cand)
+ "Annotate multi-category CAND, dispatching to the appropriate annotator."
+ (if-let* ((multi (get-text-property 0 'multi-category cand))
+ (fun (marginalia--annotator (car multi))))
+ ;; Use the Marginalia annotator corresponding to the multi category.
+ (funcall fun (cdr multi))
+ ;; Apply the original annotation function on the original candidate. Bypass
+ ;; our `marginalia--completion-metadata-get' advice.
+ (if-let* ((fun (marginalia--orig-completion-metadata-get
+ marginalia--metadata 'affixation-function)))
+ (caddar (funcall fun (list cand)))
+ (when-let* ((fun (marginalia--orig-completion-metadata-get
+ marginalia--metadata 'annotation-function)))
+ (funcall fun cand)))))
+
+(defconst marginalia--advice-regexp
+ (rx bos
+ (1+ (seq (? "This function has ")
+ (or ":before" ":after" ":around" ":override"
+ ":before-while" ":before-until" ":after-while"
+ ":after-until" ":filter-args" ":filter-return")
+ " advice: " (0+ nonl) "\n"))
+ "\n")
+ "Regexp to match lines about advice in function documentation strings.")
+
+;; Taken from advice--make-docstring, is this robust?
+(defun marginalia--advised (fun)
+ "Return t if function FUN is advised."
+ (let ((flist (indirect-function fun)))
+ (advice--p (if (eq 'macro (car-safe flist)) (cdr flist) flist))))
+
+(defun marginalia--symbol-class (s)
+ "Return symbol class characters for symbol S.
+
+This function is an extension of `help--symbol-class'. It returns
+more fine-grained and more detailed symbol information.
+
+Function:
+f function
+c command
+C interactive-only command
+m macro
+F special-form
+M module function
+P primitive
+g cl-generic
+p pure
+s side-effect-free
+@ autoloaded
+! advised
+- obsolete
+& alias
+
+Variable:
+u custom (U modified compared to global value)
+v variable
+l local (L modified compared to default value)
+- obsolete
+& alias
+
+Other:
+G custom group
+a face
+t cl-type"
+ (let ((class
+ (append
+ (when (fboundp s)
+ (list
+ (cond
+ ((get s 'pure) '("p" . "pure"))
+ ((get s 'side-effect-free) '("s" . "side-effect-free")))
+ (cond
+ ((commandp s)
+ (if (get s 'interactive-only)
+ '("C" . "interactive-only command")
+ '("c" . "command")))
+ ((cl-generic-p s) '("g" . "cl-generic"))
+ ((macrop (symbol-function s)) '("m" . "macro"))
+ ((special-form-p (symbol-function s)) '("F" . "special-form"))
+ ((subr-primitive-p (symbol-function s)) '("P" . "primitive"))
+ ((module-function-p (symbol-function s)) '("M" . "module function"))
+ (t '("f" . "function")))
+ (and (autoloadp (symbol-function s)) '("@" . "autoload"))
+ (and (marginalia--advised s) '("!" . "advised"))
+ (and (symbolp (symbol-function s))
+ (cons "&" (format "alias for `%s'" (symbol-function s))))
+ (and (get s 'byte-obsolete-info) '("-" . "obsolete"))))
+ (when (boundp s)
+ (list
+ (when (local-variable-if-set-p s)
+ (if (ignore-errors
+ (not (equal (symbol-value s)
+ (default-value s))))
+ '("L" . "local, modified from global")
+ '("l" . "local, unmodified")))
+ (if (get s 'standard-value)
+ (if (ignore-errors
+ (not (equal (symbol-value s)
+ (eval (car (get s 'standard-value))))))
+ '("U" . "custom, modified from standard")
+ '("u" . "custom, unmodified"))
+ '("v" . "variable"))
+ (and (not (eq (ignore-errors (indirect-variable s)) s))
+ (cons "&" (format "alias for `%s'" (ignore-errors (indirect-variable s)))))
+ (and (get s 'byte-obsolete-variable) '("-" . "obsolete"))))
+ (list
+ (and (get s 'group-documentation) '("G" . "custom group"))
+ (and (facep s) '("a" . "face"))
+ (and (get s 'cl--class) '("t" . "cl-type")))))) ;; cl-find-class, cl--find-class
+ (setq class (delq nil class))
+ (propertize
+ (format "%-6s" (mapconcat #'car class ""))
+ 'help-echo
+ (mapconcat (pcase-lambda (`(,x . ,y)) (concat x " " y)) class "\n"))))
+
+(defun marginalia--definition-prefix (sym)
+ "Return annotation string if SYM is a definition prefix.
+Sometimes symbols which are not yet loaded appear in completion tables
+if `help-enable-completion-autoload' is enabled. These symbols
+originate from the `definition-prefixes' hash table."
+ (when-let* (((bound-and-true-p help-enable-completion-autoload))
+ (files (gethash (symbol-name sym) definition-prefixes)))
+ (format "[Not yet loaded from %s. See `help-enable-completion-autoload'.]"
+ (string-join files ", "))))
+
+(defun marginalia--function-doc (sym)
+ "Documentation string of function SYM."
+ (if-let* ((str (ignore-errors (documentation sym))))
+ (save-match-data
+ (if (string-match marginalia--advice-regexp str)
+ (substring str (match-end 0))
+ str))
+ (marginalia--definition-prefix sym)))
+
+;; Derived from elisp-get-fnsym-args-string
+(defun marginalia--function-args (sym)
+ "Return function arguments for SYM."
+ (let (tmp)
+ (elisp-function-argstring
+ (cond
+ ((listp (setq tmp (gethash (indirect-function sym)
+ advertised-signature-table t)))
+ tmp)
+ ((setq tmp (help-split-fundoc
+ (ignore-errors (documentation sym t))
+ sym))
+ (car tmp))
+ ((setq tmp (help-function-arglist sym))
+ (if (and (stringp tmp) (string-search "not available" tmp))
+ ;; A shorter text fits better into the limited Marginalia space.
+ "[autoload]"
+ tmp))))))
+
+(defun marginalia-annotate-symbol (cand)
+ "Annotate symbol CAND with its documentation string."
+ (when-let* ((sym (intern-soft cand)))
+ (marginalia--fields
+ (:left (marginalia-annotate-binding cand))
+ ((marginalia--symbol-class sym) :face 'marginalia-type)
+ ((if (fboundp sym)
+ (marginalia--function-doc sym)
+ (or (cl-loop
+ for doc in '(variable-documentation
+ face-documentation
+ group-documentation)
+ thereis (ignore-errors (documentation-property sym doc)))
+ (marginalia--definition-prefix sym)))
+ :truncate 1.0 :face 'marginalia-documentation)
+ ((marginalia--abbreviate-file-name (or (symbol-file sym) ""))
+ :truncate -0.5 :face 'marginalia-file-name))))
+
+(defun marginalia-annotate-command (cand)
+ "Annotate command CAND with its documentation string.
+Similar to `marginalia-annotate-symbol', but does not show symbol class."
+ (when-let* ((sym (intern-soft cand)))
+ (concat
+ (marginalia-annotate-binding cand)
+ (marginalia--documentation (marginalia--function-doc sym)))))
+
+(defun marginalia-annotate-embark-keybinding (cand)
+ "Annotate Embark keybinding CAND with its documentation string.
+Similar to `marginalia-annotate-command', but does not show the
+keybinding since CAND includes it."
+ (when-let* ((cmd (get-text-property 0 'embark-command cand))
+ ((symbolp cmd)))
+ (marginalia--documentation (marginalia--function-doc cmd))))
+
+(defun marginalia-annotate-imenu (cand)
+ "Annotate imenu CAND with its documentation string."
+ (when (derived-mode-p 'emacs-lisp-mode)
+ ;; Strip until the last whitespace in order to support flat imenu
+ (marginalia-annotate-symbol (replace-regexp-in-string "\\`.* " "" cand))))
+
+(defun marginalia-annotate-function (cand)
+ "Annotate function CAND with its documentation string."
+ (when-let* ((sym (intern-soft cand)))
+ (marginalia--fields
+ (:left (marginalia-annotate-binding cand))
+ ((marginalia--symbol-class sym) :face 'marginalia-type)
+ ((marginalia--function-args sym) :face 'marginalia-value
+ :truncate 0.5)
+ ((marginalia--function-doc sym)
+ :truncate 1.0 :face 'marginalia-documentation))))
+
+(defun marginalia--variable-value (sym)
+ "Return the variable value of SYM as string."
+ (cond
+ ((not (boundp sym))
+ (propertize "#<unbound>" 'face 'marginalia-null))
+ ((and marginalia-censor-variables
+ (let ((name (symbol-name sym))
+ case-fold-search)
+ (cl-loop for r in marginalia-censor-variables
+ thereis (if (symbolp r)
+ (eq r sym)
+ (string-match-p r name)))))
+ (propertize "*****"
+ 'face 'marginalia-null
+ 'help-echo "Hidden due to `marginalia-censor-variables'"))
+ (t
+ (let ((val (symbol-value sym)))
+ (pcase val
+ ('nil (propertize "nil" 'face 'marginalia-null))
+ ('t (propertize "t" 'face 'marginalia-true))
+ ((pred keymapp) (propertize "#<keymap>" 'face 'marginalia-value))
+ ((pred bool-vector-p) (propertize "#<bool-vector>" 'face 'marginalia-value))
+ ((pred hash-table-p) (propertize "#<hash-table>" 'face 'marginalia-value))
+ ((pred syntax-table-p) (propertize "#<syntax-table>" 'face 'marginalia-value))
+ ;; Emacs bug#53988: abbrev-table-p throws an error
+ ((guard (static-if (< emacs-major-version 30)
+ (and (vectorp val) (ignore-errors (abbrev-table-p val)))
+ (abbrev-table-p val)))
+ (propertize "#<abbrev-table>" 'face 'marginalia-value))
+ ((pred char-table-p) (propertize "#<char-table>" 'face 'marginalia-value))
+ ;; Callable objects or object closures (OClosures)
+ ((guard (oclosure-type val))
+ (format (propertize "#<oclosure %s>" 'face 'marginalia-function) (oclosure-type val)))
+ ((pred byte-code-function-p) (propertize "#<byte-code-function>" 'face 'marginalia-function))
+ ((and (pred functionp) (pred symbolp))
+ ;; We are not consistent here, values are generally printed
+ ;; unquoted. But we make an exception for function symbols to visually
+ ;; distinguish them from symbols. I am not entirely happy with this,
+ ;; but we should not add quotation to every type.
+ (format (propertize "#'%s" 'face 'marginalia-function) val))
+ ((pred recordp) (format (propertize "#<record %s>" 'face 'marginalia-value) (type-of val)))
+ ((pred symbolp) (propertize (symbol-name val) 'face 'marginalia-symbol))
+ ((pred numberp)
+ (propertize (number-to-string val)
+ 'face 'marginalia-number
+ 'help-echo (and (integerp val)
+ (format "%d, #o%o, #x%x%s" val val val
+ (if (characterp val) (format ", ?%c" val) "")))))
+ (_ (let ((print-escape-newlines t)
+ (print-escape-control-characters t)
+ ;;(print-escape-multibyte t)
+ (print-level 3)
+ (print-length marginalia-field-width))
+ (propertize
+ (replace-regexp-in-string
+ ;; `print-escape-control-characters' does not escape Unicode control characters.
+ "[\x0-\x1F\x7f-\x9f\x061c\x200e\x200f\x202a-\x202e\x2066-\x2069]"
+ (lambda (x) (format "\\x%x" (string-to-char x)))
+ (prin1-to-string
+ (if (stringp val)
+ ;; Get rid of string properties to save some of the precious space
+ (substring-no-properties
+ val 0
+ (min (length val) marginalia-field-width))
+ val))
+ 'fixedcase 'literal)
+ 'face
+ (cond
+ ((listp val) 'marginalia-list)
+ ((stringp val) 'marginalia-string)
+ (t 'marginalia-value))))))))))
+
+(defun marginalia-annotate-variable (cand)
+ "Annotate variable CAND with its documentation string."
+ (when-let* ((sym (intern-soft cand)))
+ (marginalia--fields
+ ((marginalia--symbol-class sym) :face 'marginalia-type)
+ ((marginalia--variable-value sym) :truncate 0.5)
+ ((or (documentation-property sym 'variable-documentation)
+ (marginalia--definition-prefix sym))
+ :truncate 1.0 :face 'marginalia-documentation))))
+
+(defun marginalia-annotate-environment-variable (cand)
+ "Annotate environment variable CAND with its current value."
+ (when-let* ((val (getenv cand)))
+ (marginalia--fields
+ (val :truncate 1.0 :face 'marginalia-value))))
+
+(defun marginalia-annotate-face (cand)
+ "Annotate face CAND with its documentation string and face example."
+ (when-let* ((sym (intern-soft cand)))
+ (marginalia--fields
+ ;; HACK: Manual alignment to fix misalignment due to face
+ ((concat marginalia--pangram #(" " 0 1 (display (space :align-to center))))
+ :face sym)
+ ((documentation-property sym 'face-documentation)
+ :truncate 1.0 :face 'marginalia-documentation))))
+
+(defun marginalia-annotate-color (cand)
+ "Annotate face CAND with its documentation string and face example."
+ (when-let* ((rgb (color-name-to-rgb cand)))
+ (pcase-let* ((`(,r ,g ,b) rgb)
+ (`(,h ,s ,l) (apply #'color-rgb-to-hsl rgb))
+ (cr (color-rgb-to-hex r 0 0))
+ (cg (color-rgb-to-hex 0 g 0))
+ (cb (color-rgb-to-hex 0 0 b))
+ (ch (apply #'color-rgb-to-hex (color-hsl-to-rgb h 1 0.5)))
+ (cs (apply #'color-rgb-to-hex (color-hsl-to-rgb h s 0.5)))
+ (cl (apply #'color-rgb-to-hex (color-hsl-to-rgb 0 0 l))))
+ (marginalia--fields
+ (" " :face `(:background ,(apply #'color-rgb-to-hex rgb)))
+ ((format
+ "%s%s%s %s"
+ (propertize "r" 'face `(:background ,cr :foreground ,(readable-foreground-color cr)))
+ (propertize "g" 'face `(:background ,cg :foreground ,(readable-foreground-color cg)))
+ (propertize "b" 'face `(:background ,cb :foreground ,(readable-foreground-color cb)))
+ (color-rgb-to-hex r g b 2)))
+ ((format
+ "%s%s%s %3s° %3s%% %3s%%"
+ (propertize "h" 'face `(:background ,ch :foreground ,(readable-foreground-color ch)))
+ (propertize "s" 'face `(:background ,cs :foreground ,(readable-foreground-color cs)))
+ (propertize "l" 'face `(:background ,cl :foreground ,(readable-foreground-color cl)))
+ (round (* 360 h))
+ (round (* 100 s))
+ (round (* 100 l))))))))
+
+(defun marginalia-annotate-char (cand)
+ "Annotate character CAND with its general character category and character code."
+ (when-let* ((char (char-from-name cand t)))
+ (marginalia--fields
+ (:left char :format" (%c)" :face 'marginalia-char)
+ (char :format "%06X" :face 'marginalia-number)
+ ((char-code-property-description
+ 'general-category
+ (get-char-code-property char 'general-category))
+ :width 30 :face 'marginalia-documentation))))
+
+(defun marginalia-annotate-minor-mode (cand)
+ "Annotate minor-mode CAND with status and documentation string."
+ (let* ((sym (intern-soft cand))
+ (message-log-max nil)
+ (mode (if (and sym (boundp sym))
+ sym
+ (lookup-minor-mode-from-indicator cand)))
+ (lighter (cdr (assq mode minor-mode-alist)))
+ (lighter-str (and lighter (string-trim (format-mode-line (cons t lighter))))))
+ (marginalia--fields
+ ((if (and (boundp mode) (symbol-value mode))
+ (propertize "On" 'face 'marginalia-on)
+ (propertize "Off" 'face 'marginalia-off)) :width 3)
+ ((if (local-variable-if-set-p mode) "Local" "Global") :width 6 :face 'marginalia-type)
+ (lighter-str :width 20 :face 'marginalia-lighter)
+ ((marginalia--function-doc mode)
+ :truncate 1.0 :face 'marginalia-documentation))))
+
+(defun marginalia-annotate-package (cand)
+ "Annotate package CAND with its description summary."
+ (when-let* ((pkg-alist (bound-and-true-p package-alist))
+ ;; See `package-get-version'.
+ (name (replace-regexp-in-string
+ "-[0-9]\\(?:[0-9.]\\|pre\\|beta\\|alpha\\|snapshot\\)+\\'" "" cand))
+ (pkg (intern-soft name))
+ (desc (or (unless (equal name cand)
+ (cl-loop with version = (substring cand (1+ (length name)))
+ for d in (alist-get pkg pkg-alist)
+ if (equal (package-version-join (package-desc-version d)) version)
+ return d))
+ ;; taken from `describe-package-1'
+ (car (alist-get pkg pkg-alist))
+ (if-let* ((built-in (assq pkg package--builtins)))
+ (package--from-builtin built-in)
+ (car (alist-get pkg package-archive-contents))))))
+ (marginalia--fields
+ ((package-version-join (package-desc-version desc)) :truncate 16 :face 'marginalia-version)
+ ((cond
+ ((package-desc-archive desc) (propertize (package-desc-archive desc) 'face 'marginalia-archive))
+ (t (propertize (or (package-desc-status desc) "orphan") 'face 'marginalia-installed))) :truncate 12)
+ ((package-desc-summary desc) :truncate 1.0 :face 'marginalia-documentation))))
+
+(defun marginalia--bookmark-type (bm)
+ "Return bookmark type string of BM.
+The string is transformed according to `marginalia--bookmark-type-transforms'."
+ (let ((handler (or (bookmark-prop-get bm 'handler) 'bookmark-default-handler)))
+ (and
+ ;; Some libraries use lambda handlers instead of symbols. For
+ ;; example the function `xwidget-webkit-bookmark-make-record' is
+ ;; affected. I consider this bad style since then the lambda is
+ ;; persisted.
+ (symbolp handler)
+ (or (get handler 'bookmark-handler-type)
+ (let ((str (symbol-name handler))
+ case-fold-search)
+ (dolist (transformer marginalia--bookmark-type-transforms str)
+ (when (string-match-p (car transformer) str)
+ (setq str
+ (if (stringp (cdr transformer))
+ (replace-regexp-in-string (car transformer) (cdr transformer) str)
+ (funcall (cdr transformer) str))))))))))
+
+(defun marginalia-annotate-bookmark (cand)
+ "Annotate bookmark CAND with its file name and front context string."
+ (when-let* ((bm (assoc cand (bound-and-true-p bookmark-alist))))
+ (marginalia--fields
+ ((marginalia--bookmark-type bm) :width 10 :face 'marginalia-type)
+ ((or (bookmark-prop-get bm 'filename)
+ (bookmark-prop-get bm 'location))
+ :truncate (if (bookmark-prop-get bm 'filename) -0.5 0.5)
+ :face 'marginalia-file-name)
+ ((let ((front (or (bookmark-prop-get bm 'front-context-string) ""))
+ (rear (or (bookmark-prop-get bm 'rear-context-string) "")))
+ (unless (and (string-blank-p front) (string-blank-p rear))
+ (string-clean-whitespace
+ (concat front (marginalia--ellipsis) rear))))
+ :truncate 0.5 :face 'marginalia-documentation))))
+
+(defun marginalia-annotate-customize-group (cand)
+ "Annotate customization group CAND with its documentation string."
+ (marginalia--documentation (documentation-property (intern cand) 'group-documentation)))
+
+(defun marginalia-annotate-input-method (cand)
+ "Annotate input method CAND with its description."
+ (marginalia--documentation (nth 4 (assoc cand input-method-alist))))
+
+(defun marginalia-annotate-charset (cand)
+ "Annotate charset CAND with its description."
+ (marginalia--documentation (charset-description (intern cand))))
+
+(defun marginalia-annotate-coding-system (cand)
+ "Annotate coding system CAND with its description."
+ (marginalia--documentation (coding-system-doc-string (intern cand))))
+
+(defun marginalia--buffer-status (buffer)
+ "Return the status of BUFFER as a string."
+ (format-mode-line '((:propertize "%1*%1+%1@" face marginalia-modified)
+ marginalia-separator
+ (7 (:propertize "%I" face marginalia-size))
+ marginalia-separator
+ ;; InactiveMinibuffer has 18 letters, but there are longer names.
+ ;; For example Org-Agenda produces very long mode names.
+ ;; Therefore we have to truncate.
+ (20 (-20 (:propertize mode-name face marginalia-mode))))
+ nil nil buffer))
+
+(defun marginalia--buffer-file (buffer)
+ "Return the file or process name of BUFFER."
+ (if-let* ((proc (get-buffer-process buffer)))
+ (format "(%s %s) %s"
+ proc (process-status proc)
+ (marginalia--abbreviate-file-name (buffer-local-value 'default-directory buffer)))
+ (marginalia--abbreviate-file-name
+ (or (cond
+ ;; see ibuffer-buffer-file-name
+ ((buffer-file-name buffer))
+ ((when-let* ((dir (and (local-variable-p 'dired-directory buffer)
+ (buffer-local-value 'dired-directory buffer))))
+ (expand-file-name (if (stringp dir) dir (car dir))
+ (buffer-local-value 'default-directory buffer))))
+ ((local-variable-p 'list-buffers-directory buffer)
+ (buffer-local-value 'list-buffers-directory buffer)))
+ ""))))
+
+(defun marginalia-annotate-buffer (cand)
+ "Annotate buffer CAND with modification status, file name and major mode."
+ ;; Emacs 31: `project--read-project-buffer' uses `uniquify-get-unique-names'
+ (when-let* ((buffer (or (and (stringp cand)
+ (get-text-property 0 'uniquify-orig-buffer cand))
+ (get-buffer cand))))
+ (if (buffer-live-p buffer)
+ (marginalia--fields
+ ((marginalia--buffer-status buffer))
+ ((marginalia--buffer-file buffer)
+ :truncate -0.5 :face 'marginalia-file-name))
+ (marginalia--fields ("(dead buffer)" :face 'error)))))
+
+(defun marginalia--full-candidate (cand)
+ "Return completion candidate CAND in full.
+For some completion tables, the completion candidates offered are
+meant to be only a part of the full minibuffer contents. For
+example, during file name completion the candidates are one path
+component of a full file path."
+ (if-let* ((win (active-minibuffer-window)))
+ (with-current-buffer (window-buffer win)
+ (concat (let ((end (minibuffer-prompt-end)))
+ (buffer-substring-no-properties
+ end (+ end marginalia--base-position)))
+ cand))
+ ;; no minibuffer is active, trust that cand already conveys all
+ ;; necessary information (there's not much else we can do)
+ cand))
+
+(defun marginalia--remote-file-p (file)
+ "Return non-nil if FILE is remote.
+The return value is a string describing the remote location,
+e.g., the protocol."
+ (save-match-data
+ (setq file (let (file-name-handler-alist)
+ (substitute-in-file-name file)))
+ (cl-loop for r in marginalia-remote-file-regexps
+ if (string-match r file)
+ return (or (match-string 1 file) "remote"))))
+
+(defun marginalia--annotate-local-file (cand)
+ "Annotate local file CAND."
+ (marginalia--in-minibuffer
+ (when-let* ((attrs (ignore-errors
+ ;; may throw permission denied errors
+ (file-attributes (substitute-in-file-name
+ (marginalia--full-candidate cand))
+ 'integer))))
+ ;; HACK: Format differently accordingly to alignment, since the file owner
+ ;; is usually not displayed. Otherwise we will see an excessive amount of
+ ;; whitespace in front of the file permissions. Furthermore the alignment
+ ;; in `consult-buffer' will look ugly. Find a better solution!
+ (if (eq marginalia-align 'right)
+ (marginalia--fields
+ ;; File owner at the left
+ ((marginalia--file-owner attrs) :face 'marginalia-file-owner)
+ ((marginalia--file-modes attrs))
+ ((marginalia--file-size attrs) :face 'marginalia-size :width -7)
+ ((marginalia--time (file-attribute-modification-time attrs))
+ :face 'marginalia-date :width -12))
+ (marginalia--fields
+ ((marginalia--file-modes attrs))
+ ((marginalia--file-size attrs) :face 'marginalia-size :width -7)
+ ((marginalia--time (file-attribute-modification-time attrs))
+ :face 'marginalia-date :width -12)
+ ;; File owner at the right
+ ((marginalia--file-owner attrs) :face 'marginalia-file-owner))))))
+
+(defun marginalia-annotate-file (cand)
+ "Annotate file CAND with its size, modification time and other attributes.
+These annotations are skipped for remote paths."
+ (if-let* ((remote (or (marginalia--remote-file-p cand)
+ (when-let* ((win (active-minibuffer-window)))
+ (with-current-buffer (window-buffer win)
+ (marginalia--remote-file-p (minibuffer-contents-no-properties)))))))
+ (marginalia--fields (remote :format "*%s*" :face 'marginalia-documentation))
+ (marginalia--annotate-local-file cand)))
+
+(defun marginalia--file-owner (attrs)
+ "Return file owner given ATTRS."
+ (let ((uid (file-attribute-user-id attrs))
+ (gid (file-attribute-group-id attrs)))
+ (when (or (/= (user-uid) uid) (/= (group-gid) gid))
+ (format "%s:%s"
+ (or (user-login-name uid) uid)
+ (or (group-name gid) gid)))))
+
+(defun marginalia--file-size (attrs)
+ "Return formatted file size given ATTRS."
+ (propertize (file-size-human-readable (file-attribute-size attrs))
+ 'help-echo (number-to-string (file-attribute-size attrs))))
+
+(defun marginalia--file-modes (attrs)
+ "Return fontified file modes given the ATTRS."
+ ;; Without caching this can a be significant portion of the time
+ ;; `marginalia-annotate-file' takes to execute. Caching improves performance
+ ;; by about a factor of 20.
+ (setq attrs (file-attribute-modes attrs))
+ (or (car (member attrs marginalia--fontified-file-modes))
+ (progn
+ (setq attrs (substring attrs)) ;; copy because attrs is about to be modified
+ (dotimes (i (length attrs))
+ (put-text-property
+ i (1+ i) 'face
+ (pcase (aref attrs i)
+ (?- 'marginalia-file-priv-no)
+ (?d 'marginalia-file-priv-dir)
+ (?l 'marginalia-file-priv-link)
+ (?r 'marginalia-file-priv-read)
+ (?w 'marginalia-file-priv-write)
+ (?x 'marginalia-file-priv-exec)
+ ((or ?s ?S ?t ?T) 'marginalia-file-priv-other)
+ (_ 'marginalia-file-priv-rare))
+ attrs))
+ (push attrs marginalia--fontified-file-modes)
+ attrs)))
+
+(defconst marginalia--time-relative
+ `((100 "sec" 1)
+ (,(* 60 100) "min" 60.0)
+ (,(* 3600 30) "hour" 3600.0)
+ (,(* 3600 24 400) "day" ,(* 3600.0 24.0))
+ (nil "year" ,(* 365.25 24 3600)))
+ "Formatting used by the function `marginalia--time-relative'.")
+
+;; Taken from `seconds-to-string'.
+(defun marginalia--time-relative (time)
+ "Format TIME as a relative age."
+ (setq time (max 0 (float-time (time-since time))))
+ (let ((sts marginalia--time-relative) here)
+ (while (and (car (setq here (pop sts))) (<= (car here) time)))
+ (setq time (round time (caddr here)))
+ (format "%s %s%s ago" time (cadr here) (if (= time 1) "" "s"))))
+
+(defun marginalia--time-absolute (time)
+ "Format TIME as an absolute age."
+ (let ((system-time-locale "C"))
+ (format-time-string
+ (if (> (decoded-time-year (decode-time (current-time)))
+ (decoded-time-year (decode-time time)))
+ " %Y %b %d"
+ "%b %d %H:%M")
+ time)))
+
+(defun marginalia--time (time)
+ "Format file age TIME, suitably for use in annotations."
+ (propertize
+ (if (< (float-time (time-since time)) marginalia-max-relative-age)
+ (marginalia--time-relative time)
+ (marginalia--time-absolute time))
+ 'help-echo (format-time-string "%Y-%m-%d %T" time)))
+
+(defvar-local marginalia--project-root 'unset)
+(defun marginalia--project-root ()
+ "Return project root."
+ (marginalia--in-minibuffer
+ (when (eq marginalia--project-root 'unset)
+ (setq marginalia--project-root
+ (or (let ((prompt (minibuffer-prompt))
+ case-fold-search)
+ (and (string-match
+ "\\`\\(?:Dired\\|Find file\\) in \\(.*\\): \\'"
+ prompt)
+ (match-string 1 prompt)))
+ (when-let* ((proj (project-current)))
+ (project-root proj)))))
+ marginalia--project-root))
+
+(defun marginalia-annotate-project-file (cand)
+ "Annotate file CAND with its size, modification time and other attributes."
+ ;; Absolute project directories also report project-file category
+ (if (file-name-absolute-p cand)
+ (marginalia-annotate-file cand)
+ (when-let* ((root (marginalia--project-root)))
+ (marginalia-annotate-file (expand-file-name cand root)))))
+
+(defvar-local marginalia--library-cache nil)
+(defun marginalia--library-cache ()
+ "Return hash table from library name to library file."
+ (marginalia--in-minibuffer
+ ;; `locate-file' and `locate-library' are bottlenecks for the
+ ;; annotator. Therefore we compute all the library paths first.
+ (unless marginalia--library-cache
+ (setq marginalia--library-cache (make-hash-table :test #'equal))
+ (dolist (dir (delete-dups
+ (reverse ;; Reverse because of shadowing
+ (append load-path (custom-theme--load-path))))) ;; Include themes
+ (dolist (file (ignore-errors
+ (directory-files dir 'full
+ "\\.el\\(?:\\.gz\\)?\\'")))
+ (puthash (marginalia--library-name file)
+ file marginalia--library-cache))))
+ marginalia--library-cache))
+
+(defun marginalia--library-name (file)
+ "Get name of library FILE."
+ (replace-regexp-in-string "\\(\\.gz\\|\\.elc?\\)+\\'" ""
+ (file-name-nondirectory file)))
+
+(defun marginalia--library-doc (file)
+ "Return library documentation string for FILE."
+ (let ((doc (get-text-property 0 'marginalia--library-doc file)))
+ (unless doc
+ ;; Extract documentation string. We cannot use `lm-summary' here,
+ ;; since it decompresses the whole file, which is slower.
+ (setq doc (or (ignore-errors
+ (let ((shell-file-name "sh")
+ (shell-command-switch "-c"))
+ (shell-command-to-string
+ (format (if (string-suffix-p ".gz" file)
+ "gzip -c -q -d %s | head -n1"
+ "head -n1 %s")
+ (shell-quote-argument file)))))
+ ""))
+ (cond
+ ((string-match "\\`(define-package\\s-+\"\\([^\"]+\\)\"" doc)
+ (setq doc (format "Generated package description from %s.el"
+ (match-string 1 doc))))
+ ((string-match "\\`;+\\s-*" doc)
+ (setq doc (substring doc (match-end 0)))
+ (when (string-match "\\`[^ \t]+\\s-+-+\\s-+" doc)
+ (setq doc (substring doc (match-end 0))))
+ (when (string-match "\\s-*-\\*-" doc)
+ (setq doc (substring doc 0 (match-beginning 0)))))
+ (t (setq doc "")))
+ ;; Add the documentation string to the cache
+ (put-text-property 0 1 'marginalia--library-doc doc file))
+ doc))
+
+(defun marginalia-annotate-library (cand)
+ "Annotate library CAND with documentation and path."
+ (setq cand (marginalia--library-name cand))
+ (when-let* ((file (gethash cand (marginalia--library-cache))))
+ (marginalia--fields
+ ;; Display if the corresponding feature is loaded.
+ ;; feature/=library file, but better than nothing.
+ ((when-let* ((sym (intern-soft cand)))
+ (when (memq sym features)
+ (propertize "Loaded" 'face 'marginalia-on)))
+ :width 8)
+ ((marginalia--library-doc file)
+ :truncate 1.0 :face 'marginalia-documentation)
+ ((marginalia--abbreviate-file-name (file-name-directory file))
+ :truncate -0.5 :face 'marginalia-file-name))))
+
+(defun marginalia-annotate-theme (cand)
+ "Annotate theme CAND with documentation and path."
+ (when-let* ((file (gethash (concat cand "-theme") (marginalia--library-cache))))
+ (marginalia--fields
+ ((marginalia--library-doc file)
+ :truncate 1.0 :face 'marginalia-documentation)
+ ((marginalia--abbreviate-file-name (file-name-directory file))
+ :truncate -1.0 :face 'marginalia-file-name))))
+
+(defun marginalia-annotate-frame (cand)
+ "Annotate frame named CAND with window and buffer information."
+ (when-let* ((frame (cl-loop
+ for f in (frame-list)
+ if (or (equal cand (frame-parameter f 'name))
+ ;; `frame-id' is an Emacs 31 addition
+ (when (fboundp 'frame-id)
+ (equal cand (number-to-string (frame-id f)))))
+ return f)))
+ (let ((wins (window-list frame)))
+ (marginalia--fields
+ ((length wins) :format "win:%s" :face 'marginalia-size)
+ ((if (eq frame (selected-frame))
+ "(current frame)"
+ (mapconcat (lambda (w) (buffer-name (window-buffer w))) wins " "))
+ :face 'marginalia-documentation)))))
+
+(defun marginalia-annotate-tab (cand)
+ "Annotate named tab CAND with tab index, window and buffer information."
+ (when-let* ((tabs (funcall tab-bar-tabs-function))
+ (index (seq-position
+ tabs nil
+ (lambda (tab _) (equal (alist-get 'name tab) cand)))))
+ (let* ((tab (nth index tabs))
+ (ws (alist-get 'ws tab))
+ (bufs (window-state-buffers ws)))
+ ;; When the buffer key is present in the window state it is added in front
+ ;; of the window buffer list and gets duplicated.
+ (when (cadr (assq 'buffer ws)) (pop bufs))
+ (marginalia--fields
+ (:left (1+ index) :format " (%s)" :face 'marginalia-key)
+ ((if (eq (car tab) 'current-tab)
+ (length (window-list nil 'no-minibuf))
+ (length bufs))
+ :format "win:%s" :face 'marginalia-size)
+ ((or (alist-get 'group tab) 'none)
+ :format "group:%s" :face 'marginalia-type :truncate 20)
+ ((if (eq (car tab) 'current-tab)
+ "(current tab)"
+ (string-join bufs " "))
+ :face 'marginalia-documentation)))))
+
+(defun marginalia-classify-by-command-name ()
+ "Lookup category for current command."
+ (and marginalia--command
+ (or (alist-get marginalia--command marginalia-command-categories)
+ ;; The command can be an alias, e.g., `recentf' -> `recentf-open'.
+ (when-let* ((chain (function-alias-p marginalia--command)))
+ (alist-get (car (last chain)) marginalia-command-categories)))))
+
+(defun marginalia-classify-original-category ()
+ "Return original category reported by completion metadata."
+ ;; Bypass our `marginalia--completion-metadata-get' advice.
+ (when-let* ((cat (marginalia--orig-completion-metadata-get marginalia--metadata 'category)))
+ ;; Ignore `symbol-help' category in order to ensure that the categories are
+ ;; refined to our categories function and variable.
+ (and (not (eq cat 'symbol-help)) cat)))
+
+(defun marginalia-classify-symbol ()
+ "Determine if currently completing symbols."
+ (when-let* ((mct minibuffer-completion-table))
+ (when (or (eq mct 'help--symbol-completion-table)
+ (obarrayp mct)
+ (and (not (functionp mct)) (consp mct) (symbolp (car mct)))) ; assume list of symbols
+ 'symbol)))
+
+(defun marginalia-classify-by-prompt ()
+ "Determine category by matching regexps against the minibuffer prompt.
+This runs through the `marginalia-prompt-categories' alist
+looking for a regexp that matches the prompt."
+ (when-let* ((prompt (minibuffer-prompt)))
+ (setq prompt
+ (replace-regexp-in-string "(.*?default.*?)\\|\\[.*?\\]" "" prompt))
+ (cl-loop with case-fold-search = t
+ for (regexp . category) in marginalia-prompt-categories
+ when (string-match-p regexp prompt)
+ return category)))
+
+(defun marginalia--cache-reset (&rest _)
+ "Reset the cache."
+ (setq marginalia--cache (and marginalia--cache (> marginalia--cache-size 0)
+ (cons nil (make-hash-table :test #'equal
+ :size marginalia--cache-size)))))
+
+(defun marginalia--cached (cache fun key)
+ "Cached application of function FUN with KEY.
+The CACHE keeps around the last `marginalia--cache-size' computed
+annotations. The cache is mainly useful when scrolling in
+completion UIs like Vertico or Icomplete."
+ (if cache
+ (let ((ht (cdr cache)))
+ (or (gethash key ht)
+ (let ((val (funcall fun key)))
+ (push key (car cache))
+ (puthash key val ht)
+ (when (>= (hash-table-count ht) marginalia--cache-size)
+ (let ((end (last (car cache) 2)))
+ (remhash (cadr end) ht)
+ (setcdr end nil)))
+ val)))
+ (funcall fun key)))
+
+(defun marginalia--align (cands)
+ "Align annotations of CANDS according to `marginalia-align'."
+ (cl-loop
+ for (cand . ann) in cands do
+ (when-let* ((align (text-property-any 0 (length ann) 'marginalia--align t ann)))
+ (setq marginalia--cand-width-max
+ (max marginalia--cand-width-max
+ (* (ceiling (+ (string-width cand) (string-width ann 0 align))
+ marginalia--cand-width-step)
+ marginalia--cand-width-step)))))
+ (cl-loop
+ for (cand . ann) in cands collect
+ (progn
+ (when-let* ((align (text-property-any 0 (length ann) 'marginalia--align t ann)))
+ (put-text-property
+ align (1+ align) 'display
+ `(space :align-to
+ ,(pcase-exhaustive marginalia-align
+ ('center `(+ center ,marginalia-align-offset))
+ ('left `(+ left ,(+ marginalia-align-offset marginalia--cand-width-max)))
+ ('right `(+ right ,(+ marginalia-align-offset 1
+ (- (string-width ann 0 align)
+ (string-width ann)))))))
+ ann))
+ (list cand "" ann))))
+
+(defun marginalia--affixate (metadata annotator cands)
+ "Affixate CANDS given METADATA and Marginalia ANNOTATOR."
+ ;; Compute minimum width of windows, which display the minibuffer, including
+ ;; the miniwindow. In general the computed width corresponds to the full
+ ;; frame width, since the miniwindow spans the full frame. For example
+ ;; `vertico-buffer' displays the minibuffer in a separate window. Similarly,
+ ;; we could detect other types of completion buffers, e.g., Embark Collect or
+ ;; the default completion buffer, and compute smaller widths.
+ (let* ((width (cl-loop for win in (get-buffer-window-list) minimize (window-width win)))
+ (marginalia-field-width (min (/ width 2) marginalia-field-width))
+ (marginalia--metadata metadata)
+ (cache marginalia--cache)
+ (orig-buf minibuffer--original-buffer))
+ (marginalia--align
+ ;; Run the annotators in the original window. `with-selected-window'
+ ;; is necessary because of `lookup-minor-mode-from-indicator'.
+ ;; Otherwise it would suffice to only change the current buffer. We
+ ;; need the `selected-window' fallback for Embark Occur.
+ (with-selected-window (or (minibuffer-selected-window) (selected-window))
+ (with-current-buffer (if (buffer-live-p orig-buf) orig-buf (current-buffer))
+ (cl-loop for cand in cands collect
+ (let ((ann (or (marginalia--cached cache annotator cand) "")))
+ (cons cand (if (string-blank-p ann) "" ann)))))))))
+
+(defun marginalia--completion-metadata-get (metadata prop)
+ "Meant as :before-until advice for `completion-metadata-get'.
+METADATA is the metadata.
+PROP is the property which is looked up."
+ (pcase prop
+ ('affixation-function
+ ;; We do want the advice triggered for `completion-metadata-get'.
+ (when-let* ((cat (completion-metadata-get metadata 'category))
+ (annotator (marginalia--annotator cat)))
+ (apply-partially #'marginalia--affixate metadata annotator)))
+ ('category
+ ;; Find the completion category by trying each of our classifiers.
+ ;; Store the metadata for `marginalia-classify-original-category'.
+ (let ((marginalia--metadata metadata))
+ (run-hook-with-args-until-success 'marginalia-classifiers)))))
+
+(defun marginalia--minibuffer-setup ()
+ "Setup the minibuffer for Marginalia.
+Remember `this-command' for `marginalia-classify-by-command-name'."
+ (setq marginalia--cache t marginalia--command this-command)
+ ;; Reset cache if window size changes, recompute alignment
+ (add-hook 'window-state-change-hook #'marginalia--cache-reset nil 'local)
+ (add-hook 'context-menu-functions #'marginalia--context-menu nil t)
+ (marginalia--cache-reset))
+
+(defun marginalia--base-position (completions)
+ "Record the base position of COMPLETIONS."
+ ;; As a small optimization we track the base position only for file
+ ;; completions, since `marginalia--full-candidate' is currently used only by
+ ;; the file annotation function.
+ ;; bug#75910: category instead of `minibuffer-completing-file-name'
+ (when minibuffer-completing-file-name
+ (let ((base (or (cdr (last completions)) 0)))
+ (unless (= marginalia--base-position base)
+ (marginalia--cache-reset)
+ (setq marginalia--base-position base
+ marginalia--cand-width-max (default-value 'marginalia--cand-width-max)))))
+ completions)
+
+;;;###autoload
+(define-minor-mode marginalia-mode
+ "Annotate completion candidates with richer information."
+ :global t :group 'marginalia
+ (if marginalia-mode
+ (progn
+ ;; Remember `this-command' in order to select the annotation function.
+ (add-hook 'minibuffer-setup-hook #'marginalia--minibuffer-setup)
+ ;; Replace the metadata function.
+ (advice-add (compat-function completion-metadata-get) :before-until #'marginalia--completion-metadata-get)
+ (advice-add #'completion-metadata-get :before-until #'marginalia--completion-metadata-get)
+ ;; Record completion base position, for `marginalia--full-candidate'
+ (advice-add #'completion-all-completions :filter-return #'marginalia--base-position))
+ (advice-remove #'completion-all-completions #'marginalia--base-position)
+ (advice-remove (compat-function completion-metadata-get) #'marginalia--completion-metadata-get)
+ (advice-remove #'completion-metadata-get #'marginalia--completion-metadata-get)
+ (remove-hook 'minibuffer-setup-hook #'marginalia--minibuffer-setup)))
+
+(defun marginalia--completion-metadata ()
+ "Get completion metadata."
+ (let* ((end (minibuffer-prompt-end))
+ (pt (max 0 (- (point) end))))
+ (completion-metadata (buffer-substring-no-properties end (+ end pt))
+ minibuffer-completion-table
+ minibuffer-completion-predicate)))
+
+(defun marginalia--builtin-annotator-p (md)
+ "Builtin annotator available in metadata MD?"
+ (or (marginalia--orig-completion-metadata-get md 'annotation-function)
+ (marginalia--orig-completion-metadata-get md 'affixation-function)))
+
+;;;###autoload
+(defun marginalia-cycle ()
+ "Cycle between annotators in `marginalia-annotators'."
+ ;; Only show `marginalia-cycle' in M-x in recursive minibuffers
+ (declare (completion (lambda (&rest _) (> (minibuffer-depth) 1))))
+ (interactive)
+ (with-current-buffer (window-buffer
+ (or (active-minibuffer-window)
+ (user-error "Marginalia: No active minibuffer")))
+ (let* ((md (marginalia--completion-metadata))
+ (cat (or (completion-metadata-get md 'category)
+ (user-error "Marginalia: Unknown completion category")))
+ (ann (or (assq cat marginalia-annotators)
+ (user-error "Marginalia: No annotators found for category `%s'" cat))))
+ (setcdr ann (append (cddr ann) (list (cadr ann))))
+ ;; When the builtin annotator is selected and no builtin function is
+ ;; available, skip to the next annotator. Bypass the
+ ;; `marginalia--completion-metadata-get' advice.
+ (when (and (eq (cadr ann) 'builtin) (not (marginalia--builtin-annotator-p md)))
+ (setcdr ann (append (cddr ann) (list (cadr ann)))))
+ (marginalia--cache-reset)
+ (message "Marginalia: Use annotator `%s' for category `%s'" (cadr ann) cat))))
+
+(defun marginalia--context-menu (menu _event)
+ "Add Marginalia commands to context MENU."
+ (when-let* ((md (marginalia--completion-metadata))
+ (cat (completion-metadata-get md 'category))
+ (ann (assq cat marginalia-annotators))
+ (items (cl-loop
+ for fun in (cdr ann) for i from 0
+ if (or (not (eq fun 'builtin)) (marginalia--builtin-annotator-p md))
+ collect
+ (vector
+ (thread-last (symbol-name fun)
+ (replace-regexp-in-string ".*?-+annotate-+" "")
+ (replace-regexp-in-string "-+" " ")
+ capitalize)
+ (let ((i i))
+ (lambda ()
+ (interactive)
+ (setcdr ann (append (drop i (cdr ann)) (take i (cdr ann))))
+ (marginalia--cache-reset)
+ (message "Marginalia: Use annotator `%s' for category `%s'" (cadr ann) cat)))
+ :style 'radio :selected (eq fun (cadr ann))))))
+ (define-key menu [marginalia]
+ `("Marginalia" . ,(easy-menu-create-menu
+ "" `(["Cycle" marginalia-cycle] "---" ,@items)))))
+ menu)
+
+(provide 'marginalia)
+;;; marginalia.el ends here
diff --git a/.config/emacs/lisp/minadstack/orderless.el b/.config/emacs/lisp/minadstack/orderless.el
new file mode 100644
index 0000000..7cee5c8
--- /dev/null
+++ b/.config/emacs/lisp/minadstack/orderless.el
@@ -0,0 +1,672 @@
+;;; orderless.el --- Completion style for matching regexps in any order -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021-2026 Free Software Foundation, Inc.
+
+;; Author: Omar Antolín Camarena <omar@matem.unam.mx>
+;; Maintainer: Omar Antolín Camarena <omar@matem.unam.mx>, Daniel Mendler <mail@daniel-mendler.de>
+;; Keywords: matching, completion
+;; Version: 1.6
+;; URL: https://github.com/oantolin/orderless
+;; Package-Requires: ((emacs "27.1") (compat "30"))
+
+;; This file is 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:
+
+;; This package provides an `orderless' completion style that divides
+;; the pattern into components (space-separated by default), and
+;; matches candidates that match all of the components in any order.
+
+;; Completion styles are used as entries in the variables
+;; `completion-styles' and `completion-category-overrides', see their
+;; documentation.
+
+;; To use this completion style you can use the following minimal
+;; configuration:
+
+;; (setq completion-styles '(orderless basic))
+
+;; You can customize the `orderless-component-separator' to decide how
+;; the input pattern is split into component regexps. The default
+;; splits on spaces. You might want to add hyphens and slashes, for
+;; example, to ease completion of symbols and file paths,
+;; respectively.
+
+;; Each component can match in any one of several matching styles:
+;; literally, as a regexp, as an initialism, in the flex style, or as
+;; word prefixes. It is easy to add new styles: they are functions
+;; from strings to strings that map a component to a regexp to match
+;; against. The variable `orderless-matching-styles' lists the
+;; matching styles to be used for components, by default it allows
+;; literal and regexp matching.
+
+;;; Code:
+
+(require 'compat)
+(eval-when-compile (require 'cl-lib))
+
+(defgroup orderless nil
+ "Completion method that matches space-separated regexps in any order."
+ :link '(info-link :tag "Info Manual" "(orderless)")
+ :link '(url-link :tag "Website" "https://github.com/oantolin/orderless")
+ :link '(emacs-library-link :tag "Library Source" "orderless.el")
+ :group 'minibuffer)
+
+(defface orderless-match-face-0
+ '((default :weight bold)
+ (((class color) (min-colors 88) (background dark)) :foreground "#72a4ff")
+ (((class color) (min-colors 88) (background light)) :foreground "#223fbf")
+ (t :foreground "blue"))
+ "Face for matches of components numbered 0 mod 4.")
+
+(defface orderless-match-face-1
+ '((default :weight bold)
+ (((class color) (min-colors 88) (background dark)) :foreground "#ed92f8")
+ (((class color) (min-colors 88) (background light)) :foreground "#8f0075")
+ (t :foreground "magenta"))
+ "Face for matches of components numbered 1 mod 4.")
+
+(defface orderless-match-face-2
+ '((default :weight bold)
+ (((class color) (min-colors 88) (background dark)) :foreground "#90d800")
+ (((class color) (min-colors 88) (background light)) :foreground "#145a00")
+ (t :foreground "green"))
+ "Face for matches of components numbered 2 mod 4.")
+
+(defface orderless-match-face-3
+ '((default :weight bold)
+ (((class color) (min-colors 88) (background dark)) :foreground "#f0ce43")
+ (((class color) (min-colors 88) (background light)) :foreground "#804000")
+ (t :foreground "yellow"))
+ "Face for matches of components numbered 3 mod 4.")
+
+(defcustom orderless-component-separator #'orderless-escapable-split-on-space
+ "Component separators for orderless completion.
+This can either be a string, which is passed to `split-string',
+or a function of a single string argument."
+ :type `(choice (const :tag "Spaces" " +")
+ (const :tag "Spaces, hyphen or slash" " +\\|[-/]")
+ (const :tag "Escapable space"
+ ,#'orderless-escapable-split-on-space)
+ (const :tag "Quotable spaces" ,#'split-string-and-unquote)
+ (regexp :tag "Custom regexp")
+ (function :tag "Custom function")))
+
+(defcustom orderless-match-faces
+ [orderless-match-face-0
+ orderless-match-face-1
+ orderless-match-face-2
+ orderless-match-face-3]
+ "Vector of faces used (cyclically) for component matches."
+ :type '(vector face))
+
+(defcustom orderless-matching-styles
+ (list #'orderless-literal #'orderless-regexp)
+ "List of component matching styles.
+If this variable is nil, regexp matching is assumed.
+
+A matching style is simply a function from strings to regexps.
+The returned regexps can be either strings or s-expressions in
+`rx' syntax. If the resulting regexp has no capturing groups,
+the entire match is highlighted, otherwise just the captured
+groups are. Several are provided with this package: try
+customizing this variable to see a list of them."
+ :type '(repeat function)
+ :options (list #'orderless-regexp
+ #'orderless-literal
+ #'orderless-initialism
+ #'orderless-prefixes
+ #'orderless-flex))
+
+(defcustom orderless-affix-dispatch-alist
+ `((?% . ,#'char-fold-to-regexp)
+ (?! . ,#'orderless-not)
+ (?& . ,#'orderless-annotation)
+ (?, . ,#'orderless-initialism)
+ (?= . ,#'orderless-literal)
+ (?^ . ,#'orderless-literal-prefix)
+ (?~ . ,#'orderless-flex))
+ "Alist associating characters to matching styles.
+The function `orderless-affix-dispatch' uses this list to
+determine how to match a pattern component: if the component
+either starts or ends with a character used as a key in this
+alist, the character is removed from the component and the rest is
+matched according the style associated to it."
+ :type `(alist
+ :key-type character
+ :value-type (choice
+ (const :tag "Annotation" ,#'orderless-annotation)
+ (const :tag "Literal" ,#'orderless-literal)
+ (const :tag "Without literal" ,#'orderless-without-literal)
+ (const :tag "Literal prefix" ,#'orderless-literal-prefix)
+ (const :tag "Regexp" ,#'orderless-regexp)
+ (const :tag "Not" ,#'orderless-not)
+ (const :tag "Flex" ,#'orderless-flex)
+ (const :tag "Initialism" ,#'orderless-initialism)
+ (const :tag "Prefixes" ,#'orderless-prefixes)
+ (const :tag "Ignore diacritics" ,#'char-fold-to-regexp)
+ (function :tag "Custom matching style"))))
+
+(defun orderless-affix-dispatch (component _index _total)
+ "Match COMPONENT according to the styles in `orderless-affix-dispatch-alist'.
+If the COMPONENT starts or ends with one of the characters used
+as a key in `orderless-affix-dispatch-alist', then that character
+is removed and the remainder of the COMPONENT is matched in the
+style associated to the character."
+ (let ((len (length component))
+ (alist orderless-affix-dispatch-alist))
+ (when (> len 0)
+ (cond
+ ;; Ignore single dispatcher character
+ ((and (= len 1) (alist-get (aref component 0) alist)) #'ignore)
+ ;; Prefix
+ ((when-let* ((style (alist-get (aref component 0) alist)))
+ (cons style (substring component 1))))
+ ;; Suffix
+ ((when-let* ((style (alist-get (aref component (1- len)) alist)))
+ (cons style (substring component 0 -1))))))))
+
+(defcustom orderless-style-dispatchers (list #'orderless-affix-dispatch)
+ "List of style dispatchers.
+Style dispatchers are used to override the matching styles
+based on the actual component and its place in the list of
+components. A style dispatcher is a function that takes a string
+and two integers as arguments, it gets called with a component,
+the 0-based index of the component and the total number of
+components. It can decide what matching styles to use for the
+component and optionally replace the component with a different
+string, or it can decline to handle the component leaving it for
+future dispatchers. For details see `orderless--dispatch'.
+
+For example, a style dispatcher could arrange for the first
+component to match as an initialism and subsequent components to
+match as literals. As another example, a style dispatcher could
+arrange for a component starting with `~' to match the rest of
+the component in the `orderless-flex' style. See
+`orderless-affix-dispatch' and `orderless-affix-dispatch-alist'
+for such a configuration. For more information on how this
+variable is used, see `orderless-compile'."
+ :type '(repeat function))
+
+(defcustom orderless-smart-case t
+ "Whether to use smart case.
+If this variable is t, then case-sensitivity is decided as
+follows: if any component contains upper case letters, the
+matches are case sensitive; otherwise case-insensitive. This
+is like the behavior of `isearch' when `search-upper-case' is
+non-nil.
+
+On the other hand, if this variable is nil, then case-sensitivity
+is determined by the values of `completion-ignore-case',
+`read-file-name-completion-ignore-case' and
+`read-buffer-completion-ignore-case', as usual for completion."
+ :type 'boolean)
+
+(defcustom orderless-expand-substring 'prefix
+ "Whether to perform literal substring expansion.
+This configuration option affects the behavior of some completion
+interfaces when pressing TAB. If enabled `orderless-try-completion'
+will first attempt literal substring expansion. If disabled,
+expansion is only performed for single unique matches. For
+performance reasons only `prefix' expansion is enabled by default.
+Set the variable to `substring' for full substring expansion."
+ :type '(choice (const :tag "No expansion" nil)
+ (const :tag "Substring" substring)
+ (const :tag "Prefix (efficient)" prefix)))
+
+;;; Matching styles
+
+(defun orderless-regexp (component)
+ "Match COMPONENT as a regexp."
+ (condition-case nil
+ (progn (string-match-p component "") component)
+ (invalid-regexp nil)))
+
+(defun orderless-literal (component)
+ "Match COMPONENT as a literal string."
+ ;; Do not use (literal component) here, such that `delete-dups' in
+ ;; `orderless--compile-component' has a chance to delete duplicates for
+ ;; literal input. The default configuration of `orderless-matching-styles'
+ ;; with `orderless-regexp' and `orderless-literal' leads to duplicates.
+ (regexp-quote component))
+
+(defun orderless-literal-prefix (component)
+ "Match COMPONENT as a literal prefix string."
+ `(seq bos (literal ,component)))
+
+(defun orderless--separated-by (sep rxs &optional before after)
+ "Return a regexp to match the rx-regexps RXS with SEP in between.
+If BEFORE is specified, add it to the beginning of the rx
+sequence. If AFTER is specified, add it to the end of the rx
+sequence."
+ (declare (indent 1))
+ `(seq
+ ,(or before "")
+ ,@(cl-loop for (sexp . more) on rxs
+ collect `(group ,sexp)
+ when more collect sep)
+ ,(or after "")))
+
+(defun orderless-flex (component)
+ "Match a component in flex style.
+This means the characters in COMPONENT must occur in the
+candidate in that order, but not necessarily consecutively."
+ `(seq
+ ,@(cdr (cl-loop for char across component
+ append `((zero-or-more (not ,char)) (group ,char))))))
+
+(defun orderless-initialism (component)
+ "Match a component as an initialism.
+This means the characters in COMPONENT must occur in the
+candidate, in that order, at the beginning of words."
+ (orderless--separated-by '(zero-or-more nonl)
+ (cl-loop for char across component collect `(seq word-start ,char))))
+
+(defun orderless-prefixes (component)
+ "Match a component as multiple word prefixes.
+The COMPONENT is split at word endings, and each piece must match
+at a word boundary in the candidate. This is similar to the
+`partial-completion' completion style."
+ (orderless--separated-by '(zero-or-more nonl)
+ (cl-loop for prefix in (split-string component "\\>")
+ collect `(seq word-boundary ,prefix))))
+
+(defun orderless-without-literal (component)
+ "Match strings that do *not* contain COMPONENT as a literal match.
+You may prefer to use the more general `orderless-not' instead
+which can invert any predicate or regexp."
+ `(seq
+ (group string-start) ; highlight nothing!
+ (zero-or-more
+ (or ,@(cl-loop for i below (length component)
+ collect `(seq ,(substring component 0 i)
+ (or (not (any ,(aref component i)))
+ string-end)))))
+ string-end))
+
+(defsubst orderless--match-p (pred regexp str)
+ "Return t if STR matches PRED and REGEXP."
+ (and str
+ (or (not pred) (funcall pred str))
+ (or (not regexp)
+ (let ((case-fold-search completion-ignore-case))
+ (string-match-p regexp str)))))
+
+(defun orderless-not (pred regexp)
+ "Match strings that do *not* match PRED and REGEXP."
+ (lambda (str)
+ (not (orderless--match-p pred regexp str))))
+
+(defun orderless--metadata ()
+ "Return completion metadata iff inside minibuffer."
+ (when-let* (((minibufferp))
+ (table minibuffer-completion-table))
+ ;; Return non-nil metadata iff inside minibuffer
+ (or (completion-metadata (buffer-substring-no-properties
+ (minibuffer-prompt-end) (point))
+ table minibuffer-completion-predicate)
+ '((nil . nil)))))
+
+(defun orderless-annotation (pred regexp)
+ "Match candidates where the annotation matches PRED and REGEXP."
+ (let ((md (orderless--metadata)))
+ (if-let* ((fun (compat-call completion-metadata-get md 'affixation-function)))
+ (lambda (str)
+ (cl-loop for s in (cdar (funcall fun (list str)))
+ thereis (orderless--match-p pred regexp s)))
+ (when-let* ((fun (compat-call completion-metadata-get md 'annotation-function)))
+ (lambda (str) (orderless--match-p pred regexp (funcall fun str)))))))
+
+;;; Highlighting matches
+
+(defun orderless--highlight (regexps ignore-case string)
+ "Destructively propertize STRING to highlight a match of each of the REGEXPS.
+The search is case insensitive if IGNORE-CASE is non-nil."
+ (cl-loop with case-fold-search = ignore-case
+ with n = (length orderless-match-faces)
+ for regexp in regexps and i from 0
+ when (string-match regexp string) do
+ (cl-loop
+ for (x y) on (let ((m (match-data))) (or (cddr m) m)) by #'cddr
+ when x do
+ (add-face-text-property
+ x y
+ (aref orderless-match-faces (mod i n))
+ nil string)))
+ string)
+
+(defun orderless-highlight-matches (regexps strings)
+ "Highlight a match of each of the REGEXPS in each of the STRINGS.
+Warning: only use this if you know all REGEXPs match all STRINGS!
+For the user's convenience, if REGEXPS is a string, it is
+converted to a list of regexps according to the value of
+`orderless-matching-styles'."
+ (when (stringp regexps)
+ (setq regexps (cdr (orderless-compile regexps))))
+ (cl-loop with ignore-case = (orderless--ignore-case-p regexps)
+ for str in strings
+ collect (orderless--highlight regexps ignore-case (substring str))))
+
+;;; Compiling patterns to lists of regexps
+
+(defun orderless-escapable-split-on-space (string)
+ "Split STRING on spaces, which can be escaped with backslash."
+ (mapcar
+ (lambda (piece) (replace-regexp-in-string (string 0) " " piece))
+ (split-string (replace-regexp-in-string
+ "\\\\\\\\\\|\\\\ "
+ (lambda (x) (if (equal x "\\ ") (string 0) x))
+ string 'fixedcase 'literal)
+ " +")))
+
+(defun orderless--dispatch (dispatchers default string index total)
+ "Run DISPATCHERS to compute matching styles for STRING.
+
+A style dispatcher is a function that takes a STRING, component
+INDEX and the TOTAL number of components. It should either
+return (a) nil to indicate the dispatcher will not handle the
+string, (b) a new string to replace the current string and
+continue dispatch, or (c) the matching styles to use and, if
+needed, a new string to use in place of the current one (for
+example, a dispatcher can decide which style to use based on a
+suffix of the string and then it must also return the component
+stripped of the suffix).
+
+More precisely, the return value of a style dispatcher can be of
+one of the following forms:
+
+- nil (to continue dispatching)
+
+- a string (to replace the component and continue dispatching),
+
+- a matching style or non-empty list of matching styles to
+ return,
+
+- a `cons' whose `car' is either as in the previous case or
+ nil (to request returning the DEFAULT matching styles), and
+ whose `cdr' is a string (to replace the current one).
+
+This function tries all DISPATCHERS in sequence until one returns
+a list of styles. When that happens it returns a `cons' of the
+list of styles and the possibly updated STRING. If none of the
+DISPATCHERS returns a list of styles, the return value will use
+DEFAULT as the list of styles."
+ (cl-loop for dispatcher in dispatchers
+ for result = (funcall dispatcher string index total)
+ if (stringp result)
+ do (setq string result result nil)
+ else if (and (consp result) (null (car result)))
+ do (setf (car result) default)
+ else if (and (consp result) (stringp (cdr result)))
+ do (setq string (cdr result) result (car result))
+ when result return (cons result string)
+ finally (return (cons default string))))
+
+(defun orderless--compile-component (component index total styles dispatchers)
+ "Compile COMPONENT at INDEX of TOTAL components with STYLES and DISPATCHERS."
+ (cl-loop
+ with pred = nil
+ with (newsty . newcomp) = (orderless--dispatch dispatchers styles
+ component index total)
+ for style in (if (functionp newsty) (list newsty) newsty)
+ for res = (condition-case nil
+ (funcall style newcomp)
+ (wrong-number-of-arguments
+ (when-let* ((res (orderless--compile-component
+ newcomp index total styles dispatchers)))
+ (funcall style (car res) (cdr res)))))
+ if (functionp res) do (cl-callf orderless--predicate-and pred res)
+ else if res collect (if (stringp res) `(regexp ,res) res) into regexps
+ finally return
+ (when (or pred regexps)
+ (cons pred (and regexps (rx-to-string `(or ,@(delete-dups regexps)) t))))))
+
+(defun orderless-compile (pattern &optional styles dispatchers)
+ "Build regexps to match the components of PATTERN.
+Split PATTERN on `orderless-component-separator' and compute
+matching styles for each component. For each component the style
+DISPATCHERS are run to determine the matching styles to be used;
+they are called with arguments the component, the 0-based index
+of the component and the total number of components. If the
+DISPATCHERS decline to handle the component, then the list of
+matching STYLES is used. See `orderless--dispatch' for details
+on dispatchers.
+
+The STYLES default to `orderless-matching-styles', and the
+DISPATCHERS default to `orderless-dipatchers'. Since nil gets
+you the default, if you want no dispatchers to be run, use
+\\='(ignore) as the value of DISPATCHERS.
+
+The return value is a pair of a predicate function and a list of
+regexps. The predicate function can also be nil. It takes a
+string as argument."
+ (unless styles (setq styles orderless-matching-styles))
+ (unless dispatchers (setq dispatchers orderless-style-dispatchers))
+ (cl-loop
+ with predicate = nil
+ with temp = (if (functionp orderless-component-separator)
+ (funcall orderless-component-separator pattern)
+ (split-string pattern orderless-component-separator))
+ with components = (if (equal (car (last temp)) "") (nbutlast temp) temp)
+ with total = (length components)
+ for comp in components and index from 0
+ for (pred . regexp) = (orderless--compile-component
+ comp index total styles dispatchers)
+ when regexp collect regexp into regexps
+ when pred do (cl-callf orderless--predicate-and predicate pred)
+ finally return (cons predicate regexps)))
+
+;;; Completion style implementation
+
+(defun orderless--predicate-normalized-and (p q)
+ "Combine two predicate functions P and Q with `and'.
+The first function P is a completion predicate which can receive
+up to two arguments. The second function Q always receives a
+normalized string as argument."
+ (cond
+ ((and p q)
+ (lambda (k &rest v) ;; v for hash table
+ (when (if v (funcall p k (car v)) (funcall p k))
+ (setq k (if (consp k) (car k) k)) ;; alist
+ (funcall q (if (symbolp k) (symbol-name k) k)))))
+ (q
+ (lambda (k &optional _) ;; _ for hash table
+ (setq k (if (consp k) (car k) k)) ;; alist
+ (funcall q (if (symbolp k) (symbol-name k) k))))
+ (p)))
+
+(defun orderless--predicate-and (p q)
+ "Combine two predicate functions P and Q with `and'."
+ (or (and p q (lambda (x) (and (funcall p x) (funcall q x)))) p q))
+
+(defun orderless--compile (string table pred)
+ "Compile STRING to a prefix and a list of regular expressions.
+The predicate PRED is used to constrain the entries in TABLE."
+ (pcase-let* ((limit (car (completion-boundaries string table pred "")))
+ (prefix (substring string 0 limit))
+ (pattern (substring string limit))
+ (`(,fun . ,regexps) (orderless-compile pattern)))
+ (list prefix regexps (orderless--ignore-case-p pattern)
+ (orderless--predicate-normalized-and pred fun))))
+
+;; Thanks to @jakanakaevangeli for writing a version of this function:
+;; https://github.com/oantolin/orderless/issues/79#issuecomment-916073526
+(defun orderless--literal-prefix-p (regexp)
+ "Determine if REGEXP is a quoted regexp anchored at the beginning.
+If REGEXP is of the form \"\\`q\" for q = (regexp-quote u),
+then return (cons REGEXP u); else return nil."
+ (when (and (string-prefix-p "\\`" regexp)
+ (not (string-match-p "[$*+.?[\\^]"
+ (replace-regexp-in-string
+ "\\\\[$*+.?[\\^]" "" regexp
+ 'fixedcase 'literal nil 2))))
+ (cons regexp
+ (replace-regexp-in-string "\\\\\\([$*+.?[\\^]\\)" "\\1"
+ regexp 'fixedcase nil nil 2))))
+
+(defun orderless--ignore-case-p (regexps)
+ "Return non-nil if case should be ignored for REGEXPS."
+ (if orderless-smart-case
+ (cl-loop for regexp in (ensure-list regexps)
+ always (isearch-no-upper-case-p regexp t))
+ completion-ignore-case))
+
+(defun orderless--filter (prefix regexps ignore-case table pred)
+ "Filter TABLE by PREFIX, REGEXPS and PRED.
+The matching should be case-insensitive if IGNORE-CASE is non-nil."
+ ;; If there is a regexp of the form \`quoted-regexp then
+ ;; remove the first such and add the unquoted form to the prefix.
+ (pcase (cl-loop for r in regexps
+ thereis (orderless--literal-prefix-p r))
+ (`(,regexp . ,literal)
+ (setq prefix (concat prefix literal)
+ regexps (remove regexp regexps))))
+ (let ((completion-regexp-list regexps)
+ (completion-ignore-case ignore-case))
+ (all-completions prefix table pred)))
+
+(defun orderless-filter (string table &optional pred)
+ "Split STRING into components and find entries TABLE matching all.
+The predicate PRED is used to constrain the entries in TABLE."
+ (pcase-let ((`(,prefix ,regexps ,ignore-case ,pred)
+ (orderless--compile string table pred)))
+ (orderless--filter prefix regexps ignore-case table pred)))
+
+;;;###autoload
+(defun orderless-all-completions (string table pred _point)
+ "Split STRING into components and find entries TABLE matching all.
+The predicate PRED is used to constrain the entries in TABLE. The
+matching portions of each candidate are highlighted.
+This function is part of the `orderless' completion style."
+ (pcase-let ((`(,prefix ,regexps ,ignore-case ,pred)
+ (orderless--compile string table pred)))
+ (when-let* ((completions (orderless--filter prefix regexps ignore-case table pred)))
+ (if completion-lazy-hilit
+ (setq completion-lazy-hilit-fn
+ (apply-partially #'orderless--highlight regexps ignore-case))
+ (cl-loop for str in-ref completions do
+ (setf str (orderless--highlight regexps ignore-case (substring str)))))
+ (nconc completions (length prefix)))))
+
+;;;###autoload
+(defun orderless-try-completion (string table pred point)
+ "Complete STRING to unique matching entry in TABLE.
+This uses `orderless-all-completions' to find matches for STRING
+in TABLE among entries satisfying PRED. If there is only one
+match, it completes to that match. If there are no matches, it
+returns nil. In any other case it \"completes\" STRING to
+itself, without moving POINT.
+This function is part of the `orderless' completion style."
+ (or
+ (pcase orderless-expand-substring
+ ('nil nil)
+ ('prefix (completion-emacs21-try-completion string table pred point))
+ (_ (completion-substring-try-completion string table pred point)))
+ (catch 'orderless--many
+ (pcase-let ((`(,prefix ,regexps ,ignore-case ,pred)
+ (orderless--compile string table pred))
+ (one nil))
+ ;; Abuse all-completions/orderless--filter as a fast search loop.
+ ;; Should be almost allocation-free since our "predicate" is not
+ ;; called more than two times.
+ (orderless--filter
+ prefix regexps ignore-case table
+ (orderless--predicate-normalized-and
+ pred
+ (lambda (arg)
+ ;; Check if there is more than a single match (= many).
+ (when (and one (not (equal one arg)))
+ (throw 'orderless--many (cons string point)))
+ (setq one arg)
+ t)))
+ (when one
+ ;; Prepend prefix if the candidate does not already have the same
+ ;; prefix. This workaround is needed since the predicate may either
+ ;; receive an unprefixed or a prefixed candidate as argument. Most
+ ;; completion tables consistently call the predicate with unprefixed
+ ;; candidates, for example `completion-file-name-table'. In contrast,
+ ;; `completion-table-with-context' calls the predicate with prefixed
+ ;; candidates. This could be an unintended bug or oversight in
+ ;; `completion-table-with-context'.
+ (unless (or (equal prefix "")
+ (and (string-prefix-p prefix one)
+ (test-completion one table pred)))
+ (setq one (concat prefix one)))
+ (or (equal string one) ;; Return t for unique exact match
+ (cons one (length one))))))))
+
+;;;###autoload
+(add-to-list 'completion-styles-alist
+ '(orderless
+ orderless-try-completion orderless-all-completions
+ "Completion of multiple components, in any order."))
+
+(defmacro orderless-define-completion-style
+ (name &optional docstring &rest configuration)
+ "Define an orderless completion style with given CONFIGURATION.
+The CONFIGURATION should be a list of bindings that you could use
+with `let' to configure orderless. You can include bindings for
+`orderless-matching-styles' and `orderless-style-dispatchers',
+for example.
+
+The completion style consists of two functions that this macro
+defines for you, NAME-try-completion and NAME-all-completions.
+This macro registers those in `completion-styles-alist' as
+forming the completion style NAME.
+
+The optional DOCSTRING argument is used as the documentation
+string for the completion style."
+ (declare (doc-string 2) (indent 1))
+ (unless (stringp docstring)
+ (push docstring configuration)
+ (setq docstring nil))
+ (let* ((fn-name (lambda (string) (intern (concat (symbol-name name) string))))
+ (try-completion (funcall fn-name "-try-completion"))
+ (all-completions (funcall fn-name "-all-completions"))
+ (doc-fmt "`%s' function for the %s style.
+This function delegates to `orderless-%s'.
+The orderless configuration is locally modified
+specifically for the %s style.")
+ (fn-doc (lambda (fn) (format doc-fmt fn name fn name name))))
+ `(progn
+ (defun ,try-completion (string table pred point)
+ ,(funcall fn-doc "try-completion")
+ (let ,configuration
+ (orderless-try-completion string table pred point)))
+ (defun ,all-completions (string table pred point)
+ ,(funcall fn-doc "all-completions")
+ (let ,configuration
+ (orderless-all-completions string table pred point)))
+ (add-to-list 'completion-styles-alist
+ '(,name ,try-completion ,all-completions ,docstring)))))
+
+;;; Ivy integration
+
+;;;###autoload
+(defun orderless-ivy-re-builder (str)
+ "Convert STR into regexps for use with ivy.
+This function is for integration of orderless with ivy, use it as
+a value in `ivy-re-builders-alist'."
+ (or (mapcar (lambda (x) (cons x t)) (cdr (orderless-compile str))) ""))
+
+(defvar ivy-regex)
+(defun orderless-ivy-highlight (str)
+ "Highlight a match in STR of each regexp in `ivy-regex'.
+This function is for integration of orderless with ivy."
+ (orderless--highlight (mapcar #'car ivy-regex) t str) str)
+
+(provide 'orderless)
+;;; orderless.el ends here
diff --git a/.config/emacs/lisp/minadstack/vertico-directory.el b/.config/emacs/lisp/minadstack/vertico-directory.el
new file mode 100644
index 0000000..3328dd1
--- /dev/null
+++ b/.config/emacs/lisp/minadstack/vertico-directory.el
@@ -0,0 +1,136 @@
+;;; vertico-directory.el --- Ido-like directory navigation for Vertico -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021-2026 Free Software Foundation, Inc.
+
+;; Author: Daniel Mendler <mail@daniel-mendler.de>
+;; Maintainer: Daniel Mendler <mail@daniel-mendler.de>
+;; Created: 2021
+;; Version: 2.8
+;; Package-Requires: ((emacs "29.1") (compat "30") (vertico "2.8"))
+;; URL: https://github.com/minad/vertico
+
+;; This file is 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:
+
+;; This package is a Vertico extension, which provides Ido-like
+;; directory navigation commands. The commands can be bound in the
+;; `vertico-map'.
+;;
+;; (keymap-set vertico-map "RET" #'vertico-directory-enter)
+;; (keymap-set vertico-map "DEL" #'vertico-directory-delete-char)
+;; (keymap-set vertico-map "M-DEL" #'vertico-directory-delete-word)
+;;
+;; Alternatively use `vertico-directory-map' together with
+;; `vertico-multiform-mode'.
+;;
+;; (setq vertico-multiform-categories
+;; '((file (:keymap . vertico-directory-map))))
+;; (vertico-multiform-mode)
+;;
+;; Furthermore a cleanup function for shadowed file paths is provided.
+;;
+;; (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy)
+
+;;; Code:
+
+(require 'vertico)
+(eval-when-compile (require 'subr-x))
+
+;;;###autoload
+(defun vertico-directory-enter (&optional arg)
+ "Enter directory or exit completion with current candidate.
+Exit with current input if prefix ARG is given."
+ (interactive "P")
+ (if-let* (((not arg))
+ ((>= vertico--index 0))
+ ((eq 'file (vertico--metadata-get 'category)))
+ ;; Check vertico--base for stepwise file path completion
+ ((not (equal vertico--base "")))
+ (cand (vertico--candidate))
+ ((or (string-suffix-p "/" cand)
+ (and (vertico--remote-p cand)
+ (string-suffix-p ":" cand))))
+ ;; Handle /./ and /../ manually instead of via `expand-file-name'
+ ;; and `abbreviate-file-name', such that we don't accidentally
+ ;; perform unwanted substitutions in the existing completion.
+ ((progn
+ (setq cand (string-replace "/./" "/" cand))
+ (unless (string-suffix-p "/../../" cand)
+ (setq cand (replace-regexp-in-string "/[^/|:]+/\\.\\./\\'" "/" cand)))
+ (not (equal (minibuffer-contents-no-properties) cand)))))
+ (progn
+ (delete-minibuffer-contents)
+ (insert cand))
+ (vertico-exit arg)))
+
+;;;###autoload
+(defun vertico-directory-up (&optional n)
+ "Delete N names before point."
+ (interactive "p")
+ (when (and (> (point) (minibuffer-prompt-end))
+ (eq 'file (vertico--metadata-get 'category)))
+ (let ((path (buffer-substring-no-properties (minibuffer-prompt-end) (point)))
+ found)
+ (when (string-match-p "\\`~[^/]*/\\'" path)
+ (delete-minibuffer-contents)
+ (insert (expand-file-name path)))
+ (dotimes (_ (or n 1) found)
+ (save-excursion
+ (let ((end (point)))
+ (goto-char (1- end))
+ (when (search-backward "/" (minibuffer-prompt-end) t)
+ (delete-region (1+ (point)) end)
+ (setq found t))))))))
+
+;;;###autoload
+(defun vertico-directory-delete-char (n)
+ "Delete N directories or chars before point."
+ (interactive "p")
+ (unless (and (not (and (use-region-p) delete-active-region (= n 1)))
+ (eq (char-before) ?/) (vertico-directory-up n))
+ (with-no-warnings (delete-backward-char n))))
+
+;;;###autoload
+(defun vertico-directory-delete-word (n)
+ "Delete N directories or words before point."
+ (interactive "p")
+ (unless (and (eq (char-before) ?/) (vertico-directory-up n))
+ (delete-region (prog1 (point) (backward-word n)) (point))))
+
+;;;###autoload
+(defun vertico-directory-tidy ()
+ "Tidy shadowed file name, see `rfn-eshadow-overlay'."
+ (when (eq this-command #'self-insert-command)
+ (dolist (ov '(tramp-rfn-eshadow-overlay rfn-eshadow-overlay))
+ (when (and (boundp ov)
+ (setq ov (symbol-value ov))
+ (overlay-buffer ov)
+ (= (point) (point-max))
+ (> (point) (overlay-end ov)))
+ (delete-region (overlay-start ov) (overlay-end ov))))))
+
+(defvar-keymap vertico-directory-map
+ :doc "File name editing map."
+ "RET" #'vertico-directory-enter
+ "DEL" #'vertico-directory-delete-char
+ "M-DEL" #'vertico-directory-delete-word)
+
+;;;###autoload (autoload 'vertico-directory-map "vertico-directory" nil t 'keymap)
+(defalias 'vertico-directory-map vertico-directory-map)
+
+(provide 'vertico-directory)
+;;; vertico-directory.el ends here
diff --git a/.config/emacs/lisp/minadstack/vertico.el b/.config/emacs/lisp/minadstack/vertico.el
new file mode 100644
index 0000000..c646f0b
--- /dev/null
+++ b/.config/emacs/lisp/minadstack/vertico.el
@@ -0,0 +1,736 @@
+;;; vertico.el --- VERTical Interactive COmpletion -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021-2026 Free Software Foundation, Inc.
+
+;; Author: Daniel Mendler <mail@daniel-mendler.de>
+;; Maintainer: Daniel Mendler <mail@daniel-mendler.de>
+;; Created: 2021
+;; Version: 2.8
+;; Package-Requires: ((emacs "29.1") (compat "30"))
+;; URL: https://github.com/minad/vertico
+;; Keywords: convenience, files, matching, completion
+
+;; This file is 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:
+
+;; Vertico provides a performant and minimalistic vertical completion UI
+;; based on the default completion system. By reusing the built-in
+;; facilities, Vertico achieves full compatibility with built-in Emacs
+;; completion commands and completion tables.
+
+;;; Code:
+
+(require 'compat)
+(eval-when-compile
+ (require 'cl-lib)
+ (require 'subr-x))
+
+(defgroup vertico nil
+ "VERTical Interactive COmpletion."
+ :link '(info-link :tag "Info Manual" "(vertico)")
+ :link '(url-link :tag "Website" "https://github.com/minad/vertico")
+ :link '(url-link :tag "Wiki" "https://github.com/minad/vertico/wiki")
+ :link '(emacs-library-link :tag "Library Source" "vertico.el")
+ :group 'convenience
+ :group 'minibuffer
+ :prefix "vertico-")
+
+(defcustom vertico-count-format (cons "%-6s " "%s/%s")
+ "Format string used for the candidate count."
+ :type '(choice (const :tag "No candidate count" nil) (cons string string)))
+
+(defcustom vertico-group-format
+ (concat #(" " 0 4 (face vertico-group-separator))
+ #(" %s " 0 4 (face vertico-group-title))
+ #(" " 0 1 (face vertico-group-separator display (space :align-to (- right 1)))))
+ "Format string used for the group title."
+ :type '(choice (const :tag "No group titles" nil) string))
+
+(defcustom vertico-count 10
+ "Maximal number of candidates to show."
+ :type 'natnum)
+
+(defcustom vertico-preselect 'directory
+ "Configure if the prompt or first candidate is preselected.
+- prompt: Always select the prompt.
+- first: Select the first candidate, allow prompt selection.
+- no-prompt: Like first, but forbid selection of the prompt entirely.
+- directory: Like first, but select the prompt if it is a directory."
+ :type '(choice (const prompt) (const first) (const no-prompt) (const directory)))
+
+(defcustom vertico-scroll-margin 2
+ "Number of lines at the top and bottom when scrolling.
+The value should lie between 0 and vertico-count/2."
+ :type 'natnum)
+
+(defcustom vertico-resize resize-mini-windows
+ "How to resize the Vertico minibuffer window, see `resize-mini-windows'."
+ :type '(choice (const :tag "Fixed" nil)
+ (const :tag "Shrink and grow" t)
+ (const :tag "Grow-only" grow-only)))
+
+(defcustom vertico-cycle nil
+ "Enable cycling for `vertico-next' and `vertico-previous'."
+ :type 'boolean)
+
+(defcustom vertico-multiline
+ (cons #("↲" 0 1 (face vertico-multiline)) #("…" 0 1 (face vertico-multiline)))
+ "Replacements for multiline strings."
+ :type '(cons (string :tag "Newline") (string :tag "Truncation")))
+
+(defcustom vertico-sort-function
+ (and (fboundp 'vertico-sort-history-length-alpha) 'vertico-sort-history-length-alpha)
+ "Default sorting function, used if no `display-sort-function' is specified."
+ :type '(choice
+ (const :tag "No sorting" nil)
+ (const :tag "By history, length and alpha" vertico-sort-history-length-alpha)
+ (const :tag "By history and alpha" vertico-sort-history-alpha)
+ (const :tag "By length and alpha" vertico-sort-length-alpha)
+ (const :tag "Alphabetically" vertico-sort-alpha)
+ (function :tag "Custom function")))
+
+(defcustom vertico-sort-override-function nil
+ "Override sort function which overrides the `display-sort-function'."
+ :type '(choice (const nil) function))
+
+(defgroup vertico-faces nil
+ "Faces used by Vertico."
+ :group 'vertico
+ :group 'faces)
+
+(defface vertico-multiline '((t :inherit shadow))
+ "Face used to highlight multiline replacement characters.")
+
+(defface vertico-group-title '((t :inherit shadow :slant italic))
+ "Face used for the title text of the candidate group headlines.")
+
+(defface vertico-group-separator '((t :inherit vertico-group-title :strike-through t))
+ "Face used for the separator lines of the candidate groups.")
+
+(defface vertico-current '((t :inherit highlight :extend t))
+ "Face used to highlight the currently selected candidate.")
+
+(defvar-keymap vertico-map
+ :doc "Vertico minibuffer keymap derived from `minibuffer-local-map'."
+ :parent minibuffer-local-map
+ "<remap> <beginning-of-buffer>" #'vertico-first
+ "<remap> <minibuffer-beginning-of-buffer>" #'vertico-first
+ "<remap> <end-of-buffer>" #'vertico-last
+ "<remap> <scroll-down-command>" #'vertico-scroll-down
+ "<remap> <scroll-up-command>" #'vertico-scroll-up
+ "<remap> <next-line>" #'vertico-next
+ "<remap> <previous-line>" #'vertico-previous
+ "<remap> <next-line-or-history-element>" #'vertico-next
+ "<remap> <previous-line-or-history-element>" #'vertico-previous
+ "<remap> <backward-paragraph>" #'vertico-previous-group
+ "<remap> <forward-paragraph>" #'vertico-next-group
+ "<remap> <exit-minibuffer>" #'vertico-exit
+ "<remap> <kill-ring-save>" #'vertico-save
+ "M-RET" #'vertico-exit-input
+ "TAB" #'vertico-insert
+ "<touchscreen-begin>" #'ignore)
+
+(defvar vertico--locals
+ '((scroll-margin . 0)
+ (completion-auto-help . nil)
+ (pixel-scroll-precision-mode . nil))
+ "Vertico minibuffer local variables.")
+
+(defvar-local vertico--hilit #'identity
+ "Lazy candidate highlighting function.")
+
+(defvar-local vertico--candidates-ov nil
+ "Overlay showing the candidates.")
+
+(defvar-local vertico--count-ov nil
+ "Overlay showing the number of candidates.")
+
+(defvar-local vertico--index -1
+ "Index of current candidate or negative for prompt selection.")
+
+(defvar-local vertico--scroll 0
+ "Scroll position.")
+
+(defvar-local vertico--input nil
+ "Cons of last minibuffer contents and point or t.")
+
+(defvar-local vertico--candidates nil
+ "List of candidates.")
+
+(defvar-local vertico--metadata nil
+ "Completion metadata.")
+
+(defvar-local vertico--base ""
+ "Base string, which is concatenated with the candidate.")
+
+(defvar-local vertico--total 0
+ "Length of the candidate list `vertico--candidates'.")
+
+(defvar-local vertico--lock-candidate nil
+ "Lock-in current candidate.")
+
+(defvar-local vertico--lock-groups nil
+ "Lock-in current group order.")
+
+(defvar-local vertico--groups nil
+ "List of current group titles.")
+
+(defvar-local vertico--allow-prompt nil
+ "Prompt selection is allowed.")
+
+(defun vertico--affixate (cands)
+ "Annotate CANDS with annotation function."
+ (if-let* ((aff (vertico--metadata-get 'affixation-function)))
+ (funcall aff cands)
+ (if-let* ((ann (vertico--metadata-get 'annotation-function)))
+ (cl-loop for cand in cands collect
+ (let ((suff (or (funcall ann cand) "")))
+ ;; The default completion UI adds the `completions-annotations'
+ ;; face if no other faces are present.
+ (unless (text-property-not-all 0 (length suff) 'face nil suff)
+ (setq suff (propertize suff 'face 'completions-annotations)))
+ (list cand "" suff)))
+ (cl-loop for cand in cands collect (list cand "" "")))))
+
+(defun vertico--move-to-front (elem list)
+ "Move ELEM to front of LIST."
+ (if-let* ((found (member elem list))) ;; No duplicates, compare with Corfu.
+ (nconc (list (car found)) (delq (setcar found nil) list))
+ list))
+
+(defun vertico--filter-completions (&rest args)
+ "Compute all completions for ARGS with lazy highlighting."
+ (dlet ((completion-lazy-hilit t) (completion-lazy-hilit-fn nil))
+ (static-if (>= emacs-major-version 30)
+ (cons (apply #'completion-all-completions args) completion-lazy-hilit-fn)
+ (cl-letf* ((orig-pcm (symbol-function #'completion-pcm--hilit-commonality))
+ (orig-flex (symbol-function #'completion-flex-all-completions))
+ ((symbol-function #'completion-flex-all-completions)
+ (lambda (&rest args)
+ ;; Unfortunately for flex we have to undo the lazy highlighting, since flex uses
+ ;; the completion-score for sorting, which is applied during highlighting.
+ (cl-letf (((symbol-function #'completion-pcm--hilit-commonality) orig-pcm))
+ (apply orig-flex args))))
+ ((symbol-function #'completion-pcm--hilit-commonality)
+ (lambda (pattern cands)
+ (setq completion-lazy-hilit-fn
+ (lambda (x)
+ ;; `completion-pcm--hilit-commonality' sometimes throws an internal error
+ ;; for example when entering "/sudo:://u".
+ (condition-case nil
+ (car (completion-pcm--hilit-commonality pattern (list x)))
+ (t x))))
+ cands))
+ ((symbol-function #'completion-hilit-commonality)
+ (lambda (cands prefix &optional base)
+ (setq completion-lazy-hilit-fn
+ (lambda (x) (car (completion-hilit-commonality (list x) prefix base))))
+ (and cands (nconc cands base)))))
+ (cons (apply #'completion-all-completions args) completion-lazy-hilit-fn)))))
+
+(defun vertico--metadata-get (prop)
+ "Return PROP from completion metadata."
+ (compat-call completion-metadata-get vertico--metadata prop))
+
+(defun vertico--sort-function ()
+ "Return the sorting function."
+ (or vertico-sort-override-function
+ (vertico--metadata-get 'display-sort-function)
+ vertico-sort-function))
+
+(defun vertico--compute (input)
+ "Compute state given INPUT."
+ (pcase-let* ((`(,str . ,pt) input)
+ (table minibuffer-completion-table)
+ (pred minibuffer-completion-predicate)
+ (before (substring str 0 pt))
+ (after (substring str pt))
+ ;; bug#47678: `completion-boundaries' fails for `partial-completion'
+ ;; if the cursor is moved before the slashes of "~//".
+ ;; See also corfu.el which has the same issue.
+ (bounds (condition-case nil
+ (completion-boundaries before table pred after)
+ (t (cons 0 (length after)))))
+ (field (substring str (car bounds) (+ pt (cdr bounds))))
+ ;; bug#75910: category instead of `minibuffer-completing-file-name'
+ (completing-file (eq 'file (vertico--metadata-get 'category)))
+ (`(,all . ,hl) (vertico--filter-completions str table pred pt vertico--metadata))
+ (base (or (when-let* ((z (last all))) (prog1 (cdr z) (setcdr z nil))) 0))
+ (vertico--base (substring str 0 base))
+ (def (or (car-safe minibuffer-default) minibuffer-default))
+ (groups) (def-missing) (lock))
+ ;; Filter the ignored file extensions. We cannot use modified predicate for this filtering,
+ ;; since this breaks the special casing in the `completion-file-name-table' for `file-exists-p'
+ ;; and `file-directory-p'.
+ (when completing-file (setq all (completion-pcm--filename-try-filter all)))
+ ;; Sort using the `display-sort-function' or the Vertico sort functions
+ (setq all (delete-consecutive-dups (funcall (or (vertico--sort-function) #'identity) all)))
+ ;; Move special candidates: "field" appears at the top, before "field/", before default value
+ (when (stringp def)
+ (setq all (vertico--move-to-front def all)))
+ (when (and completing-file (not (string-suffix-p "/" field)))
+ (setq all (vertico--move-to-front (concat field "/") all)))
+ (setq all (vertico--move-to-front field all))
+ (when-let* ((fun (and all (vertico--metadata-get 'group-function))))
+ (setq groups (vertico--group-by fun all) all (car groups)))
+ (setq def-missing (and def (equal str "") (not (member def all)))
+ lock (and vertico--lock-candidate ;; Locked position of old candidate.
+ (if (< vertico--index 0) -1
+ (seq-position all (nth vertico--index vertico--candidates)))))
+ `((vertico--input . ,input)
+ (vertico--base . ,vertico--base)
+ (vertico--metadata . ,vertico--metadata)
+ (vertico--candidates . ,all)
+ (vertico--total . ,(length all))
+ (vertico--hilit . ,(or hl #'identity))
+ (vertico--allow-prompt . ,(and (not (eq vertico-preselect 'no-prompt))
+ (or def-missing (eq vertico-preselect 'prompt)
+ (memq minibuffer--require-match
+ '(nil confirm confirm-after-completion)))))
+ (vertico--lock-candidate . ,lock)
+ (vertico--groups . ,(cdr groups))
+ (vertico--index . ,(or lock
+ (if (or def-missing (eq vertico-preselect 'prompt) (not all)
+ (and completing-file (eq vertico-preselect 'directory)
+ (= (length vertico--base) (length str))
+ (test-completion str table pred)))
+ -1 0))))))
+
+(defun vertico--hilit (cand)
+ "Highlight CAND string with lazy highlighting."
+ ;; bug#77754: Highlight unquoted string.
+ (funcall vertico--hilit (substring (or (get-text-property
+ 0 'completion--unquoted cand) cand))))
+
+(defun vertico--cycle (list n)
+ "Rotate LIST to position N."
+ (nconc (copy-sequence (nthcdr n list)) (seq-take list n)))
+
+(defun vertico--group-by (fun elems)
+ "Group ELEMS by FUN."
+ (let ((ht (make-hash-table :test #'equal)) titles groups)
+ ;; Build hash table of groups
+ (cl-loop for elem on elems
+ for title = (funcall fun (car elem) nil) do
+ (if-let* ((group (gethash title ht)))
+ (setcdr group (setcdr (cdr group) elem)) ;; Append to tail of group
+ (puthash title (cons elem elem) ht) ;; New group element (head . tail)
+ (push title titles)))
+ (setq titles (nreverse titles))
+ ;; Cycle groups if `vertico--lock-groups' is set
+ (when-let* ((group (seq-find (lambda (group) (gethash group ht))
+ vertico--lock-groups)))
+ (setq titles (vertico--cycle titles (seq-position titles group))))
+ ;; Build group list
+ (dolist (title titles)
+ (push (gethash title ht) groups))
+ ;; Unlink last tail
+ (setcdr (cdar groups) nil)
+ (setq groups (nreverse groups))
+ ;; Link groups
+ (let ((link groups))
+ (while (cdr link)
+ (setcdr (cdar link) (caadr link))
+ (pop link)))
+ (cons (caar groups) titles)))
+
+(defun vertico--remote-p (path)
+ "Return t if PATH is a remote path."
+ (string-match-p "\\`/[^/|:]+:" (substitute-in-file-name path)))
+
+(defun vertico--update (&optional interruptible)
+ "Update state, optionally INTERRUPTIBLE."
+ (let* ((pt (max 0 (- (point) (minibuffer-prompt-end))))
+ (str (minibuffer-contents-no-properties))
+ (input (cons str pt)))
+ (unless (or (and interruptible (input-pending-p)) (equal vertico--input input))
+ ;; Redisplay to make input immediately visible before expensive candidate
+ ;; recomputation (gh:minad/vertico#89). No redisplay during init because
+ ;; of flicker.
+ (when (and interruptible (consp vertico--input))
+ ;; Prevent recursive exhibit from timer (`consult-vertico--refresh').
+ (cl-letf (((symbol-function #'vertico--exhibit) #'ignore)) (redisplay)))
+ (pcase (let ((vertico--metadata (completion-metadata (substring str 0 pt)
+ minibuffer-completion-table
+ minibuffer-completion-predicate)))
+ ;; If Tramp is used, do not compute the candidates in an
+ ;; interruptible fashion, since this will break the Tramp
+ ;; password and user name prompts (See gh:minad/vertico#23).
+ (if (or (not interruptible)
+ (and (eq 'file (vertico--metadata-get 'category))
+ (or (vertico--remote-p str) (vertico--remote-p default-directory))))
+ (vertico--compute input)
+ (let ((non-essential t))
+ (while-no-input (vertico--compute input)))))
+ ('nil (abort-recursive-edit))
+ ((and state (pred consp))
+ (dolist (s state) (set (car s) (cdr s))))))))
+
+(defun vertico--display-string (str)
+ "Return display STR without display and invisible properties."
+ (let ((end (length str)) (pos 0) chunks)
+ (while (< pos end)
+ (let ((nextd (next-single-property-change pos 'display str end))
+ (disp (get-text-property pos 'display str)))
+ (if (stringp disp)
+ (let ((face (get-text-property pos 'face str)))
+ (when face
+ (add-face-text-property 0 (length disp) face t (setq disp (concat disp))))
+ (setq pos nextd chunks (cons disp chunks)))
+ (while (< pos nextd)
+ (let ((nexti (next-single-property-change pos 'invisible str nextd)))
+ (unless (or (get-text-property pos 'invisible str)
+ (and (= pos 0) (= nexti end))) ;; full string -> no allocation
+ (push (substring str pos nexti) chunks))
+ (setq pos nexti))))))
+ (if chunks (apply #'concat (nreverse chunks)) str)))
+
+(defun vertico--window-width ()
+ "Return minimum width of windows, which display the minibuffer."
+ (cl-loop for win in (get-buffer-window-list) minimize (window-width win)))
+
+(defun vertico--truncate-multiline (str max)
+ "Truncate multiline STR to MAX."
+ (let ((pos 0) (res ""))
+ (while (and (< (length res) (* 2 max)) (string-match "\\(\\S-+\\)\\|\\s-+" str pos))
+ (setq res (concat res (if (match-end 1) (match-string 0 str)
+ (if (string-search "\n" (match-string 0 str))
+ (car vertico-multiline) " ")))
+ pos (match-end 0)))
+ (truncate-string-to-width (string-trim res) max 0 nil (cdr vertico-multiline))))
+
+(defun vertico--compute-scroll ()
+ "Compute new scroll position."
+ (let ((off (max (min vertico-scroll-margin (/ vertico-count 2)) 0))
+ (corr (if (= vertico-scroll-margin (/ vertico-count 2)) (1- (mod vertico-count 2)) 0)))
+ (setq vertico--scroll (min (max 0 (- vertico--total vertico-count))
+ (max 0 (+ vertico--index off 1 (- vertico-count))
+ (min (- vertico--index off corr) vertico--scroll))))))
+
+(defun vertico--format-group-title (title cand)
+ "Format group TITLE given the current CAND."
+ ;; Copy candidate highlighting if title is a prefix of the candidate.
+ (when (string-prefix-p title cand)
+ (setq title (substring cand 0 (length title)))
+ (vertico--remove-face 0 (length title) 'completions-first-difference title))
+ (setq title (substring title))
+ (add-face-text-property 0 (length title) 'vertico-group-title t title)
+ (format (concat vertico-group-format "\n") title))
+
+(defun vertico--format-count ()
+ "Format the count string."
+ (format (car vertico-count-format)
+ (format (cdr vertico-count-format)
+ (cond ((>= vertico--index 0) (1+ vertico--index))
+ (vertico--allow-prompt "*")
+ (t "!"))
+ vertico--total)))
+
+(defun vertico--display-count ()
+ "Update count overlay `vertico--count-ov'."
+ (move-overlay vertico--count-ov (point-min) (point-min))
+ (overlay-put vertico--count-ov 'before-string
+ (if vertico-count-format (vertico--format-count) "")))
+
+(defun vertico--prompt-selection ()
+ "Highlight the prompt if selected."
+ (let ((inhibit-modification-hooks t))
+ (if (and (< vertico--index 0) vertico--allow-prompt)
+ (add-face-text-property (minibuffer-prompt-end) (point-max) 'vertico-current 'append)
+ (vertico--remove-face (minibuffer-prompt-end) (point-max) 'vertico-current))))
+
+(defun vertico--remove-face (beg end face &optional obj)
+ "Remove FACE between BEG and END from OBJ."
+ (while (< beg end)
+ (let ((next (next-single-property-change beg 'face obj end)))
+ (when-let* ((val (get-text-property beg 'face obj)))
+ (put-text-property beg next 'face (remq face (ensure-list val)) obj))
+ (setq beg next))))
+
+(defun vertico--debug (&rest _)
+ "Debugger used by `vertico--protect'."
+ (let ((inhibit-message t))
+ (require 'backtrace)
+ (declare-function backtrace-to-string "backtrace")
+ (message "Vertico detected an error:\n%s" (backtrace-to-string)))
+ (let (message-log-max)
+ (message "%s %s"
+ (propertize "Vertico detected an error:" 'face 'error)
+ (substitute-command-keys "Press \\[view-echo-area-messages] to see the stack trace")))
+ nil)
+
+(defun vertico--protect (fun)
+ "Protect FUN such that errors are caught.
+If an error occurs, the FUN is retried with `debug-on-error' enabled and
+the stack trace is shown in the *Messages* buffer."
+ (static-if (fboundp 'handler-bind) ;; Available on Emacs 30
+ (ignore-errors
+ (handler-bind ((error #'vertico--debug))
+ (funcall fun)))
+ (when (or debug-on-error (condition-case nil
+ (progn (funcall fun) nil)
+ (error t)))
+ (let ((debug-on-error t)
+ (debugger #'vertico--debug))
+ (condition-case nil
+ (funcall fun)
+ ((debug error) nil))))))
+
+(defun vertico--exhibit ()
+ "Exhibit completion UI."
+ (vertico--protect
+ (lambda ()
+ (let ((buffer-undo-list t)) ;; Overlays affect point position and undo list!
+ (vertico--update 'interruptible)
+ (vertico--prompt-selection)
+ (vertico--display-count)
+ (vertico--display-candidates (vertico--arrange-candidates))))))
+
+(defun vertico--goto (index)
+ "Go to candidate with INDEX."
+ (setq vertico--index
+ (max (if (or vertico--allow-prompt (= 0 vertico--total)) -1 0)
+ (min index (1- vertico--total)))
+ vertico--lock-candidate (or (>= vertico--index 0) vertico--allow-prompt)))
+
+(defun vertico--candidate (&optional hl)
+ "Return current candidate string with optional highlighting if HL is non-nil."
+ (let ((content (or (car-safe vertico--input) (minibuffer-contents-no-properties))))
+ (cond
+ ((>= vertico--index 0)
+ (let ((cand (substring (nth vertico--index vertico--candidates))))
+ ;; XXX Drop the completions-common-part face which is added by the
+ ;; `completion--twq-all' hack. This should better be fixed in Emacs
+ ;; itself, the corresponding code is already marked as fixme.
+ (vertico--remove-face 0 (length cand) 'completions-common-part cand)
+ (concat vertico--base (if hl (vertico--hilit cand) cand))))
+ ((and (equal content "") (or (car-safe minibuffer-default) minibuffer-default)))
+ (t content))))
+
+(defun vertico--match-p (input)
+ "Return t if INPUT is a valid match."
+ (let ((rm minibuffer--require-match))
+ (or (memq rm '(nil confirm-after-completion))
+ (equal "" input) ;; Null completion, returns default value
+ (if (functionp rm) (funcall rm input) ;; require-match can be a function
+ (test-completion input minibuffer-completion-table minibuffer-completion-predicate))
+ (if (eq rm 'confirm) (eq (ignore-errors (read-char "Confirm")) 13)
+ (minibuffer-message "Match required") nil))))
+
+(cl-defgeneric vertico--format-candidate (cand prefix suffix index _start)
+ "Format CAND given PREFIX, SUFFIX and INDEX."
+ (setq cand (vertico--display-string (concat prefix cand suffix "\n")))
+ (when (= index vertico--index)
+ (add-face-text-property 0 (length cand) 'vertico-current 'append cand))
+ cand)
+
+(cl-defgeneric vertico--arrange-candidates ()
+ "Arrange candidates."
+ (vertico--compute-scroll)
+ (let ((curr-line 0) lines)
+ ;; Compute group titles
+ (let* (title (index vertico--scroll)
+ (group-fun (and vertico-group-format (vertico--metadata-get 'group-function)))
+ (candidates
+ (vertico--affixate
+ (cl-loop repeat vertico-count for c in (nthcdr index vertico--candidates)
+ collect (vertico--hilit c)))))
+ (pcase-dolist ((and cand `(,str . ,_)) candidates)
+ (when-let* ((new-title (and group-fun (funcall group-fun str nil))))
+ (unless (equal title new-title)
+ (setq title new-title)
+ (push (vertico--format-group-title title str) lines))
+ (setcar cand (funcall group-fun str 'transform)))
+ (when (= index vertico--index)
+ (setq curr-line (length lines)))
+ (push (cons index cand) lines)
+ (cl-incf index)))
+ ;; Drop excess lines
+ (setq lines (nreverse lines))
+ (cl-loop for count from (length lines) above vertico-count do
+ (if (< curr-line (/ count 2))
+ (nbutlast lines)
+ (setq curr-line (1- curr-line) lines (cdr lines))))
+ ;; Format candidates
+ (let ((max-width (- (vertico--window-width) 4)) start)
+ (cl-loop for line on lines do
+ (pcase (car line)
+ (`(,index ,cand ,prefix ,suffix)
+ (setq start (or start index))
+ (when (string-search "\n" cand)
+ (setq cand (vertico--truncate-multiline cand max-width)))
+ (setcar line (vertico--format-candidate cand prefix suffix index start))))))
+ lines))
+
+(cl-defgeneric vertico--display-candidates (lines)
+ "Update candidates overlay `vertico--candidates-ov' with LINES."
+ (move-overlay vertico--candidates-ov (point-max) (point-max))
+ (overlay-put vertico--candidates-ov 'before-string
+ (apply #'concat #(" " 0 1 (cursor t)) (and lines "\n") lines))
+ (vertico--resize-window (length lines)))
+
+(cl-defgeneric vertico--resize-window (height)
+ "Resize active minibuffer window to HEIGHT."
+ (setq-local truncate-lines (< (point) (* 0.8 (vertico--window-width)))
+ resize-mini-windows 'grow-only
+ max-mini-window-height 1.0)
+ (unless truncate-lines (set-window-hscroll nil 0))
+ (unless (frame-root-window-p (active-minibuffer-window))
+ (unless vertico-resize (setq height (max height vertico-count)))
+ (let ((dp (- (max (cdr (window-text-pixel-size))
+ (* (default-line-height) (1+ height)))
+ (window-pixel-height))))
+ (when (or (and (> dp 0) (/= height 0))
+ (and (< dp 0) (eq vertico-resize t)))
+ (window-resize nil dp nil nil 'pixelwise)))))
+
+(cl-defgeneric vertico--prepare ()
+ "Ensure that the state is prepared before running the next command."
+ (when-let* ((cmd (and (symbolp this-command) (symbol-name this-command)))
+ ((string-prefix-p "vertico-" cmd))
+ ((not (and vertico--metadata (string-prefix-p "vertico-directory-" cmd)))))
+ (vertico--update)))
+
+(cl-defgeneric vertico--setup ()
+ "Setup completion UI."
+ (dolist (var vertico--locals)
+ (set (make-local-variable (car var)) (cdr var)))
+ (setq-local vertico--input t
+ vertico--candidates-ov (make-overlay (point-max) (point-max) nil t t)
+ vertico--count-ov (make-overlay (point-min) (point-min) nil t t))
+ (overlay-put vertico--count-ov 'priority 1) ;; For `minibuffer-depth-indicate-mode'
+ (use-local-map vertico-map)
+ (add-hook 'pre-command-hook #'vertico--prepare nil 'local)
+ (add-hook 'post-command-hook #'vertico--exhibit nil 'local))
+
+(cl-defgeneric vertico--advice (&rest app)
+ "Advice for completion function, apply APP."
+ (dlet ((completion-eager-display nil)) ;; Available on Emacs 31
+ (minibuffer-with-setup-hook #'vertico--setup (apply app))))
+
+(defun vertico-first ()
+ "Go to first candidate, or to the prompt when the first candidate is selected."
+ (interactive)
+ (vertico--goto (if (> vertico--index 0) 0 -1)))
+
+(defun vertico-last ()
+ "Go to last candidate."
+ (interactive)
+ (vertico--goto (1- vertico--total)))
+
+(defun vertico-scroll-down (&optional n)
+ "Go back by N pages."
+ (interactive "p")
+ (vertico--goto (max 0 (- vertico--index (* (or n 1) vertico-count)))))
+
+(defun vertico-scroll-up (&optional n)
+ "Go forward by N pages."
+ (interactive "p")
+ (vertico-scroll-down (- (or n 1))))
+
+(defun vertico-next (&optional n)
+ "Go forward N candidates."
+ (interactive "p")
+ (let ((index (+ vertico--index (or n 1))))
+ (vertico--goto
+ (cond
+ ((not vertico-cycle) index)
+ ((= vertico--total 0) -1)
+ (vertico--allow-prompt (1- (mod (1+ index) (1+ vertico--total))))
+ (t (mod index vertico--total))))))
+
+(defun vertico-previous (&optional n)
+ "Go backward N candidates."
+ (interactive "p")
+ (vertico-next (- (or n 1))))
+
+(defun vertico-exit (&optional arg)
+ "Exit minibuffer with current candidate or input if prefix ARG is given."
+ (interactive "P")
+ (when (and (not arg) (>= vertico--index 0))
+ (vertico-insert))
+ (when (vertico--match-p (minibuffer-contents-no-properties))
+ (exit-minibuffer)))
+
+(defun vertico-next-group (&optional n)
+ "Cycle N groups forward.
+When the prefix argument is 0, the group order is reset."
+ (interactive "p")
+ (when (cdr vertico--groups)
+ (setq vertico--groups (and (not (eq n 0))
+ (vertico--cycle vertico--groups
+ (let ((len (length vertico--groups)))
+ (- len (mod (- (or n 1)) len)))))
+ vertico--lock-groups vertico--groups
+ vertico--lock-candidate nil
+ vertico--input nil)))
+
+(defun vertico-previous-group (&optional n)
+ "Cycle N groups backward.
+When the prefix argument is 0, the group order is reset."
+ (interactive "p")
+ (vertico-next-group (- (or n 1))))
+
+(defun vertico-exit-input ()
+ "Exit minibuffer with input."
+ (interactive)
+ (vertico-exit t))
+
+(defun vertico-save ()
+ "Save current candidate to kill ring."
+ (interactive)
+ (if (or (use-region-p) (not transient-mark-mode))
+ (call-interactively #'kill-ring-save)
+ (kill-new (substring-no-properties (vertico--candidate)))))
+
+(defun vertico-insert ()
+ "Insert current candidate in minibuffer."
+ (interactive)
+ ;; XXX There is a small bug here, depending on interpretation. When completing
+ ;; "~/emacs/master/li|/calc" where "|" is the cursor, then the returned
+ ;; candidate only includes the prefix "~/emacs/master/lisp/", but not the
+ ;; suffix "/calc". Default completion has the same problem when selecting in
+ ;; the *Completions* buffer. See bug#48356.
+ (when (> vertico--total 0)
+ (let ((vertico--index (max 0 vertico--index)))
+ (insert (prog1 (vertico--candidate) (delete-minibuffer-contents))))))
+
+;;;###autoload
+(define-minor-mode vertico-mode
+ "VERTical Interactive COmpletion."
+ :global t :group 'vertico
+ (dolist (fun '(completing-read-default completing-read-multiple))
+ (if vertico-mode
+ (advice-add fun :around #'vertico--advice)
+ (advice-remove fun #'vertico--advice))))
+
+(defun vertico--command-p (_sym buffer)
+ "Return non-nil if Vertico is active in BUFFER."
+ (buffer-local-value 'vertico--input buffer))
+
+;; Do not show Vertico commands in M-X
+(dolist (sym '( vertico-next vertico-next-group vertico-previous vertico-previous-group
+ vertico-scroll-down vertico-scroll-up vertico-exit vertico-insert
+ vertico-exit-input vertico-save vertico-first vertico-last
+ vertico-repeat-next ;; autoloads in vertico-repeat.el
+ vertico-quick-jump vertico-quick-exit vertico-quick-insert ;; autoloads in vertico-quick.el
+ vertico-directory-up vertico-directory-enter ;; autoloads in vertico-directory.el
+ vertico-directory-delete-char vertico-directory-delete-word))
+ (put sym 'completion-predicate #'vertico--command-p))
+
+(provide 'vertico)
+;;; vertico.el ends here