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