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