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