config

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

consult.el (230446B)


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