dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

consult.el (223287B)


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