diff options
Diffstat (limited to '.config/emacs/lisp/minadstack')
| -rw-r--r-- | .config/emacs/lisp/minadstack/cape.el | 1359 | ||||
| -rw-r--r-- | .config/emacs/lisp/minadstack/consult.el | 5739 | ||||
| -rw-r--r-- | .config/emacs/lisp/minadstack/corfu-history.el | 114 | ||||
| -rw-r--r-- | .config/emacs/lisp/minadstack/corfu.el | 1444 | ||||
| -rw-r--r-- | .config/emacs/lisp/minadstack/marginalia.el | 1461 | ||||
| -rw-r--r-- | .config/emacs/lisp/minadstack/orderless.el | 672 | ||||
| -rw-r--r-- | .config/emacs/lisp/minadstack/vertico-directory.el | 136 | ||||
| -rw-r--r-- | .config/emacs/lisp/minadstack/vertico.el | 736 |
8 files changed, 0 insertions, 11661 deletions
diff --git a/.config/emacs/lisp/minadstack/cape.el b/.config/emacs/lisp/minadstack/cape.el deleted file mode 100644 index c180044..0000000 --- a/.config/emacs/lisp/minadstack/cape.el +++ /dev/null @@ -1,1359 +0,0 @@ -;;; 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 deleted file mode 100644 index d500c9c..0000000 --- a/.config/emacs/lisp/minadstack/consult.el +++ /dev/null @@ -1,5739 +0,0 @@ -;;; 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 deleted file mode 100644 index ba7af47..0000000 --- a/.config/emacs/lisp/minadstack/corfu-history.el +++ /dev/null @@ -1,114 +0,0 @@ -;;; 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 deleted file mode 100644 index bd3b314..0000000 --- a/.config/emacs/lisp/minadstack/corfu.el +++ /dev/null @@ -1,1444 +0,0 @@ -;;; 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 deleted file mode 100644 index 3c49eed..0000000 --- a/.config/emacs/lisp/minadstack/marginalia.el +++ /dev/null @@ -1,1461 +0,0 @@ -;;; 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 deleted file mode 100644 index 7cee5c8..0000000 --- a/.config/emacs/lisp/minadstack/orderless.el +++ /dev/null @@ -1,672 +0,0 @@ -;;; 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 deleted file mode 100644 index 3328dd1..0000000 --- a/.config/emacs/lisp/minadstack/vertico-directory.el +++ /dev/null @@ -1,136 +0,0 @@ -;;; 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 deleted file mode 100644 index c646f0b..0000000 --- a/.config/emacs/lisp/minadstack/vertico.el +++ /dev/null @@ -1,736 +0,0 @@ -;;; 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 |
