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