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