dotemacs

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

consult.el (202935B)


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