From 9b308fde875c50e5425153f085f6bc02f4fdafe1 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 28 Aug 2026 17:24:11 +0300 Subject: [PATCH 1/2] Give todos and multi-occur the same group entry point as the rest The sibling commands are meant to be thin wrappers over a generic that takes any list of projects, which is what lets you write the same command for a group of your own. That held for find-file, search and switch-to-buffer; todos reached the generic through a private `projectile--todos', and multi-occur had none at all. So `projectile-todos-in-projects' is the old private function under a public name, and `projectile-multi-occur-in-projects' is lifted out of the sibling command, which now reads like its three neighbours. --- .../ROOT/pages/across_repositories.adoc | 13 ++++---- projectile.el | 27 ++++++++++------ test/projectile-project-group-test.el | 31 +++++++++++++++++++ 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/doc/modules/ROOT/pages/across_repositories.adoc b/doc/modules/ROOT/pages/across_repositories.adoc index 3c6c281e2..2e6eb76df 100644 --- a/doc/modules/ROOT/pages/across_repositories.adoc +++ b/doc/modules/ROOT/pages/across_repositories.adoc @@ -247,9 +247,10 @@ reviewable replacement across all of them. See xref:usage.adoc[the search and replace reviewers]. These all take a plain list of projects underneath - -`projectile-find-file-in-projects`, `projectile-search-in-projects` and -`projectile-switch-to-buffer-in-projects` - so a command for a group of -your own is a two-line wrapper: +`projectile-find-file-in-projects`, `projectile-search-in-projects`, +`projectile-switch-to-buffer-in-projects`, +`projectile-multi-occur-in-projects` and `projectile-todos-in-projects` - +so a command for a group of your own is a two-line wrapper: [source,elisp] ---- @@ -260,9 +261,9 @@ your own is a two-line wrapper: "Find file in infra: ")) ---- -`projectile-search-in-projects` and -`projectile-switch-to-buffer-in-projects` are the same shape for -searching and for buffers. +The others are the same shape: each takes the project list first, and the +sibling commands are nothing more than these with +`projectile-sibling-projects` filled in. NOTE: A literal group search runs `rg` over each project in turn when ripgrep is installed - one per project rather than one over the lot, diff --git a/projectile.el b/projectile.el index b2d3c11ed..287df8800 100644 --- a/projectile.el +++ b/projectile.el @@ -11541,9 +11541,10 @@ in the results buffer. The keyword does not have to sit in a comment. With a prefix argument ARG, prompt for which keywords to search for instead of using all of them." (interactive "P") - (projectile--todos (list (projectile-acquire-root)) arg)) + (projectile-todos-in-projects (list (projectile-acquire-root)) arg)) -(defun projectile--todos (projects &optional arg) +;;;###autoload +(defun projectile-todos-in-projects (projects &optional arg) "Collect the TODO-style annotations of PROJECTS into the search reviewer. With ARG non-nil, prompt for which keywords to search for. PROJECTS is a list of project roots, so one project and a whole group take the same @@ -15290,25 +15291,31 @@ Related is what `projectile-switch-sibling-project' means by it." (projectile--sibling-group) "Switch to sibling buffer: ")) ;;;###autoload -(defun projectile-multi-occur-in-sibling-projects (&optional nlines) - "Do a `multi-occur' in the buffers of the current project and related ones. -Related is what `projectile-switch-sibling-project' means by it. With a -prefix argument, show NLINES of context. +(defun projectile-multi-occur-in-projects (projects &optional nlines) + "Do a `multi-occur' in the buffers of any of PROJECTS. +NLINES is the number of context lines `multi-occur' shows. Note this searches the buffers you have open, not the projects on disk - -`projectile-search-in-sibling-projects' is the one that reads files." - (interactive "P") - (multi-occur (projectile-project-group-buffers (projectile--sibling-group)) +`projectile-search-in-projects' is the one that reads files." + (multi-occur (projectile-project-group-buffers projects) (car (occur-read-primary-args)) nlines)) +;;;###autoload +(defun projectile-multi-occur-in-sibling-projects (&optional nlines) + "Do a `multi-occur' in the buffers of the current project and related ones. +Related is what `projectile-switch-sibling-project' means by it. With a +prefix argument, show NLINES of context." + (interactive "P") + (projectile-multi-occur-in-projects (projectile--sibling-group) nlines)) + ;;;###autoload (defun projectile-todos-in-sibling-projects () "Collect TODO-style annotations across the current project and related ones. Related is what `projectile-switch-sibling-project' means by it. See `projectile-todos' for what counts as an annotation." (interactive) - (projectile--todos (projectile--sibling-group))) + (projectile-todos-in-projects (projectile--sibling-group))) ;;; Project bookmarks diff --git a/test/projectile-project-group-test.el b/test/projectile-project-group-test.el index 86e397ab5..f353d72bd 100644 --- a/test/projectile-project-group-test.el +++ b/test/projectile-project-group-test.el @@ -386,6 +386,37 @@ the two truename'd roots and `parent' to the directory holding them." ;; the prefix argument is passed through as the context line count (expect (nth 2 args) :to-equal 3)))))) +;; Every sibling command is a wrapper over a generic that takes any list of +;; projects, so a group of your own drives them the same way. `todos' and +;; `multi-occur' were the two that had no such generic to call. +(describe "the generic group commands" + (it "projectile-multi-occur-in-projects takes the projects it is given" + (projectile-group-test--with-projects + (let ((ba (find-file-noselect (expand-file-name "src/a.txt" alpha))) + (bb (find-file-noselect (expand-file-name "lib/b.txt" beta)))) + (spy-on 'occur-read-primary-args :and-return-value '("needle")) + (spy-on 'multi-occur) + ;; no sibling lookup involved + (spy-on 'projectile-sibling-projects :and-throw-error 'error) + (projectile-multi-occur-in-projects (list alpha beta) 2) + (let ((args (spy-calls-args-for 'multi-occur 0))) + (expect (nth 0 args) :to-contain ba) + (expect (nth 0 args) :to-contain bb) + (expect (nth 2 args) :to-equal 2))))) + + (it "projectile-todos-in-projects takes the projects it is given" + (projectile-group-test--with-projects + (with-temp-file (expand-file-name "src/todo.txt" alpha) + (insert "TODO: alpha thing\n")) + (with-temp-file (expand-file-name "lib/todo.txt" beta) + (insert "FIXME: beta thing\n")) + (spy-on 'projectile-sibling-projects :and-throw-error 'error) + (cl-letf (((symbol-function 'pop-to-buffer) #'ignore)) + (projectile-todos-in-projects (list alpha beta))) + (expect (projectile-test-match-files + (get-buffer projectile-search-buffer-name)) + :to-equal '("alpha/src/todo.txt" "beta/lib/todo.txt"))))) + ;;; Ripgrep across a group (defun projectile-group-test--seed-rg (buf root projects term) From 6b22251e1951eb728f3bb4ad5c9409f913a35ce5 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 28 Aug 2026 17:24:23 +0300 Subject: [PATCH 2/2] Add CHANGELOG entry for the generic group commands --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f461460f2..c7fdfa1aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### New features +- [#2182](https://github.com/bbatsov/projectile/pull/2182): Add `projectile-todos-in-projects` and `projectile-multi-occur-in-projects`, so every sibling command now has a public generic taking any list of projects behind it and a command for a group of your own is a two-line wrapper for all five. - [#2181](https://github.com/bbatsov/projectile/pull/2181): Sapling and Bazaar projects can now list their own ignored files, via the new `projectile-sapling-ignored-command` and `projectile-bzr-ignored-command`. - [#2180](https://github.com/bbatsov/projectile/pull/2180): Every shell backend can now open in another window (`s-p x 4 `), not just vterm, eat and ghostel: `shell`, `eshell`, `ielm`, `term` and the backend-dispatching `projectile-run` gained `-other-window` commands. - [#2160](https://github.com/bbatsov/projectile/pull/2160): Add `projectile-find-file-in-sibling-projects` (`s-p n f`) and `projectile-search-in-sibling-projects` (`s-p n s`), which work across the family of related projects rather than just the one you're in.