config

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

consult.el (229430B)


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