branch: elpa/projectile
commit 095f493b6e9091d41247d2d4a741c84f9fc8158c
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>
Advertise the correct completion category per command
projectile-completing-read hardcoded the `project-file' completion
category for every prompt, so marginalia and embark mis-annotated
non-file completions (a buffer or a project got file annotations and
file actions).
Add a :category keyword (default project-file) and pass the right one:
buffer switching now advertises `buffer' (marginalia's rich buffer
annotations kick in), and the project-switch commands advertise `file'
since their candidates are directory paths. File commands keep the
project-file default.
---
CHANGELOG.md | 1 +
projectile.el | 29 ++++++++++++++++++++++-------
test/projectile-core-test.el | 37 ++++++++++++++++++++++++++++++++++++-
3 files changed, 59 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aaff9b9606..11a61f2f15 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -43,6 +43,7 @@
* Add `projectile-frecency-max-projects` (default 100), which caps how many
projects' frecency history is kept so the store can't grow without bound.
* `projectile-replace-scan-chunk-size` is now a user-facing `defcustom` (was
an internal variable).
+* Advertise the right completion metadata category per command so marginalia
and embark annotate and act on candidates correctly: buffer switching now uses
the `buffer` category (so marginalia shows its rich buffer annotations),
project switching uses `file`, and file commands keep `project-file` -
previously every completion was hardcoded to `project-file`.
`projectile-completing-read` gained a `:category` keyword for this.
* Add whole-word matching to the reviewable search/replace: toggle it with `w`
in the results buffer, default it with `projectile-search-whole-word`, or seed
it for one run with the `projectile-dispatch` `--word` switch. The elisp scan
fences the pattern with word boundaries and the ripgrep fast-path passes
`--word-regexp`, so both engines agree.
* Wire the reviewable search/replace into the `projectile-dispatch` Modifiers:
the two literal/regexp search entries are folded into one `sR` driven by
`--regexp` (matching how `--regexp` already drives the ag/ripgrep search), and
a new `--case-sensitive` switch seeds the reviewable search/replace
case-sensitive. Both can still be flipped with `x`/`c` in the results buffer.
* Highlight the search tool and the default value in the search prompts, and
give them one consistent `Search [tool] for (default: X)` format across
`projectile-search`, `projectile-grep`/`-ag`/`-ripgrep` and the reviewable
search. The tool that will run (`grep`/`ag`/`ripgrep`, or `ripgrep`/`elisp` for
the reviewable search's literal fast-path) is faced with
`projectile-search-prompt-tool` and the symbol-at-point default with
`projectile-search-prompt-default`. The reviewable search now [...]
diff --git a/projectile.el b/projectile.el
index b42a779b8c..30a48f7d8a 100644
--- a/projectile.el
+++ b/projectile.el
@@ -2739,6 +2739,7 @@ See also `projectile-acquire-root'."
(cond
((eq projectile-require-project-root 'prompt) (projectile-completing-read
"Switch to project: "
projectile-known-projects
+ :category 'file
:caller
'projectile-read-project))
(projectile-require-project-root (user-error "Projectile cannot find a
project definition in %s" default-directory))
(t default-directory))))
@@ -3902,6 +3903,7 @@ choices."
prompt
(delete (buffer-name (current-buffer))
(projectile-project-buffer-names))
+ :category 'buffer
:caller 'projectile-read-buffer))
;;; Other window/frame display variants
@@ -3990,6 +3992,7 @@ yields the other-window/-frame variants."
(projectile-completing-read
"Display buffer: "
(projectile-project-buffer-names)
+ :category 'buffer
:caller 'projectile-read-buffer)))
;;;###autoload
@@ -4365,7 +4368,7 @@ Never use on many files since it's going to recalculate
the
project-root for every file."
(expand-file-name name (projectile-project-root dir)))
-(cl-defun projectile-completing-read (prompt choices &key initial-input action
caller sort-function)
+(cl-defun projectile-completing-read (prompt choices &key initial-input action
caller sort-function (category 'project-file))
"Present a project tailored PROMPT with CHOICES.
Reads with `completing-read', unless `projectile-completion-system' is a
@@ -4375,8 +4378,15 @@ INITIAL-INPUT is passed to `completing-read'. ACTION,
when non-nil, is
called on the selected candidate and its result returned. SORT-FUNCTION,
when non-nil, is exposed as the completion metadata's
`display-sort-function' and `cycle-sort-function', so completion UIs
-that honor metadata present the candidates in that order. CALLER is
-accepted for backward compatibility but no longer used."
+that honor metadata present the candidates in that order.
+
+CATEGORY is the completion metadata category advertised to UIs like
+marginalia and embark so they annotate and act on the candidates
+appropriately; it defaults to `project-file' (the candidates are project
+files) and should be overridden when they are not - e.g. `buffer' for a
+buffer switch or `file' for a directory. A nil CATEGORY omits it.
+
+CALLER is accepted for backward compatibility but no longer used."
(ignore caller)
(let* ((prompt (projectile-prepend-project-name prompt))
(res (if (functionp projectile-completion-system)
@@ -4384,11 +4394,10 @@ accepted for backward compatibility but no longer used."
(completing-read
prompt
(lambda (string pred action)
- ;; The `project-file' category lets packages like
- ;; marginalia and embark enhance how candidates are
- ;; presented.
+ ;; The completion category lets packages like marginalia
+ ;; and embark enhance how candidates are presented.
(if (eq action 'metadata)
- `(metadata (category . project-file)
+ `(metadata ,@(when category `((category . ,category)))
,@(when sort-function
`((display-sort-function .
,sort-function)
(cycle-sort-function .
,sort-function))))
@@ -9818,6 +9827,7 @@ prompt for a known project to open instead of the current
one."
(if arg
(projectile-completing-read
"Dired in project: " (projectile-relevant-known-projects)
+ :category 'file
:caller 'projectile-read-project)
(projectile-acquire-root))))
@@ -9851,6 +9861,7 @@ directory to open."
(projectile-completing-read
"Open project VC in: "
projectile-known-projects
+ :category 'file
:caller 'projectile-read-project))))
(unless project-root
(setq project-root (projectile-acquire-root)))
@@ -11024,6 +11035,7 @@ of `projectile-switch-project-action'."
"Switch to project: " projects
:action (lambda (project)
(projectile-switch-project-by-name project arg))
+ :category 'file
:caller 'projectile-read-project)
(user-error "There are no known projects"))))
@@ -11040,6 +11052,7 @@ of `projectile-switch-project-action'."
"Switch to open project: " projects
:action (lambda (project)
(projectile-switch-project-by-name project arg))
+ :category 'file
:caller 'projectile-read-project)
(user-error "There are no open projects"))))
@@ -11317,6 +11330,7 @@ projects removed."
(interactive (list (projectile-completing-read
"Remove from known projects: " projectile-known-projects
:action 'projectile-remove-known-project
+ :category 'file
:caller 'projectile-read-project)))
(unless (called-interactively-p 'any)
(setq projectile-known-projects
@@ -11447,6 +11461,7 @@ Let user choose another project when PROMPT-FOR-PROJECT
is supplied."
(projectile-completing-read
"Project name: "
(projectile-relevant-known-projects)
+ :category 'file
:caller 'projectile-read-project)
(projectile-acquire-root))))
(projectile-ibuffer-by-project project-root)))
diff --git a/test/projectile-core-test.el b/test/projectile-core-test.el
index ee9266b0b4..c849046558 100644
--- a/test/projectile-core-test.el
+++ b/test/projectile-core-test.el
@@ -279,7 +279,42 @@
(let ((projectile-completion-system 'ido))
(spy-on 'completing-read :and-return-value "x")
(expect (projectile-completing-read "Prompt" '("x")) :to-equal "x")
- (expect 'completing-read :to-have-been-called))))
+ (expect 'completing-read :to-have-been-called)))
+
+ ;; The completion metadata category is what marginalia and embark key off
+ ;; to annotate and act on candidates, so it must reflect what they are.
+ (it "advertises the project-file category by default"
+ (let ((projectile-completion-system 'default) table)
+ (spy-on 'completing-read :and-call-fake
+ (lambda (_prompt coll &rest _) (setq table coll) "x"))
+ (projectile-completing-read "Prompt" '("a"))
+ (expect (alist-get 'category (cdr (funcall table "" nil 'metadata)))
+ :to-be 'project-file)))
+
+ (it "honors an explicit :category"
+ (let ((projectile-completion-system 'default) table)
+ (spy-on 'completing-read :and-call-fake
+ (lambda (_prompt coll &rest _) (setq table coll) "x"))
+ (projectile-completing-read "Prompt" '("a") :category 'buffer)
+ (expect (alist-get 'category (cdr (funcall table "" nil 'metadata)))
+ :to-be 'buffer)))
+
+ (it "omits the category when :category is nil"
+ (let ((projectile-completion-system 'default) table)
+ (spy-on 'completing-read :and-call-fake
+ (lambda (_prompt coll &rest _) (setq table coll) "x"))
+ (projectile-completing-read "Prompt" '("a") :category nil)
+ (expect (assq 'category (cdr (funcall table "" nil 'metadata)))
+ :to-be nil)))
+
+ (it "buffer switching advertises the buffer category"
+ (let (table)
+ (spy-on 'projectile-project-buffer-names :and-return-value '("b1" "b2"))
+ (spy-on 'completing-read :and-call-fake
+ (lambda (_prompt coll &rest _) (setq table coll) "b1"))
+ (projectile-read-buffer-to-switch "Switch: ")
+ (expect (alist-get 'category (cdr (funcall table "" nil 'metadata)))
+ :to-be 'buffer))))
(describe "projectile-sort-files"
(it "returns the files unchanged for the default sort order"