dotfiles/emacs/.emacs.d/elpa/projectile-20260824.913/projectile-consult.el

125 lines
5.5 KiB
EmacsLisp
Raw Normal View History

;;; projectile-consult.el --- Consult integration for Projectile -*- lexical-binding: t -*-
;; Copyright © 2011-2026 Bozhidar Batsov
;; Author: Bozhidar Batsov <bozhidar@batsov.dev>
;; URL: https://github.com/bbatsov/projectile
;; Keywords: project, convenience, matching
;; This file is an optional module shipped inside the Projectile package; it
;; carries no `Package-Requires' header of its own so it isn't mistaken for a
;; standalone package. At runtime it needs `consult' 2.0+, which in turn
;; requires Emacs 29.1+, but Projectile core never loads it, so those are not
;; Projectile dependencies (see the Commentary).
;; This file is NOT part of GNU Emacs.
;; This program is free software: you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see `http://www.gnu.org/licenses/'.
;;; Commentary:
;; An optional layer that drives a streaming Consult finder from
;; Projectile's own project indexing command (via
;; `projectile-project-files-producer').
;;
;; Unlike `consult-find' / `consult-fd', which run their own find/fd, this
;; reuses whatever indexing Projectile is configured to use - `git ls-files',
;; `fd', the `find' fallback, other VCS commands - so it stays VCS-aware and
;; honours the project's setup. Candidates stream into the minibuffer as the
;; command runs and are narrowed client-side by your completion style (e.g.
;; orderless), so there's no wait for the whole project to be indexed before
;; you can start typing.
;;
;; Projectile core does NOT load this file; require it yourself (it pulls in
;; `consult', which needs Emacs 29.1+):
;;
;; (require 'projectile-consult)
;; (define-key projectile-command-map (kbd "f") #'projectile-consult-find-file)
;;; Code:
(require 'projectile)
;; `consult' is a hard runtime dependency (see Package-Requires), but
;; Projectile core deliberately does not load this file, and external build
;; systems byte-compile it as part of the Projectile package without `consult'
;; on the load path. Load it softly here (the real `require' happens in the
;; command below) and forward-declare the functions we use, so the file still
;; byte-compiles cleanly when `consult' is absent. See
;; https://github.com/bbatsov/projectile/issues/2037.
(require 'consult nil t)
(declare-function consult--read "consult")
(declare-function consult--process-collection "consult")
(declare-function consult--async-map "consult")
(defvar projectile-consult-find-file-history nil
"Minibuffer history for `projectile-consult-find-file'.")
(defun projectile-consult--file-command (command)
"Wrap a Projectile indexing COMMAND for Consult's process collection.
COMMAND emits NUL-separated paths (Projectile uses -z/-0/-print0), but
Consult's process pipeline splits its input on newlines, so translate
NUL to newline with `tr'. Returns a command list (PROGRAM ARGS...)
suitable as the return value of a Consult process builder."
;; Octal \\000 rather than \\0 so the escape is understood by both GNU and
;; BSD `tr'.
(list shell-file-name shell-command-switch
(concat command " | tr '\\000' '\\n'")))
(defun projectile-consult--builder (command)
"Return a Consult process builder that lists the project's files via COMMAND.
The builder ignores its input: Projectile's indexing command lists the
whole project once (Consult does not restart it while the command stays
the same) and Consult narrows the streamed candidates client-side."
(lambda (_input)
(projectile-consult--file-command command)))
;;;###autoload
(defun projectile-consult-find-file ()
"Find a file in the current project with a streaming Consult UI.
The candidate list is produced by Projectile's own indexing command (see
`projectile-project-files-producer'), so it honours the project's VCS and
indexing configuration, and it streams into the minibuffer as the command
runs instead of blocking until the whole project is indexed."
(interactive)
(require 'consult)
(let* ((producer (projectile-project-files-producer))
(root (plist-get producer :directory))
(command (plist-get producer :command)))
(unless command
(user-error "External-command indexing is disabled for this project"))
(let* ((default-directory root)
(selected
(consult--read
(consult--process-collection (projectile-consult--builder command)
;; List immediately, with no input required.
:min-input 0
;; Match Projectile's own `./'-stripping.
:transform (consult--async-map
(lambda (path) (string-remove-prefix "./" path)))
;; Route the process through TRAMP for remote projects.
:file-handler t)
:prompt (format "Find file in %s: " (projectile-project-name))
:sort nil
:require-match t
:category 'file
:history '(:input projectile-consult-find-file-history))))
(find-file (expand-file-name selected root)))))
(provide 'projectile-consult)
;;; projectile-consult.el ends here