config

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

consult.el (229599B)


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