branch: elpa/projectile
commit 03a11020dd4004298a57fb2478a780eb654c7969
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>
Highlight the tool and default value in the search prompts
The search prompts now make two things obvious at a glance: which tool
will run and what the default search value is.
- projectile-search/grep/ag/ripgrep face the backend name (a new
projectile-search-prompt-tool face) and the symbol-at-point default (a
new projectile-search-prompt-default face, shown as "(default X)").
- The reviewable search grew the same treatment and now advertises
whether the literal search will take the ripgrep fast-path or the
elisp scan ([ripgrep] vs [elisp]), and shows its default up front
instead of only pre-filling it.
---
CHANGELOG.md | 1 +
projectile.el | 60 +++++++++++++++++++++++++++--------
test/projectile-search-review-test.el | 22 +++++++++++++
test/projectile-search-test.el | 32 +++++++++++++++++++
4 files changed, 102 insertions(+), 13 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd989ff4d4..6f0a14968a 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).
+* 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 [...]
### Bugs fixed
diff --git a/projectile.el b/projectile.el
index 77ce578503..9b2ca2e05b 100644
--- a/projectile.el
+++ b/projectile.el
@@ -7012,14 +7012,39 @@ This is a subset of `grep-read-files', where either a
matching entry from
"Return ignored file suffixes as a list of glob patterns."
(mapcar (lambda (pat) (concat "*" pat))
projectile-globally-ignored-file-suffixes))
-(defun projectile--read-search-string-with-default (prefix-label)
- (let* ((prefix-label (projectile-prepend-project-name prefix-label))
+(defface projectile-search-prompt-tool
+ '((t :inherit font-lock-function-name-face :weight bold))
+ "Face for the tool/backend highlighted in Projectile's search prompts."
+ :group 'projectile
+ :package-version '(projectile . "3.2.0"))
+
+(defface projectile-search-prompt-default
+ '((t :inherit font-lock-constant-face))
+ "Face for the default value highlighted in Projectile's search prompts."
+ :group 'projectile
+ :package-version '(projectile . "3.2.0"))
+
+(defun projectile--search-tool-tag (tool)
+ "Return a faced `[TOOL]' tag for a search prompt.
+Used where the backend varies (e.g. `projectile-search' or the
+reviewable search's ripgrep/elisp fast-path) so the prompt makes clear
+which tool will run."
+ (format "[%s]" (propertize tool 'face 'projectile-search-prompt-tool)))
+
+(defun projectile--read-search-string-with-default (prompt-label)
+ "Read a search string, defaulting to the symbol or region at point.
+PROMPT-LABEL is the action shown in the prompt (already carrying any
+faced tool tag its caller wants). The default value, when there is one,
+is appended and faced as `(default VALUE)'."
+ (let* ((prompt-label (projectile-prepend-project-name prompt-label))
(default-value (projectile-symbol-or-selection-at-point))
(default-label (if (or (not default-value)
(string= default-value ""))
""
- (format " (default %s)" default-value))))
- (read-string (format "%s%s: " prefix-label default-label) nil nil
default-value)))
+ (format " (default: %s)"
+ (propertize default-value
+ 'face
'projectile-search-prompt-default)))))
+ (read-string (format "%s%s: " prompt-label default-label) nil nil
default-value)))
(defvar projectile-grep-find-ignored-paths)
(defvar projectile-grep-find-unignored-paths)
@@ -7353,8 +7378,9 @@ The backend is chosen from `projectile-search-backends'
according to
"search"))
(term (or search-term
(projectile--read-search-string-with-default
- (format "Search [%s]%s for"
- (car backend) (if regexp " regexp" ""))))))
+ (format "Search %s%s for"
+ (projectile--search-tool-tag (car backend))
+ (if regexp " regexp" ""))))))
(funcall (plist-get (cdr backend) :search) term regexp)))
;;;###autoload
@@ -7370,7 +7396,9 @@ With REGEXP given, don't query the user for a regexp."
;; Fail fast (with a friendly error) before prompting when not in a project.
(projectile-acquire-root)
(let ((search-regexp (or regexp
- (projectile--read-search-string-with-default "Grep
for")))
+ (projectile--read-search-string-with-default
+ (format "Search %s for"
+ (projectile--search-tool-tag "grep")))))
(files (and arg (or (and (equal current-prefix-arg '-)
(projectile-grep-default-files))
(read-string (projectile-prepend-project-name
"Grep in: ")
@@ -7386,7 +7414,9 @@ With an optional prefix argument ARG SEARCH-TERM is
interpreted as a
regular expression."
(interactive
(list (projectile--read-search-string-with-default
- (format "Ag %ssearch for" (if current-prefix-arg "regexp " "")))
+ (format "Search %s%s for"
+ (projectile--search-tool-tag "ag")
+ (if current-prefix-arg " regexp" "")))
current-prefix-arg))
(let ((projectile-search-backend 'ag))
(projectile-search search-term arg)))
@@ -7403,7 +7433,9 @@ This command depends on the Emacs packages ripgrep or rg
being
installed to work."
(interactive
(list (projectile--read-search-string-with-default
- (format "Ripgrep %ssearch for" (if current-prefix-arg "regexp " "")))
+ (format "Search %s%s for"
+ (projectile--search-tool-tag "ripgrep")
+ (if current-prefix-arg " regexp" "")))
current-prefix-arg))
(let ((projectile-search-backend 'ripgrep))
(projectile-search search-term arg)))
@@ -9587,10 +9619,12 @@ matches to a `grep-mode' buffer for wgrep or Emacs 31's
`grep-edit-mode'.
LITERAL non-nil searches for a literal string; otherwise the term is an
Emacs regexp. There is no replacement prompt."
(let* ((root (projectile-acquire-root))
- (term (read-string
- (projectile-prepend-project-name
- (if literal "Search: " "Search regexp: "))
- (projectile-symbol-or-selection-at-point)))
+ (term (projectile--read-search-string-with-default
+ (format "Search %s%s for"
+ (projectile--search-tool-tag
+ (if (and literal (projectile-search--rg-fastpath-p t))
+ "ripgrep" "elisp"))
+ (if literal "" " regexp"))))
(regexp (if literal (regexp-quote term) term))
(case-fold case-fold-search)
(candidates (projectile-replace--candidates term literal case-fold
root)))
diff --git a/test/projectile-search-review-test.el
b/test/projectile-search-review-test.el
index 8ba419978a..aae37d1445 100644
--- a/test/projectile-search-review-test.el
+++ b/test/projectile-search-review-test.el
@@ -89,6 +89,28 @@ REGEXP-P selects `projectile-search-regexp-review'."
(let ((buf (projectile-search-review-test--run "absent")))
(expect buf :to-be nil)))))
+(describe "projectile-search-review prompt tool tag"
+ (before-each
+ (spy-on 'projectile-acquire-root :and-return-value "/proj/")
+ (spy-on 'projectile-replace--candidates :and-return-value nil)
+ (spy-on 'projectile-replace--open))
+
+ (it "advertises [ripgrep] when the literal fast-path will run"
+ (spy-on 'projectile-search--rg-fastpath-p :and-return-value t)
+ (let (label)
+ (spy-on 'projectile--read-search-string-with-default :and-call-fake
+ (lambda (l) (setq label l) "foo"))
+ (projectile-search--review t)
+ (expect label :to-match (regexp-quote "[ripgrep]"))))
+
+ (it "advertises [elisp] for a regexp search that never takes the fast-path"
+ (spy-on 'projectile-search--rg-fastpath-p :and-return-value nil)
+ (let (label)
+ (spy-on 'projectile--read-search-string-with-default :and-call-fake
+ (lambda (l) (setq label l) "foo"))
+ (projectile-search--review nil)
+ (expect label :to-match (regexp-quote "[elisp]")))))
+
(describe "projectile-search-regexp-review"
(it "honors Emacs-only regexp constructs a shell regexp couldn't express"
(projectile-test-with-project
diff --git a/test/projectile-search-test.el b/test/projectile-search-test.el
index 7ec218b29d..397f5e359a 100644
--- a/test/projectile-search-test.el
+++ b/test/projectile-search-test.el
@@ -87,6 +87,38 @@
(projectile-search "foo" t)
(expect captured :to-equal '("foo" t)))))
+(describe "search prompt highlighting"
+ (it "wraps the tool in brackets and faces it"
+ (let ((tag (projectile--search-tool-tag "ripgrep")))
+ ;; face aside, it's a plain [tool] tag
+ (expect tag :to-equal "[ripgrep]")
+ (expect (get-text-property 1 'face tag)
+ :to-equal 'projectile-search-prompt-tool)))
+
+ (describe "projectile--read-search-string-with-default"
+ (before-each
+ (spy-on 'projectile-prepend-project-name :and-call-fake #'identity))
+
+ (it "shows the symbol-at-point default, faced, and returns it on RET"
+ (spy-on 'projectile-symbol-or-selection-at-point :and-return-value "foo")
+ (let (prompt)
+ (spy-on 'read-string :and-call-fake
+ (lambda (p &rest _) (setq prompt p) "foo"))
+ (expect (projectile--read-search-string-with-default "Grep for")
:to-equal "foo")
+ (expect prompt :to-match (regexp-quote "(default: foo)"))
+ ;; the default value itself carries the highlight face
+ (let ((i (string-match "foo)" prompt)))
+ (expect (get-text-property i 'face prompt)
+ :to-equal 'projectile-search-prompt-default))))
+
+ (it "omits the default label when there is no symbol at point"
+ (spy-on 'projectile-symbol-or-selection-at-point :and-return-value nil)
+ (let (prompt)
+ (spy-on 'read-string :and-call-fake
+ (lambda (p &rest _) (setq prompt p) "typed"))
+ (projectile--read-search-string-with-default "Grep for")
+ (expect prompt :not :to-match "default")))))
+
(describe "the search wrapper commands"
(it "projectile-ripgrep forces the ripgrep backend"
(let (seen)