config

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

consult.el (228244B)


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