dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

consult.el (208598B)


      1 ;;; consult.el --- Consulting completing-read -*- lexical-binding: t -*-
      2 
      3 ;; Copyright (C) 2021, 2022  Free Software Foundation, Inc.
      4 
      5 ;; Author: Daniel Mendler and Consult contributors
      6 ;; Maintainer: Daniel Mendler <mail@daniel-mendler.de>
      7 ;; Created: 2020
      8 ;; Version: 0.17
      9 ;; Package-Requires: ((emacs "27.1"))
     10 ;; Homepage: https://github.com/minad/consult
     11 
     12 ;; This file is part of GNU Emacs.
     13 
     14 ;; This program is free software: you can redistribute it and/or modify
     15 ;; it under the terms of the GNU General Public License as published by
     16 ;; the Free Software Foundation, either version 3 of the License, or
     17 ;; (at your option) any later version.
     18 
     19 ;; This program is distributed in the hope that it will be useful,
     20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     22 ;; GNU General Public License for more details.
     23 
     24 ;; You should have received a copy of the GNU General Public License
     25 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
     26 
     27 ;;; Commentary:
     28 
     29 ;; Consult implements a set of `consult-<thing>' commands which use
     30 ;; `completing-read' to select from a list of candidates. Consult provides an
     31 ;; enhanced buffer switcher `consult-buffer' and search and navigation commands
     32 ;; like `consult-imenu' and `consult-line'. Searching through multiple files is
     33 ;; supported by the asynchronous `consult-grep' command. Many Consult commands
     34 ;; allow previewing candidates - if a candidate is selected in the completion
     35 ;; view, the buffer shows the candidate immediately.
     36 
     37 ;; The Consult commands are compatible with completion systems based
     38 ;; on the Emacs `completing-read' API, including the default completion
     39 ;; system, Vertico, Mct, Icomplete and Selectrum.
     40 
     41 ;; Consult has been inspired by Counsel. Some of the Consult commands
     42 ;; originated in the Counsel package or the Selectrum wiki. See the
     43 ;; README for a full list of contributors.
     44 
     45 ;;; Code:
     46 
     47 (eval-when-compile
     48   (require 'cl-lib)
     49   (require 'subr-x))
     50 (require 'bookmark)
     51 (require 'kmacro)
     52 (require 'recentf)
     53 (require 'seq)
     54 
     55 (defgroup consult nil
     56   "Consulting `completing-read'."
     57   :group 'convenience
     58   :group 'minibuffer
     59   :prefix "consult-")
     60 
     61 ;;;; Customization
     62 
     63 (defcustom consult-narrow-key nil
     64   "Prefix key for narrowing during completion.
     65 
     66 Good choices for this key are (kbd \"<\") or (kbd \"C-+\") for example.
     67 
     68 The key must be either a string or a vector.
     69 This is the key representation accepted by `define-key'."
     70   :type '(choice key-sequence (const nil)))
     71 
     72 (defcustom consult-widen-key nil
     73   "Key used for widening during completion.
     74 
     75 If this key is unset, defaults to twice the `consult-narrow-key'.
     76 
     77 The key must be either a string or a vector.
     78 This is the key representation accepted by `define-key'."
     79   :type '(choice key-sequence (const nil)))
     80 
     81 (defcustom consult-project-function
     82   #'consult--default-project-function
     83   "Function which returns project root directory.
     84 The function takes one boolargument MAY-PROMPT. If MAY-PROMPT is non-nil,
     85 the function may ask the prompt the user for a project directory.
     86 The root directory is used by `consult-buffer' and `consult-grep'."
     87   :type '(choice function (const nil)))
     88 
     89 (defcustom consult-async-refresh-delay 0.2
     90   "Refreshing delay of the completion ui for asynchronous commands.
     91 
     92 The completion ui is only updated every `consult-async-refresh-delay'
     93 seconds. This applies to asynchronous commands like for example
     94 `consult-grep'."
     95   :type 'float)
     96 
     97 (defcustom consult-async-input-throttle 0.4
     98   "Input throttle for asynchronous commands.
     99 
    100 The asynchronous process is started only every
    101 `consult-async-input-throttle' seconds. This applies to asynchronous
    102 commands, e.g., `consult-grep'."
    103   :type 'float)
    104 
    105 (defcustom consult-async-input-debounce 0.2
    106   "Input debounce for asynchronous commands.
    107 
    108 The asynchronous process is started only when there has not been new
    109 input for `consult-async-input-debounce' seconds. This applies to
    110 asynchronous commands, e.g., `consult-grep'."
    111   :type 'float)
    112 
    113 (defcustom consult-async-min-input 3
    114   "Minimum number of letters needed, before asynchronous process is called.
    115 
    116 This applies to asynchronous commands, e.g., `consult-grep'."
    117   :type 'integer)
    118 
    119 (defcustom consult-async-split-style 'perl
    120   "Async splitting style, see `consult-async-split-styles-alist'."
    121   :type '(choice (const :tag "No splitting" nil)
    122                  (const :tag "Comma" comma)
    123                  (const :tag "Semicolon" semicolon)
    124                  (const :tag "Perl" perl)))
    125 
    126 (defcustom consult-async-split-styles-alist
    127   '((nil :type nil)
    128     (comma :separator ?, :type separator)
    129     (semicolon :separator ?\; :type separator)
    130     (perl :initial "#" :type perl))
    131   "Async splitting styles."
    132   :type '(alist :key-type symbol :value-type plist))
    133 
    134 (defcustom consult-mode-histories
    135   '((eshell-mode . eshell-history-ring)
    136     (comint-mode . comint-input-ring)
    137     (term-mode   . term-input-ring))
    138   "Alist of (mode . history) pairs of mode histories.
    139 The histories can be rings or lists."
    140   :type '(alist :key-type symbol :value-type symbol))
    141 
    142 (defcustom consult-themes nil
    143   "List of themes to be presented for selection.
    144 nil shows all `custom-available-themes'."
    145   :type '(repeat symbol))
    146 
    147 (defcustom consult-after-jump-hook '(recenter)
    148   "Function called after jumping to a location.
    149 
    150 Commonly used functions for this hook are `recenter' and
    151 `reposition-window'. You may want to add a function which pulses the
    152 current line, e.g., `pulse-momentary-highlight-one-line' is supported on
    153 Emacs 28 and newer. The hook called during preview and for the jump
    154 after selection."
    155   :type 'hook)
    156 
    157 (defcustom consult-line-start-from-top nil
    158   "Start search from the top if non-nil.
    159 Otherwise start the search at the current line and wrap around."
    160   :type 'boolean)
    161 
    162 (defcustom consult-line-point-placement 'match-beginning
    163   "Where to leave point after `consult-line' jumps to a match."
    164   :type '(choice (const :tag "Beginning of the line" line-beginning)
    165                  (const :tag "Beginning of the match" match-beginning)
    166                  (const :tag "End of the match" match-end)))
    167 
    168 (defcustom consult-line-numbers-widen t
    169   "Show absolute line numbers when narrowing is active.
    170 
    171 See also `display-line-numbers-widen'."
    172   :type 'boolean)
    173 
    174 (defcustom consult-goto-line-numbers t
    175   "Show line numbers for `consult-goto-line'."
    176   :type 'boolean)
    177 
    178 (defcustom consult-fontify-preserve t
    179   "Preserve fontification for line-based commands."
    180   :type 'boolean)
    181 
    182 (defcustom consult-fontify-max-size 1048576
    183   "Buffers larger than this byte limit are not fontified.
    184 
    185 This is necessary in order to prevent a large startup time
    186 for navigation commands like `consult-line'."
    187   :type 'integer)
    188 
    189 (defvar consult-recent-file-filter nil)
    190 (make-obsolete-variable 'consult-recent-file-filter
    191                         "Deprecated in favor of `recentf-exclude'." "0.16")
    192 
    193 (defcustom consult-buffer-filter
    194   '("\\` "
    195     "\\`\\*Completions\\*\\'"
    196     "\\`\\*Flymake log\\*\\'"
    197     "\\`\\*Semantic SymRef\\*\\'"
    198     "\\`\\*tramp/.*\\*\\'")
    199   "Filter regexps for `consult-buffer'.
    200 
    201 The default setting is to filter ephemeral buffer names beginning with a space
    202 character, the *Completions* buffer and a few log buffers."
    203   :type '(repeat regexp))
    204 
    205 (defcustom consult-buffer-sources
    206   '(consult--source-hidden-buffer
    207     consult--source-buffer
    208     consult--source-recent-file
    209     consult--source-bookmark
    210     consult--source-project-buffer
    211     consult--source-project-recent-file)
    212   "Sources used by `consult-buffer'.
    213 See also `consult-project-buffer-sources'.
    214 See `consult--multi' for a description of the source data structure."
    215   :type '(repeat symbol))
    216 
    217 (defcustom consult-project-buffer-sources nil
    218   "Sources used by `consult-project-buffer'.
    219 See also `consult-buffer-sources'.
    220 See `consult--multi' for a description of the source data structure."
    221   :type '(repeat symbol))
    222 
    223 (defcustom consult-mode-command-filter
    224   '(;; Filter commands
    225     "-mode\\'" "--"
    226     ;; Filter whole features
    227     simple mwheel time so-long recentf)
    228   "Filter commands for `consult-mode-command'."
    229   :type '(repeat (choice symbol regexp)))
    230 
    231 (defcustom consult-grep-max-columns 300
    232   "Maximal number of columns of grep output."
    233   :type 'integer)
    234 
    235 (defconst consult--grep-match-regexp
    236   "\\`\\(?:\\./\\)?\\([^\n\0]+\\)\0\\([0-9]+\\)\\([-:\0]\\)"
    237   "Regexp used to match file and line of grep output.")
    238 
    239 (defcustom consult-grep-args
    240   "grep --null --line-buffered --color=never --ignore-case\
    241    --exclude-dir=.git --line-number -I -r ."
    242   "Command line arguments for grep, see `consult-grep'.
    243 The dynamically computed arguments are appended."
    244   :type 'string)
    245 
    246 (defcustom consult-git-grep-args
    247   "git --no-pager grep --null --color=never --ignore-case\
    248    --extended-regexp --line-number -I"
    249   "Command line arguments for git-grep, see `consult-git-grep'.
    250 The dynamically computed arguments are appended."
    251   :type 'string)
    252 
    253 (defcustom consult-ripgrep-args
    254   "rg --null --line-buffered --color=never --max-columns=1000 --path-separator /\
    255    --smart-case --no-heading --line-number ."
    256   "Command line arguments for ripgrep, see `consult-ripgrep'.
    257 The dynamically computed arguments are appended."
    258   :type 'string)
    259 
    260 (defcustom consult-find-args
    261   "find . -not ( -wholename */.* -prune )"
    262   "Command line arguments for find, see `consult-find'.
    263 The dynamically computed arguments are appended."
    264   :type 'string)
    265 
    266 (defcustom consult-locate-args
    267   "locate --ignore-case --existing"
    268   "Command line arguments for locate, see `consult-locate'.
    269 The dynamically computed arguments are appended."
    270   :type 'string)
    271 
    272 (defcustom consult-man-args
    273   "man -k"
    274   "Command line arguments for man, see `consult-man'.
    275 The dynamically computed arguments are appended."
    276   :type 'string)
    277 
    278 (defcustom consult-preview-key 'any
    279   "Preview trigger keys, can be nil, 'any, a single key or a list of keys."
    280   :type '(choice (const :tag "Any key" any)
    281                  (list :tag "Debounced" (const :debounce) (float :tag "Seconds" 0.1) (const any))
    282                  (const :tag "No preview" nil)
    283                  (key-sequence :tag "Key")
    284                  (repeat :tag "List of keys" key-sequence)))
    285 
    286 (defcustom consult-preview-max-size 10485760
    287   "Files larger than this byte limit are not previewed."
    288   :type 'integer)
    289 
    290 (defcustom consult-preview-raw-size 524288
    291   "Files larger than this byte limit are previewed in raw form."
    292   :type 'integer)
    293 
    294 (defcustom consult-preview-max-count 10
    295   "Number of files to keep open at once during preview."
    296   :type 'integer)
    297 
    298 (defvar consult-preview-excluded-hooks nil)
    299 (make-obsolete-variable 'consult-preview-excluded-hooks
    300                         "Deprecated in favor of `consult-preview-allowed-hooks'." "0.16")
    301 
    302 (defcustom consult-preview-allowed-hooks
    303   '(global-font-lock-mode-check-buffers
    304     save-place-find-file-hook)
    305   "List of `find-file' hooks, which should be executed during file preview."
    306   :type '(repeat symbol))
    307 
    308 (defcustom consult-preview-variables
    309   '((inhibit-message . t)
    310     (enable-dir-local-variables . nil)
    311     (enable-local-variables . :safe)
    312     (non-essential . t)
    313     (delay-mode-hooks . t))
    314   "Variables which are bound for file preview."
    315   :type '(alist :key-type symbol))
    316 
    317 (defcustom consult-bookmark-narrow
    318   `((?f "File" ,#'bookmark-default-handler)
    319     (?h "Help" ,#'help-bookmark-jump)
    320     (?i "Info" ,#'Info-bookmark-jump)
    321     (?p "Picture" ,#'image-bookmark-jump)
    322     (?d "Docview" ,#'doc-view-bookmark-jump)
    323     (?m "Man" ,#'Man-bookmark-jump)
    324     (?w "Woman" ,#'woman-bookmark-jump)
    325     (?g "Gnus" ,#'gnus-summary-bookmark-jump)
    326     ;; Introduced on Emacs 28
    327     (?s "Eshell" eshell-bookmark-jump)
    328     (?e "Eww" eww-bookmark-jump)
    329     (?v "VC Directory" vc-dir-bookmark-jump))
    330   "Bookmark narrowing configuration.
    331 
    332 Each element of the list must have the form '(char name handler)."
    333   :type '(repeat (list character string function)))
    334 
    335 (defcustom consult-crm-prefix
    336   (cons "  " (propertize "✓ " 'face 'success))
    337   "Prefix for `consult-completing-read-multiple' candidates."
    338   :type '(cons (string :tag "Not selected") (string :tag "Selected")))
    339 
    340 ;;;; Faces
    341 
    342 (defgroup consult-faces nil
    343   "Faces used by Consult."
    344   :group 'consult
    345   :group 'faces)
    346 
    347 (defface consult-preview-line
    348   '((t :inherit consult-preview-insertion :extend t))
    349   "Face used for line previews.")
    350 
    351 (defface consult-preview-match
    352   '((t :inherit match))
    353   "Face used for match previews in `consult-grep'.")
    354 
    355 (defface consult-preview-cursor
    356   '((t :inherit consult-preview-match))
    357   "Face used for cursor previews and marks in `consult-mark'.")
    358 
    359 (defface consult-preview-error
    360   '((t :inherit isearch-fail))
    361   "Face used for cursor previews and marks in `consult-compile-error'.")
    362 
    363 (defface consult-preview-insertion
    364   '((t :inherit region))
    365   "Face used for previews of text to be inserted.
    366 Used by `consult-completion-in-region', `consult-yank' and `consult-history'.")
    367 
    368 (defface consult-narrow-indicator
    369   '((t :inherit warning))
    370   "Face used for the narrowing indicator.")
    371 
    372 (defface consult-async-running
    373   '((t :inherit consult-narrow-indicator))
    374   "Face used if asynchronous process is running.")
    375 
    376 (defface consult-async-finished
    377   '((t :inherit success))
    378   "Face used if asynchronous process has finished.")
    379 
    380 (defface consult-async-failed
    381   '((t :inherit error))
    382   "Face used if asynchronous process has failed.")
    383 
    384 (defface consult-async-split
    385   '((t :inherit font-lock-negation-char-face))
    386   "Face used to highlight punctuation character.")
    387 
    388 (defface consult-help
    389   '((t :inherit shadow))
    390   "Face used to highlight help, e.g., in `consult-register-store'.")
    391 
    392 (defface consult-key
    393   '((t :inherit font-lock-keyword-face))
    394   "Face used to highlight keys, e.g., in `consult-register'.")
    395 
    396 (defface consult-line-number
    397   '((t :inherit consult-key))
    398   "Face used to highlight location line in `consult-global-mark'.")
    399 
    400 (defface consult-file
    401   '((t :inherit font-lock-function-name-face))
    402   "Face used to highlight files in `consult-buffer'.")
    403 
    404 (defface consult-grep-context
    405   '((t :inherit shadow))
    406   "Face used to highlight grep context in `consult-grep'.")
    407 
    408 (defface consult-bookmark
    409   '((t :inherit font-lock-constant-face))
    410   "Face used to highlight bookmarks in `consult-buffer'.")
    411 
    412 (defface consult-buffer
    413   '((t))
    414   "Face used to highlight buffers in `consult-buffer'.")
    415 
    416 (defface consult-crm-selected
    417   '((t :inherit secondary-selection))
    418   "Face used to highlight selected items in `consult-completing-read-multiple'.")
    419 
    420 (defface consult-line-number-prefix
    421   '((t :inherit line-number))
    422   "Face used to highlight line number prefixes.")
    423 
    424 (defface consult-line-number-wrapped
    425   '((t :inherit consult-line-number-prefix :inherit font-lock-warning-face))
    426   "Face used to highlight line number prefixes, if the line number wrapped around.")
    427 
    428 (defface consult-separator
    429   '((((class color) (min-colors 88) (background light))
    430      :foreground "#ccc")
    431     (((class color) (min-colors 88) (background dark))
    432      :foreground "#333"))
    433   "Face used for thin line separators in `consult-register-window'.")
    434 
    435 ;;;; History variables
    436 
    437 (defvar consult--keep-lines-history nil)
    438 (defvar consult--grep-history nil)
    439 (defvar consult--find-history nil)
    440 (defvar consult--man-history nil)
    441 (defvar consult--line-history nil)
    442 (defvar consult--apropos-history nil)
    443 (defvar consult--theme-history nil)
    444 (defvar consult--minor-mode-menu-history nil)
    445 (defvar consult--kmacro-history nil)
    446 (defvar consult--buffer-history nil)
    447 (defvar consult--crm-history nil)
    448 
    449 ;;;; Internal variables
    450 
    451 (defvar consult--regexp-compiler
    452   #'consult--default-regexp-compiler
    453   "Regular expression compiler used by `consult-grep' and other commands.
    454 The function must return a list of regular expressions and a highlighter
    455 function.")
    456 
    457 (defvar consult--customize-alist nil
    458   "Command configuration alist for fine-grained configuration.
    459 
    460 Each element of the list must have the form (command-name plist...). The
    461 options set here will be evaluated and passed to `consult--read', when
    462 called from the corresponding command. Note that the options depend on
    463 the private `consult--read' API and should not be considered as stable
    464 as the public API.")
    465 
    466 (defvar consult--buffer-display #'switch-to-buffer
    467   "Buffer display function.")
    468 
    469 (defvar consult--completion-candidate-hook
    470   (list #'consult--default-completion-mb-candidate
    471         #'consult--default-completion-list-candidate)
    472   "Get candidate from completion system.")
    473 
    474 (defvar consult--completion-refresh-hook nil
    475   "Refresh completion system.")
    476 
    477 (defvar-local consult--preview-function nil
    478   "Minibuffer-local variable which exposes the current preview function.
    479 This function can be called by custom completion systems from
    480 outside the minibuffer.")
    481 
    482 (defconst consult--tofu-char #x200000
    483   "Special character used to encode line prefixes for disambiguation.
    484 We use invalid characters outside the Unicode range.")
    485 
    486 (defconst consult--tofu-range #x100000
    487   "Special character range.")
    488 
    489 (defvar-local consult--narrow nil
    490   "Current narrowing key.")
    491 
    492 (defvar-local consult--narrow-keys nil
    493   "Narrowing prefixes of the current completion.")
    494 
    495 (defvar-local consult--narrow-predicate nil
    496   "Narrowing predicate of the current completion.")
    497 
    498 (defvar-local consult--narrow-overlay nil
    499   "Narrowing indicator overlay.")
    500 
    501 (defvar consult--gc-threshold (* 64 1024 1024)
    502   "Large gc threshold for temporary increase.")
    503 
    504 (defvar consult--gc-percentage 0.5
    505   "Large gc percentage for temporary increase.")
    506 
    507 (defvar consult--process-chunk (* 1024 1024)
    508   "Increase process output chunk size.")
    509 
    510 (defvar consult--async-log
    511   " *consult-async*"
    512   "Buffer for async logging output used by `consult--async-process'.")
    513 
    514 (defvar-local consult--focus-lines-overlays nil
    515   "Overlays used by `consult-focus-lines'.")
    516 
    517 ;;;; Customization helper
    518 
    519 (defun consult--customize-put (cmds prop form)
    520   "Set property PROP to FORM of commands CMDS."
    521   (dolist (cmd cmds)
    522     (cond
    523      ((and (boundp cmd) (consp (symbol-value cmd)))
    524       (set cmd (plist-put (symbol-value cmd) prop (eval form 'lexical))))
    525      ((functionp cmd)
    526       (setf (alist-get cmd consult--customize-alist)
    527             (plist-put (alist-get cmd consult--customize-alist) prop form)))
    528      (t (user-error "%s is neither a Consult command nor a Consult source"
    529                     cmd))))
    530   nil)
    531 
    532 (defmacro consult-customize (&rest args)
    533   "Set properties of commands or sources.
    534 ARGS is a list of commands or sources followed by the list of keyword-value
    535 pairs."
    536   (let ((setter))
    537     (while args
    538       (let ((cmds (seq-take-while (lambda (x) (not (keywordp x))) args)))
    539         (setq args (seq-drop-while (lambda (x) (not (keywordp x))) args))
    540         (while (keywordp (car args))
    541           (push `(consult--customize-put ',cmds ,(car args) ',(cadr args)) setter)
    542           (setq args (cddr args)))))
    543     (macroexp-progn setter)))
    544 
    545 (defun consult--customize-get (&optional cmd)
    546   "Get configuration from `consult--customize-alist' for CMD."
    547   (mapcar (lambda (x) (eval x 'lexical))
    548           (alist-get (or cmd this-command) consult--customize-alist)))
    549 
    550 ;;;; Helper functions and macros
    551 
    552 (defun consult--command-split (str)
    553   "Return command argument and options list given input STR."
    554   (save-match-data
    555     (let ((opts (when (string-match " +--\\( +\\|\\'\\)" str)
    556                   (prog1 (substring str (match-end 0))
    557                     (setq str (substring str 0 (match-beginning 0)))))))
    558       ;; split-string-and-unquote fails if the quotes are invalid. Ignore it.
    559       (cons str (and opts (ignore-errors (split-string-and-unquote opts)))))))
    560 
    561 (defun consult--highlight-regexps (regexps str)
    562   "Highlight REGEXPS in STR.
    563 If a regular expression contains capturing groups, only these are highlighted.
    564 If no capturing groups are used highlight the whole match."
    565   (dolist (re regexps)
    566     (when (string-match re str)
    567       ;; Unfortunately there is no way to avoid the allocation of the match
    568       ;; data, since the number of capturing groups is unknown.
    569       (let ((m (match-data)))
    570         (setq m (or (cddr m) m))
    571         (while m
    572           (when (car m)
    573             (add-face-text-property (car m) (cadr m)
    574                                     'consult-preview-match nil str))
    575           (setq m (cddr m)))))))
    576 
    577 (defconst consult--convert-regexp-table
    578   (append
    579    ;; For simplicity, treat word beginning/end as word boundaries,
    580    ;; since PCRE does not make this distinction. Usually the
    581    ;; context determines if \b is the beginning or the end.
    582    '(("\\<" . "\\b") ("\\>" . "\\b")
    583      ("\\_<" . "\\b") ("\\_>" . "\\b"))
    584    ;; Treat \` and \' as beginning and end of line. This is more
    585    ;; widely supported and makes sense for line-based commands.
    586    '(("\\`" . "^") ("\\'" . "$"))
    587    ;; Historical: Unescaped *, +, ? are supported at the beginning
    588    (mapcan (lambda (x)
    589              (mapcar (lambda (y)
    590                        (cons (concat x y)
    591                              (concat (string-remove-prefix "\\" x) "\\" y)))
    592                      '("*" "+" "?")))
    593            '("" "\\(" "\\(?:" "\\|" "^"))
    594    ;; Different escaping
    595    (mapcan (lambda (x) `(,x (,(cdr x) . ,(car x))))
    596            '(("\\|" . "|")
    597              ("\\(" . "(") ("\\)" . ")")
    598              ("\\{" . "{") ("\\}" . "}"))))
    599   "Regexp conversion table.")
    600 
    601 (defun consult--convert-regexp (regexp type)
    602   "Convert Emacs REGEXP to regexp syntax TYPE."
    603   (if (memq type '(emacs basic))
    604       regexp
    605     ;; Support for Emacs regular expressions is fairly complete for basic
    606     ;; usage. There are a few unsupported Emacs regexp features:
    607     ;; - \= point matching
    608     ;; - Syntax classes \sx \Sx
    609     ;; - Character classes \cx \Cx
    610     ;; - Explicitly numbered groups (?3:group)
    611     (replace-regexp-in-string
    612      (rx (or "\\\\" "\\^"                         ;; Pass through
    613              (seq (or "\\(?:" "\\|") (any "*+?")) ;; Historical: \|+ or \(?:* etc
    614              (seq "\\(" (any "*+"))               ;; Historical: \(* or \(+
    615              (seq (or bos "^") (any "*+?"))       ;; Historical: + or * at the beginning
    616              (seq (opt "\\") (any "(){|}"))       ;; Escape parens/braces/pipe
    617              (seq "\\" (any "'<>`"))              ;; Special escapes
    618              (seq "\\_" (any "<>"))))             ;; Beginning or end of symbol
    619      (lambda (x) (or (cdr (assoc x consult--convert-regexp-table)) x))
    620      regexp 'fixedcase 'literal)))
    621 
    622 (defun consult--default-regexp-compiler (input type ignore-case)
    623   "Compile the INPUT string to a list of regular expressions.
    624 The function should return a pair, the list of regular expressions and a
    625 highlight function. The highlight function should take a single
    626 argument, the string to highlight given the INPUT. TYPE is the desired
    627 type of regular expression, which can be `basic', `extended', `emacs' or
    628 `pcre'. If IGNORE-CASE is non-nil return a highlight function which
    629 matches case insensitively."
    630   (setq input (consult--split-escaped input))
    631   (cons (mapcar (lambda (x) (consult--convert-regexp x type)) input)
    632         (when-let (regexps (seq-filter #'consult--valid-regexp-p input))
    633           (lambda (str)
    634             (let ((case-fold-search ignore-case))
    635               (consult--highlight-regexps regexps str))))))
    636 
    637 (defun consult--split-escaped (str)
    638   "Split STR at spaces, which can be escaped with backslash."
    639   (mapcar
    640    (lambda (x) (replace-regexp-in-string (string 0) " " x))
    641    (split-string (replace-regexp-in-string
    642                   "\\\\\\\\\\|\\\\ "
    643                   (lambda (x) (if (equal x "\\ ") (string 0) x))
    644                   str 'fixedcase 'literal)
    645                  " +" t)))
    646 
    647 (defun consult--join-regexps (regexps type)
    648   "Join REGEXPS of TYPE."
    649   ;; Add lookahead wrapper only if there is more than one regular expression
    650   (cond
    651    ((and (eq type 'pcre) (cdr regexps))
    652     (concat "^" (mapconcat (lambda (x) (format "(?=.*%s)" x))
    653                            regexps "")))
    654    ((eq type 'basic)
    655     (string-join regexps ".*"))
    656    (t
    657     (when (> (length regexps) 3)
    658       (message "Too many regexps, %S ignored. Use post-filtering!"
    659                (string-join (seq-drop regexps 3) " "))
    660       (setq regexps (seq-take regexps 3)))
    661     (consult--regexp-join-permutations regexps
    662                                        (and (memq type '(basic emacs)) "\\")))))
    663 
    664 (defun consult--regexp-join-permutations (regexps esc)
    665   "Join all permutations of REGEXPS.
    666 ESC is the escaping string for choice and groups."
    667   (pcase regexps
    668     ('nil "")
    669     (`(,r) r)
    670     (`(,r1 ,r2) (concat r1 ".*" r2 esc "|" r2 ".*" r1))
    671     (_ (mapconcat
    672         (lambda (r)
    673           (concat r ".*" esc "("
    674                   (consult--regexp-join-permutations (remove r regexps) esc)
    675                   esc ")"))
    676         regexps (concat esc "|")))))
    677 
    678 (defun consult--valid-regexp-p (re)
    679   "Return t if regexp RE is valid."
    680   (condition-case nil
    681       (progn (string-match-p re "") t)
    682     (invalid-regexp nil)))
    683 
    684 (defun consult--regexp-filter (regexps)
    685   "Create filter regexp from REGEXPS."
    686   (if (stringp regexps)
    687       regexps
    688     (mapconcat (lambda (x) (concat "\\(?:" x "\\)")) regexps "\\|")))
    689 
    690 (defmacro consult--keep! (list form)
    691   "Evaluate FORM for every element of LIST and keep the non-nil results."
    692   (declare (indent 1))
    693   (let ((head (make-symbol "head"))
    694         (prev (make-symbol "prev"))
    695         (result (make-symbol "result")))
    696     `(let* ((,head (cons nil ,list))
    697             (,prev ,head))
    698        (while (cdr ,prev)
    699          (if-let (,result (let ((it (cadr ,prev))) ,form))
    700              (progn
    701                (pop ,prev)
    702                (setcar ,prev ,result))
    703            (setcdr ,prev (cddr ,prev))))
    704        (setq ,list (cdr ,head))
    705        nil)))
    706 
    707 ;; Upstream bug#46326, Consult issue https://github.com/minad/consult/issues/193
    708 (defmacro consult--minibuffer-with-setup-hook (fun &rest body)
    709   "Variant of `minibuffer-with-setup-hook' using a symbol and `fset'.
    710 
    711 This macro is only needed to prevent memory leaking issues with
    712 the upstream `minibuffer-with-setup-hook' macro.
    713 FUN is the hook function and BODY opens the minibuffer."
    714   (declare (indent 1) (debug t))
    715   (let ((hook (make-symbol "hook"))
    716         (append))
    717     (when (eq (car-safe fun) :append)
    718       (setq append '(t) fun (cadr fun)))
    719     `(let ((,hook (make-symbol "consult--minibuffer-setup")))
    720        (fset ,hook (lambda ()
    721                      (remove-hook 'minibuffer-setup-hook ,hook)
    722                      (funcall ,fun)))
    723        (unwind-protect
    724            (progn
    725              (add-hook 'minibuffer-setup-hook ,hook ,@append)
    726              ,@body)
    727          (remove-hook 'minibuffer-setup-hook ,hook)))))
    728 
    729 (defun consult--completion-filter (pattern cands category _highlight)
    730   "Filter CANDS with PATTERN.
    731 
    732 CATEGORY is the completion category, used to find the completion style via
    733 `completion-category-defaults' and `completion-category-overrides'.
    734 HIGHLIGHT must be non-nil if the resulting strings should be highlighted."
    735   ;; completion-all-completions returns an improper list
    736   ;; where the last link is not necessarily nil.
    737   (nconc (completion-all-completions pattern cands nil (length pattern)
    738                                      `(metadata (category . ,category)))
    739          nil))
    740 
    741 (defun consult--completion-filter-complement (pattern cands category _highlight)
    742   "Filter CANDS with complement of PATTERN.
    743 See `consult--completion-filter' for the arguments CATEGORY and HIGHLIGHT."
    744   (let ((ht (consult--string-hash (consult--completion-filter pattern cands category nil))))
    745     (seq-remove (lambda (x) (gethash x ht)) cands)))
    746 
    747 (defun consult--completion-filter-dispatch (pattern cands category highlight)
    748   "Filter CANDS with PATTERN with optional complement.
    749 Either using `consult--completion-filter' or
    750 `consult--completion-filter-complement', depending on if the pattern starts
    751 with a bang. See `consult--completion-filter' for the arguments CATEGORY and
    752 HIGHLIGHT."
    753   (cond
    754    ((string-match-p "\\`!? ?\\'" pattern) cands) ;; empty pattern
    755    ((string-prefix-p "! " pattern) (consult--completion-filter-complement
    756                                     (substring pattern 2) cands category nil))
    757    (t (consult--completion-filter pattern cands category highlight))))
    758 
    759 (defmacro consult--each-line (beg end &rest body)
    760   "Iterate over each line.
    761 
    762 The line beginning/ending BEG/END is bound in BODY."
    763   (declare (indent 2))
    764   (let ((max (make-symbol "max")))
    765     `(save-excursion
    766        (let ((,beg (point-min)) (,max (point-max)) end)
    767          (while (< ,beg ,max)
    768            (goto-char ,beg)
    769            (let ((inhibit-field-text-motion t))
    770              (setq ,end (line-end-position)))
    771            ,@body
    772            (setq ,beg (1+ ,end)))))))
    773 
    774 (defmacro consult--static-if (cond then &rest else)
    775   "If COND yields non-nil at compile time, do THEN, else do ELSE."
    776   (declare (indent 2))
    777   (if (eval cond 'lexical) then (macroexp-progn else)))
    778 
    779 (defun consult--display-width (string)
    780   "Compute width of STRING taking display and invisible properties into account."
    781   (let ((pos 0) (width 0) (end (length string)))
    782     (while (< pos end)
    783       (let ((nextd (next-single-property-change pos 'display string end))
    784             (display (get-text-property pos 'display string)))
    785         (if (stringp display)
    786             (setq width (+ width (string-width display))
    787                   pos nextd)
    788           (while (< pos nextd)
    789             (let ((nexti (next-single-property-change pos 'invisible string nextd)))
    790               (unless (get-text-property pos 'invisible string)
    791                 (setq width (+ width
    792                                ;; bug#47712: Emacs 28 can compute `string-width' of substrings
    793                                (consult--static-if (eq 3 (cdr (func-arity #'string-width)))
    794                                    (string-width string pos nexti)
    795                                  (string-width
    796                                   ;; Avoid allocation for the full string.
    797                                   (if (and (= pos 0) (= nexti end))
    798                                       string
    799                                     (substring-no-properties string pos nexti)))))))
    800               (setq pos nexti))))))
    801     width))
    802 
    803 (defun consult--string-hash (strings)
    804   "Create hashtable from STRINGS."
    805   (let ((ht (make-hash-table :test #'equal :size (length strings))))
    806     (dolist (str strings)
    807       (puthash str t ht))
    808     ht))
    809 
    810 (defmacro consult--local-let (binds &rest body)
    811   "Buffer local let BINDS of dynamic variables in BODY."
    812   (declare (indent 1))
    813   (let ((buffer (make-symbol "buffer"))
    814         (local (mapcar (lambda (x) (cons (make-symbol "local") (car x))) binds)))
    815     `(let ((,buffer (current-buffer))
    816            ,@(mapcar (lambda (x) `(,(car x) (local-variable-p ',(cdr x)))) local))
    817        (unwind-protect
    818            (progn
    819              ,@(mapcar (lambda (x) `(make-local-variable ',(car x))) binds)
    820              (let (,@binds)
    821                ,@body))
    822          (when (buffer-live-p ,buffer)
    823            (with-current-buffer ,buffer
    824              ,@(mapcar (lambda (x)
    825                          `(unless ,(car x)
    826                             (kill-local-variable ',(cdr x))))
    827                        local)))))))
    828 
    829 (defun consult--abbreviate-directory (dir)
    830   "Return abbreviated directory DIR for use in `completing-read' prompt."
    831   (save-match-data
    832     (let ((adir (abbreviate-file-name dir)))
    833       (if (string-match "/\\([^/]+\\)/\\([^/]+\\)/\\'" adir)
    834           (format "…/%s/%s/" (match-string 1 adir) (match-string 2 adir))
    835         adir))))
    836 
    837 (defun consult--directory-prompt (prompt dir)
    838   "Return prompt and directory.
    839 
    840 PROMPT is the prompt prefix. The directory
    841 is appended to the prompt prefix. For projects
    842 only the project name is shown. The `default-directory'
    843 is not shown. Other directories are abbreviated and
    844 only the last two path components are shown.
    845 
    846 If DIR is a string, it is returned.
    847 If DIR is a true value, the user is asked.
    848 Then the `consult-project-function' is tried.
    849 Otherwise the `default-directory' is returned."
    850   (let* ((dir
    851           (cond
    852            ((stringp dir) dir)
    853            (dir
    854             ;; HACK Preserve this-command across `read-directory-name' call,
    855             ;; such that `consult-customize' continues to work.
    856             ;; TODO Find a better and more general solution which preserves `this-command'.
    857             (let ((this-command this-command))
    858               (read-directory-name "Directory: " nil nil t)))
    859            (t (or (consult--project-root) default-directory))))
    860          (edir (file-name-as-directory (expand-file-name dir)))
    861          ;; Bind default-directory in order to find the project
    862          (pdir (let ((default-directory edir)) (consult--project-root))))
    863     (cons
    864      (cond
    865       ((equal edir pdir)
    866        (format "%s (Project %s): " prompt (consult--project-name pdir)))
    867       ((equal edir (file-name-as-directory (expand-file-name default-directory)))
    868        (concat prompt ": "))
    869       (t (format "%s (%s): " prompt (consult--abbreviate-directory dir))))
    870      edir)))
    871 
    872 (defun consult--default-project-function (may-prompt)
    873   "Return project root directory.
    874 When no project is found and MAY-PROMPT is non-nil ask the user."
    875   (when-let (proj (project-current may-prompt))
    876     (cond
    877      ((fboundp 'project-root) (project-root proj))
    878      ((fboundp 'project-roots) (car (project-roots proj))))))
    879 
    880 (defun consult--project-root (&optional may-prompt)
    881   "Return project root as absolute path.
    882 When no project is found and MAY-PROMPT is non-nil ask the user."
    883   (when-let (root (and consult-project-function
    884                        (funcall consult-project-function may-prompt)))
    885     (expand-file-name root)))
    886 
    887 (defun consult--project-name (dir)
    888   "Return the project name for DIR."
    889   (if (string-match "/\\([^/]+\\)/\\'" dir)
    890       (match-string 1 dir)
    891     dir))
    892 
    893 (defun consult--format-location (file line &optional str)
    894   "Format location string 'FILE:LINE:STR'."
    895   (setq line (number-to-string line)
    896         str (concat file ":" line (and str ":") str)
    897         file (length file))
    898   (put-text-property 0 file 'face 'consult-file str)
    899   (put-text-property (1+ file) (+ 1 file (length line)) 'face 'consult-line-number str)
    900   str)
    901 
    902 (defmacro consult--overlay (beg end &rest props)
    903   "Make consult overlay between BEG and END with PROPS."
    904   (let ((ov (make-symbol "ov"))
    905         (puts))
    906     (while props
    907       (push `(overlay-put ,ov ,(car props) ,(cadr props)) puts)
    908       (setq props (cddr props)))
    909     `(let ((,ov (make-overlay ,beg ,end)))
    910        ,@puts
    911        ,ov)))
    912 
    913 (defun consult--remove-dups (list)
    914   "Remove duplicate strings from LIST."
    915   (delete-dups (copy-sequence list)))
    916 
    917 (defsubst consult--in-range-p (pos)
    918   "Return t if position POS lies in range `point-min' to `point-max'."
    919   (<= (point-min) pos (point-max)))
    920 
    921 (defun consult--type-group (types)
    922   "Return group function for TYPES."
    923   (lambda (cand transform)
    924     (if transform
    925         cand
    926       (alist-get (get-text-property 0 'consult--type cand) types))))
    927 
    928 (defun consult--type-narrow (types)
    929   "Return narrowing configuration from TYPES."
    930   (list :predicate
    931         (lambda (cand) (eq (get-text-property 0 'consult--type cand) consult--narrow))
    932         :keys types))
    933 
    934 (defun consult--completion-window-p ()
    935   "Return non-nil if the selected window belongs to the completion UI."
    936   (or (eq (selected-window) (active-minibuffer-window))
    937       (eq #'completion-list-mode (buffer-local-value 'major-mode (window-buffer)))))
    938 
    939 (defun consult--location-state (candidates)
    940   "Location state function.
    941 The cheap location markers from CANDIDATES are upgraded on window
    942 selection change to full Emacs markers."
    943   (let ((jump (consult--jump-state))
    944         (hook (make-symbol "consult--location-upgrade")))
    945     (fset hook
    946           (lambda (_)
    947             (unless (consult--completion-window-p)
    948               (remove-hook 'window-selection-change-functions hook)
    949               (mapc #'consult--get-location candidates))))
    950     (lambda (action cand)
    951       (pcase action
    952         ('setup (add-hook 'window-selection-change-functions hook))
    953         ('exit (remove-hook 'window-selection-change-functions hook)))
    954       (funcall jump action cand))))
    955 
    956 (defun consult--get-location (cand)
    957   "Return location from CAND."
    958   (let ((loc (get-text-property 0 'consult-location cand)))
    959     (when (consp (car loc))
    960       ;; Transform cheap marker to real marker
    961       (setcar loc (set-marker (make-marker) (cdar loc) (caar loc))))
    962     loc))
    963 
    964 (defun consult--lookup-member (selected candidates &rest _)
    965   "Lookup SELECTED in CANDIDATES list, return original element."
    966   (car (member selected candidates)))
    967 
    968 (defun consult--lookup-cons (selected candidates &rest _)
    969   "Lookup SELECTED in CANDIDATES alist, return cons."
    970   (assoc selected candidates))
    971 
    972 (defun consult--lookup-cdr (selected candidates &rest _)
    973   "Lookup SELECTED in CANDIDATES alist, return cdr of element."
    974   (cdr (assoc selected candidates)))
    975 
    976 (defun consult--lookup-location (selected candidates &rest _)
    977   "Lookup SELECTED in CANDIDATES list of `consult-location' category.
    978 Return the location marker."
    979   (when-let (found (member selected candidates))
    980     (car (consult--get-location (car found)))))
    981 
    982 (defun consult--lookup-candidate (selected candidates &rest _)
    983   "Lookup SELECTED in CANDIDATES list and return property `consult--candidate'."
    984   (when-let (found (member selected candidates))
    985     (get-text-property 0 'consult--candidate (car found))))
    986 
    987 (defun consult--forbid-minibuffer ()
    988   "Raise an error if executed from the minibuffer."
    989   (when (minibufferp)
    990     (user-error "`%s' called inside the minibuffer" this-command)))
    991 
    992 (defun consult--require-minibuffer ()
    993   "Raise an error if executed outside the minibuffer."
    994   (unless (minibufferp)
    995     (user-error "`%s' must be called inside the minibuffer" this-command)))
    996 
    997 (defun consult--fontify-all ()
    998   "Ensure that the whole buffer is fontified."
    999   ;; Font-locking is lazy, i.e., if a line has not been looked at yet, the line
   1000   ;; is not font-locked. We would observe this if consulting an unfontified
   1001   ;; line. Therefore we have to enforce font-locking now, which is slow. In
   1002   ;; order to prevent is hang-up we check the buffer size against
   1003   ;; `consult-fontify-max-size'.
   1004   (when (and consult-fontify-preserve jit-lock-mode
   1005              (< (buffer-size) consult-fontify-max-size))
   1006     (jit-lock-fontify-now)))
   1007 
   1008 (defun consult--fontify-region (start end)
   1009   "Ensure that region between START and END is fontified."
   1010   (when (and consult-fontify-preserve jit-lock-mode)
   1011     (jit-lock-fontify-now start end)))
   1012 
   1013 (defmacro consult--with-increased-gc (&rest body)
   1014   "Temporarily increase the gc limit in BODY to optimize for throughput."
   1015   (let ((overwrite (make-symbol "overwrite")))
   1016     `(let* ((,overwrite (> consult--gc-threshold gc-cons-threshold))
   1017             (gc-cons-threshold (if ,overwrite consult--gc-threshold gc-cons-threshold))
   1018             (gc-cons-percentage (if ,overwrite consult--gc-percentage gc-cons-percentage)))
   1019        ,@body)))
   1020 
   1021 (defun consult--count-lines (pos)
   1022   "Move to position POS and return number of lines."
   1023   (let ((line 0))
   1024     (while (< (point) pos)
   1025       (forward-line)
   1026       (when (<= (point) pos)
   1027         (setq line (1+ line))))
   1028     (goto-char pos)
   1029     line))
   1030 
   1031 (defun consult--position-marker (buffer line column)
   1032   "Get marker in BUFFER from LINE and COLUMN."
   1033   (when (buffer-live-p buffer)
   1034     (with-current-buffer buffer
   1035       (save-restriction
   1036         (save-excursion
   1037           (widen)
   1038           (goto-char (point-min))
   1039           ;; Location data might be invalid by now!
   1040           (ignore-errors
   1041             (forward-line (1- line))
   1042             (forward-char column))
   1043           (point-marker))))))
   1044 
   1045 (defun consult--line-group (cand transform)
   1046   "Group function used by `consult-line-multi'.
   1047 If TRANSFORM non-nil, return transformed CAND, otherwise return title."
   1048   (if transform
   1049       cand
   1050     (let ((marker (car (get-text-property 0 'consult-location cand))))
   1051       (buffer-name
   1052        ;; Handle cheap marker
   1053        (if (consp marker)
   1054            (car marker)
   1055          (marker-buffer marker))))))
   1056 
   1057 (defun consult--line-prefix (&optional curr-line)
   1058   "Annotate `consult-location' candidates with line numbers.
   1059 CURR-LINE is the current line number."
   1060   (setq curr-line (or curr-line -1))
   1061   (let* ((width (length (number-to-string (line-number-at-pos
   1062                                            (point-max)
   1063                                            consult-line-numbers-widen))))
   1064          (fmt-before (propertize (format "%%%dd " width) 'face 'consult-line-number-wrapped))
   1065          (fmt-after (propertize (format "%%%dd " width) 'face 'consult-line-number-prefix)))
   1066     (lambda (cand)
   1067       (let ((line (cdr (get-text-property 0 'consult-location cand))))
   1068         (list cand (format (if (< line curr-line) fmt-before fmt-after) line) "")))))
   1069 
   1070 (defun consult--location-candidate (cand marker line &rest props)
   1071   "Add MARKER and LINE as 'consult-location text property to CAND.
   1072 Furthermore add the additional text properties PROPS, and append
   1073 tofu-encoded MARKER suffix for disambiguation."
   1074   ;; Handle cheap marker
   1075   (setq cand (concat cand (consult--tofu-encode (if (consp marker) (cdr marker) marker))))
   1076   (add-text-properties 0 1 `(consult-location (,marker . ,line) ,@props) cand)
   1077   cand)
   1078 
   1079 ;; There is a similar variable `yank-excluded-properties'. Unfortunately
   1080 ;; we cannot use it here since it excludes too much (e.g., invisible)
   1081 ;; and at the same time not enough (e.g., cursor-sensor-functions).
   1082 (defconst consult--remove-text-properties
   1083   '(category cursor cursor-intangible cursor-sensor-functions field follow-link
   1084     fontified front-sticky help-echo insert-behind-hooks insert-in-front-hooks intangible keymap
   1085     local-map modification-hooks mouse-face pointer read-only rear-nonsticky yank-handler)
   1086   "List of text properties to remove from buffer strings.")
   1087 
   1088 (defsubst consult--buffer-substring (beg end &optional fontify)
   1089   "Return buffer substring between BEG and END.
   1090 If FONTIFY and `consult-fontify-preserve' are non-nil, first ensure that the
   1091 region has been fontified."
   1092   (if consult-fontify-preserve
   1093       (let (str)
   1094         (when fontify (consult--fontify-region beg end))
   1095         (setq str (buffer-substring beg end))
   1096         ;; TODO Propose the addition of a function `preserve-list-of-text-properties'
   1097         (remove-list-of-text-properties 0 (- end beg) consult--remove-text-properties str)
   1098         str)
   1099     (buffer-substring-no-properties beg end)))
   1100 
   1101 (defun consult--region-with-cursor (beg end marker)
   1102   "Return region string with a marking at the cursor position.
   1103 
   1104 BEG is the begin position.
   1105 END is the end position.
   1106 MARKER is the cursor position."
   1107   (let ((str (consult--buffer-substring beg end 'fontify)))
   1108     (if (>= marker end)
   1109         (concat str #(" " 0 1 (face consult-preview-cursor)))
   1110       (put-text-property (- marker beg) (- (1+ marker) beg)
   1111                          'face 'consult-preview-cursor str)
   1112       str)))
   1113 
   1114 (defun consult--line-with-cursor (marker)
   1115   "Return current line where the cursor MARKER is highlighted."
   1116   (let ((inhibit-field-text-motion t))
   1117     (consult--region-with-cursor (line-beginning-position) (line-end-position) marker)))
   1118 
   1119 ;;;; Preview support
   1120 
   1121 (defun consult--filter-find-file-hook (orig &rest hooks)
   1122   "Filter `find-file-hook' by `consult-preview-allowed-hooks'.
   1123 This function is an advice for `run-hooks'.
   1124 ORIG is the original function, HOOKS the arguments."
   1125   (if (memq 'find-file-hook hooks)
   1126       (cl-letf* (((default-value 'find-file-hook)
   1127                   (seq-filter (lambda (x)
   1128                                 (memq x consult-preview-allowed-hooks))
   1129                               (default-value 'find-file-hook)))
   1130                  (find-file-hook (default-value 'find-file-hook)))
   1131         (apply orig hooks))
   1132     (apply orig hooks)))
   1133 
   1134 (defun consult--find-file-temporarily (name)
   1135   "Open file NAME temporarily for preview."
   1136   ;; file-attributes may throw permission denied error
   1137   (when-let* ((attrs (ignore-errors (file-attributes name)))
   1138               (size (file-attribute-size attrs)))
   1139     (if (<= size consult-preview-max-size)
   1140         (let* ((vars (delq nil
   1141                            (mapcar
   1142                             (pcase-lambda (`(,k . ,v))
   1143                               (if (boundp k)
   1144                                   (list k v (default-value k) (symbol-value k))
   1145                                 (message "consult-preview-variables: The variable `%s' is not bound" k)
   1146                                 nil))
   1147                             consult-preview-variables)))
   1148                (buf (unwind-protect
   1149                         (progn
   1150                           (advice-add #'run-hooks :around #'consult--filter-find-file-hook)
   1151                           (pcase-dolist (`(,k ,v . ,_) vars)
   1152                             (set-default k v)
   1153                             (set k v))
   1154                           (find-file-noselect name 'nowarn (> size consult-preview-raw-size)))
   1155                       (advice-remove #'run-hooks #'consult--filter-find-file-hook)
   1156                       (pcase-dolist (`(,k ,_ ,d ,v) vars)
   1157                         (set-default k d)
   1158                         (set k v)))))
   1159           (if (not (ignore-errors (buffer-local-value 'so-long-detected-p buf)))
   1160               buf
   1161             (kill-buffer buf)
   1162             (message "File `%s' has long lines, not previewed" name)
   1163             nil))
   1164       (message "File `%s' (%s) is too large for preview"
   1165                name (file-size-human-readable size))
   1166       nil)))
   1167 
   1168 (defun consult--temporary-files ()
   1169   "Return a function to open files temporarily for preview."
   1170   (let ((dir default-directory)
   1171         (hook (make-symbol "consult--temporary-files"))
   1172         (orig-buffers (buffer-list))
   1173         temporary-buffers)
   1174     (fset hook
   1175           (lambda (_)
   1176             ;; Fully initialize previewed files and keep them alive.
   1177             (unless (consult--completion-window-p)
   1178               (let (live-files)
   1179                 (pcase-dolist (`(,file . ,buf) temporary-buffers)
   1180                   (when-let (wins (and (buffer-live-p buf)
   1181                                        (get-buffer-window-list buf)))
   1182                     (push (cons file (mapcar
   1183                                       (lambda (win)
   1184                                         (cons win (window-state-get win t)))
   1185                                       wins))
   1186                           live-files)))
   1187                 (pcase-dolist (`(,_ . ,buf) temporary-buffers)
   1188                   (kill-buffer buf))
   1189                 (setq temporary-buffers nil)
   1190                 (pcase-dolist (`(,file . ,wins) live-files)
   1191                   (when-let (buf (find-file-noselect file))
   1192                     (push buf orig-buffers)
   1193                     (pcase-dolist (`(,win . ,state) wins)
   1194                       (setf (car (alist-get 'buffer state)) buf)
   1195                       (window-state-put state win))))))))
   1196     (lambda (&optional name)
   1197       (if name
   1198           (let ((default-directory dir))
   1199             (setq name (abbreviate-file-name (expand-file-name name)))
   1200             (or
   1201              ;; get-file-buffer is only a small optimization here. It
   1202              ;; may not find the actual buffer, for directories it
   1203              ;; returns nil instead of returning the Dired buffer.
   1204              (get-file-buffer name)
   1205              (cdr (assoc name temporary-buffers))
   1206              (when-let (buf (consult--find-file-temporarily name))
   1207                ;; Only add new buffer if not already in the list
   1208                (unless (or (rassq buf temporary-buffers) (memq buf orig-buffers))
   1209                  (add-hook 'window-selection-change-functions hook)
   1210                  (push (cons name buf) temporary-buffers)
   1211                  ;; Disassociate buffer from file by setting
   1212                  ;; `buffer-file-name' to nil and rename the buffer.
   1213                  ;; This lets us open an already previewed buffer with
   1214                  ;; the Embark default action C-. RET.
   1215                  (with-current-buffer buf
   1216                    (rename-buffer
   1217                     (format "Preview:%s"
   1218                             (file-name-nondirectory (directory-file-name name)))
   1219                     'unique))
   1220                  ;; The buffer disassociation is delayed to avoid breaking
   1221                  ;; modes like pdf-view-mode or doc-view-mode which rely on
   1222                  ;; buffer-file-name. Executing (set-visited-file-name nil)
   1223                  ;; early also prevents the major mode initialization.
   1224                  (let ((hook (make-symbol "consult--temporary-files-disassociate")))
   1225                    (fset hook (lambda ()
   1226                                 (when (buffer-live-p buf)
   1227                                   (with-current-buffer buf
   1228                                     (remove-hook 'pre-command-hook hook)
   1229                                     (setq buffer-read-only t
   1230                                           buffer-file-name nil)))))
   1231                    (add-hook 'pre-command-hook hook))
   1232                  ;; Only keep a few buffers alive
   1233                  (while (> (length temporary-buffers) consult-preview-max-count)
   1234                    (kill-buffer (cdar (last temporary-buffers)))
   1235                    (setq temporary-buffers (nbutlast temporary-buffers))))
   1236                buf)))
   1237         (remove-hook 'window-selection-change-functions hook)
   1238         (pcase-dolist (`(,_ . ,buf) temporary-buffers)
   1239           (kill-buffer buf))
   1240         (setq temporary-buffers nil)))))
   1241 
   1242 (defun consult--invisible-open-permanently ()
   1243   "Open overlays which hide the current line.
   1244 See `isearch-open-necessary-overlays' and `isearch-open-overlay-temporary'."
   1245   (dolist (ov (let ((inhibit-field-text-motion t))
   1246                 (overlays-in (line-beginning-position) (line-end-position))))
   1247     (when-let (fun (overlay-get ov 'isearch-open-invisible))
   1248       (when (invisible-p (overlay-get ov 'invisible))
   1249         (funcall fun ov)))))
   1250 
   1251 (defun consult--invisible-open-temporarily ()
   1252   "Temporarily open overlays which hide the current line.
   1253 See `isearch-open-necessary-overlays' and `isearch-open-overlay-temporary'."
   1254   (let (restore)
   1255     (dolist (ov (let ((inhibit-field-text-motion t))
   1256                   (overlays-in (line-beginning-position) (line-end-position))))
   1257       (let ((inv (overlay-get ov 'invisible)))
   1258         (when (and (invisible-p inv) (overlay-get ov 'isearch-open-invisible))
   1259           (push (if-let (fun (overlay-get ov 'isearch-open-invisible-temporary))
   1260                     (progn
   1261                       (funcall fun ov nil)
   1262                       (lambda () (funcall fun ov t)))
   1263                   (overlay-put ov 'invisible nil)
   1264                   (lambda () (overlay-put ov 'invisible inv)))
   1265                 restore))))
   1266     restore))
   1267 
   1268 (defun consult--jump-1 (pos)
   1269   "Go to POS and recenter."
   1270   (cond
   1271    ((and (markerp pos) (not (marker-buffer pos)))
   1272     ;; Only print a message, no error in order to not mess
   1273     ;; with the minibuffer update hook.
   1274     (message "Buffer is dead"))
   1275    (t
   1276     ;; Switch to buffer if it is not visible
   1277     (when (and (markerp pos) (not (eq (current-buffer) (marker-buffer pos))))
   1278       (consult--buffer-action (marker-buffer pos) 'norecord))
   1279     ;; Widen if we cannot jump to the position (idea from flycheck-jump-to-error)
   1280     (unless (= (goto-char pos) (point))
   1281       (widen)
   1282       (goto-char pos)))))
   1283 
   1284 (defun consult--jump (pos)
   1285   "Push current position to mark ring, go to POS and recenter."
   1286   (when pos
   1287     ;; When the marker is in the same buffer,
   1288     ;; record previous location such that the user can jump back quickly.
   1289     (unless (and (markerp pos) (not (eq (current-buffer) (marker-buffer pos))))
   1290       (push-mark (point) t))
   1291     (consult--jump-1 pos)
   1292     (consult--invisible-open-permanently)
   1293     (run-hooks 'consult-after-jump-hook))
   1294   nil)
   1295 
   1296 ;; Matched strings are not highlighted as of now.
   1297 ;; see https://github.com/minad/consult/issues/7
   1298 (defun consult--jump-preview (&optional face)
   1299   "The preview function used if selecting from a list of candidate positions.
   1300 The function can be used as the `:state' argument of `consult--read'.
   1301 FACE is the cursor face."
   1302   (let ((face (or face 'consult-preview-cursor))
   1303         (saved-min (point-min-marker))
   1304         (saved-max (point-max-marker))
   1305         (saved-pos (point-marker))
   1306         overlays invisible)
   1307     (set-marker-insertion-type saved-max t) ;; Grow when text is inserted
   1308     (lambda (action cand)
   1309       (when (eq action 'preview)
   1310         (mapc #'funcall invisible)
   1311         (mapc #'delete-overlay overlays)
   1312         (setq invisible nil overlays nil)
   1313         (if (not cand)
   1314             ;; If position cannot be previewed, return to saved position
   1315             (let ((saved-buffer (marker-buffer saved-pos)))
   1316               (if (not saved-buffer)
   1317                   (message "Buffer is dead")
   1318                 (set-buffer saved-buffer)
   1319                 (narrow-to-region saved-min saved-max)
   1320                 (goto-char saved-pos)))
   1321           ;; Jump to position
   1322           (consult--jump-1 cand)
   1323           (setq invisible (consult--invisible-open-temporarily)
   1324                 overlays
   1325                 (list (save-excursion
   1326                         (let ((vbeg (progn (beginning-of-visual-line) (point)))
   1327                               (vend (progn (end-of-visual-line) (point)))
   1328                               (end (line-end-position)))
   1329                           (consult--overlay vbeg (if (= vend end) (1+ end) vend)
   1330                                             'face 'consult-preview-line
   1331                                             'window (selected-window))))
   1332                       (consult--overlay (point) (1+ (point))
   1333                                         'face face
   1334                                         'window (selected-window))))
   1335           (run-hooks 'consult-after-jump-hook))))))
   1336 
   1337 (defun consult--jump-state (&optional face)
   1338   "The state function used if selecting from a list of candidate positions.
   1339 The function can be used as the `:state' argument of `consult--read'.
   1340 FACE is the cursor face."
   1341   (let ((preview (consult--jump-preview face)))
   1342     (lambda (action cand)
   1343       (funcall preview action cand)
   1344       (when (and cand (eq action 'return))
   1345         (consult--jump cand)))))
   1346 
   1347 (defmacro consult--define-state (type)
   1348   "Define state function for TYPE."
   1349   `(defun ,(intern (format "consult--%s-state" type)) ()
   1350      (let ((preview (,(intern (format "consult--%s-preview" type)))))
   1351        (lambda (action cand)
   1352          (funcall preview action cand)
   1353          (when (and cand (eq action 'return))
   1354            (,(intern (format "consult--%s-action" type)) cand))))))
   1355 
   1356 (defun consult--preview-key-normalize (preview-key)
   1357   "Normalize PREVIEW-KEY, return alist of keys and debounce times."
   1358   (let ((keys)
   1359         (debounce 0))
   1360     (setq preview-key (consult--ensure-list preview-key))
   1361     (while preview-key
   1362       (if (eq (car preview-key) :debounce)
   1363           (setq debounce (cadr preview-key)
   1364                 preview-key (cddr preview-key))
   1365         (push (cons (car preview-key) debounce) keys)
   1366         (pop preview-key)))
   1367     keys))
   1368 
   1369 (defun consult--preview-key-debounce (preview-key cand)
   1370   "Return debounce value of PREVIEW-KEY given the current candidate CAND."
   1371   (when (and (consp preview-key) (memq :keys preview-key))
   1372     (setq preview-key (funcall (plist-get preview-key :predicate) cand)))
   1373   (let ((map (make-sparse-keymap))
   1374         (keys (this-single-command-keys))
   1375         any)
   1376     (dolist (x (consult--preview-key-normalize preview-key))
   1377       (if (eq (car x) 'any)
   1378           (setq any (cdr x))
   1379         (define-key map (car x) (cdr x))))
   1380     (setq keys (lookup-key map keys))
   1381     (if (numberp keys) keys any)))
   1382 
   1383 ;; TODO Remove this function after upgrades of :state functions
   1384 (defun consult--protected-state-call (fun action cand)
   1385   "Call state FUN with ACTION and CAND and protect against errors."
   1386   (condition-case err
   1387       (funcall fun action cand)
   1388     (t (message "consult--read: No preview, the :state function protocol changed: %S" err))))
   1389 
   1390 (defun consult--append-local-post-command-hook (fun)
   1391   "Append FUN to local `post-command-hook' list."
   1392   ;; Symbol indirection because of bug#46407.
   1393   (let ((hook (make-symbol "consult--preview-post-command")))
   1394     (fset hook fun)
   1395     ;; TODO Emacs 28 has a bug, where the hook--depth-alist is not cleaned up properly
   1396     ;; Do not use the broken add-hook here.
   1397     ;;(add-hook 'post-command-hook sym 'append 'local)
   1398     (setq-local post-command-hook
   1399                 (append
   1400                  (remove t post-command-hook)
   1401                  (list hook)
   1402                  (and (memq t post-command-hook) '(t))))))
   1403 
   1404 (defun consult--with-preview-1 (preview-key state transform candidate fun)
   1405   "Add preview support for FUN.
   1406 See `consult--with-preview' for the arguments
   1407 PREVIEW-KEY, STATE, TRANSFORM and CANDIDATE."
   1408   (let ((input "") narrow selected timer last-preview)
   1409     (consult--minibuffer-with-setup-hook
   1410         (if (and state preview-key)
   1411             (lambda ()
   1412               (let ((exit-hook (make-symbol "consult--preview-minibuffer-exit")))
   1413                 (fset exit-hook
   1414                       (lambda ()
   1415                         (when timer
   1416                           (cancel-timer timer)
   1417                           (setq timer nil))
   1418                         (with-selected-window (or (minibuffer-selected-window) (next-window))
   1419                           ;; STEP 3: Reset preview
   1420                           (when last-preview
   1421                             (consult--protected-state-call state 'preview nil))
   1422                           ;; STEP 4: Notify the preview function of the minibuffer exit
   1423                           (consult--protected-state-call state 'exit nil))))
   1424                 (add-hook 'minibuffer-exit-hook exit-hook nil 'local))
   1425               ;; STEP 1: Setup the preview function
   1426               (with-selected-window (or (minibuffer-selected-window) (next-window))
   1427                 (consult--protected-state-call state 'setup nil))
   1428               (setq consult--preview-function
   1429                     (lambda ()
   1430                       (when-let ((cand (funcall candidate)))
   1431                         (with-selected-window (active-minibuffer-window)
   1432                           (let ((input (minibuffer-contents-no-properties)))
   1433                             (with-selected-window (or (minibuffer-selected-window) (next-window))
   1434                               (let ((transformed (funcall transform narrow input cand))
   1435                                     (new-preview (cons input cand)))
   1436                                 (when-let (debounce (consult--preview-key-debounce preview-key transformed))
   1437                                   (when timer
   1438                                     (cancel-timer timer)
   1439                                     (setq timer nil))
   1440                                   (unless (equal last-preview new-preview)
   1441                                     (if (> debounce 0)
   1442                                         (let ((win (selected-window)))
   1443                                           (setq timer
   1444                                                 (run-at-time
   1445                                                  debounce nil
   1446                                                  (lambda ()
   1447                                                    (when (window-live-p win)
   1448                                                      (with-selected-window win
   1449                                                        ;; STEP 2: Preview candidate
   1450                                                        (consult--protected-state-call
   1451                                                         state 'preview transformed)
   1452                                                        (setq last-preview new-preview)))))))
   1453                                       ;; STEP 2: Preview candidate
   1454                                       (consult--protected-state-call state 'preview transformed)
   1455                                       (setq last-preview new-preview)))))))))))
   1456               (consult--append-local-post-command-hook
   1457                (lambda ()
   1458                  (setq input (minibuffer-contents-no-properties)
   1459                        narrow consult--narrow)
   1460                  (funcall consult--preview-function))))
   1461           (lambda ()
   1462             (consult--append-local-post-command-hook
   1463              (lambda () (setq input (minibuffer-contents-no-properties)
   1464                               narrow consult--narrow)))))
   1465       (unwind-protect
   1466           (cons (setq selected (when-let (result (funcall fun))
   1467                                  (funcall transform narrow input result)))
   1468                 input)
   1469         (when state
   1470           ;; STEP 5: The preview function should perform its final action
   1471           (consult--protected-state-call state 'return selected))))))
   1472 
   1473 (defmacro consult--with-preview (preview-key state transform candidate &rest body)
   1474   "Add preview support to BODY.
   1475 
   1476 STATE is the state function.
   1477 TRANSFORM is the transformation function.
   1478 CANDIDATE is the function returning the current candidate.
   1479 PREVIEW-KEY are the keys which triggers the preview.
   1480 
   1481 The state function takes two arguments, an action argument and the
   1482 selected candidate. The candidate argument can be nil if no candidate is
   1483 selected or if the selection was aborted. The function is called in
   1484 sequence with the following arguments:
   1485 
   1486   1. 'setup nil         After entering the minibuffer (minibuffer-setup-hook).
   1487 ⎧ 2. 'preview CAND/nil  Preview candidate CAND or reset if CAND is nil.
   1488 ⎪    'preview CAND/nil
   1489 ⎪    'preview CAND/nil
   1490 ⎪    ...
   1491 ⎩ 3. 'preview nil       Reset preview.
   1492   4. 'exit nil          Before exiting the minibuffer (minibuffer-exit-hook).
   1493   5. 'return CAND/nil   After leaving the minibuffer, CAND has been selected.
   1494 
   1495 The state function is always executed with the original window selected,
   1496 see `minibuffer-selected-window'. The state function is called once in
   1497 the beginning of the minibuffer setup with the `setup' argument. This is
   1498 useful in order to perform certain setup operations which require that
   1499 the minibuffer is initialized. During completion candidates are
   1500 previewed. Then the function is called with the `preview' argument and a
   1501 candidate CAND or nil if no candidate is selected. Furthermore if nil is
   1502 passed for CAND, then the preview must be undone and the original state
   1503 must be restored. The call with the `exit' argument happens once at the
   1504 end of the completion process, just before exiting the minibuffer. The
   1505 minibuffer is still alive at that point. Both `setup' and `exit' are
   1506 only useful for setup and cleanup operations. They don't receive a
   1507 candidate as argument. After leaving the minibuffer, the selected
   1508 candidate or nil is passed to the state function with the action
   1509 argument `return'. At this point the state function can perform the
   1510 actual action on the candidate. The state function with the `return'
   1511 argument is the continuation of `consult--read'. Via `unwind-protect' it
   1512 is guaranteed, that if the `setup' action of a state function is
   1513 invoked, the state function will also be called with `exit' and
   1514 `return'."
   1515   (declare (indent 4))
   1516   `(consult--with-preview-1 ,preview-key ,state ,transform ,candidate (lambda () ,@body)))
   1517 
   1518 ;;;; Narrowing support
   1519 
   1520 (defun consult--widen-key ()
   1521   "Return widening key, if `consult-widen-key' is not set.
   1522 The default is twice the `consult-narrow-key'."
   1523   (or consult-widen-key (and consult-narrow-key (vconcat consult-narrow-key consult-narrow-key))))
   1524 
   1525 (defun consult-narrow (key)
   1526   "Narrow current completion with KEY.
   1527 
   1528 This command is used internally by the narrowing system of `consult--read'."
   1529   (interactive
   1530    (list (unless (equal (this-single-command-keys) (consult--widen-key))
   1531            last-command-event)))
   1532   (consult--require-minibuffer)
   1533   (setq consult--narrow key)
   1534   (when consult--narrow-predicate
   1535     (setq minibuffer-completion-predicate (and consult--narrow consult--narrow-predicate)))
   1536   (when consult--narrow-overlay
   1537     (delete-overlay consult--narrow-overlay))
   1538   (when consult--narrow
   1539     (setq consult--narrow-overlay
   1540           (consult--overlay
   1541            (1- (minibuffer-prompt-end)) (minibuffer-prompt-end)
   1542            'before-string
   1543            (propertize (format " [%s]" (alist-get consult--narrow
   1544                                                   consult--narrow-keys))
   1545                        'face 'consult-narrow-indicator))))
   1546   (run-hooks 'consult--completion-refresh-hook))
   1547 
   1548 (defconst consult--narrow-delete
   1549   `(menu-item
   1550     "" nil :filter
   1551     ,(lambda (&optional _)
   1552        (when (string= (minibuffer-contents-no-properties) "")
   1553          (lambda ()
   1554            (interactive)
   1555            (consult-narrow nil))))))
   1556 
   1557 (defconst consult--narrow-space
   1558   `(menu-item
   1559     "" nil :filter
   1560     ,(lambda (&optional _)
   1561        (let ((str (minibuffer-contents-no-properties)))
   1562          (when-let (pair (or (and (= 1 (length str))
   1563                                   (assoc (aref str 0) consult--narrow-keys))
   1564                              (and (string= str "")
   1565                                   (assoc 32 consult--narrow-keys))))
   1566            (lambda ()
   1567              (interactive)
   1568              (delete-minibuffer-contents)
   1569              (consult-narrow (car pair))))))))
   1570 
   1571 (defun consult-narrow-help ()
   1572   "Print narrowing help as a `minibuffer-message'.
   1573 
   1574 This command can be bound to a key in `consult-narrow-map',
   1575 to make it available for commands with narrowing."
   1576   (interactive)
   1577   (consult--require-minibuffer)
   1578   (let ((minibuffer-message-timeout 1000000))
   1579     (minibuffer-message
   1580      (mapconcat
   1581       (lambda (x) (concat
   1582                    (propertize (char-to-string (car x)) 'face 'consult-key) " "
   1583                    (propertize (cdr x) 'face 'consult-help)))
   1584       (seq-filter (lambda (x) (/= (car x) 32))
   1585                   consult--narrow-keys)
   1586       " "))))
   1587 
   1588 (defun consult--narrow-setup (settings map)
   1589   "Setup narrowing with SETTINGS and keymap MAP."
   1590   (if (memq :keys settings)
   1591       (setq consult--narrow-predicate (plist-get settings :predicate)
   1592             consult--narrow-keys (plist-get settings :keys))
   1593     (setq consult--narrow-predicate nil
   1594           consult--narrow-keys settings))
   1595   (when consult-narrow-key
   1596     (dolist (pair consult--narrow-keys)
   1597       (define-key map
   1598         (vconcat consult-narrow-key (vector (car pair)))
   1599         (cons (cdr pair) #'consult-narrow))))
   1600   (when-let (widen (consult--widen-key))
   1601     (define-key map widen (cons "All" #'consult-narrow))))
   1602 
   1603 ;; Emacs 28: hide in M-X
   1604 (put #'consult-narrow-help 'completion-predicate #'ignore)
   1605 (put #'consult-narrow 'completion-predicate #'ignore)
   1606 
   1607 ;;;; Splitting completion style
   1608 
   1609 (defun consult--split-perl (str point)
   1610   "Split input STR in async input and filtering part.
   1611 
   1612 The function returns a list with four elements: The async string, the
   1613 completion filter string, the new point position computed from POINT and a
   1614 force flag. If the first character is a punctuation character it determines the
   1615 separator. Examples: \"/async/filter\", \"#async#filter\"."
   1616   (if (string-match-p "^[[:punct:]]" str)
   1617       (save-match-data
   1618         (let ((q (regexp-quote (substring str 0 1))))
   1619           (string-match (concat "^" q "\\([^" q "]*\\)\\(" q "\\)?") str)
   1620           `(,(match-string 1 str)
   1621             ,(substring str (match-end 0))
   1622             ,(max 0 (- point (match-end 0)))
   1623             ;; Force update it two punctuation characters are entered.
   1624             ,(match-end 2)
   1625             ;; List of highlights
   1626             (0 . ,(match-beginning 1))
   1627             ,@(and (match-end 2) `((,(match-beginning 2) . ,(match-end 2)))))))
   1628     `(,str "" 0)))
   1629 
   1630 (defun consult--split-nil (str _point)
   1631   "Treat the complete input STR as async input."
   1632   `(,str "" 0))
   1633 
   1634 (defun consult--split-separator (sep str point)
   1635   "Split input STR in async input and filtering part at the first separator SEP.
   1636 POINT is the point position."
   1637   (setq sep (regexp-quote (char-to-string sep)))
   1638   (save-match-data
   1639     (if (string-match (format "^\\([^%s]+\\)\\(%s\\)?" sep sep) str)
   1640         `(,(match-string 1 str)
   1641           ,(substring str (match-end 0))
   1642           ,(max 0 (- point (match-end 0)))
   1643           ;; Force update it space is entered.
   1644           ,(match-end 2)
   1645           ;; List of highlights
   1646           (0 . ,(match-end 1)))
   1647       `(,str "" 0))))
   1648 
   1649 (defun consult--split-setup (split)
   1650   "Setup splitting completion style with splitter function SPLIT."
   1651   (let* ((styles completion-styles)
   1652          (catdef completion-category-defaults)
   1653          (catovr completion-category-overrides)
   1654          (try (lambda (str table pred point)
   1655                 (let ((completion-styles styles)
   1656                       (completion-category-defaults catdef)
   1657                       (completion-category-overrides catovr)
   1658                       (parts (funcall split str point)))
   1659                   (completion-try-completion (cadr parts) table pred (caddr parts)))))
   1660          (all (lambda (str table pred point)
   1661                 (let ((completion-styles styles)
   1662                       (completion-category-defaults catdef)
   1663                       (completion-category-overrides catovr)
   1664                       (parts (funcall split str point)))
   1665                   (completion-all-completions (cadr parts) table pred (caddr parts))))))
   1666     (setq-local completion-styles-alist (cons `(consult--split ,try ,all "")
   1667                                               completion-styles-alist)
   1668                 completion-styles '(consult--split)
   1669                 completion-category-defaults nil
   1670                 completion-category-overrides nil)))
   1671 
   1672 ;;;; Async support
   1673 
   1674 (defmacro consult--with-async (bind &rest body)
   1675   "Setup asynchronous completion in BODY.
   1676 
   1677 BIND is the asynchronous function binding."
   1678   (declare (indent 1))
   1679   (let ((async (car bind)))
   1680     `(let ((,async ,@(cdr bind))
   1681            (new-chunk (max read-process-output-max consult--process-chunk))
   1682            orig-chunk)
   1683        (consult--minibuffer-with-setup-hook
   1684            ;; Append such that we overwrite the completion style setting of
   1685            ;; `fido-mode'. See `consult--async-split' and
   1686            ;; `consult--split-setup'.
   1687            (:append
   1688             (lambda ()
   1689               (when (functionp ,async)
   1690                 (setq orig-chunk read-process-output-max
   1691                       read-process-output-max new-chunk)
   1692                 (funcall ,async 'setup)
   1693                 ;; Push input string to request refresh.
   1694                 ;; We use a symbol in order to avoid adding lambdas to the hook variable.
   1695                 ;; Symbol indirection because of bug#46407.
   1696                 (let ((sym (make-symbol "consult--async-after-change")))
   1697                   (fset sym (lambda (&rest _) (funcall ,async (minibuffer-contents-no-properties))))
   1698                   (run-at-time 0 nil sym)
   1699                   (add-hook 'after-change-functions sym nil 'local)))))
   1700          (let ((,async (if (functionp ,async) ,async (lambda (_) ,async))))
   1701            (unwind-protect
   1702                ,(macroexp-progn body)
   1703              (funcall ,async 'destroy)
   1704              (when (and orig-chunk (eq read-process-output-max new-chunk))
   1705                (setq read-process-output-max orig-chunk))))))))
   1706 
   1707 (defun consult--async-sink ()
   1708   "Create ASYNC sink function.
   1709 
   1710 An async function must accept a single action argument. For the 'setup action
   1711 it is guaranteed that the call originates from the minibuffer. For the other
   1712 actions no assumption about the context can be made.
   1713 
   1714 'setup   Setup the internal closure state. Return nil.
   1715 'destroy Destroy the internal closure state. Return nil.
   1716 'flush   Flush the list of candidates. Return nil.
   1717 'refresh Request UI refresh. Return nil.
   1718 nil      Return the list of candidates.
   1719 list     Append the list to the already existing candidates list and return it.
   1720 string   Update with the current user input string. Return nil."
   1721   (let (candidates last buffer previewed)
   1722     (lambda (action)
   1723       (pcase-exhaustive action
   1724         ('setup
   1725          (setq buffer (current-buffer))
   1726          nil)
   1727         ((or (pred stringp) 'destroy) nil)
   1728         ('flush (setq candidates nil last nil previewed nil))
   1729         ('refresh
   1730          ;; Refresh the UI when the current minibuffer window belongs
   1731          ;; to the current asynchronous completion session.
   1732          (when-let (win (active-minibuffer-window))
   1733            (when (eq (window-buffer win) buffer)
   1734              (with-selected-window win
   1735                (run-hooks 'consult--completion-refresh-hook)
   1736                ;; Interaction between asynchronous completion tables and
   1737                ;; preview: We have to trigger preview immediately when
   1738                ;; candidates arrive (Issue #436).
   1739                (when (and consult--preview-function candidates (not previewed))
   1740                  (setq previewed t)
   1741                  (funcall consult--preview-function)))))
   1742          nil)
   1743         ('nil candidates)
   1744         ((pred consp)
   1745          (setq last (last (if last (setcdr last action) (setq candidates action))))
   1746          candidates)))))
   1747 
   1748 (defun consult--async-split-style ()
   1749   "Return the async splitting style function and initial string."
   1750   (or (alist-get consult-async-split-style consult-async-split-styles-alist)
   1751       (user-error "Splitting style `%s' not found" consult-async-split-style)))
   1752 
   1753 (defun consult--async-split-initial (initial)
   1754   "Return initial string for async command.
   1755 INITIAL is the additional initial string."
   1756   (concat (plist-get (consult--async-split-style) :initial) initial))
   1757 
   1758 (defun consult--async-split-thingatpt (thing)
   1759   "Return THING at point with async initial prefix."
   1760   (when-let (str (thing-at-point thing))
   1761     (consult--async-split-initial str)))
   1762 
   1763 (defun consult--async-split (async &optional split)
   1764   "Create async function, which splits the input string.
   1765 ASYNC is the async sink.
   1766 SPLIT is the splitting function."
   1767   (unless split
   1768     (let ((style (consult--async-split-style)))
   1769       (setq split (pcase (plist-get style :type)
   1770                     ('separator (apply-partially #'consult--split-separator
   1771                                                  (plist-get style :separator)))
   1772                     ('perl #'consult--split-perl)
   1773                     ('nil #'consult--split-nil)
   1774                     (type (user-error "Invalid style type `%s'" type))))))
   1775   (lambda (action)
   1776     (pcase action
   1777       ('setup
   1778        (consult--split-setup split)
   1779        (funcall async 'setup))
   1780       ((pred stringp)
   1781        (pcase-let* ((`(,async-str ,_ ,_ ,force . ,highlights)
   1782                      (funcall split action 0))
   1783                     (async-len (length async-str))
   1784                     (input-len (length action))
   1785                     (end (minibuffer-prompt-end)))
   1786          ;; Highlight punctuation characters
   1787          (remove-list-of-text-properties end (+ end input-len) '(face))
   1788          (dolist (hl highlights)
   1789            (put-text-property (+ end (car hl)) (+ end (cdr hl))
   1790                               'face 'consult-async-split))
   1791          (funcall async
   1792                   ;; Pass through if the input is long enough!
   1793                   (if (or force (>= async-len consult-async-min-input))
   1794                       async-str
   1795                     ;; Pretend that there is no input
   1796                     ""))))
   1797       (_ (funcall async action)))))
   1798 
   1799 (defun consult--async-log (formatted &rest args)
   1800   "Log FORMATTED ARGS to variable `consult--async-log'."
   1801   (with-current-buffer (get-buffer-create consult--async-log)
   1802     (goto-char (point-max))
   1803     (insert (apply #'format formatted args))))
   1804 
   1805 (defun consult--process-indicator (event)
   1806   "Return the process indicator character for EVENT."
   1807   (cond
   1808    ((string-prefix-p "killed" event)
   1809     #(";" 0 1 (face consult-async-failed)))
   1810    ((string-prefix-p "finished" event)
   1811     #(":" 0 1 (face consult-async-finished)))
   1812    (t
   1813     #("!" 0 1 (face consult-async-failed)))))
   1814 
   1815 (defun consult--async-process (async cmd &rest props)
   1816   "Create process source async function.
   1817 
   1818 ASYNC is the async function which receives the candidates.
   1819 CMD is the command line builder function.
   1820 PROPS are optional properties passed to `make-process'."
   1821   (let (proc proc-buf last-args indicator count)
   1822     (lambda (action)
   1823       (pcase action
   1824         ("" ;; If no input is provided kill current process
   1825          (when proc
   1826            (delete-process proc)
   1827            (kill-buffer proc-buf)
   1828            (setq proc nil proc-buf nil))
   1829          (setq last-args nil))
   1830         ((pred stringp)
   1831          (funcall async action)
   1832          (let* ((args (funcall cmd action))
   1833                 (flush t)
   1834                 (rest "")
   1835                 (proc-filter
   1836                  (lambda (_ out)
   1837                    (when flush
   1838                      (setq flush nil)
   1839                      (funcall async 'flush))
   1840                    (let ((lines (split-string out "[\r\n]+")))
   1841                      (if (not (cdr lines))
   1842                          (setq rest (concat rest (car lines)))
   1843                        (setcar lines (concat rest (car lines)))
   1844                        (let* ((len (length lines))
   1845                               (last (nthcdr (- len 2) lines)))
   1846                          (setq rest (cadr last)
   1847                                count (+ count len -1))
   1848                          (setcdr last nil)
   1849                          (funcall async lines))))))
   1850                 (proc-sentinel
   1851                  (lambda (_ event)
   1852                    (when flush
   1853                      (setq flush nil)
   1854                      (funcall async 'flush))
   1855                    (overlay-put indicator 'display (consult--process-indicator event))
   1856                    (when (and (string-prefix-p "finished" event) (not (string= rest "")))
   1857                      (setq count (+ count 1))
   1858                      (funcall async (list rest)))
   1859                    (consult--async-log
   1860                     "consult--async-process sentinel: event=%s lines=%d\n"
   1861                     (string-trim event) count)
   1862                    (with-current-buffer (get-buffer-create consult--async-log)
   1863                      (goto-char (point-max))
   1864                      (insert ">>>>> stderr >>>>>\n")
   1865                      (insert-buffer-substring proc-buf)
   1866                      (insert "<<<<< stderr <<<<<\n")))))
   1867            (unless (equal args last-args)
   1868              (setq last-args args)
   1869              (when proc
   1870                (delete-process proc)
   1871                (kill-buffer proc-buf)
   1872                (setq proc nil proc-buf nil))
   1873              (when args
   1874                (overlay-put indicator 'display #("*" 0 1 (face consult-async-running)))
   1875                (consult--async-log "consult--async-process started %S\n" args)
   1876                (setq count 0
   1877                      proc-buf (generate-new-buffer " *consult-async-stderr*")
   1878                      proc (apply #'make-process
   1879                                  `(,@props
   1880                                    :connection-type pipe
   1881                                    :name ,(car args)
   1882                                    ;;; XXX tramp bug, the stderr buffer must be empty
   1883                                    :stderr ,proc-buf
   1884                                    :noquery t
   1885                                    :command ,args
   1886                                    :filter ,proc-filter
   1887                                    :sentinel ,proc-sentinel))))))
   1888          nil)
   1889         ('destroy
   1890          (when proc
   1891            (delete-process proc)
   1892            (kill-buffer proc-buf)
   1893            (setq proc nil proc-buf nil))
   1894          (delete-overlay indicator)
   1895          (funcall async 'destroy))
   1896         ('setup
   1897          (setq indicator (make-overlay (- (minibuffer-prompt-end) 2)
   1898                                        (- (minibuffer-prompt-end) 1)))
   1899          (funcall async 'setup))
   1900         (_ (funcall async action))))))
   1901 
   1902 (defun consult--async-highlight (async builder)
   1903   "Return ASYNC function which highlightes the candidates.
   1904 BUILDER is the command line builder."
   1905   (let ((highlight))
   1906     (lambda (action)
   1907       (cond
   1908        ((stringp action)
   1909         (setq highlight (plist-get (funcall builder action) :highlight))
   1910         (funcall async action))
   1911        ((and (consp action) highlight)
   1912         (dolist (str action)
   1913           (funcall highlight str))
   1914         (funcall async action))
   1915        (t (funcall async action))))))
   1916 
   1917 (defun consult--async-throttle (async &optional throttle debounce)
   1918   "Create async function from ASYNC which throttles input.
   1919 
   1920 The THROTTLE delay defaults to `consult-async-input-throttle'.
   1921 The DEBOUNCE delay defaults to `consult-async-input-debounce'."
   1922   (setq throttle (or throttle consult-async-input-throttle)
   1923         debounce (or debounce consult-async-input-debounce))
   1924   (let ((input "") (last) (timer))
   1925     (lambda (action)
   1926       (pcase action
   1927         ((pred stringp)
   1928          (unless (string= action input)
   1929            (when timer
   1930              (cancel-timer timer)
   1931              (setq timer nil))
   1932            (funcall async "") ;; cancel running process
   1933            (setq input action)
   1934            (unless (string= action "")
   1935              (setq timer
   1936                    (run-at-time
   1937                     (+ debounce
   1938                        (if last
   1939                            (min (- (float-time) last) throttle)
   1940                          0))
   1941                     nil
   1942                     (lambda ()
   1943                       (setq last (float-time))
   1944                       (funcall async action))))))
   1945          nil)
   1946         ('destroy
   1947          (when timer (cancel-timer timer))
   1948          (funcall async 'destroy))
   1949         (_ (funcall async action))))))
   1950 
   1951 (defun consult--async-refresh-immediate (async)
   1952   "Create async function from ASYNC, which refreshes the display.
   1953 
   1954 The refresh happens immediately when candidates are pushed."
   1955   (lambda (action)
   1956     (pcase action
   1957       ((or (pred consp) 'flush)
   1958        (prog1 (funcall async action)
   1959          (funcall async 'refresh)))
   1960       (_ (funcall async action)))))
   1961 
   1962 (defun consult--async-refresh-timer (async &optional delay)
   1963   "Create async function from ASYNC, which refreshes the display.
   1964 
   1965 The refresh happens after a DELAY, defaulting to `consult-async-refresh-delay'."
   1966   (let ((timer) (refresh) (delay (or delay consult-async-refresh-delay)))
   1967     (lambda (action)
   1968       (prog1 (funcall async action)
   1969         (pcase action
   1970           ((or (pred consp) 'flush)
   1971            (setq refresh t)
   1972            (unless timer
   1973              (setq timer (run-at-time
   1974                           nil delay
   1975                           (lambda ()
   1976                             (when refresh
   1977                               (setq refresh nil)
   1978                               (funcall async 'refresh)))))))
   1979           ('destroy (when timer (cancel-timer timer))))))))
   1980 
   1981 (defmacro consult--async-transform (async &rest transform)
   1982   "Use FUN to TRANSFORM candidates of ASYNC."
   1983   (let ((async-var (make-symbol "async"))
   1984         (action-var (make-symbol "action")))
   1985     `(let ((,async-var ,async))
   1986        (lambda (,action-var)
   1987          (funcall ,async-var (if (consp ,action-var) (,@transform ,action-var) ,action-var))))))
   1988 
   1989 (defun consult--async-map (async fun)
   1990   "Map candidates of ASYNC by FUN."
   1991   (consult--async-transform async mapcar fun))
   1992 
   1993 (defun consult--async-filter (async fun)
   1994   "Filter candidates of ASYNC by FUN."
   1995   (consult--async-transform async seq-filter fun))
   1996 
   1997 (defun consult--ensure-list (list)
   1998   "Ensure that LIST is a list."
   1999   (if (listp list) list (list list))) ;; Emacs 28 ensure-list
   2000 
   2001 (defun consult--command-builder (builder)
   2002   "Return command line builder given CMD.
   2003 BUILDER is the command line builder function."
   2004   (lambda (input)
   2005     (setq input (funcall builder input))
   2006     (if (stringp (car input))
   2007         input
   2008       (plist-get input :command))))
   2009 
   2010 (defmacro consult--async-command (builder &rest args)
   2011   "Asynchronous command pipeline.
   2012 ARGS is a list of `make-process' properties and transforms. BUILDER is the
   2013 command line builder function, which takes the input string and must either
   2014 return a list of command line arguments or a plist with the command line
   2015 argument list :command and a highlighting function :highlight."
   2016   (declare (indent 1))
   2017   `(thread-first (consult--async-sink)
   2018      (consult--async-refresh-timer)
   2019      ,@(seq-take-while (lambda (x) (not (keywordp x))) args)
   2020      (consult--async-process
   2021       (consult--command-builder ,builder)
   2022       ,@(seq-drop-while (lambda (x) (not (keywordp x))) args))
   2023      (consult--async-throttle)
   2024      (consult--async-split)))
   2025 
   2026 ;;;; Special keymaps
   2027 
   2028 (defvar consult-async-map
   2029   (let ((map (make-sparse-keymap)))
   2030     ;; Async keys overwriting some unusable defaults for the default completion
   2031     (define-key map [remap minibuffer-complete-word] #'self-insert-command)
   2032     (define-key map [remap minibuffer-complete] #'minibuffer-completion-help)
   2033     map)
   2034   "Keymap added for commands with asynchronous candidates.")
   2035 
   2036 (defvar consult-crm-map (make-sparse-keymap)
   2037   "Keymap added by `consult-completing-read-multiple'.")
   2038 
   2039 (defvar consult-narrow-map
   2040   (let ((map (make-sparse-keymap)))
   2041     (define-key map " " consult--narrow-space)
   2042     (define-key map "\d" consult--narrow-delete)
   2043     map)
   2044   "Narrowing keymap which is added to the local minibuffer map.
   2045 Note that `consult-narrow-key' and `consult-widen-key' are bound dynamically.")
   2046 
   2047 ;;;; Internal API: consult--read
   2048 
   2049 (defun consult--add-history (async items)
   2050   "Add ITEMS to the minibuffer future history.
   2051 ASYNC must be non-nil for async completion functions."
   2052   (delete-dups
   2053    (append
   2054     ;; the defaults are at the beginning of the future history
   2055     (consult--ensure-list minibuffer-default)
   2056     ;; then our custom items
   2057     (remove "" (remq nil (consult--ensure-list items)))
   2058     ;; Add all the completions for non-async commands. For async commands this feature
   2059     ;; is not useful, since if one selects a completion candidate, the async search is
   2060     ;; restarted using that candidate string. This usually does not yield a desired
   2061     ;; result since the async input uses a special format, e.g., `#grep#filter'.
   2062     (unless async
   2063       (all-completions ""
   2064                        minibuffer-completion-table
   2065                        minibuffer-completion-predicate)))))
   2066 
   2067 (defun consult--setup-keymap (keymap async narrow preview-key)
   2068   "Setup minibuffer keymap.
   2069 
   2070 KEYMAP is a command-specific keymap.
   2071 ASYNC must be non-nil for async completion functions.
   2072 NARROW are the narrow settings.
   2073 PREVIEW-KEY are the preview keys."
   2074   (let ((old-map (current-local-map))
   2075         (map (make-sparse-keymap)))
   2076 
   2077     ;; Add narrow keys
   2078     (when narrow
   2079       (consult--narrow-setup narrow map))
   2080 
   2081     ;; Preview trigger keys
   2082     (when (and (consp preview-key) (memq :keys preview-key))
   2083       (setq preview-key (plist-get preview-key :keys)))
   2084     (setq preview-key (mapcar #'car (consult--preview-key-normalize preview-key)))
   2085     (when preview-key
   2086       (dolist (key preview-key)
   2087         (unless (or (eq key 'any) (lookup-key old-map key))
   2088           (define-key map key #'ignore))))
   2089 
   2090     ;; Put the keymap together
   2091     (use-local-map
   2092      (make-composed-keymap
   2093       (delq nil (list keymap
   2094                       (and async consult-async-map)
   2095                       (and narrow consult-narrow-map)
   2096                       map))
   2097       old-map))))
   2098 
   2099 (defsubst consult--tofu-p (char)
   2100   "Return non-nil if CHAR is a tofu."
   2101   (<= consult--tofu-char char (+ consult--tofu-char consult--tofu-range -1)))
   2102 
   2103 (defun consult--fry-the-tofus (&rest _)
   2104   "Fry the tofus in the minibuffer."
   2105   (let* ((min (minibuffer-prompt-end))
   2106          (max (point-max))
   2107          (pos max))
   2108     (while (and (> pos min) (consult--tofu-p (char-before pos)))
   2109       (setq pos (1- pos)))
   2110     (when (< pos max)
   2111       (add-text-properties pos max '(invisible t rear-nonsticky t cursor-intangible t)))))
   2112 
   2113 (defsubst consult--tofu-append (cand id)
   2114   "Append tofu-encoded ID to CAND."
   2115   (setq id (char-to-string (+ consult--tofu-char id)))
   2116   (add-text-properties 0 1 '(invisible t consult-strip t) id)
   2117   (concat cand id))
   2118 
   2119 (defsubst consult--tofu-get (cand)
   2120   "Extract tofu-encoded ID from CAND."
   2121   (- (aref cand (1- (length cand))) consult--tofu-char))
   2122 
   2123 ;; We must disambiguate the lines by adding a prefix such that two lines with
   2124 ;; the same text can be distinguished. In order to avoid matching the line
   2125 ;; number, such that the user can search for numbers with `consult-line', we
   2126 ;; encode the line number as characters outside the unicode range.
   2127 ;; By doing that, no accidential matching can occur.
   2128 (defun consult--tofu-encode (n)
   2129   "Return tofu-encoded number N."
   2130   (let ((str ""))
   2131     (while (progn
   2132              (setq str (concat (char-to-string (+ consult--tofu-char
   2133                                                   (% n consult--tofu-range)))
   2134                                str))
   2135              (and (>= n consult--tofu-range) (setq n (/ n consult--tofu-range)))))
   2136     (add-text-properties 0 (length str) '(invisible t consult-strip t) str)
   2137     str))
   2138 
   2139 (defun consult--read-annotate (fun cand)
   2140   "Annotate CAND with annotation function FUN."
   2141   (pcase (funcall fun cand)
   2142     (`(,_ ,_ ,suffix) suffix)
   2143     (ann ann)))
   2144 
   2145 (defun consult--read-affixate (fun cands)
   2146   "Affixate CANDS with annotation function FUN."
   2147   (mapcar (lambda (cand)
   2148             (let ((ann (funcall fun cand)))
   2149               (if (consp ann)
   2150                   ann
   2151                 (setq ann (or ann ""))
   2152                 (list cand ""
   2153                       ;; The default completion UI adds the `completions-annotations' face
   2154                       ;; if no other faces are present.
   2155                       (if (text-property-not-all 0 (length ann) 'face nil ann)
   2156                           ann
   2157                         (propertize ann 'face 'completions-annotations))))))
   2158           cands))
   2159 
   2160 (cl-defun consult--read-1 (candidates &key
   2161                                       prompt predicate require-match history default
   2162                                       keymap category initial narrow add-history annotate
   2163                                       state preview-key sort lookup group inherit-input-method)
   2164   "See `consult--read' for the documentation of the arguments."
   2165   (consult--minibuffer-with-setup-hook
   2166       (:append (lambda ()
   2167                  (add-hook 'after-change-functions #'consult--fry-the-tofus nil 'local)
   2168                  (consult--setup-keymap keymap (functionp candidates) narrow preview-key)
   2169                  (setq-local minibuffer-default-add-function
   2170                              (apply-partially #'consult--add-history (functionp candidates) add-history))))
   2171     (consult--with-async (async candidates)
   2172       ;; NOTE: Do not unnecessarily let-bind the lambdas to avoid
   2173       ;; overcapturing in the interpreter. This will make closures and the
   2174       ;; lambda string representation larger, which makes debugging much worse.
   2175       ;; Fortunately the overcapturing problem does not affect the bytecode
   2176       ;; interpreter which does a proper scope analyis.
   2177       (let* ((metadata `(metadata
   2178                          ,@(when category `((category . ,category)))
   2179                          ,@(when group `((group-function . ,group)))
   2180                          ,@(when annotate
   2181                              `((affixation-function
   2182                                 . ,(apply-partially #'consult--read-affixate annotate))
   2183                                (annotation-function
   2184                                 . ,(apply-partially #'consult--read-annotate annotate))))
   2185                          ,@(unless sort '((cycle-sort-function . identity)
   2186                                           (display-sort-function . identity)))))
   2187              (result
   2188               (consult--with-preview
   2189                   preview-key state
   2190                   (lambda (narrow input cand)
   2191                     (condition-case nil
   2192                         (funcall lookup cand (funcall async nil) input narrow)
   2193                       (wrong-number-of-arguments
   2194                        ;; TODO Remove the condition-case after upgrades of :lookup functions
   2195                        (message "consult--read: The :lookup function protocol changed")
   2196                        (funcall lookup input (funcall async nil) cand))))
   2197                   (apply-partially #'run-hook-with-args-until-success
   2198                                    'consult--completion-candidate-hook)
   2199                 (completing-read prompt
   2200                                  (lambda (str pred action)
   2201                                    (if (eq action 'metadata)
   2202                                        metadata
   2203                                      (complete-with-action action (funcall async nil) str pred)))
   2204                                  predicate require-match initial
   2205                                  (if (symbolp history) history (cadr history))
   2206                                  default
   2207                                  inherit-input-method))))
   2208         (pcase-exhaustive history
   2209           (`(:input ,var)
   2210            (set var (cdr (symbol-value var)))
   2211            (add-to-history var (cdr result)))
   2212           ((pred symbolp)))
   2213         (car result)))))
   2214 
   2215 (cl-defun consult--read (candidates &rest options &key
   2216                                     prompt predicate require-match history default
   2217                                     keymap category initial narrow add-history annotate
   2218                                     state preview-key sort lookup group inherit-input-method)
   2219   "Enhanced completing read function selecting from CANDIDATES.
   2220 
   2221 Keyword OPTIONS:
   2222 
   2223 PROMPT is the string which is shown as prompt message in the minibuffer.
   2224 PREDICATE is a filter function called for each candidate.
   2225 REQUIRE-MATCH equals t means that an exact match is required.
   2226 HISTORY is the symbol of the history variable.
   2227 DEFAULT is the default selected value.
   2228 ADD-HISTORY is a list of items to add to the history.
   2229 CATEGORY is the completion category.
   2230 SORT should be set to nil if the candidates are already sorted.
   2231 LOOKUP is a lookup function passed selected, candidates, input and narrow.
   2232 ANNOTATE is a function passed a candidate string to return an annotation.
   2233 INITIAL is the initial input.
   2234 STATE is the state function, see `consult--with-preview'.
   2235 GROUP is a completion metadata `group-function'.
   2236 PREVIEW-KEY are the preview keys (nil, 'any, a single key or a list of keys).
   2237 NARROW is an alist of narrowing prefix strings and description.
   2238 KEYMAP is a command-specific keymap.
   2239 INHERIT-INPUT-METHOD, if non-nil the minibuffer inherits the input method."
   2240   ;; supported types
   2241   (cl-assert (or (functionp candidates)     ;; async table
   2242                  (obarrayp candidates)      ;; obarray
   2243                  (hash-table-p candidates)  ;; hash table
   2244                  (not candidates)           ;; empty list
   2245                  (stringp (car candidates)) ;; string list
   2246                  (and (consp (car candidates)) (stringp (caar candidates)))   ;; string alist
   2247                  (and (consp (car candidates)) (symbolp (caar candidates))))) ;; symbol alist
   2248   (ignore prompt predicate require-match history default
   2249           keymap category initial narrow add-history annotate
   2250           state preview-key sort lookup group inherit-input-method)
   2251   (apply #'consult--read-1 candidates
   2252          (append
   2253           (consult--customize-get)
   2254           options
   2255           (list :prompt "Select: "
   2256                 :preview-key consult-preview-key
   2257                 :sort t
   2258                 :lookup (lambda (selected &rest _) selected)))))
   2259 
   2260 ;;;; Internal API: consult--multi
   2261 
   2262 (defsubst consult--multi-source (sources cand)
   2263   "Lookup source for CAND in SOURCES list."
   2264   (aref sources (consult--tofu-get cand)))
   2265 
   2266 (defun consult--multi-predicate (sources cand)
   2267   "Predicate function called for each candidate CAND given SOURCES."
   2268   (let* ((src (consult--multi-source sources cand))
   2269          (narrow (plist-get src :narrow))
   2270          (type (or (car-safe narrow) narrow -1)))
   2271     (or (eq consult--narrow type)
   2272         (not (or consult--narrow (plist-get src :hidden))))))
   2273 
   2274 (defun consult--multi-narrow (sources)
   2275   "Return narrow list from SOURCES."
   2276   (thread-last sources
   2277     (mapcar (lambda (src)
   2278               (when-let (narrow (plist-get src :narrow))
   2279                 (if (consp narrow)
   2280                     narrow
   2281                   (when-let (name (plist-get src :name))
   2282                     (cons narrow name))))))
   2283     (delq nil)
   2284     (delete-dups)))
   2285 
   2286 (defun consult--multi-annotate (sources align cand)
   2287   "Annotate candidate CAND with `consult--multi' type, given SOURCES and ALIGN."
   2288   (let* ((src (consult--multi-source sources cand))
   2289          (annotate (plist-get src :annotate))
   2290          (ann (if annotate
   2291                   (funcall annotate (cdr (get-text-property 0 'multi-category cand)))
   2292                 (plist-get src :name))))
   2293     (and ann (concat align ann))))
   2294 
   2295 (defun consult--multi-group (sources cand transform)
   2296   "Return title of candidate CAND or TRANSFORM the candidate given SOURCES."
   2297   (if transform
   2298       cand
   2299     (plist-get (consult--multi-source sources cand) :name)))
   2300 
   2301 (defun consult--multi-preview-key (sources)
   2302   "Return preview keys from SOURCES."
   2303   (list :predicate
   2304         (lambda (cand)
   2305           (if (plist-member (cdr cand) :preview-key)
   2306               (plist-get (cdr cand) :preview-key)
   2307             consult-preview-key))
   2308         :keys
   2309         (delete-dups
   2310          (seq-mapcat (lambda (src)
   2311                        (let ((key (if (plist-member src :preview-key)
   2312                                       (plist-get src :preview-key)
   2313                                     consult-preview-key)))
   2314                          (consult--ensure-list key)))
   2315                      sources))))
   2316 
   2317 (defun consult--multi-lookup (sources selected candidates _input narrow &rest _)
   2318   "Lookup SELECTED in CANDIDATES given SOURCES, with potential NARROW."
   2319   (unless (string-blank-p selected)
   2320     (if-let (found (member selected candidates))
   2321         (cons (cdr (get-text-property 0 'multi-category (car found)))
   2322               (consult--multi-source sources selected))
   2323       (let* ((tofu (consult--tofu-p (aref selected (1- (length selected)))))
   2324              (src (cond
   2325                    (tofu (consult--multi-source sources selected))
   2326                    (narrow (seq-find (lambda (src)
   2327                                        (let ((n (plist-get src :narrow)))
   2328                                          (eq (or (car-safe n) n -1) narrow)))
   2329                                      sources))
   2330                    ((seq-find (lambda (src) (plist-get src :default)) sources))
   2331                    ((aref sources 0)))))
   2332         `(,(if tofu (substring selected 0 -1) selected) :match nil ,@src)))))
   2333 
   2334 (defun consult--multi-candidates (sources)
   2335   "Return `consult--multi' candidates from SOURCES."
   2336   (let ((def) (idx 0) (max-width 0) (candidates))
   2337     (seq-doseq (src sources)
   2338       (let* ((face (and (plist-member src :face) `(face ,(plist-get src :face))))
   2339              (cat (plist-get src :category))
   2340              (items (plist-get src :items))
   2341              (items (if (functionp items) (funcall items) items)))
   2342         (when (and (not def) (plist-get src :default) items)
   2343           (setq def (consult--tofu-append (car items) idx)))
   2344         (dolist (item items)
   2345           (let ((cand (consult--tofu-append item idx))
   2346                 (width (consult--display-width item)))
   2347             ;; Preserve existing `multi-category' datum of the candidate.
   2348             (if (get-text-property 0 'multi-category cand)
   2349                 (when face (add-text-properties 0 (length item) face cand))
   2350               ;; Attach `multi-category' datum and face.
   2351               (add-text-properties 0 (length item)
   2352                                    `(multi-category (,cat . ,item) ,@face) cand))
   2353             (when (> width max-width) (setq max-width width))
   2354             (push cand candidates))))
   2355       (setq idx (1+ idx)))
   2356     (list def (+ 3 max-width) (nreverse candidates))))
   2357 
   2358 (defun consult--multi-enabled-sources (sources)
   2359   "Return vector of enabled SOURCES."
   2360   (vconcat
   2361    (seq-filter (lambda (src)
   2362                  (if-let (pred (plist-get src :enabled))
   2363                      (funcall pred)
   2364                    t))
   2365                (mapcar (lambda (src)
   2366                          (if (symbolp src) (symbol-value src) src))
   2367                        sources))))
   2368 
   2369 (defun consult--multi-state (sources)
   2370   "State function given SOURCES."
   2371   (when-let (states (delq nil (mapcar (lambda (src)
   2372                                         (when-let (fun (plist-get src :state))
   2373                                           (cons src (funcall fun))))
   2374                                       sources)))
   2375     (let (last-fun)
   2376       (pcase-lambda (action `(,cand . ,src))
   2377         (pcase action
   2378           ('setup
   2379            (pcase-dolist (`(,_ . ,fun) states)
   2380              (funcall fun 'setup nil)))
   2381           ('exit
   2382            (pcase-dolist (`(,_ . ,fun) states)
   2383              (funcall fun 'exit nil)))
   2384           ('preview
   2385            (let ((selected-fun (cdr (assq src states))))
   2386              ;; If the candidate source changed during preview communicate to
   2387              ;; the last source, that none of its candidates is previewed anymore.
   2388              (when (and last-fun (not (eq last-fun selected-fun)))
   2389                (funcall last-fun 'preview nil))
   2390              (setq last-fun selected-fun)
   2391              (when selected-fun
   2392                (funcall selected-fun 'preview cand))))
   2393           ('return
   2394            (let ((selected-fun (cdr (assq src states))))
   2395              ;; Finish all the sources, except the selected one.
   2396              (pcase-dolist (`(,_ . ,fun) states)
   2397                (unless (eq fun selected-fun)
   2398                  (funcall fun 'return nil)))
   2399              ;; Finish the source with the selected candidate
   2400              (when selected-fun
   2401                (funcall selected-fun 'return cand)))))))))
   2402 
   2403 (defun consult--multi (sources &rest options)
   2404   "Select from candidates taken from a list of SOURCES.
   2405 
   2406 OPTIONS is the plist of options passed to `consult--read'. The following
   2407 options are supported: :require-match, :history, :keymap, :initial,
   2408 :add-history, :sort and :inherit-input-method. The other options of
   2409 `consult--read' are used by the implementation of `consult--multi' and
   2410 should be overwritten only in special scenarios.
   2411 
   2412 The function returns the selected candidate in the form (cons candidate
   2413 source-plist). The plist has the key :match with a value nil if the
   2414 candidate does not exist, t if the candidate exists and `new' if the
   2415 candidate has been created. The sources of the source list can either be
   2416 symbols of source variables or source values. Source values must be
   2417 plists with the following fields:
   2418 
   2419 Required source fields:
   2420 * :category - Completion category.
   2421 * :items - List of strings to select from or function returning list of strings.
   2422 
   2423 Optional source fields:
   2424 * :name - Name of the source, used for narrowing, group titles and annotations.
   2425 * :narrow - Narrowing character or (character . string) pair.
   2426 * :enabled - Function which must return t if the source is enabled.
   2427 * :hidden - When t candidates of this source are hidden by default.
   2428 * :face - Face used for highlighting the candidates.
   2429 * :annotate - Annotation function called for each candidate, returns string.
   2430 * :history - Name of history variable to add selected candidate.
   2431 * :default - Must be t if the first item of the source is the default value.
   2432 * :action - Function called with the selected candidate.
   2433 * :new - Function called with new candidate name, only if :require-match is nil.
   2434 * :state - State constructor for the source, must return the state function.
   2435 * Other source fields can be added specifically to the use case."
   2436   (let* ((sources (consult--multi-enabled-sources sources))
   2437          (candidates (consult--with-increased-gc
   2438                       (consult--multi-candidates sources)))
   2439          (align (propertize
   2440                  " " 'display
   2441                  `(space :align-to (+ left ,(cadr candidates)))))
   2442          (selected (apply #'consult--read
   2443                           (caddr candidates)
   2444                           (append
   2445                            options
   2446                            (list
   2447                             :default     (car candidates)
   2448                             :category    'multi-category
   2449                             :predicate   (apply-partially #'consult--multi-predicate sources)
   2450                             :annotate    (apply-partially #'consult--multi-annotate sources align)
   2451                             :group       (apply-partially #'consult--multi-group sources)
   2452                             :lookup      (apply-partially #'consult--multi-lookup sources)
   2453                             :preview-key (consult--multi-preview-key sources)
   2454                             :narrow      (consult--multi-narrow sources)
   2455                             :state       (consult--multi-state sources))))))
   2456     (when-let (history (plist-get (cdr selected) :history))
   2457       (add-to-history history (car selected)))
   2458     (if (plist-member (cdr selected) :match)
   2459         (when-let (fun (plist-get (cdr selected) :new))
   2460           (funcall fun (car selected))
   2461           (plist-put (cdr selected) :match 'new))
   2462       (when-let (fun (plist-get (cdr selected) :action))
   2463         (funcall fun (car selected)))
   2464       (setq selected `(,(car selected) :match t ,@(cdr selected))))
   2465     selected))
   2466 
   2467 ;;;; Internal API: consult--prompt
   2468 
   2469 (cl-defun consult--prompt-1 (&key prompt history add-history initial default
   2470                                   keymap state preview-key transform inherit-input-method)
   2471   "See `consult--prompt' for documentation."
   2472   (consult--minibuffer-with-setup-hook
   2473       (:append (lambda ()
   2474                  (consult--setup-keymap keymap nil nil preview-key)
   2475                  (setq-local minibuffer-default-add-function
   2476                              (apply-partially #'consult--add-history nil add-history))))
   2477     (car (consult--with-preview
   2478              preview-key state
   2479              (lambda (_narrow inp _cand) (funcall transform inp)) (lambda () t)
   2480            (read-from-minibuffer prompt initial nil nil history default inherit-input-method)))))
   2481 
   2482 (cl-defun consult--prompt (&rest options &key prompt history add-history initial default
   2483                                  keymap state preview-key transform inherit-input-method)
   2484   "Read from minibuffer.
   2485 
   2486 Keyword OPTIONS:
   2487 
   2488 PROMPT is the string to prompt with.
   2489 TRANSFORM is a function which is applied to the current input string.
   2490 HISTORY is the symbol of the history variable.
   2491 INITIAL is initial input.
   2492 DEFAULT is the default selected value.
   2493 ADD-HISTORY is a list of items to add to the history.
   2494 STATE is the state function, see `consult--with-preview'.
   2495 PREVIEW-KEY are the preview keys (nil, 'any, a single key or a list of keys).
   2496 KEYMAP is a command-specific keymap."
   2497   (ignore prompt history add-history initial default
   2498           keymap state preview-key transform inherit-input-method)
   2499   (apply #'consult--prompt-1
   2500          (append
   2501           (consult--customize-get)
   2502           options
   2503           (list :prompt "Input: "
   2504                 :preview-key consult-preview-key
   2505                 :transform #'identity))))
   2506 
   2507 ;;;; Functions
   2508 
   2509 ;;;;; Function: consult-completion-in-region
   2510 
   2511 (defun consult--insertion-preview (start end)
   2512   "State function for previewing a candidate in a specific region.
   2513 The candidates are previewed in the region from START to END. This function is
   2514 used as the `:state' argument for `consult--read' in the `consult-yank' family
   2515 of functions and in `consult-completion-in-region'."
   2516   (unless (or (minibufferp)
   2517               ;; XXX Disable preview if anything odd is going on with the markers. Otherwise we get
   2518               ;; "Marker points into wrong buffer errors". See
   2519               ;; https://github.com/minad/consult/issues/375, where Org mode source blocks are
   2520               ;; completed in a different buffer than the original buffer. This completion is
   2521               ;; probably also problematic in my Corfu completion package.
   2522               (not (eq (window-buffer) (current-buffer)))
   2523               (and (markerp start) (not (eq (marker-buffer start) (current-buffer))))
   2524               (and (markerp end) (not (eq (marker-buffer end) (current-buffer)))))
   2525     (let (ov)
   2526       (lambda (action cand)
   2527         (cond
   2528          ((and (not cand) ov)
   2529           (delete-overlay ov)
   2530           (setq ov nil))
   2531          ((and (eq action 'preview) cand)
   2532            (unless ov
   2533              (setq ov (consult--overlay start end
   2534                                         'invisible t
   2535                                         'window (selected-window))))
   2536            ;; Use `add-face-text-property' on a copy of "cand in order to merge face properties
   2537            (setq cand (copy-sequence cand))
   2538            (add-face-text-property 0 (length cand) 'consult-preview-insertion t cand)
   2539            ;; Use the `before-string' property since the overlay might be empty.
   2540            (overlay-put ov 'before-string cand)))))))
   2541 
   2542 ;;;###autoload
   2543 (defun consult-completion-in-region (start end collection &optional predicate)
   2544   "Use minibuffer completion as the UI for `completion-at-point'.
   2545 
   2546 The function is called with 4 arguments: START END COLLECTION PREDICATE.
   2547 The arguments and expected return value are as specified for
   2548 `completion-in-region'. Use as a value for `completion-in-region-function'.
   2549 
   2550 The function can be configured via `consult-customize'.
   2551 
   2552     (consult-customize consult-completion-in-region
   2553                        :completion-styles (basic)
   2554                        :cycle-threshold 3)
   2555 
   2556 These configuration options are supported:
   2557 
   2558     * :cycle-threshold - Cycling threshold (def: `completion-cycle-threshold')
   2559     * :completion-styles - Use completion styles (def: `completion-styles')
   2560     * :require-match - Require matches when completing (def: nil)
   2561     * :prompt - The prompt string shown in the minibuffer"
   2562   (barf-if-buffer-read-only)
   2563   (cl-letf* ((config (consult--customize-get #'consult-completion-in-region))
   2564              ;; Overwrite both the local and global value of `completion-styles', such that the
   2565              ;; `completing-read' minibuffer sees the overwritten value in any case. This is
   2566              ;; necessary if `completion-styles' is buffer-local.
   2567              ;; NOTE: The completion-styles will be overwritten for recursive editing sessions!
   2568              (cs (or (plist-get config :completion-styles) completion-styles))
   2569              (completion-styles cs)
   2570              ((default-value 'completion-styles) cs)
   2571              (prompt (or (plist-get config :prompt) "Completion: "))
   2572              (require-match (plist-get config :require-match))
   2573              (preview-key (if (plist-member config :preview-key)
   2574                               (plist-get config :preview-key)
   2575                             consult-preview-key))
   2576              (initial (buffer-substring-no-properties start end))
   2577              (metadata (completion-metadata initial collection predicate))
   2578              (threshold (or (plist-get config :cycle-threshold) (completion--cycle-threshold metadata)))
   2579              (all (completion-all-completions initial collection predicate (length initial)))
   2580              ;; Provide `:annotation-function' if `:company-docsig' is specified
   2581              (completion-extra-properties
   2582               (if-let (fun (and (not (plist-get completion-extra-properties :annotation-function))
   2583                                 (plist-get completion-extra-properties :company-docsig)))
   2584                   `(:annotation-function
   2585                     ,(lambda (cand)
   2586                        (concat (propertize " " 'display '(space :align-to center))
   2587                                (funcall fun cand)))
   2588                     ,@completion-extra-properties)
   2589                 completion-extra-properties)))
   2590     ;; error if `threshold' is t or the improper list `all' is too short
   2591     (if (and threshold
   2592              (or (not (consp (ignore-errors (nthcdr threshold all))))
   2593                  (and completion-cycling completion-all-sorted-completions)))
   2594         (completion--in-region start end collection predicate)
   2595       (let* ((limit (car (completion-boundaries initial collection predicate "")))
   2596              (category (completion-metadata-get metadata 'category))
   2597              (buffer (current-buffer))
   2598              (completion
   2599               (cond
   2600                ((atom all) nil)
   2601                ((and (consp all) (atom (cdr all)))
   2602                 (concat (substring initial 0 limit) (car all)))
   2603                (t (car
   2604                    (consult--with-preview
   2605                        preview-key
   2606                        ;; preview state
   2607                        (consult--insertion-preview start end)
   2608                        ;; transformation function
   2609                        (if (eq category 'file)
   2610                            (cond
   2611                             ;; Transform absolute file names
   2612                             ((file-name-absolute-p initial)
   2613                              (lambda (_narrow _inp cand)
   2614                                (substitute-in-file-name cand)))
   2615                             ;; Ensure that ./ prefix is kept for the shell (#356)
   2616                             ((string-match-p "\\`\\.\\.?/" initial)
   2617                              (lambda (_narrow _inp cand)
   2618                                (setq cand (file-relative-name (substitute-in-file-name cand)))
   2619                                (if (string-match-p "\\`\\.\\.?/" cand) cand (concat "./" cand))))
   2620                             ;; Simplify relative file names
   2621                             (t
   2622                              (lambda (_narrow _inp cand)
   2623                                (file-relative-name (substitute-in-file-name cand)))))
   2624                          (lambda (_narrow _inp cand) cand))
   2625                        ;; candidate function
   2626                        (apply-partially #'run-hook-with-args-until-success
   2627                                         'consult--completion-candidate-hook)
   2628                      (consult--local-let ((enable-recursive-minibuffers t))
   2629                        (if (eq category 'file)
   2630                            ;; We use read-file-name, since many completion UIs make it nicer to
   2631                            ;; navigate the file system this way; and we insert the initial text
   2632                            ;; directly into the minibuffer to allow the user's completion
   2633                            ;; styles to expand it as appropriate (particularly useful for the
   2634                            ;; partial-completion and initials styles, which allow for very
   2635                            ;; condensed path specification).
   2636                            (consult--minibuffer-with-setup-hook
   2637                                (lambda () (insert initial))
   2638                              (read-file-name prompt nil initial require-match nil predicate))
   2639                          (completing-read prompt
   2640                                           ;; Evaluate completion table in the original buffer.
   2641                                           ;; This is a reasonable thing to do and required
   2642                                           ;; by some completion tables in particular by lsp-mode.
   2643                                           ;; See https://github.com/minad/vertico/issues/61.
   2644                                           (if (functionp collection)
   2645                                               (lambda (&rest args)
   2646                                                 (with-current-buffer buffer
   2647                                                   (apply collection args)))
   2648                                             collection)
   2649                                           predicate require-match initial)))))))))
   2650         (if completion
   2651             (progn
   2652               ;; completion--replace removes properties!
   2653               (completion--replace start end (setq completion (concat completion)))
   2654               (when-let (exit (plist-get completion-extra-properties :exit-function))
   2655                 (funcall exit completion
   2656                          ;; If completion is finished and cannot be further completed,
   2657                          ;; return 'finished. Otherwise return 'exact.
   2658                          (if (eq (try-completion completion collection predicate) t)
   2659                              'finished 'exact)))
   2660               t)
   2661           (message "No completion")
   2662           nil)))))
   2663 
   2664 ;;;;; Function: consult-completing-read-multiple
   2665 
   2666 (defun consult--crm-selected ()
   2667   "Return selected candidates from `consult-completing-read-multiple'."
   2668   (when (eq minibuffer-history-variable 'consult--crm-history)
   2669     (mapcar
   2670      (apply-partially #'get-text-property 0 'consult--crm-selected)
   2671      (all-completions
   2672       "" minibuffer-completion-table
   2673       (lambda (cand)
   2674         (and (stringp cand)
   2675              (get-text-property 0 'consult--crm-selected cand)
   2676              (or (not minibuffer-completion-predicate)
   2677                  (funcall minibuffer-completion-predicate cand))))))))
   2678 
   2679 ;;;###autoload
   2680 (defun consult-completing-read-multiple (prompt table &optional
   2681                                                 pred require-match initial-input
   2682                                                 hist def inherit-input-method)
   2683   "Enhanced replacement for `completing-read-multiple'.
   2684 See `completing-read-multiple' for the documentation of the arguments."
   2685   (let* ((orig-items (all-completions "" table pred))
   2686          (prefixed-orig-items
   2687           (funcall
   2688            (if-let (prefix (car consult-crm-prefix))
   2689                (apply-partially #'mapcar (lambda (item) (propertize item 'line-prefix prefix)))
   2690              #'identity)
   2691            orig-items))
   2692          (format-item
   2693           (lambda (item)
   2694             ;; Restore original candidate in order to preserve formatting
   2695             (setq item (or (car (member item orig-items)) item)
   2696                   item (propertize item 'consult--crm-selected item
   2697                                    'line-prefix (cdr consult-crm-prefix)))
   2698             (add-face-text-property 0 (length item) 'consult-crm-selected 'append item)
   2699             item))
   2700          (separator (or (bound-and-true-p crm-separator) "[ \t]*,[ \t]*"))
   2701          (hist-sym (pcase hist
   2702                      ('nil 'minibuffer-history)
   2703                      ('t 'consult--crm-history)
   2704                      (`(,sym . ,_) sym) ;; ignore history position
   2705                      (_ hist)))
   2706          (hist-val (symbol-value hist-sym))
   2707          (selected
   2708           (and initial-input
   2709                (or
   2710                 ;; initial-input is multiple items
   2711                 (string-match-p separator initial-input)
   2712                 ;; initial-input is a single candidate
   2713                 (member initial-input orig-items))
   2714                (prog1
   2715                    (mapcar format-item
   2716                            (split-string initial-input separator 'omit-nulls))
   2717                  (setq initial-input nil))))
   2718          (consult--crm-history (append (mapcar #'substring-no-properties selected) hist-val))
   2719          (items (append selected
   2720                         (seq-remove (lambda (x) (member x selected))
   2721                                     prefixed-orig-items)))
   2722          (orig-md (and (functionp table) (cdr (funcall table "" nil 'metadata))))
   2723          (group-fun (alist-get 'group-function orig-md))
   2724          (sort-fun
   2725           (lambda (sort)
   2726             (pcase (alist-get sort orig-md)
   2727               ('identity `((,sort . identity)))
   2728               ((and sort (guard sort))
   2729                `((,sort . ,(lambda (cands)
   2730                              (setq cands (funcall sort cands))
   2731                              (nconc
   2732                               (seq-filter (lambda (x) (member x selected)) cands)
   2733                               (seq-remove (lambda (x) (member x selected)) cands)))))))))
   2734          (md
   2735           `(metadata
   2736             (group-function
   2737              . ,(lambda (cand transform)
   2738                   (if (get-text-property 0 'consult--crm-selected cand)
   2739                       (if transform cand "Selected")
   2740                     (or (and group-fun (funcall group-fun cand transform))
   2741                         (if transform cand "Select multiple")))))
   2742             ,@(funcall sort-fun 'cycle-sort-function)
   2743             ,@(funcall sort-fun 'display-sort-function)
   2744             ,@(seq-filter (lambda (x) (memq (car x) '(annotation-function
   2745                                                       affixation-function
   2746                                                       category)))
   2747                           orig-md)))
   2748          (overlay)
   2749          (command)
   2750          (depth (1+ (recursion-depth)))
   2751          (hook (make-symbol "consult--crm-pre-command-hook"))
   2752          (wrapper (make-symbol "consult--crm-command-wrapper")))
   2753     (fset wrapper
   2754           (lambda ()
   2755             (interactive)
   2756             (pcase (catch 'exit
   2757                      (call-interactively (setq this-command command))
   2758                      'consult--continue)
   2759               ('nil
   2760                (with-selected-window (active-minibuffer-window)
   2761                  (let ((item (minibuffer-contents-no-properties)))
   2762                    (when (equal item "")
   2763                      (throw 'exit nil))
   2764                    (setq selected (if (member item selected)
   2765                                       ;; Multi selections are not possible.
   2766                                       ;; This is probably no problem, since this is rarely desired.
   2767                                       (delete item selected)
   2768                                     (nconc selected (list (funcall format-item item))))
   2769                          consult--crm-history (append (mapcar #'substring-no-properties selected) hist-val)
   2770                          items (append selected
   2771                                        (seq-remove (lambda (x) (member x selected))
   2772                                                    prefixed-orig-items)))
   2773                    (when overlay
   2774                      (overlay-put overlay 'display
   2775                                   (when selected
   2776                                     (format " (%s selected): " (length selected)))))
   2777                    (delete-minibuffer-contents)
   2778                    (run-hook-with-args 'consult--completion-refresh-hook 'reset))))
   2779               ('consult--continue nil)
   2780               (other (throw 'exit other)))))
   2781     (fset hook (lambda ()
   2782                  (when (and this-command (= depth (recursion-depth)))
   2783                    (setq command this-command this-command wrapper))))
   2784     (consult--minibuffer-with-setup-hook
   2785         (:append
   2786          (lambda ()
   2787            (when-let (pos (string-match-p "\\(?: (default[^)]+)\\)?: \\'" prompt))
   2788              (setq overlay (make-overlay (+ (point-min) pos) (+ (point-min) (length prompt))))
   2789              (when selected
   2790                (overlay-put overlay 'display (format " (%s selected): " (length selected)))))
   2791            (use-local-map (make-composed-keymap (list consult-crm-map) (current-local-map)))))
   2792       (unwind-protect
   2793           (progn
   2794             (add-hook 'pre-command-hook hook 90)
   2795             (let ((result
   2796                    (completing-read
   2797                     prompt
   2798                     (lambda (str pred action)
   2799                       (if (eq action 'metadata)
   2800                           md
   2801                         (complete-with-action action items str pred)))
   2802                     nil ;; predicate
   2803                     require-match
   2804                     initial-input
   2805                     'consult--crm-history
   2806                     "" ;; default
   2807                     inherit-input-method)))
   2808               (unless (or (equal result "") selected)
   2809                 (setq selected (split-string result separator 'omit-nulls)
   2810                       consult--crm-history (append (mapcar #'substring-no-properties selected) hist-val)))))
   2811         (remove-hook 'pre-command-hook hook)))
   2812     (when (consp def)
   2813       (setq def (car def)))
   2814     (if (and def (not (equal "" def)) (not selected))
   2815         (split-string def separator 'omit-nulls)
   2816       (setq selected (mapcar #'substring-no-properties selected))
   2817       (set hist-sym (append selected (symbol-value hist-sym)))
   2818       selected)))
   2819 
   2820 ;;;; Commands
   2821 
   2822 ;;;;; Command: consult-multi-occur
   2823 
   2824 ;; see https://github.com/raxod502/selectrum/issues/226
   2825 ;;;###autoload
   2826 (defun consult-multi-occur (bufs regexp &optional nlines)
   2827   "Improved version of `multi-occur' based on `completing-read-multiple'.
   2828 
   2829 See `multi-occur' for the meaning of the arguments BUFS, REGEXP and NLINES."
   2830   (interactive (cons
   2831                 (mapcar #'get-buffer
   2832                         (completing-read-multiple "Buffer: "
   2833                                                   #'internal-complete-buffer))
   2834                 (occur-read-primary-args)))
   2835   (occur-1 regexp nlines bufs))
   2836 
   2837 ;;;;; Command: consult-outline
   2838 
   2839 (defun consult--outline-candidates ()
   2840   "Return alist of outline headings and positions."
   2841   (consult--forbid-minibuffer)
   2842   (let* ((line (line-number-at-pos (point-min) consult-line-numbers-widen))
   2843          (heading-regexp (concat "^\\(?:"
   2844                                  ;; default definition from outline.el
   2845                                  (or (bound-and-true-p outline-regexp) "[*\^L]+")
   2846                                  "\\)"))
   2847          (heading-alist (bound-and-true-p outline-heading-alist))
   2848          (level-fun (or (bound-and-true-p outline-level)
   2849                         (lambda () ;; as in the default from outline.el
   2850                           (or (cdr (assoc (match-string 0) heading-alist))
   2851                               (- (match-end 0) (match-beginning 0))))))
   2852          (inhibit-field-text-motion t)
   2853          (buffer (current-buffer))
   2854          (candidates))
   2855     (save-excursion
   2856       (goto-char (point-min))
   2857       (while (save-excursion (re-search-forward heading-regexp nil t))
   2858         (setq line (+ line (consult--count-lines (match-beginning 0))))
   2859         (push (consult--location-candidate
   2860                (consult--buffer-substring (line-beginning-position)
   2861                                           (line-end-position)
   2862                                           'fontify)
   2863                (cons buffer (point)) line
   2864                'consult--outline-level (funcall level-fun))
   2865               candidates)
   2866         (unless (eobp) (forward-char 1))))
   2867     (unless candidates
   2868       (user-error "No headings"))
   2869     (nreverse candidates)))
   2870 
   2871 ;;;###autoload
   2872 (defun consult-outline ()
   2873   "Jump to an outline heading, obtained by matching against `outline-regexp'.
   2874 
   2875 This command supports narrowing to a heading level and candidate preview.
   2876 The symbol at point is added to the future history."
   2877   (interactive)
   2878   (let* ((candidates
   2879           (consult--with-increased-gc (consult--outline-candidates)))
   2880          (min-level (- (apply #'min (mapcar
   2881                                      (lambda (cand)
   2882                                        (get-text-property 0 'consult--outline-level cand))
   2883                                      candidates))
   2884                        ?1))
   2885          (narrow-pred (lambda (cand)
   2886                         (<= (get-text-property 0 'consult--outline-level cand)
   2887                             (+ consult--narrow min-level))))
   2888          (narrow-keys (mapcar (lambda (c) (cons c (format "Level %c" c)))
   2889                               (number-sequence ?1 ?9))))
   2890     (consult--read
   2891      candidates
   2892      :prompt "Go to heading: "
   2893      :annotate (consult--line-prefix)
   2894      :category 'consult-location
   2895      :sort nil
   2896      :require-match t
   2897      :lookup #'consult--line-match
   2898      :narrow `(:predicate ,narrow-pred :keys ,narrow-keys)
   2899      :history '(:input consult--line-history)
   2900      :add-history (thing-at-point 'symbol)
   2901      :state (consult--location-state candidates))))
   2902 
   2903 ;;;;; Command: consult-mark
   2904 
   2905 (defun consult--mark-candidates (markers)
   2906   "Return list of candidates strings for MARKERS."
   2907   (consult--forbid-minibuffer)
   2908   (let ((candidates)
   2909         (current-buf (current-buffer)))
   2910     (save-excursion
   2911       (dolist (marker markers)
   2912         (when-let ((pos (marker-position marker))
   2913                    (buf (marker-buffer marker)))
   2914           (when (and (eq buf current-buf)
   2915                      (consult--in-range-p pos))
   2916             (goto-char pos)
   2917             ;; `line-number-at-pos' is a very slow function, which should be replaced everywhere.
   2918             ;; However in this case the slow line-number-at-pos does not hurt much, since
   2919             ;; the mark ring is usually small since it is limited by `mark-ring-max'.
   2920             (push (consult--location-candidate
   2921                    (consult--line-with-cursor marker) marker
   2922                    (line-number-at-pos pos consult-line-numbers-widen))
   2923                   candidates)))))
   2924     (unless candidates
   2925       (user-error "No marks"))
   2926     (nreverse (delete-dups candidates))))
   2927 
   2928 ;;;###autoload
   2929 (defun consult-mark (&optional markers)
   2930   "Jump to a marker in MARKERS list (defaults to buffer-local `mark-ring').
   2931 
   2932 The command supports preview of the currently selected marker position.
   2933 The symbol at point is added to the future history."
   2934   (interactive)
   2935   (consult--read
   2936    (consult--with-increased-gc
   2937     (consult--mark-candidates
   2938      (or markers (cons (mark-marker) mark-ring))))
   2939    :prompt "Go to mark: "
   2940    :annotate (consult--line-prefix)
   2941    :category 'consult-location
   2942    :sort nil
   2943    :require-match t
   2944    :lookup #'consult--lookup-location
   2945    :history '(:input consult--line-history)
   2946    :add-history (thing-at-point 'symbol)
   2947    :state (consult--jump-state)))
   2948 
   2949 ;;;;; Command: consult-global-mark
   2950 
   2951 (defun consult--global-mark-candidates (markers)
   2952   "Return list of candidates strings for MARKERS."
   2953   (consult--forbid-minibuffer)
   2954   (let ((candidates))
   2955     (save-excursion
   2956       (dolist (marker markers)
   2957         (when-let ((pos (marker-position marker))
   2958                    (buf (marker-buffer marker)))
   2959           (unless (minibufferp buf)
   2960             (with-current-buffer buf
   2961               (when (consult--in-range-p pos)
   2962                 (goto-char pos)
   2963                 ;; `line-number-at-pos' is slow, see comment in `consult--mark-candidates'.
   2964                 (let ((line (line-number-at-pos pos consult-line-numbers-widen)))
   2965                   (push (concat
   2966                          (propertize (consult--format-location (buffer-name buf) line "")
   2967                                      'consult-location (cons marker line)
   2968                                      'consult-strip t)
   2969                          (consult--line-with-cursor marker)
   2970                          (consult--tofu-encode marker))
   2971                         candidates))))))))
   2972     (unless candidates
   2973       (user-error "No global marks"))
   2974     (nreverse (delete-dups candidates))))
   2975 
   2976 ;;;###autoload
   2977 (defun consult-global-mark (&optional markers)
   2978   "Jump to a marker in MARKERS list (defaults to `global-mark-ring').
   2979 
   2980 The command supports preview of the currently selected marker position.
   2981 The symbol at point is added to the future history."
   2982   (interactive)
   2983   (consult--read
   2984    (consult--with-increased-gc
   2985     (consult--global-mark-candidates
   2986      (or markers global-mark-ring)))
   2987    :prompt "Go to global mark: "
   2988    ;; Despite `consult-global-mark' formating the candidates in grep-like
   2989    ;; style, we are not using the 'consult-grep category, since the candidates
   2990    ;; have location markers attached.
   2991    :category 'consult-location
   2992    :sort nil
   2993    :require-match t
   2994    :lookup #'consult--lookup-location
   2995    :history '(:input consult--line-history)
   2996    :add-history (thing-at-point 'symbol)
   2997    :state (consult--jump-state)))
   2998 
   2999 ;;;;; Command: consult-line
   3000 
   3001 (defun consult--line-candidates (top curr-line)
   3002   "Return list of line candidates.
   3003 Start from top if TOP non-nil.
   3004 CURR-LINE is the current line number."
   3005   (consult--forbid-minibuffer)
   3006   (consult--fontify-all)
   3007   (let* (default-cand candidates
   3008          (buffer (current-buffer))
   3009          (line (line-number-at-pos (point-min) consult-line-numbers-widen)))
   3010     (consult--each-line beg end
   3011       (let ((str (consult--buffer-substring beg end)))
   3012         (unless (string-blank-p str)
   3013           (push (consult--location-candidate str (cons buffer (point)) line) candidates)
   3014           (when (and (not default-cand) (>= line curr-line))
   3015             (setq default-cand candidates)))
   3016         (setq line (1+ line))))
   3017     (when candidates
   3018       (nreverse
   3019        (if (or top (not default-cand))
   3020            candidates
   3021          (let ((before (cdr default-cand)))
   3022            (setcdr default-cand nil)
   3023            (nconc before candidates)))))))
   3024 
   3025 (defun consult--line-match (selected candidates input &rest _)
   3026   "Lookup position of match.
   3027 
   3028 SELECTED is the currently selected candidate.
   3029 CANDIDATES is the list of candidates.
   3030 INPUT is the input string entered by the user."
   3031   (when-let (pos (consult--lookup-location selected candidates))
   3032     (if (or (string-blank-p input)
   3033             (eq consult-line-point-placement 'line-beginning))
   3034         pos
   3035       (let ((beg 0)
   3036             (end (length selected)))
   3037         ;; Ignore tofu-encoded unique line number suffix
   3038         (while (and (> end 0) (consult--tofu-p (aref selected (1- end))))
   3039           (setq end (1- end)))
   3040         ;; Find match end position, remove characters from line end until
   3041         ;; matching fails
   3042         (let ((step 16))
   3043           (while (> step 0)
   3044             (while (and (> (- end step) 0)
   3045                         ;; Use consult-location completion category when
   3046                         ;; filtering lines. Highlighting is not necessary here,
   3047                         ;; but it is actually cheaper to highlight a single
   3048                         ;; candidate, since setting up deferred highlighting is
   3049                         ;; costly.
   3050                         (consult--completion-filter input
   3051                                                     (list (substring selected 0 (- end step)))
   3052                                                     'consult-location 'highlight))
   3053               (setq end (- end step)))
   3054             (setq step (/ step 2))))
   3055         ;; Find match beginning position, remove characters from line beginning
   3056         ;; until matching fails
   3057         (when (eq consult-line-point-placement 'match-beginning)
   3058           (let ((step 16))
   3059             (while (> step 0)
   3060               (while (and (< (+ beg step) end)
   3061                           ;; See comment above, call to `consult--completion-filter'.
   3062                           (consult--completion-filter input
   3063                                                       (list (substring selected (+ beg step) end))
   3064                                                       'consult-location 'highlight))
   3065                 (setq beg (+ beg step)))
   3066               (setq step (/ step 2)))
   3067             (setq end beg)))
   3068         ;; Marker can be dead, therefore ignore errors. Create a new marker instead of an integer,
   3069         ;; since the location may be in another buffer, e.g., for `consult-line-all'.
   3070         (ignore-errors
   3071           (if (or (not (markerp pos)) (eq (marker-buffer pos) (current-buffer)))
   3072               (+ pos end)
   3073             ;; Only create a new marker when jumping across buffers, to avoid
   3074             ;; creating unnecessary markers, when scrolling through candidates.
   3075             ;; Creating markers is not free.
   3076             (move-marker
   3077              (make-marker)
   3078              (+ pos end)
   3079              (marker-buffer pos))))))))
   3080 
   3081 (cl-defun consult--line (candidates &key curr-line prompt initial group)
   3082   "Select from from line CANDIDATES and jump to the match.
   3083 CURR-LINE is the current line. See `consult--read' for the arguments PROMPT,
   3084 INITIAL and GROUP."
   3085   (consult--read
   3086    candidates
   3087    :prompt prompt
   3088    :annotate (consult--line-prefix curr-line)
   3089    :group group
   3090    :category 'consult-location
   3091    :sort nil
   3092    :require-match t
   3093    ;; Always add last isearch string to future history
   3094    :add-history (list (thing-at-point 'symbol) isearch-string)
   3095    :history '(:input consult--line-history)
   3096    :lookup #'consult--line-match
   3097    :default (car candidates)
   3098    ;; Add isearch-string as initial input if starting from isearch
   3099    :initial (or initial
   3100                 (and isearch-mode
   3101                      (prog1 isearch-string (isearch-done))))
   3102    :state (consult--location-state candidates)))
   3103 
   3104 ;;;###autoload
   3105 (defun consult-line (&optional initial start)
   3106   "Search for a matching line.
   3107 
   3108 Depending on the setting `consult-line-point-placement' the command jumps to
   3109 the beginning or the end of the first match on the line or the line beginning.
   3110 The default candidate is the non-empty line next to point. This command obeys
   3111 narrowing. Optional INITIAL input can be provided. The search starting point is
   3112 changed if the START prefix argument is set. The symbol at point and the last
   3113 `isearch-string' is added to the future history."
   3114   (interactive (list nil (not (not current-prefix-arg))))
   3115   (let ((curr-line (line-number-at-pos (point) consult-line-numbers-widen))
   3116         (top (not (eq start consult-line-start-from-top))))
   3117     (consult--line
   3118      (or (consult--with-increased-gc
   3119           (consult--line-candidates top curr-line))
   3120          (user-error "No lines"))
   3121      :curr-line (and (not top) curr-line)
   3122      :prompt (if top "Go to line from top: " "Go to line: ")
   3123      :initial initial)))
   3124 
   3125 ;;;;; Command: consult-line-multi
   3126 
   3127 (defun consult--line-multi-candidates (buffers)
   3128   "Collect the line candidates from multiple buffers.
   3129 BUFFERS is the list of buffers."
   3130   (or (apply #'nconc
   3131              (consult--buffer-map buffers
   3132               #'consult--line-candidates 'top most-positive-fixnum))
   3133       (user-error "No lines")))
   3134 
   3135 ;;;###autoload
   3136 (defun consult-line-multi (query &optional initial)
   3137   "Search for a matching line in multiple buffers.
   3138 
   3139 By default search across all project buffers. If the prefix argument QUERY is
   3140 non-nil, all buffers are searched. Optional INITIAL input can be provided. See
   3141 `consult-line' for more information. In order to search a subset of buffers,
   3142 QUERY can be set to a plist according to `consult--buffer-query'."
   3143   (interactive "P")
   3144   (unless (keywordp (car-safe query))
   3145     (setq query (list :sort 'alpha :directory (and (not query) 'project))))
   3146   (let ((buffers (consult--buffer-query-prompt "Go to line" query)))
   3147     (consult--line
   3148      (consult--line-multi-candidates (cdr buffers))
   3149      :prompt (car buffers)
   3150      :initial initial
   3151      :group #'consult--line-group)))
   3152 
   3153 ;;;;; Command: consult-keep-lines
   3154 
   3155 (defun consult--keep-lines-state (filter)
   3156   "State function for `consult-keep-lines' with FILTER function."
   3157   (let ((font-lock-orig font-lock-mode)
   3158         (hl-line-orig (bound-and-true-p hl-line-mode))
   3159         (point-orig (point))
   3160         lines content-orig replace last-input)
   3161     (if (use-region-p)
   3162         (save-restriction
   3163           ;; Use the same behavior as `keep-lines'.
   3164           (let ((rbeg (region-beginning))
   3165                 (rend (save-excursion
   3166                         (goto-char (region-end))
   3167                         (unless (or (bolp) (eobp))
   3168                           (forward-line 0))
   3169                         (point))))
   3170             (consult--fontify-region rbeg rend)
   3171             (narrow-to-region rbeg rend)
   3172             (consult--each-line beg end
   3173               (push (consult--buffer-substring beg end) lines))
   3174             (setq content-orig (buffer-string)
   3175                   replace (lambda (content &optional pos)
   3176                             (delete-region rbeg rend)
   3177                             (insert content)
   3178                             (goto-char (or pos rbeg))
   3179                             (setq rend (+ rbeg (length content)))
   3180                             (add-face-text-property rbeg rend 'region t)))))
   3181       (consult--fontify-all)
   3182       (setq content-orig (buffer-string)
   3183             replace (lambda (content &optional pos)
   3184                       (delete-region (point-min) (point-max))
   3185                       (insert content)
   3186                       (goto-char (or pos (point-min)))))
   3187       (consult--each-line beg end
   3188         (push (consult--buffer-substring beg end) lines)))
   3189     (setq lines (nreverse lines))
   3190     (lambda (action input)
   3191       ;; Restoring content and point position
   3192       (when (and (eq action 'return) last-input)
   3193         ;; No undo recording, modification hooks, buffer modified-status
   3194         (with-silent-modifications (funcall replace content-orig point-orig)))
   3195       ;; Committing or new input provided -> Update
   3196       (when (and input ;; Input has been povided
   3197                  (or
   3198                   ;; Committing, but not with empty input
   3199                   (and (eq action 'return) (not (string-match-p "\\`!? ?\\'" input)))
   3200                   ;; Input has changed
   3201                   (not (equal input last-input))))
   3202         (let ((filtered-content
   3203                (if (string-match-p "\\`!? ?\\'" input)
   3204                    ;; Special case the empty input for performance.
   3205                    ;; Otherwise it could happen that the minibuffer is empty,
   3206                    ;; but the buffer has not been updated.
   3207                    content-orig
   3208                  (if (eq action 'return)
   3209                      (apply #'concat (mapcan (lambda (x) (list x "\n"))
   3210                                              (funcall filter input lines)))
   3211                    (while-no-input
   3212                      ;; Heavy computation is interruptible if *not* committing!
   3213                      ;; Allocate new string candidates since the matching function mutates!
   3214                      (apply #'concat (mapcan (lambda (x) (list x "\n"))
   3215                                              (funcall filter input (mapcar #'copy-sequence lines)))))))))
   3216           (when (stringp filtered-content)
   3217             (when font-lock-mode (font-lock-mode -1))
   3218             (when (bound-and-true-p hl-line-mode) (hl-line-mode -1))
   3219             (if (eq action 'return)
   3220                 (atomic-change-group
   3221                   ;; Disable modification hooks for performance
   3222                   (let ((inhibit-modification-hooks t))
   3223                     (funcall replace filtered-content)))
   3224               ;; No undo recording, modification hooks, buffer modified-status
   3225               (with-silent-modifications
   3226                 (funcall replace filtered-content)
   3227                 (setq last-input input))))))
   3228       ;; Restore modes
   3229       (when (eq action 'return)
   3230         (when hl-line-orig (hl-line-mode 1))
   3231         (when font-lock-orig (font-lock-mode 1))))))
   3232 
   3233 ;;;###autoload
   3234 (defun consult-keep-lines (&optional filter initial)
   3235   "Select a subset of the lines in the current buffer with live preview.
   3236 
   3237 The selected lines are kept and the other lines are deleted. When called
   3238 interactively, the lines selected are those that match the minibuffer input. In
   3239 order to match the inverse of the input, prefix the input with `! '. When
   3240 called from elisp, the filtering is performed by a FILTER function. This
   3241 command obeys narrowing.
   3242 
   3243 FILTER is the filter function.
   3244 INITIAL is the initial input."
   3245   (interactive
   3246    (list (lambda (pattern cands)
   3247            ;; Use consult-location completion category when filtering lines
   3248            (consult--completion-filter-dispatch
   3249             pattern cands 'consult-location 'highlight))))
   3250   (consult--forbid-minibuffer)
   3251   (cl-letf ((ro buffer-read-only)
   3252             ((buffer-local-value 'buffer-read-only (current-buffer)) nil))
   3253     (consult--minibuffer-with-setup-hook
   3254         (lambda ()
   3255           (when ro
   3256             (minibuffer-message
   3257              (substitute-command-keys
   3258               " [Unlocked read-only buffer. \\[minibuffer-keyboard-quit] to quit.]"))))
   3259         (consult--with-increased-gc
   3260          (consult--prompt
   3261           :prompt "Keep lines: "
   3262           :initial initial
   3263           :history 'consult--keep-lines-history
   3264           :state (consult--keep-lines-state filter))))))
   3265 
   3266 ;;;;; Command: consult-focus-lines
   3267 
   3268 (defun consult--focus-lines-state (filter)
   3269   "State function for `consult-focus-lines' with FILTER function."
   3270   (let (lines overlays last-input pt-orig pt-min pt-max)
   3271     (save-excursion
   3272       (save-restriction
   3273         (if (not (use-region-p))
   3274             (consult--fontify-all)
   3275           (consult--fontify-region (region-beginning) (region-end))
   3276           (narrow-to-region
   3277            (region-beginning)
   3278            ;; Behave the same as `keep-lines'.
   3279            ;; Move to the next line.
   3280            (save-excursion
   3281              (goto-char (region-end))
   3282              (unless (or (bolp) (eobp))
   3283                (forward-line 0))
   3284              (point))))
   3285         (setq pt-orig (point) pt-min (point-min) pt-max (point-max))
   3286         (let ((i 0))
   3287           (consult--each-line beg end
   3288             ;; NOTE: Use "\n" for empty lines, since we need
   3289             ;; a string to attach the text property to.
   3290             (let ((line (if (eq beg end) (char-to-string ?\n)
   3291                           (buffer-substring-no-properties beg end))))
   3292               (put-text-property 0 1 'consult--focus-line (cons (cl-incf i) beg) line)
   3293               (push line lines)))
   3294           (setq lines (nreverse lines)))))
   3295     (lambda (action input)
   3296       ;; New input provided -> Update
   3297       (when (and input (not (equal input last-input)))
   3298         (let (new-overlays)
   3299           (pcase (while-no-input
   3300                    (unless (string-match-p "\\`!? ?\\'" input) ;; empty input.
   3301                      (let* ((inhibit-quit (eq action 'return)) ;; Non interruptible, when quitting!
   3302                             (not (string-prefix-p "! " input))
   3303                             (stripped (string-remove-prefix "! " input))
   3304                             (matches (funcall filter stripped lines))
   3305                             (old-ind 0)
   3306                             (block-beg pt-min)
   3307                             (block-end pt-min))
   3308                        (while old-ind
   3309                          (let ((match (pop matches)) (ind nil) (beg pt-max) (end pt-max) prop)
   3310                            (when match
   3311                              (setq prop (get-text-property 0 'consult--focus-line match)
   3312                                    ind (car prop)
   3313                                    beg (cdr prop)
   3314                                    ;; NOTE: Check for empty lines, see above!
   3315                                    end (+ 1 beg (if (equal match "\n") 0 (length match)))))
   3316                            (unless (eq ind (1+ old-ind))
   3317                              (let ((a (if not block-beg block-end))
   3318                                    (b (if not block-end beg)))
   3319                                (when (/= a b)
   3320                                  (push (consult--overlay a b 'invisible t) new-overlays)))
   3321                              (setq block-beg beg))
   3322                            (setq block-end end old-ind ind)))))
   3323                    'commit)
   3324             ('commit
   3325              (mapc #'delete-overlay overlays)
   3326              (setq last-input input overlays new-overlays))
   3327             (_ (mapc #'delete-overlay new-overlays)))))
   3328       (when (eq action 'return)
   3329         (cond
   3330          ((not input)
   3331           (mapc #'delete-overlay overlays)
   3332           (goto-char pt-orig))
   3333          ((equal input "")
   3334           (consult-focus-lines 'show)
   3335           (goto-char pt-orig))
   3336          (t
   3337           ;; Sucessfully terminated -> Remember invisible overlays
   3338           (setq consult--focus-lines-overlays
   3339                 (nconc consult--focus-lines-overlays overlays))
   3340           ;; move point past invisible
   3341           (goto-char (if-let (ov (and (invisible-p pt-orig)
   3342                                       (seq-find (lambda (ov) (overlay-get ov 'invisible))
   3343                                                 (overlays-at pt-orig))))
   3344                          (overlay-end ov)
   3345                        pt-orig))))))))
   3346 
   3347 ;;;###autoload
   3348 (defun consult-focus-lines (&optional show filter initial)
   3349   "Hide or show lines using overlays.
   3350 
   3351 The selected lines are shown and the other lines hidden. When called
   3352 interactively, the lines selected are those that match the minibuffer input. In
   3353 order to match the inverse of the input, prefix the input with `! '. With
   3354 optional prefix argument SHOW reveal the hidden lines. Alternatively the
   3355 command can be restarted to reveal the lines. When called from elisp, the
   3356 filtering is performed by a FILTER function. This command obeys narrowing.
   3357 
   3358 FILTER is the filter function.
   3359 INITIAL is the initial input."
   3360   (interactive
   3361    (list current-prefix-arg
   3362          (lambda (pattern cands)
   3363            ;; Use consult-location completion category when filtering lines
   3364            (consult--completion-filter-dispatch
   3365             pattern cands 'consult-location nil))))
   3366   (if show
   3367       (progn
   3368         (mapc #'delete-overlay consult--focus-lines-overlays)
   3369         (setq consult--focus-lines-overlays nil)
   3370         (message "All lines revealed"))
   3371     (consult--forbid-minibuffer)
   3372     (consult--with-increased-gc
   3373      (consult--prompt
   3374       :prompt
   3375       (if consult--focus-lines-overlays
   3376           "Focus on lines (RET to reveal): "
   3377         "Focus on lines: ")
   3378       :initial initial
   3379       :history 'consult--keep-lines-history
   3380       :state (consult--focus-lines-state filter)))))
   3381 
   3382 ;;;;; Command: consult-goto-line
   3383 
   3384 (defun consult--goto-line-position (str msg)
   3385   "Transform input STR to line number.
   3386 Print an error message with MSG function."
   3387   (if-let (line (and str
   3388                      (string-match-p "\\`[[:digit:]]+\\'" str)
   3389                      (string-to-number str)))
   3390       (let ((pos (save-excursion
   3391                    (save-restriction
   3392                      (when consult-line-numbers-widen
   3393                        (widen))
   3394                      (goto-char (point-min))
   3395                      (forward-line (1- line))
   3396                      (point)))))
   3397         (if (consult--in-range-p pos)
   3398             pos
   3399           (funcall msg "Line number out of range.")
   3400           nil))
   3401     (when (and str (not (string= str "")))
   3402       (funcall msg "Please enter a number."))
   3403     nil))
   3404 
   3405 ;;;###autoload
   3406 (defun consult-goto-line (&optional arg)
   3407   "Read line number and jump to the line with preview.
   3408 
   3409 Jump directly if a line number is given as prefix ARG. The command respects
   3410 narrowing and the settings `consult-goto-line-numbers' and
   3411 `consult-line-numbers-widen'."
   3412   (interactive "P")
   3413   (if arg
   3414       (call-interactively #'goto-line)
   3415     (consult--forbid-minibuffer)
   3416     (consult--local-let ((display-line-numbers consult-goto-line-numbers)
   3417                          (display-line-numbers-widen consult-line-numbers-widen))
   3418       (while (if-let (pos (consult--goto-line-position
   3419                            (consult--prompt
   3420                             :prompt "Go to line: "
   3421                             ;; goto-line-history is available on Emacs 28
   3422                             :history
   3423                             (and (boundp 'goto-line-history) 'goto-line-history)
   3424                             :state
   3425                             (let ((preview (consult--jump-preview)))
   3426                               (lambda (action str)
   3427                                 (funcall preview action
   3428                                          (consult--goto-line-position str #'ignore)))))
   3429                            #'minibuffer-message))
   3430                  (consult--jump pos)
   3431                t)))))
   3432 
   3433 ;;;;; Command: consult-recent-file
   3434 
   3435 (defun consult--file-preview ()
   3436   "Create preview function for files."
   3437   (let ((open (consult--temporary-files))
   3438         (preview (consult--buffer-preview)))
   3439     (lambda (action cand)
   3440       (unless cand
   3441         (funcall open))
   3442       (funcall preview action
   3443                (and cand
   3444                     (eq action 'preview)
   3445                     (funcall open cand))))))
   3446 
   3447 (defun consult--file-action (file)
   3448   "Open FILE via `consult--buffer-action'."
   3449   (consult--buffer-action (find-file-noselect file)))
   3450 
   3451 (consult--define-state file)
   3452 
   3453 ;;;###autoload
   3454 (defun consult-recent-file ()
   3455   "Find recent file using `completing-read'."
   3456   (interactive)
   3457   (find-file
   3458    (consult--read
   3459     (or (mapcar #'abbreviate-file-name recentf-list)
   3460         (user-error "No recent files, `recentf-mode' is %s"
   3461                     (if recentf-mode "on" "off")))
   3462     :prompt "Find recent file: "
   3463     :sort nil
   3464     :require-match t
   3465     :category 'file
   3466     :state (consult--file-preview)
   3467     :history 'file-name-history)))
   3468 
   3469 ;;;;; Command: consult-file-externally
   3470 
   3471 ;;;###autoload
   3472 (defun consult-file-externally (file)
   3473   "Open FILE externally using the default application of the system."
   3474   (interactive "fOpen externally: ")
   3475   (if (and (eq system-type 'windows-nt)
   3476            (fboundp 'w32-shell-execute))
   3477       (w32-shell-execute "open" file)
   3478     (call-process (pcase system-type
   3479                     ('darwin "open")
   3480                     ('cygwin "cygstart")
   3481                     (_ "xdg-open"))
   3482                   nil 0 nil
   3483                   (expand-file-name file))))
   3484 
   3485 ;;;;; Command: consult-mode-command
   3486 
   3487 (defun consult--mode-name (mode)
   3488   "Return name part of MODE."
   3489   (replace-regexp-in-string
   3490    "global-\\(.*\\)-mode" "\\1"
   3491    (replace-regexp-in-string
   3492     "\\(-global\\)?-mode\\'" ""
   3493     (if (eq mode 'c-mode)
   3494         "cc"
   3495       (symbol-name mode))
   3496     'fixedcase)
   3497    'fixedcase))
   3498 
   3499 (defun consult--mode-command-candidates (modes)
   3500   "Extract commands from MODES.
   3501 
   3502 The list of features is searched for files belonging to the modes.
   3503 From these files, the commands are extracted."
   3504   (let* ((buffer (current-buffer))
   3505          (command-filter (consult--regexp-filter (seq-filter #'stringp consult-mode-command-filter)))
   3506          (feature-filter (seq-filter #'symbolp consult-mode-command-filter))
   3507          (minor-hash (consult--string-hash minor-mode-list))
   3508          (minor-local-modes (seq-filter (lambda (m)
   3509                                           (and (gethash m minor-hash)
   3510                                                (local-variable-if-set-p m)))
   3511                                         modes))
   3512          (minor-global-modes (seq-filter (lambda (m)
   3513                                            (and (gethash m minor-hash)
   3514                                                 (not (local-variable-if-set-p m))))
   3515                                          modes))
   3516          (major-modes (seq-remove (lambda (m)
   3517                                     (gethash m minor-hash))
   3518                                   modes))
   3519          (major-paths-hash (consult--string-hash (mapcar #'symbol-file major-modes)))
   3520          (minor-local-paths-hash (consult--string-hash (mapcar #'symbol-file minor-local-modes)))
   3521          (minor-global-paths-hash (consult--string-hash (mapcar #'symbol-file minor-global-modes)))
   3522          (major-name-regexp (regexp-opt (mapcar #'consult--mode-name major-modes)))
   3523          (minor-local-name-regexp (regexp-opt (mapcar #'consult--mode-name minor-local-modes)))
   3524          (minor-global-name-regexp (regexp-opt (mapcar #'consult--mode-name minor-global-modes)))
   3525          (commands))
   3526     (dolist (feature load-history commands)
   3527       (when-let (name (alist-get 'provide feature))
   3528         (let* ((path (car feature))
   3529                (file (file-name-nondirectory path))
   3530                (key (cond
   3531                      ((memq name feature-filter) nil)
   3532                      ((or (gethash path major-paths-hash)
   3533                           (string-match-p major-name-regexp file))
   3534                       ?m)
   3535                      ((or (gethash path minor-local-paths-hash)
   3536                           (string-match-p minor-local-name-regexp file))
   3537                       ?l)
   3538                      ((or (gethash path minor-global-paths-hash)
   3539                           (string-match-p minor-global-name-regexp file))
   3540                       ?g))))
   3541           (when key
   3542             (dolist (cmd (cdr feature))
   3543               (let ((sym (cdr-safe cmd)))
   3544                 (when (and (consp cmd)
   3545                            (eq (car cmd) 'defun)
   3546                            (commandp sym)
   3547                            (not (get sym 'byte-obsolete-info))
   3548                            ;; Emacs 28 has a `read-extended-command-predicate'
   3549                            (if (bound-and-true-p read-extended-command-predicate)
   3550                                (funcall read-extended-command-predicate sym buffer)
   3551                              t))
   3552                   (let ((name (symbol-name sym)))
   3553                     (unless (string-match-p command-filter name)
   3554                       (push (propertize name
   3555                                         'consult--candidate sym
   3556                                         'consult--type key)
   3557                             commands))))))))))))
   3558 
   3559 ;;;###autoload
   3560 (defun consult-mode-command (&rest modes)
   3561   "Run a command from any of the given MODES.
   3562 
   3563 If no MODES are specified, use currently active major and minor modes."
   3564   (interactive)
   3565   (unless modes
   3566     (setq modes (cons major-mode
   3567                       (seq-filter (lambda (m)
   3568                                     (and (boundp m) (symbol-value m)))
   3569                                   minor-mode-list))))
   3570   (let ((narrow `((?m . ,(format "Major: %s" major-mode))
   3571                   (?l . "Local Minor")
   3572                   (?g . "Global Minor"))))
   3573     (command-execute
   3574      (consult--read
   3575       (consult--mode-command-candidates modes)
   3576       :prompt "Mode command: "
   3577       :predicate
   3578       (lambda (cand)
   3579         (let ((key (get-text-property 0 'consult--type cand)))
   3580           (if consult--narrow
   3581               (= key consult--narrow)
   3582             (/= key ?g))))
   3583       :lookup #'consult--lookup-candidate
   3584       :group (consult--type-group narrow)
   3585       :narrow narrow
   3586       :require-match t
   3587       :history 'extended-command-history
   3588       :category 'command))))
   3589 
   3590 ;;;;; Command: consult-yank
   3591 
   3592 (defun consult--read-from-kill-ring ()
   3593   "Open kill ring menu and return selected string."
   3594   ;; `current-kill' updates `kill-ring' with a possible interprogram-paste (#443)
   3595   (current-kill 0)
   3596   ;; Do not specify a :lookup function in order to preserve completion-styles
   3597   ;; highlighting of the current candidate. We have to perform a final lookup
   3598   ;; to obtain the original candidate which may be propertized with
   3599   ;; yank-specific properties, like 'yank-handler.
   3600   (consult--lookup-member
   3601    (consult--read
   3602     (consult--remove-dups
   3603      (or kill-ring (user-error "Kill ring is empty")))
   3604     :prompt "Yank from kill-ring: "
   3605     :history t ;; disable history
   3606     :sort nil
   3607     :category 'kill-ring
   3608     :require-match t
   3609     :state
   3610     (consult--insertion-preview
   3611      (point)
   3612      ;; If previous command is yank, hide previously yanked string
   3613      (or (and (eq last-command 'yank) (mark t)) (point))))
   3614    kill-ring))
   3615 
   3616 ;; Adapted from the Emacs `yank-from-kill-ring' function.
   3617 ;;;###autoload
   3618 (defun consult-yank-from-kill-ring (string &optional arg)
   3619   "Select STRING from the kill ring and insert it.
   3620 With prefix ARG, put point at beginning, and mark at end, like `yank' does.
   3621 
   3622 This command behaves like `yank-from-kill-ring' in Emacs 28, which also offers
   3623 a `completing-read' interface to the `kill-ring'. Additionally the Consult
   3624 version supports preview of the selected string."
   3625   (interactive (list (consult--read-from-kill-ring) current-prefix-arg))
   3626   (when string
   3627     (setq yank-window-start (window-start))
   3628     (push-mark)
   3629     (insert-for-yank string)
   3630     (setq this-command 'yank)
   3631     (when (consp arg)
   3632       ;; Swap point and mark like in `yank'.
   3633       (goto-char (prog1 (mark t)
   3634                    (set-marker (mark-marker) (point) (current-buffer)))))))
   3635 
   3636 (put 'consult-yank-replace 'delete-selection 'yank)
   3637 (put 'consult-yank-pop 'delete-selection 'yank)
   3638 (put 'consult-yank-from-kill-ring 'delete-selection 'yank)
   3639 
   3640 ;;;###autoload
   3641 (defun consult-yank-pop (&optional arg)
   3642   "If there is a recent yank act like `yank-pop'.
   3643 
   3644 Otherwise select string from the kill ring and insert it.
   3645 See `yank-pop' for the meaning of ARG.
   3646 
   3647 This command behaves like `yank-pop' in Emacs 28, which also offers a
   3648 `completing-read' interface to the `kill-ring'. Additionally the Consult
   3649 version supports preview of the selected string."
   3650   (interactive "*p")
   3651   (if (eq last-command 'yank)
   3652       (yank-pop (or arg 1))
   3653     (call-interactively #'consult-yank-from-kill-ring)))
   3654 
   3655 ;; Adapted from the Emacs yank-pop function.
   3656 ;;;###autoload
   3657 (defun consult-yank-replace (string)
   3658   "Select STRING from the kill ring.
   3659 
   3660 If there was no recent yank, insert the string.
   3661 Otherwise replace the just-yanked string with the selected string.
   3662 
   3663 There exists no equivalent of this command in Emacs 28."
   3664   (interactive (list (consult--read-from-kill-ring)))
   3665   (when string
   3666     (if (not (eq last-command 'yank))
   3667         (consult-yank-from-kill-ring string)
   3668       (let ((inhibit-read-only t)
   3669             (pt (point))
   3670             (mk (mark t)))
   3671         (setq this-command 'yank)
   3672         (funcall (or yank-undo-function 'delete-region) (min pt mk) (max pt mk))
   3673         (setq yank-undo-function nil)
   3674         (set-marker (mark-marker) pt (current-buffer))
   3675         (insert-for-yank string)
   3676         (set-window-start (selected-window) yank-window-start t)
   3677         (if (< pt mk)
   3678             (goto-char (prog1 (mark t)
   3679                          (set-marker (mark-marker) (point) (current-buffer)))))))))
   3680 
   3681 ;;;;; Command: consult-bookmark
   3682 
   3683 (defun consult--bookmark-preview ()
   3684   "Create preview function for bookmarks."
   3685   (let ((preview (consult--jump-preview))
   3686         (open (consult--temporary-files)))
   3687     (lambda (action cand)
   3688       (unless cand
   3689         (funcall open))
   3690       (funcall
   3691        preview action
   3692        (when-let (bm (and cand (eq action 'preview) (assoc cand bookmark-alist)))
   3693          (let ((handler (or (bookmark-get-handler bm) #'bookmark-default-handler)))
   3694            ;; Only preview bookmarks with the default handler.
   3695            (if-let* ((file (and (eq handler #'bookmark-default-handler)
   3696                                 (bookmark-get-filename bm)))
   3697                      (pos (bookmark-get-position bm))
   3698                      (buf (funcall open file)))
   3699                (set-marker (make-marker) pos buf)
   3700              (message "No preview for %s" handler)
   3701              nil)))))))
   3702 
   3703 (defun consult--bookmark-action (bm)
   3704   "Open BM via `consult--buffer-action'."
   3705   (bookmark-jump bm consult--buffer-display))
   3706 
   3707 (consult--define-state bookmark)
   3708 
   3709 (defun consult--bookmark-candidates ()
   3710   "Return bookmark candidates."
   3711   (bookmark-maybe-load-default-file)
   3712   (let ((narrow (mapcar (pcase-lambda (`(,y ,_ ,x)) (cons x y))
   3713                         consult-bookmark-narrow)))
   3714     (mapcar (lambda (cand)
   3715               (propertize (car cand)
   3716                           'consult--type
   3717                           (alist-get
   3718                            (or (bookmark-get-handler cand) #'bookmark-default-handler)
   3719                            narrow)))
   3720             bookmark-alist)))
   3721 
   3722 ;;;###autoload
   3723 (defun consult-bookmark (name)
   3724   "If bookmark NAME exists, open it, otherwise create a new bookmark with NAME.
   3725 
   3726 The command supports preview of file bookmarks and narrowing. See the
   3727 variable `consult-bookmark-narrow' for the narrowing configuration."
   3728   (interactive
   3729    (list
   3730     (let ((narrow (mapcar (pcase-lambda (`(,x ,y ,_)) (cons x y))
   3731                           consult-bookmark-narrow)))
   3732       (consult--read
   3733        (consult--bookmark-candidates)
   3734        :prompt "Bookmark: "
   3735        :state (consult--bookmark-preview)
   3736        :category 'bookmark
   3737        :history 'bookmark-history
   3738        ;; Add default names to future history.
   3739        ;; Ignore errors such that `consult-bookmark' can be used in
   3740        ;; buffers which are not backed by a file.
   3741        :add-history (ignore-errors (bookmark-prop-get (bookmark-make-record) 'defaults))
   3742        :group (consult--type-group narrow)
   3743        :narrow (consult--type-narrow narrow)))))
   3744   (bookmark-maybe-load-default-file)
   3745   (if (assoc name bookmark-alist)
   3746       (bookmark-jump name)
   3747     (bookmark-set name)))
   3748 
   3749 ;;;;; Command: consult-apropos
   3750 
   3751 ;;;###autoload
   3752 (defun consult-apropos ()
   3753   "Select pattern and call `apropos'.
   3754 
   3755 The default value of the completion is the symbol at point. As a better
   3756 alternative, you can run `embark-export' from commands like `M-x' and
   3757 `describe-symbol'."
   3758   (interactive)
   3759   (let ((pattern
   3760          (consult--read
   3761           obarray
   3762           :prompt "Apropos: "
   3763           :predicate (lambda (x) (or (fboundp x) (boundp x) (facep x) (symbol-plist x)))
   3764           :history 'consult--apropos-history
   3765           :category 'symbol
   3766           :default (thing-at-point 'symbol))))
   3767     (when (string= pattern "")
   3768       (user-error "No pattern given"))
   3769     (apropos pattern)))
   3770 
   3771 ;;;;; Command: consult-complex-command
   3772 
   3773 ;;;###autoload
   3774 (defun consult-complex-command ()
   3775   "Select and evaluate command from the command history.
   3776 
   3777 This command can act as a drop-in replacement for `repeat-complex-command'."
   3778   (interactive)
   3779   (let* ((history (or (delete-dups (mapcar #'prin1-to-string command-history))
   3780                       (user-error "There are no previous complex commands")))
   3781          (cmd (read (consult--read
   3782                      history
   3783                      :prompt "Command: "
   3784                      :default (car history)
   3785                      :sort nil
   3786                      :history t ;; disable history
   3787                      :category 'expression))))
   3788     ;; Taken from `repeat-complex-command'
   3789     (add-to-history 'command-history cmd)
   3790     (apply #'funcall-interactively
   3791            (car cmd)
   3792            (mapcar (lambda (e) (eval e t)) (cdr cmd)))))
   3793 
   3794 ;;;;; Command: consult-history
   3795 
   3796 (declare-function ring-elements "ring")
   3797 (defun consult--current-history (&optional history)
   3798   "Return the normalized HISTORY or the history relevant to the current buffer.
   3799 
   3800 If the minibuffer is active, returns the minibuffer history,
   3801 otherwise the history corresponding to the mode is returned.
   3802 There is a special case for `repeat-complex-command',
   3803 for which the command history is used."
   3804   (cond
   3805    (history)
   3806    ;; If pressing "C-x M-:", i.e., `repeat-complex-command',
   3807    ;; we are instead querying the `command-history' and get a full s-expression.
   3808    ;; Alternatively you might want to use `consult-complex-command',
   3809    ;; which can also be bound to "C-x M-:"!
   3810    ((eq last-command 'repeat-complex-command)
   3811     (setq history (mapcar #'prin1-to-string command-history)))
   3812    ;; In the minibuffer we use the current minibuffer history,
   3813    ;; which can be configured by setting `minibuffer-history-variable'.
   3814    ((minibufferp)
   3815     (if (eq minibuffer-history-variable t)
   3816         (user-error "Minibuffer history is disabled for `%s'" this-command)
   3817       (setq history (symbol-value minibuffer-history-variable))))
   3818    ;; Otherwise we use a mode-specific history, see `consult-mode-histories'.
   3819    (t (when-let (found
   3820                  (or (seq-find (lambda (ring)
   3821                                  (and (derived-mode-p (car ring))
   3822                                       (boundp (cdr ring))))
   3823                                consult-mode-histories)
   3824                      (user-error
   3825                       "No history configured for `%s', see `consult-mode-histories'"
   3826                       major-mode)))
   3827         (setq history (symbol-value (cdr found))))))
   3828   (consult--remove-dups (if (ring-p history) (ring-elements history) history)))
   3829 
   3830 ;; This command has been adopted from https://github.com/oantolin/completing-history/.
   3831 ;;;###autoload
   3832 (defun consult-history (&optional history)
   3833   "Insert string from HISTORY of current buffer.
   3834 
   3835 In order to select from a specific HISTORY, pass the history variable
   3836 as argument."
   3837   (interactive)
   3838   (let ((str (consult--local-let ((enable-recursive-minibuffers t))
   3839                (consult--read
   3840                 (or (consult--current-history history)
   3841                     (user-error "History is empty"))
   3842                 :prompt "History: "
   3843                 :history t ;; disable history
   3844                 :category ;; Report command category for M-x history
   3845                 (and (minibufferp)
   3846                      (eq minibuffer-history-variable 'extended-command-history)
   3847                      'command)
   3848                 :sort nil
   3849                 :state (consult--insertion-preview (point) (point))))))
   3850     (when (minibufferp)
   3851       (delete-minibuffer-contents))
   3852     (insert (substring-no-properties str))))
   3853 
   3854 ;;;;; Command: consult-isearch-history
   3855 
   3856 (defun consult-isearch-forward (&optional reverse)
   3857   "Continue isearch forward optionally in REVERSE."
   3858   (interactive)
   3859   (consult--require-minibuffer)
   3860   (setq isearch-new-forward (not reverse) isearch-new-nonincremental nil)
   3861   (funcall (or (command-remapping #'exit-minibuffer) #'exit-minibuffer)))
   3862 
   3863 (defun consult-isearch-backward (&optional reverse)
   3864   "Continue isearch backward optionally in REVERSE."
   3865   (interactive)
   3866   (consult-isearch-forward (not reverse)))
   3867 
   3868 ;; Emacs 28: hide in M-X
   3869 (put #'consult-isearch-backward 'completion-predicate #'ignore)
   3870 (put #'consult-isearch-forward 'completion-predicate #'ignore)
   3871 
   3872 (defvar consult-isearch-history-map
   3873   (let ((map (make-sparse-keymap)))
   3874     (define-key map [remap isearch-forward] #'consult-isearch-forward)
   3875     (define-key map [remap isearch-backward] #'consult-isearch-backward)
   3876     map)
   3877   "Additional keymap used by `consult-isearch-history'.")
   3878 
   3879 (defun consult--isearch-history-candidates ()
   3880   "Return isearch history candidates."
   3881   ;; NOTE: Do not throw an error on empty history,
   3882   ;; in order to allow starting a search.
   3883   ;; We do not :require-match here!
   3884   (let ((history (if (eq t search-default-mode)
   3885                      (append regexp-search-ring search-ring)
   3886                    (append search-ring regexp-search-ring))))
   3887     (cons
   3888      (delete-dups
   3889       (mapcar
   3890        (lambda (cand)
   3891          ;; The search type can be distinguished via text properties.
   3892          (let* ((props (plist-member (text-properties-at 0 cand)
   3893                                      'isearch-regexp-function))
   3894                 (type (pcase (cadr props)
   3895                         ((and 'nil (guard (not props))) ?r)
   3896                         ('nil                           ?l)
   3897                         ('word-search-regexp            ?w)
   3898                         ('isearch-symbol-regexp         ?s)
   3899                         ('char-fold-to-regexp           ?c)
   3900                         (_                              ?u))))
   3901            ;; Disambiguate history items. The same string could
   3902            ;; occur with different search types.
   3903            (consult--tofu-append cand type)))
   3904        history))
   3905      (if history
   3906          (+ 4 (apply #'max (mapcar #'length history)))
   3907        0))))
   3908 
   3909 (defconst consult--isearch-history-narrow
   3910   '((?c . "Char")
   3911     (?u . "Custom")
   3912     (?l . "Literal")
   3913     (?r . "Regexp")
   3914     (?s . "Symbol")
   3915     (?w . "Word")))
   3916 
   3917 ;;;###autoload
   3918 (defun consult-isearch-history ()
   3919   "Read a search string with completion from the Isearch history.
   3920 
   3921 This replaces the current search string if Isearch is active, and
   3922 starts a new Isearch session otherwise."
   3923   (interactive)
   3924   (consult--forbid-minibuffer)
   3925   (let* ((isearch-message-function 'ignore) ;; Avoid flicker in echo area
   3926          (inhibit-redisplay t)              ;; Avoid flicker in mode line
   3927          (candidates (consult--isearch-history-candidates))
   3928          (align (propertize " " 'display `(space :align-to (+ left ,(cdr candidates))))))
   3929     (unless isearch-mode (isearch-mode t))
   3930     (with-isearch-suspended
   3931      (setq isearch-new-string
   3932            (consult--read
   3933             (car candidates)
   3934             :prompt "I-search: "
   3935             :category 'consult-isearch
   3936             :history t ;; disable history
   3937             :sort nil
   3938             :initial isearch-string
   3939             :keymap consult-isearch-history-map
   3940             :annotate
   3941             (lambda (cand)
   3942               (concat align (alist-get (consult--tofu-get cand) consult--isearch-history-narrow)))
   3943             :group
   3944             (lambda (cand transform)
   3945               (if transform
   3946                   cand
   3947                 (alist-get (consult--tofu-get cand) consult--isearch-history-narrow)))
   3948             :lookup
   3949             (lambda (selected candidates &rest _)
   3950               (if-let (found (member selected candidates))
   3951                   (substring (car found) 0 -1)
   3952                 selected))
   3953             :state
   3954             (lambda (action cand)
   3955               (when (and (eq action 'preview) cand)
   3956                 (setq isearch-string cand)
   3957                 (isearch-update-from-string-properties cand)
   3958                 (isearch-update)))
   3959             :narrow
   3960             (list :predicate
   3961                   (lambda (cand) (= (consult--tofu-get cand) consult--narrow))
   3962                   :keys consult--isearch-history-narrow))
   3963            isearch-new-message
   3964            (mapconcat 'isearch-text-char-description isearch-new-string "")))
   3965     ;; Setting `isearch-regexp' etc only works outside of `with-isearch-suspended'.
   3966     (unless (plist-member (text-properties-at 0 isearch-string) 'isearch-regexp-function)
   3967       (setq isearch-regexp t
   3968             isearch-regexp-function nil))))
   3969 
   3970 ;;;;; Command: consult-minor-mode-menu
   3971 
   3972 (defun consult--minor-mode-candidates ()
   3973   "Return list of minor-mode candidate strings."
   3974   (mapcar
   3975    (pcase-lambda (`(,name . ,sym))
   3976      (propertize
   3977       name
   3978       'consult--candidate sym
   3979       'consult--minor-mode-narrow
   3980       (logior
   3981        (lsh (if (local-variable-if-set-p sym) ?l ?g) 8)
   3982        (if (and (boundp sym) (symbol-value sym)) ?i ?o))
   3983       'consult--minor-mode-group
   3984       (concat
   3985        (if (local-variable-if-set-p sym) "Local " "Global ")
   3986        (if (and (boundp sym) (symbol-value sym)) "On" "Off"))))
   3987    (nconc
   3988     ;; according to describe-minor-mode-completion-table-for-symbol
   3989     ;; the minor-mode-list contains *all* minor modes
   3990     (mapcar (lambda (sym) (cons (symbol-name sym) sym)) minor-mode-list)
   3991     ;; take the lighters from minor-mode-alist
   3992     (delq nil
   3993           (mapcar (pcase-lambda (`(,sym ,lighter))
   3994                     (when (and lighter (not (equal "" lighter)))
   3995                       (setq lighter (string-trim (format-mode-line lighter)))
   3996                       (unless (string-blank-p lighter)
   3997                         (cons lighter sym))))
   3998                   minor-mode-alist)))))
   3999 
   4000 (defconst consult--minor-mode-menu-narrow
   4001   '((?l . "Local")
   4002     (?g . "Global")
   4003     (?i . "On")
   4004     (?o . "Off")))
   4005 
   4006 ;;;###autoload
   4007 (defun consult-minor-mode-menu ()
   4008   "Enable or disable minor mode.
   4009 
   4010 This is an alternative to `minor-mode-menu-from-indicator'."
   4011   (interactive)
   4012   (call-interactively
   4013    (consult--read
   4014     (consult--minor-mode-candidates)
   4015     :prompt "Minor mode: "
   4016     :require-match t
   4017     :category 'minor-mode
   4018     :group
   4019     (lambda (cand transform)
   4020       (if transform cand (get-text-property 0 'consult--minor-mode-group cand)))
   4021     :narrow
   4022     (list :predicate
   4023           (lambda (cand)
   4024             (let ((narrow (get-text-property 0 'consult--minor-mode-narrow cand)))
   4025               (or (= (logand narrow 255) consult--narrow)
   4026                   (= (lsh narrow -8) consult--narrow))))
   4027           :keys
   4028           consult--minor-mode-menu-narrow)
   4029     :lookup #'consult--lookup-candidate
   4030     :history 'consult--minor-mode-menu-history)))
   4031 
   4032 ;;;;; Command: consult-theme
   4033 
   4034 ;;;###autoload
   4035 (defun consult-theme (theme)
   4036   "Disable current themes and enable THEME from `consult-themes'.
   4037 
   4038 The command supports previewing the currently selected theme."
   4039   (interactive
   4040    (list
   4041     (let ((avail-themes
   4042            (seq-filter (lambda (x) (or (not consult-themes)
   4043                                        (memq x consult-themes)))
   4044                        (cons 'default (custom-available-themes))))
   4045           (saved-theme
   4046            (car custom-enabled-themes)))
   4047       (consult--read
   4048        (mapcar #'symbol-name avail-themes)
   4049        :prompt "Theme: "
   4050        :require-match t
   4051        :category 'theme
   4052        :history 'consult--theme-history
   4053        :lookup (lambda (selected &rest _)
   4054                  (setq selected (and selected (intern-soft selected)))
   4055                  (or (and selected (car (memq selected avail-themes)))
   4056                      saved-theme))
   4057        :state (lambda (action theme)
   4058                 (pcase action
   4059                   ('return (consult-theme (or theme saved-theme)))
   4060                   ((and 'preview (guard theme)) (consult-theme theme))))
   4061        :default (symbol-name (or saved-theme 'default))))))
   4062   (when (eq theme 'default) (setq theme nil))
   4063   (unless (eq theme (car custom-enabled-themes))
   4064     (mapc #'disable-theme custom-enabled-themes)
   4065     (when theme
   4066       (if (custom-theme-p theme)
   4067           (enable-theme theme)
   4068         (load-theme theme :no-confirm)))))
   4069 
   4070 ;;;;; Command: consult-buffer
   4071 
   4072 (defun consult--buffer-sort-alpha (buffers)
   4073   "Sort BUFFERS alphabetically, but push down starred buffers."
   4074   (sort buffers
   4075         (lambda (x y)
   4076           (setq x (buffer-name x) y (buffer-name y))
   4077           (let ((a (and (> (length x) 0) (eq (aref x 0) ?*)))
   4078                 (b (and (> (length y) 0) (eq (aref y 0) ?*))))
   4079             (if (eq a b)
   4080                 (string< x y)
   4081               (not a))))))
   4082 
   4083 (defun consult--buffer-sort-visibility (buffers)
   4084   "Sort BUFFERS by visibility."
   4085   (let ((hidden)
   4086         (current (current-buffer)))
   4087     (consult--keep! buffers
   4088       (unless (eq it current)
   4089         (if (get-buffer-window it 'visible)
   4090             it
   4091           (push it hidden)
   4092           nil)))
   4093     (nconc (nreverse hidden) buffers (list (current-buffer)))))
   4094 
   4095 (defun consult--normalize-directory (dir)
   4096   "Normalize directory DIR.
   4097 DIR can be project, nil or a path."
   4098   (cond
   4099     ((eq dir 'project) (consult--project-root))
   4100     (dir (expand-file-name dir))))
   4101 
   4102 (defun consult--buffer-query-prompt (prompt query)
   4103   "Buffer query function returning a scope description.
   4104 PROMPT is the prompt format string.
   4105 QUERY is passed to `consult--buffer-query'."
   4106   (let* ((dir (plist-get query :directory))
   4107          (ndir (consult--normalize-directory dir))
   4108          (buffers (apply #'consult--buffer-query :directory ndir query))
   4109          (count (length buffers)))
   4110     (cons (format "%s (%d buffer%s%s): " prompt count
   4111                   (if (= count 1) "" "s")
   4112                   (cond
   4113                    ((and ndir (eq dir 'project))
   4114                     (format ", Project %s" (consult--project-name ndir)))
   4115                    (ndir (concat  ", " (consult--abbreviate-directory ndir)))
   4116                    (t "")))
   4117           buffers)))
   4118 
   4119 (cl-defun consult--buffer-query (&key sort directory mode as predicate (filter t)
   4120                                       include (exclude consult-buffer-filter))
   4121   "Buffer query function.
   4122 DIRECTORY can either be project or a path.
   4123 SORT can be visibility, alpha or nil.
   4124 FILTER can be either t, nil or invert.
   4125 EXCLUDE is a list of regexps.
   4126 INCLUDE is a list of regexps.
   4127 MODE can be a mode or a list of modes to restrict the returned buffers.
   4128 PREDICATE is a predicate function.
   4129 AS is a conversion function."
   4130   ;; This function is the backbone of most `consult-buffer' source. The
   4131   ;; function supports filtering by various criteria which are used throughout
   4132   ;; Consult.
   4133   (let ((root (consult--normalize-directory directory))
   4134         (buffers (buffer-list)))
   4135     (when sort
   4136       (setq buffers (funcall (intern (format "consult--buffer-sort-%s" sort)) buffers)))
   4137     (when (or filter mode as root)
   4138       (let ((mode (consult--ensure-list mode))
   4139             (exclude-re (consult--regexp-filter exclude))
   4140             (include-re (consult--regexp-filter include)))
   4141         (consult--keep! buffers
   4142           (and
   4143            (or (not mode)
   4144                (apply #'provided-mode-derived-p
   4145                       (buffer-local-value 'major-mode it) mode))
   4146            (pcase-exhaustive filter
   4147              ('nil t)
   4148              ((or 't 'invert)
   4149               (eq (eq filter t)
   4150                   (and
   4151                    (or (not exclude)
   4152                        (not (string-match-p exclude-re (buffer-name it))))
   4153                    (or (not include)
   4154                        (not (not (string-match-p include-re (buffer-name it)))))))))
   4155            (or (not root)
   4156                (when-let (dir (buffer-local-value 'default-directory it))
   4157                  (string-prefix-p root
   4158                                   (if (and (/= 0 (length dir)) (eq (aref dir 0) ?/))
   4159                                       dir
   4160                                     (expand-file-name dir)))))
   4161            (or (not predicate) (funcall predicate it))
   4162            (if as (funcall as it) it)))))
   4163     buffers))
   4164 
   4165 (defun consult--buffer-map (buffer &rest app)
   4166   "Run function application APP for each BUFFER.
   4167 Report progress and return a list of the results"
   4168   (consult--with-increased-gc
   4169    (let* ((count (length buffer))
   4170           (reporter (make-progress-reporter "Collecting" 0 count)))
   4171      (prog1
   4172          (seq-map-indexed (lambda (buf idx)
   4173                             (with-current-buffer buf
   4174                               (prog1 (apply app)
   4175                                 (progress-reporter-update
   4176                                  reporter (1+ idx) (buffer-name)))))
   4177                  buffer)
   4178        (progress-reporter-done reporter)))))
   4179 
   4180 (defun consult--buffer-file-hash ()
   4181   "Return hash table of all buffer file names."
   4182   (consult--string-hash (consult--buffer-query :as #'buffer-file-name)))
   4183 
   4184 (defun consult--buffer-preview ()
   4185   "Buffer preview function."
   4186   ;; Only preview in current window and other window.
   4187   ;; Preview in frames and tabs is not possible since these don't get cleaned up.
   4188   (if (memq consult--buffer-display
   4189             '(switch-to-buffer switch-to-buffer-other-window))
   4190       (let ((orig-buf (current-buffer)) other-win)
   4191         (lambda (action cand)
   4192           (when (eq action 'preview)
   4193             (when (and (eq consult--buffer-display #'switch-to-buffer-other-window)
   4194                        (not other-win))
   4195               (switch-to-buffer-other-window orig-buf)
   4196               (setq other-win (selected-window)))
   4197             (let ((win (or other-win (selected-window))))
   4198               (when (window-live-p win)
   4199                 (with-selected-window win
   4200                   (cond
   4201                    ((and cand (get-buffer cand))
   4202                     (switch-to-buffer cand 'norecord))
   4203                    ((buffer-live-p orig-buf)
   4204                     (switch-to-buffer orig-buf 'norecord)))))))))
   4205     #'ignore))
   4206 
   4207 (defun consult--buffer-action (buffer &optional norecord)
   4208   "Switch to BUFFER via `consult--buffer-display' function.
   4209 If NORECORD is non-nil, do not record the buffer switch in the buffer list."
   4210   (funcall consult--buffer-display buffer norecord))
   4211 
   4212 (consult--define-state buffer)
   4213 
   4214 (defvar consult--source-bookmark
   4215   `(:name     "Bookmark"
   4216     :narrow   ?m
   4217     :category bookmark
   4218     :face     consult-bookmark
   4219     :history  bookmark-history
   4220     :items    ,#'bookmark-all-names
   4221     :state    ,#'consult--bookmark-state)
   4222   "Bookmark candidate source for `consult-buffer'.")
   4223 
   4224 (defvar consult--source-project-buffer
   4225   `(:name     "Project Buffer"
   4226     :narrow   (?p . "Project")
   4227     :hidden   t
   4228     :category buffer
   4229     :face     consult-buffer
   4230     :history  buffer-name-history
   4231     :state    ,#'consult--buffer-state
   4232     :enabled  ,(lambda () consult-project-function)
   4233     :items
   4234     ,(lambda ()
   4235        (when-let (root (consult--project-root))
   4236          (consult--buffer-query :sort 'visibility
   4237                                 :directory root
   4238                                 :as #'buffer-name))))
   4239   "Project buffer candidate source for `consult-buffer'.")
   4240 
   4241 (defvar consult--source-project-recent-file
   4242   `(:name     "Project File"
   4243     :narrow   (?p . "Project")
   4244     :hidden   t
   4245     :category file
   4246     :face     consult-file
   4247     :history  file-name-history
   4248     :state    ,#'consult--file-state
   4249     :new
   4250     ,(lambda (file)
   4251        (consult--file-action
   4252         (expand-file-name file (consult--project-root))))
   4253     :enabled
   4254     ,(lambda ()
   4255        (and consult-project-function
   4256             recentf-mode))
   4257     :items
   4258     ,(lambda ()
   4259       (when-let (root (consult--project-root))
   4260         (let ((len (length root))
   4261               (ht (consult--buffer-file-hash)))
   4262           (mapcar (lambda (file)
   4263                     (let ((part (substring file len)))
   4264                       (when (equal part "") (setq part "./"))
   4265                       (put-text-property 0 (length part)
   4266                                          'multi-category `(file . ,file) part)
   4267                       part))
   4268                   (seq-filter (lambda (x)
   4269                                 (and (not (gethash x ht))
   4270                                      (string-prefix-p root x)))
   4271                               recentf-list))))))
   4272   "Project file candidate source for `consult-buffer'.")
   4273 
   4274 (defvar consult--source-hidden-buffer
   4275   `(:name     "Hidden Buffer"
   4276     :narrow   32
   4277     :hidden   t
   4278     :category buffer
   4279     :face     consult-buffer
   4280     :history  buffer-name-history
   4281     :action   ,#'consult--buffer-action
   4282     :items
   4283     ,(lambda () (consult--buffer-query :sort 'visibility
   4284                                        :filter 'invert
   4285                                        :as #'buffer-name)))
   4286   "Hidden buffer candidate source for `consult-buffer'.")
   4287 
   4288 (defvar consult--source-buffer
   4289   `(:name     "Buffer"
   4290     :narrow   ?b
   4291     :category buffer
   4292     :face     consult-buffer
   4293     :history  buffer-name-history
   4294     :state    ,#'consult--buffer-state
   4295     :default  t
   4296     :items
   4297     ,(lambda () (consult--buffer-query :sort 'visibility
   4298                                        :as #'buffer-name)))
   4299   "Buffer candidate source for `consult-buffer'.")
   4300 
   4301 (defvar consult--source-recent-file
   4302   `(:name     "File"
   4303     :narrow   ?f
   4304     :category file
   4305     :face     consult-file
   4306     :history  file-name-history
   4307     :state    ,#'consult--file-state
   4308     :new      ,#'consult--file-action
   4309     :enabled  ,(lambda () recentf-mode)
   4310     :items
   4311     ,(lambda ()
   4312        (let ((ht (consult--buffer-file-hash)))
   4313          (mapcar #'abbreviate-file-name
   4314                  (seq-remove (lambda (x) (gethash x ht)) recentf-list)))))
   4315   "Recent file candidate source for `consult-buffer'.")
   4316 
   4317 ;;;###autoload
   4318 (defun consult-buffer (&optional sources)
   4319   "Enhanced `switch-to-buffer' command with support for virtual buffers.
   4320 
   4321 The command supports recent files, bookmarks, views and project files as
   4322 virtual buffers. Buffers are previewed. Narrowing to buffers (b), files (f),
   4323 bookmarks (m) and project files (p) is supported via the corresponding
   4324 keys. In order to determine the project-specific files and buffers, the
   4325 `consult-project-function' is used. The virtual buffer SOURCES
   4326 default to `consult-buffer-sources'. See `consult--multi' for the
   4327 configuration of the virtual buffer sources."
   4328   (interactive)
   4329   (let ((selected (consult--multi (or sources consult-buffer-sources)
   4330                                   :require-match
   4331                                   (confirm-nonexistent-file-or-buffer)
   4332                                   :prompt "Switch to: "
   4333                                   :history 'consult--buffer-history
   4334                                   :sort nil)))
   4335     ;; For non-matching candidates, fall back to buffer creation.
   4336     (unless (plist-get (cdr selected) :match)
   4337       (consult--buffer-action (car selected)))))
   4338 
   4339 ;; Populate `consult-project-buffer-sources'.
   4340 (setq consult-project-buffer-sources
   4341       (list
   4342        `(:hidden nil :narrow ?b ,@consult--source-project-buffer)
   4343        `(:hidden nil :narrow ?f ,@consult--source-project-recent-file)))
   4344 
   4345 (defmacro consult--with-project (&rest body)
   4346   "Ensure that BODY is executed with a project root."
   4347   ;; We have to work quite hard here to ensure that the project root is
   4348   ;; only overriden at the current recursion level. When entering a
   4349   ;; recursive minibuffer session, we should be able to still switch the
   4350   ;; project. But who does that? Working on the first level on project A
   4351   ;; and on the second level on project B and on the third level on project C?
   4352   ;; You mustn't be afraid to dream a little bigger, darling.
   4353   `(let ((consult-project-function
   4354           (let ((root (or (consult--project-root t) (user-error "No project found")))
   4355                 (depth (recursion-depth))
   4356                 (orig consult-project-function))
   4357             (lambda (may-prompt)
   4358               (if (= depth (recursion-depth))
   4359                   root
   4360                 (funcall orig may-prompt))))))
   4361      ,@body))
   4362 
   4363 ;;;###autoload
   4364 (defun consult-project-buffer ()
   4365   "Enhanced `project-switch-to-buffer' command with support for virtual buffers.
   4366 The command may prompt you for a project directory if it is invoked from
   4367 outside a project. See `consult-buffer' for more details."
   4368   (interactive)
   4369   (consult--with-project
   4370     (consult-buffer consult-project-buffer-sources)))
   4371 
   4372 ;;;###autoload
   4373 (defun consult-buffer-other-window ()
   4374   "Variant of `consult-buffer' which opens in other window."
   4375   (interactive)
   4376   (let ((consult--buffer-display #'switch-to-buffer-other-window))
   4377     (consult-buffer)))
   4378 
   4379 ;;;###autoload
   4380 (defun consult-buffer-other-frame ()
   4381   "Variant of `consult-buffer' which opens in other frame."
   4382   (interactive)
   4383   (let ((consult--buffer-display #'switch-to-buffer-other-frame))
   4384     (consult-buffer)))
   4385 
   4386 ;;;;; Command: consult-kmacro
   4387 
   4388 (defun consult--kmacro-candidates ()
   4389   "Return alist of kmacros and indices."
   4390   (thread-last
   4391       ;; List of macros
   4392       (append (when last-kbd-macro
   4393                 `((,last-kbd-macro ,kmacro-counter ,kmacro-counter-format)))
   4394               kmacro-ring)
   4395     ;; Add indices
   4396     (seq-map-indexed #'cons)
   4397     ;; Filter mouse clicks
   4398     (seq-remove (lambda (x) (seq-some #'mouse-event-p (caar x))))
   4399     ;; Format macros
   4400     (mapcar (pcase-lambda (`((,keys ,counter ,format) . ,index))
   4401               (propertize
   4402                (format-kbd-macro keys 1)
   4403                'consult--candidate index
   4404                'consult--kmacro-annotation
   4405                ;; If the counter is 0 and the counter format is its default,
   4406                ;; then there is a good chance that the counter isn't actually
   4407                ;; being used.  This can only be wrong when a user
   4408                ;; intentionally starts the counter with a negative value and
   4409                ;; then increments it to 0.
   4410                (cond
   4411                 ((not (string= format "%d")) ;; show counter for non-default format
   4412                  (format " (counter=%d, format=%s) " counter format))
   4413                 ((/= counter 0) ;; show counter if non-zero
   4414                  (format " (counter=%d)" counter))))))
   4415     (delete-dups)))
   4416 
   4417 ;;;###autoload
   4418 (defun consult-kmacro (arg)
   4419   "Run a chosen keyboard macro.
   4420 
   4421 With prefix ARG, run the macro that many times.
   4422 Macros containing mouse clicks are omitted."
   4423   (interactive "p")
   4424   (let ((selected (consult--read
   4425                    (or (consult--kmacro-candidates)
   4426                        (user-error "No keyboard macros defined"))
   4427                    :prompt "Keyboard macro: "
   4428                    :category 'consult-kmacro
   4429                    :require-match t
   4430                    :sort nil
   4431                    :history 'consult--kmacro-history
   4432                    :annotate
   4433                    (lambda (cand)
   4434                      (get-text-property 0 'consult--kmacro-annotation cand))
   4435                    :lookup #'consult--lookup-candidate)))
   4436     (if (= 0 selected)
   4437         ;; If the first element has been selected, just run the last macro.
   4438         (kmacro-call-macro (or arg 1) t nil)
   4439       ;; Otherwise, run a kmacro from the ring.
   4440       (let* ((selected (1- selected))
   4441              (kmacro (nth selected kmacro-ring))
   4442              ;; Temporarily change the variables to retrieve the correct
   4443              ;; settings.  Mainly, we want the macro counter to persist, which
   4444              ;; automatically happens when cycling the ring.
   4445              (last-kbd-macro (car kmacro))
   4446              (kmacro-counter (cadr kmacro))
   4447              (kmacro-counter-format (caddr kmacro)))
   4448         (kmacro-call-macro (or arg 1) t)
   4449         ;; Once done, put updated variables back into the ring.
   4450         (setf (nth selected kmacro-ring)
   4451               (list last-kbd-macro
   4452                     kmacro-counter
   4453                     kmacro-counter-format))))))
   4454 
   4455 ;;;;; Command: consult-grep
   4456 
   4457 (defun consult--grep-format (async builder)
   4458   "Return ASYNC function highlighting grep match results.
   4459 BUILDER is the command argument builder."
   4460   (let ((highlight))
   4461     (lambda (action)
   4462       (cond
   4463        ((stringp action)
   4464         (setq highlight (plist-get (funcall builder action) :highlight))
   4465         (funcall async action))
   4466        ((consp action)
   4467         (let (result)
   4468           (save-match-data
   4469             (dolist (str action)
   4470               (when (and (string-match consult--grep-match-regexp str)
   4471                          ;; Filter out empty context lines
   4472                          (or (/= (aref str (match-beginning 3)) ?-)
   4473                              (/= (match-end 0) (length str))))
   4474                 (let* ((file (match-string 1 str))
   4475                        (line (match-string 2 str))
   4476                        (ctx (= (aref str (match-beginning 3)) ?-))
   4477                        (sep (if ctx "-" ":"))
   4478                        (content (substring str (match-end 0)))
   4479                        (file-len (length file))
   4480                        (line-len (length line)))
   4481                   (when (> (length content) consult-grep-max-columns)
   4482                     (setq content (substring content 0 consult-grep-max-columns)))
   4483                   (when highlight
   4484                     (funcall highlight content))
   4485                   (setq str (concat file sep line sep content))
   4486                   ;; Store file name in order to avoid allocations in `consult--grep-group'
   4487                   (add-text-properties 0 file-len `(face consult-file consult--grep-file ,file) str)
   4488                   (put-text-property (1+ file-len) (+ 1 file-len line-len) 'face 'consult-line-number str)
   4489                   (when ctx
   4490                     (add-face-text-property (+ 2 file-len line-len) (length str) 'consult-grep-context 'append str))
   4491                   (push str result)))))
   4492           (funcall async (nreverse result))))
   4493        (t (funcall async action))))))
   4494 
   4495 (defun consult--grep-position (cand &optional find-file)
   4496   "Return the grep position marker for CAND.
   4497 FIND-FILE is the file open function, defaulting to `find-file'."
   4498   (when cand
   4499     (let* ((file-end (next-single-property-change 0 'face cand))
   4500            (line-end (next-single-property-change (+ 1 file-end) 'face cand))
   4501            (col (next-single-property-change (+ 1 line-end) 'face cand))
   4502            (file (substring-no-properties cand 0 file-end))
   4503            (line (string-to-number (substring-no-properties cand (+ 1 file-end) line-end))))
   4504       (setq col (if col (- col line-end 1) 0))
   4505       (consult--position-marker
   4506        (funcall (or find-file #'find-file) file)
   4507        line col))))
   4508 
   4509 (defun consult--grep-state ()
   4510   "Grep state function."
   4511   (let ((open (consult--temporary-files))
   4512         (jump (consult--jump-state)))
   4513     (lambda (action cand)
   4514       (unless cand
   4515         (funcall open))
   4516       (funcall jump action (consult--grep-position
   4517                             cand
   4518                             (and (not (eq action 'exit)) open))))))
   4519 
   4520 (defun consult--grep-group (cand transform)
   4521   "Return title for CAND or TRANSFORM the candidate."
   4522   (if transform
   4523       (substring cand (1+ (length (get-text-property 0 'consult--grep-file cand))))
   4524     (get-text-property 0 'consult--grep-file cand)))
   4525 
   4526 (defun consult--grep (prompt builder dir initial)
   4527   "Run grep in DIR.
   4528 
   4529 BUILDER is the command builder.
   4530 PROMPT is the prompt string.
   4531 INITIAL is inital input."
   4532   (let* ((prompt-dir (consult--directory-prompt prompt dir))
   4533          (default-directory (cdr prompt-dir)))
   4534     (consult--read
   4535      (consult--async-command builder
   4536        (consult--grep-format builder)
   4537        :file-handler t) ;; allow tramp
   4538      :prompt (car prompt-dir)
   4539      :lookup #'consult--lookup-member
   4540      :state (consult--grep-state)
   4541      :initial (consult--async-split-initial initial)
   4542      :add-history (consult--async-split-thingatpt 'symbol)
   4543      :require-match t
   4544      :category 'consult-grep
   4545      :group #'consult--grep-group
   4546      :history '(:input consult--grep-history)
   4547      :sort nil)))
   4548 
   4549 (defun consult--grep-lookahead-p (&rest cmd)
   4550   "Return t if grep CMD supports lookahead."
   4551   (with-temp-buffer
   4552     (insert "xaxbx")
   4553     (eq 0 (apply #'call-process-region (point-min) (point-max)
   4554                  (car cmd) nil nil nil `(,@(cdr cmd) "^(?=.*b)(?=.*a)")))))
   4555 
   4556 (defvar consult--grep-regexp-type nil)
   4557 (defun consult--grep-regexp-type (cmd)
   4558   "Return regexp type supported by grep CMD."
   4559   (or consult--grep-regexp-type
   4560       (setq consult--grep-regexp-type
   4561             (if (consult--grep-lookahead-p cmd "-P") 'pcre 'extended))))
   4562 
   4563 (defun consult--grep-builder (input)
   4564   "Build command line given INPUT."
   4565   (pcase-let* ((cmd (split-string-and-unquote consult-grep-args))
   4566                (type (consult--grep-regexp-type (car cmd)))
   4567                (`(,arg . ,opts) (consult--command-split input))
   4568                (`(,re . ,hl) (funcall consult--regexp-compiler arg type
   4569                                       (member "--ignore-case" cmd))))
   4570     (when re
   4571       (list :command
   4572             (append cmd
   4573                     (list (if (eq type 'pcre) "--perl-regexp" "--extended-regexp")
   4574                           "-e" (consult--join-regexps re type))
   4575                     opts)
   4576             :highlight hl))))
   4577 
   4578 ;;;###autoload
   4579 (defun consult-grep (&optional dir initial)
   4580   "Search with `grep' for files in DIR where the content matches a regexp.
   4581 
   4582 The initial input is given by the INITIAL argument.
   4583 
   4584 The input string is split, the first part of the string (grep input) is
   4585 passed to the asynchronous grep process and the second part of the string is
   4586 passed to the completion-style filtering.
   4587 
   4588 The input string is split at a punctuation character, which is given as the
   4589 first character of the input string. The format is similar to Perl-style
   4590 regular expressions, e.g., /regexp/. Furthermore command line options can be
   4591 passed to grep, specified behind --. The overall prompt input has the form
   4592 `#async-input -- grep-opts#filter-string'.
   4593 
   4594 Note that the grep input string is transformed from Emacs regular expressions
   4595 to Posix regular expressions. Always enter Emacs regular expressions at the
   4596 prompt. `consult-grep' behaves like builtin Emacs search commands, e.g.,
   4597 Isearch, which take Emacs regular expressions. Furthermore the asynchronous
   4598 input split into words, each word must match separately and in any order. See
   4599 `consult--regexp-compiler' for the inner workings. In order to disable
   4600 transformations of the grep input, adjust `consult--regexp-compiler'
   4601 accordingly.
   4602 
   4603 Here we give a few example inputs:
   4604 
   4605 #alpha beta         : Search for alpha and beta in any order.
   4606 #alpha.*beta        : Search for alpha before beta.
   4607 #\\(alpha\\|beta\\) : Search for alpha or beta (Note Emacs syntax!)
   4608 #word -- -C3        : Search for word, include 3 lines as context
   4609 #first#second       : Search for first, quick filter for second.
   4610 
   4611 The symbol at point is added to the future history. If `consult-grep'
   4612 is called interactively with a prefix argument, the user can specify
   4613 the directory to search in. By default the project directory is used
   4614 if `consult-project-function' is defined and returns non-nil.
   4615 Otherwise the `default-directory' is searched."
   4616   (interactive "P")
   4617   (consult--grep "Grep" #'consult--grep-builder dir initial))
   4618 
   4619 ;;;;; Command: consult-git-grep
   4620 
   4621 (defun consult--git-grep-builder (input)
   4622   "Build command line given CONFIG and INPUT."
   4623   (pcase-let* ((cmd (split-string-and-unquote consult-git-grep-args))
   4624                (`(,arg . ,opts) (consult--command-split input))
   4625                (`(,re . ,hl) (funcall consult--regexp-compiler arg 'extended
   4626                                       (member "--ignore-case" cmd))))
   4627     (when re
   4628       (list :command
   4629             (append cmd
   4630                     (cdr (mapcan (lambda (x) (list "--and" "-e" x)) re))
   4631                     opts)
   4632             :highlight hl))))
   4633 
   4634 ;;;###autoload
   4635 (defun consult-git-grep (&optional dir initial)
   4636   "Search with `git grep' for files in DIR where the content matches a regexp.
   4637 The initial input is given by the INITIAL argument. See `consult-grep'
   4638 for more details."
   4639   (interactive "P")
   4640   (consult--grep "Git-grep" #'consult--git-grep-builder dir initial))
   4641 
   4642 ;;;;; Command: consult-ripgrep
   4643 
   4644 (defvar consult--ripgrep-regexp-type nil)
   4645 (defun consult--ripgrep-regexp-type (cmd)
   4646   "Return regexp type supported by ripgrep CMD."
   4647   (or consult--ripgrep-regexp-type
   4648       (setq consult--ripgrep-regexp-type
   4649             (if (consult--grep-lookahead-p cmd "-P") 'pcre 'extended))))
   4650 
   4651 (defun consult--ripgrep-builder (input)
   4652   "Build command line given INPUT."
   4653   (pcase-let* ((cmd (split-string-and-unquote consult-ripgrep-args))
   4654                (type (consult--ripgrep-regexp-type (car cmd)))
   4655                (`(,arg . ,opts) (consult--command-split input))
   4656                (`(,re . ,hl) (funcall consult--regexp-compiler arg type
   4657                                       (if (member "--smart-case" cmd)
   4658                                           (let ((case-fold-search nil))
   4659                                            ;; Case insensitive if there are no uppercase letters
   4660                                            (not (string-match-p "[[:upper:]]" input)))
   4661                                         (member "--ignore-case" cmd)))))
   4662     (when re
   4663       (list :command
   4664             (append cmd
   4665                     (and (eq type 'pcre) '("-P"))
   4666                     (list  "-e" (consult--join-regexps re type))
   4667                     opts)
   4668             :highlight hl))))
   4669 
   4670 ;;;###autoload
   4671 (defun consult-ripgrep (&optional dir initial)
   4672   "Search with `rg' for files in DIR where the content matches a regexp.
   4673 The initial input is given by the INITIAL argument. See `consult-grep'
   4674 for more details."
   4675   (interactive "P")
   4676   (consult--grep "Ripgrep" #'consult--ripgrep-builder dir initial))
   4677 
   4678 ;;;;; Command: consult-find
   4679 
   4680 (defun consult--find (prompt builder initial)
   4681   "Run find command in current directory.
   4682 
   4683 The function returns the selected file.
   4684 The filename at point is added to the future history.
   4685 
   4686 BUILDER is the command builder.
   4687 PROMPT is the prompt.
   4688 INITIAL is inital input."
   4689   (consult--read
   4690    (consult--async-command builder
   4691      (consult--async-map (lambda (x) (string-remove-prefix "./" x)))
   4692      (consult--async-highlight builder)
   4693      :file-handler t) ;; allow tramp
   4694    :prompt prompt
   4695    :sort nil
   4696    :require-match t
   4697    :initial (consult--async-split-initial initial)
   4698    :add-history (consult--async-split-thingatpt 'filename)
   4699    :category 'file
   4700    :history '(:input consult--find-history)))
   4701 
   4702 (defvar consult--find-regexp-type nil)
   4703 (defun consult--find-regexp-type (cmd)
   4704   "Return regexp type supported by find CMD."
   4705   (or consult--find-regexp-type
   4706       (setq consult--find-regexp-type
   4707             (if (eq 0 (call-process-shell-command
   4708                        (concat cmd " -regextype emacs -version")))
   4709                 'emacs 'basic))))
   4710 
   4711 (defun consult--find-builder (input)
   4712   "Build command line given INPUT."
   4713   (pcase-let* ((cmd (split-string-and-unquote consult-find-args))
   4714                (type (consult--find-regexp-type (car cmd)))
   4715                (`(,arg . ,opts) (consult--command-split input))
   4716                ;; ignore-case=t since -iregex is used below
   4717                (`(,re . ,hl) (funcall consult--regexp-compiler arg type t)))
   4718     (when re
   4719       (list :command
   4720             (append cmd
   4721                     (cdr (mapcan
   4722                           (lambda (x)
   4723                             `("-and" "-iregex"
   4724                               ,(format ".*%s.*"
   4725                                        ;; HACK Replace non-capturing groups with capturing groups.
   4726                                        ;; GNU find does not support non-capturing groups.
   4727                                        (replace-regexp-in-string
   4728                                         "\\\\(\\?:" "\\(" x 'fixedcase 'literal))))
   4729                           re))
   4730                     opts)
   4731             :highlight hl))))
   4732 
   4733 ;;;###autoload
   4734 (defun consult-find (&optional dir initial)
   4735   "Search for files in DIR matching input regexp given INITIAL input.
   4736 
   4737 The find process is started asynchronously, similar to `consult-grep'.
   4738 See `consult-grep' for more details regarding the asynchronous search."
   4739   (interactive "P")
   4740   (let* ((prompt-dir (consult--directory-prompt "Find" dir))
   4741          (default-directory (cdr prompt-dir)))
   4742     (find-file (consult--find (car prompt-dir) #'consult--find-builder initial))))
   4743 
   4744 ;;;;; Command: consult-locate
   4745 
   4746 (defun consult--locate-builder (input)
   4747   "Build command line given CONFIG and INPUT."
   4748   (pcase-let ((`(,arg . ,opts) (consult--command-split input)))
   4749     (unless (string-blank-p arg)
   4750       (list :command (append (split-string-and-unquote consult-locate-args)
   4751                              (list arg) opts)
   4752             :highlight (cdr (consult--default-regexp-compiler input 'basic t))))))
   4753 
   4754 ;;;###autoload
   4755 (defun consult-locate (&optional initial)
   4756   "Search with `locate' for files which match input given INITIAL input.
   4757 
   4758 The input is treated literally such that locate can take advantage of
   4759 the locate database index. Regular expressions would often force a slow
   4760 linear search through the entire database. The locate process is started
   4761 asynchronously, similar to `consult-grep'. See `consult-grep' for more
   4762 details regarding the asynchronous search."
   4763   (interactive)
   4764   (find-file (consult--find "Locate: " #'consult--locate-builder initial)))
   4765 
   4766 ;;;;; Command: consult-man
   4767 
   4768 (defun consult--man-builder (input)
   4769   "Build command line given CONFIG and INPUT."
   4770   (pcase-let ((`(,arg . ,opts) (consult--command-split input)))
   4771     (unless (string-blank-p arg)
   4772       (list :command (append (split-string-and-unquote consult-man-args)
   4773                              (list arg) opts)
   4774             :highlight (cdr (consult--default-regexp-compiler input 'basic t))))))
   4775 
   4776 (defun consult--man-format (lines)
   4777   "Format man candidates from LINES."
   4778   (let ((candidates))
   4779     (save-match-data
   4780       (dolist (str lines)
   4781         (when (string-match "\\`\\(.*?\\([^ ]+\\) *(\\([^,)]+\\)[^)]*).*?\\) +- +\\(.*\\)\\'" str)
   4782           (let ((names (match-string 1 str))
   4783                 (name (match-string 2 str))
   4784                 (section (match-string 3 str))
   4785                 (desc (match-string 4 str)))
   4786             (add-face-text-property 0 (length names) 'consult-file nil names)
   4787             (push (cons
   4788                    (format "%s - %s" names desc)
   4789                    (concat section " " name))
   4790                   candidates)))))
   4791     (nreverse candidates)))
   4792 
   4793 ;;;###autoload
   4794 (defun consult-man (&optional initial)
   4795   "Search for man page given INITIAL input.
   4796 
   4797 The input string is not preprocessed and passed literally to the
   4798 underlying man commands. The man process is started asynchronously,
   4799 similar to `consult-grep'. See `consult-grep' for more details regarding
   4800 the asynchronous search."
   4801   (interactive)
   4802   (man (consult--read
   4803         (consult--async-command #'consult--man-builder
   4804           (consult--async-transform consult--man-format)
   4805           (consult--async-highlight #'consult--man-builder))
   4806         :prompt "Manual entry: "
   4807         :require-match t
   4808         :lookup #'consult--lookup-cdr
   4809         :initial (consult--async-split-initial initial)
   4810         :add-history (consult--async-split-thingatpt 'symbol)
   4811         :history '(:input consult--man-history))))
   4812 
   4813 ;;;; Preview at point in completions buffers
   4814 
   4815 (define-minor-mode consult-preview-at-point-mode
   4816   "Preview minor mode for *Completions* buffers.
   4817 When moving around in the *Completions* buffer, the candidate at point is
   4818 automatically previewed."
   4819   :init-value nil :group 'consult
   4820   (if consult-preview-at-point-mode
   4821       (add-hook 'post-command-hook #'consult-preview-at-point nil 'local)
   4822     (remove-hook 'post-command-hook #'consult-preview-at-point 'local)))
   4823 
   4824 (defun consult-preview-at-point ()
   4825   "Preview candidate at point in *Completions* buffer."
   4826   (interactive)
   4827   (when-let* ((win (active-minibuffer-window))
   4828               (buf (window-buffer win))
   4829               (fun (buffer-local-value 'consult--preview-function buf)))
   4830     (funcall fun)))
   4831 
   4832 ;;;; Integration with the default completion system
   4833 
   4834 (defun consult--default-completion-mb-candidate ()
   4835   "Return current minibuffer candidate from default completion system or Icomplete."
   4836   (when (and (minibufferp)
   4837              (eq completing-read-function #'completing-read-default))
   4838     (let ((content (minibuffer-contents-no-properties)))
   4839       ;; When the current minibuffer content matches a candidate, return it!
   4840       (if (test-completion content
   4841                            minibuffer-completion-table
   4842                            minibuffer-completion-predicate)
   4843           content
   4844         ;; Return the full first candidate of the sorted completion list.
   4845         (when-let ((completions (completion-all-sorted-completions)))
   4846           (concat
   4847            (substring content 0 (or (cdr (last completions)) 0))
   4848            (car completions)))))))
   4849 
   4850 (defun consult--default-completion-list-candidate ()
   4851   "Return current candidate at point from completions buffer."
   4852   (let (beg end)
   4853     (when (and
   4854            (derived-mode-p 'completion-list-mode)
   4855            ;; Logic taken from `choose-completion'.
   4856            ;; TODO Upstream a `completion-list-get-candidate' function.
   4857            (cond
   4858             ((and (not (eobp)) (get-text-property (point) 'mouse-face))
   4859              (setq end (point) beg (1+ (point))))
   4860             ((and (not (bobp)) (get-text-property (1- (point)) 'mouse-face))
   4861              (setq end (1- (point)) beg (point)))))
   4862       (setq beg (previous-single-property-change beg 'mouse-face)
   4863             end (or (next-single-property-change end 'mouse-face) (point-max)))
   4864       (or (get-text-property beg 'completion--string)
   4865           (buffer-substring-no-properties beg end)))))
   4866 
   4867 ;; Announce now that consult has been loaded
   4868 (provide 'consult)
   4869 
   4870 ;;;; Integration with other completion systems
   4871 
   4872 (with-eval-after-load 'icomplete (require 'consult-icomplete))
   4873 (with-eval-after-load 'selectrum (require 'consult-selectrum))
   4874 (with-eval-after-load 'vertico (require 'consult-vertico))
   4875 (with-eval-after-load 'mct (add-hook 'consult--completion-refresh-hook
   4876                                      'mct--live-completions-refresh))
   4877 
   4878 ;;; consult.el ends here