config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

consult.el (229712B)


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