config

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

consult.el (228805B)


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