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