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