diff --git a/CHANGELOG.md b/CHANGELOG.md index 5051a7048..31795a56f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ ### New features * Add `projectile-uniquify-dirname-transform`, a project-aware value for `uniquify-dirname-transform` that disambiguates same-named buffers using the project name. Mirrors `project.el`'s `project-uniquify-dirname-transform`. +* Add `projectile-dispatch`, a `transient` menu mirroring `projectile-command-map` for more discoverable command access. `transient` is an optional dependency (it requires Emacs 28+); the menu is only available when it's installed and is not bound to a key by default. * [#2008](https://github.com/bbatsov/projectile/issues/2008): Add `projectile-remove-project-type` to unregister a project type. This is the supported way to stop Projectile auto-detecting a type; clearing its `marker-files` does not work (see the related bug fix below). * [#1936](https://github.com/bbatsov/projectile/issues/1936): Add `projectile-discard-root-cache` command to clear `projectile-project-root-cache` without touching other Projectile caches. Useful after creating or removing a project marker, since the existing `projectile-invalidate-cache` either also drops the file list cache or prompts for a project depending on context. * Warn once per session when `projectile-indexing-method' is `alien' but the project has a non-empty `.projectile' file, so users notice their dirconfig rules are being bypassed. Controlled by the new `projectile-warn-when-dirconfig-is-ignored' option. diff --git a/doc/modules/ROOT/pages/usage.adoc b/doc/modules/ROOT/pages/usage.adoc index 9e9e9a625..d267dd8ed 100644 --- a/doc/modules/ROOT/pages/usage.adoc +++ b/doc/modules/ROOT/pages/usage.adoc @@ -472,6 +472,25 @@ You can add additional commands to the commander like this: Place such snippets after ``projectile-mode``'s init code. +== Transient Dispatch Menu + +If you have the `transient` library available (it's bundled with Emacs 28+; +older Emacsen are not supported by current `transient` releases), Projectile +provides `projectile-dispatch`, a +transient menu that mirrors `projectile-command-map`. It's a more discoverable +alternative to memorizing the keybindings or using the Commander: the menu keys +match the command map (e.g. kbd:[f] to find a file, kbd:[c c] to compile, +kbd:[s g] to grep). + +`transient` is an optional dependency, so `projectile-dispatch` is only defined +when it's present. It is not bound to a key by default; bind it to whatever you +like, for instance: + +[source,elisp] +---- +(define-key projectile-mode-map (kbd "C-c P") #'projectile-dispatch) +---- + == Using Projectile with project.el Starting with version 2.7 Projectile bundles some integration with diff --git a/projectile.el b/projectile.el index 5a2fd0321..42dbeaf6e 100644 --- a/projectile.el +++ b/projectile.el @@ -7045,6 +7045,89 @@ Magit that don't trigger `find-file-hook'." "Keymap for Projectile commands after `projectile-keymap-prefix'.") (fset 'projectile-command-map projectile-command-map) +;; `projectile-dispatch' is a transient menu mirroring `projectile-command-map'. +;; `transient' is an optional dependency that requires Emacs 28.1+, so it can be +;; entirely absent (including on Emacs 27, which Projectile still supports and +;; where transient cannot even be installed). The prefix is therefore defined +;; at load time only when transient is present, and via `eval' so the +;; `transient-define-prefix' macro is not needed at byte-compile time. The menu +;; keys deliberately match the `projectile-command-map' bindings. +(when (require 'transient nil t) + (eval + '(transient-define-prefix projectile-dispatch () + "Dispatch menu for Projectile commands." + [["Find" + ("f" "file" projectile-find-file) + ("g" "file dwim" projectile-find-file-dwim) + ("a" "other file" projectile-find-other-file) + ("l" "file in dir" projectile-find-file-in-directory) + ("F" "file in known projects" projectile-find-file-in-known-projects) + ("d" "dir" projectile-find-dir) + ("D" "dired" projectile-dired) + ("e" "recentf" projectile-recentf) + ("E" "edit .dir-locals" projectile-edit-dir-locals) + ("T" "test file" projectile-find-test-file) + ("t" "toggle impl/test" projectile-toggle-between-implementation-and-test)] + ["Buffers" + ("b" "switch buffer" projectile-switch-to-buffer) + ("I" "ibuffer" projectile-ibuffer) + ("k" "kill buffers" projectile-kill-buffers) + ("S" "save buffers" projectile-save-project-buffers)] + ["Search / Replace" + ("ss" "ag" projectile-ag) + ("sg" "grep" projectile-grep) + ("sr" "ripgrep" projectile-ripgrep) + ("sx" "references" projectile-find-references) + ("o" "multi-occur" projectile-multi-occur) + ("r" "replace" projectile-replace) + ("j" "find tag" projectile-find-tag) + ("R" "regenerate tags" projectile-regenerate-tags)]] + [["Project" + ("p" "switch project" projectile-switch-project) + ("q" "switch open project" projectile-switch-open-project) + ("A" "add known project" projectile-add-known-project) + ("m" "commander" projectile-commander) + ("V" "browse dirty projects" projectile-browse-dirty-projects) + ("v" "vc" projectile-vc)] + ["Lifecycle" + ("cc" "compile" projectile-compile-project) + ("ct" "test" projectile-test-project) + ("cr" "run" projectile-run-project) + ("co" "configure" projectile-configure-project) + ("ci" "install" projectile-install-project) + ("cp" "package" projectile-package-project)] + ["Shells / Run" + ("xe" "eshell" projectile-run-eshell) + ("xs" "shell" projectile-run-shell) + ("xt" "term" projectile-run-term) + ("xi" "ielm" projectile-run-ielm) + ("xg" "gdb" projectile-run-gdb) + ("xv" "vterm" projectile-run-vterm) + ("xx" "eat" projectile-run-eat) + ("!" "shell command" projectile-run-shell-command-in-root) + ("&" "async shell command" projectile-run-async-shell-command-in-root)] + ["Cache" + ("i" "invalidate cache" projectile-invalidate-cache) + ("z" "cache current file" projectile-cache-current-file)]] + [["Other window" + ("4f" "file" projectile-find-file-other-window) + ("4g" "file dwim" projectile-find-file-dwim-other-window) + ("4a" "other file" projectile-find-other-file-other-window) + ("4d" "dir" projectile-find-dir-other-window) + ("4D" "dired" projectile-dired-other-window) + ("4b" "buffer" projectile-switch-to-buffer-other-window) + ("4t" "impl/test" projectile-find-implementation-or-test-other-window) + ("4o" "display buffer" projectile-display-buffer)] + ["Other frame" + ("5f" "file" projectile-find-file-other-frame) + ("5g" "file dwim" projectile-find-file-dwim-other-frame) + ("5a" "other file" projectile-find-other-file-other-frame) + ("5d" "dir" projectile-find-dir-other-frame) + ("5D" "dired" projectile-dired-other-frame) + ("5b" "buffer" projectile-switch-to-buffer-other-frame) + ("5t" "impl/test" projectile-find-implementation-or-test-other-frame)]]) + t)) + (defvar projectile-mode-map (let ((map (make-sparse-keymap))) (when projectile-keymap-prefix diff --git a/test/projectile-test.el b/test/projectile-test.el index e8347a4ef..05a430632 100644 --- a/test/projectile-test.el +++ b/test/projectile-test.el @@ -110,6 +110,11 @@ You'd normally combine this with `projectile-test-with-sandbox'." ;;; buttercup allowed us to specify a custom error message like this: ;;; ;;; (expect (funcall 'foo) :to-throw 'error nil "Custom error message here") +(describe "projectile-dispatch" + (it "is defined as a command when transient is available" + (assume (require 'transient nil t) "transient is not available") + (expect (commandp 'projectile-dispatch) :to-be-truthy))) + (defmacro assert-friendly-error-when-no-project (fn) "Write a test that ensures FN throws a friendly error when called without a project." (let ((description (concat "when calling " (symbol-name fn) " without a project")))