From 72c4f0ad3dbd3f9429f7d4fd878c706db5b5e0f6 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 28 Jul 2026 15:24:12 +0200 Subject: [PATCH] Put the rest of the unsolicited messages behind projectile-verbose An audit of all 60 message call sites. The option gated nine of them, and the line it was drawing was the right one - Projectile stays quiet about what it does off its own bat, and still answers for what you asked it to do - but five sites had been missed: - caching a file the find-file hook just picked up, which fires for every newly cached file you open - a search path entry that no longer exists, which the once-a-session automatic scan would otherwise report on every startup - an indexing command that exited non-zero but produced usable output, in both the sync and async runners, whose own comment already said to mention it quietly - a session file from an older format version, which projectile-session-restore-all can meet a whole directory of Two of those are also commands in their own right, so they follow the existing pattern and still speak when invoked interactively. Deliberately left alone: the 'initializing cache' notice, which is the only warning that synchronous native indexing is about to take a while, and every message that is a command answering for itself. --- CHANGELOG.md | 1 + doc/modules/ROOT/pages/configuration.adoc | 7 ++ projectile.el | 30 ++++--- test/projectile-core-test.el | 98 +++++++++++++++++++++++ 4 files changed, 127 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3336a17a2..9bf0f4462 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ ### Bugs fixed +- [#2140](https://github.com/bbatsov/projectile/pull/2140): `projectile-verbose` now covers five more messages Projectile emitted without being asked: caching a file the `find-file` hook picked up, a search path entry that doesn't exist, an indexing command that exited non-zero but produced usable output (both the sync and async runners), and a session file from an older format version. Commands you invoke still report what they did whatever the option is set to, and the two that are also commands (`projectile-cache-current-file`, `projectile-discover-projects-in-directory`) still speak when invoked directly. - [#2139](https://github.com/bbatsov/projectile/pull/2139): The file-notification cache updates now honor the VCS's ignore rules too, so a watched project no longer gains files a re-index would never have listed - the last place `.gitignore` was going unread, after [#2126](https://github.com/bbatsov/projectile/pull/2126) fixed it for files opened by hand. One `git check-ignore` covers a whole batch of events, since a batch can be an entire directory moved into the project. - [#1927](https://github.com/bbatsov/projectile/issues/1927): A known projects file Projectile can't read is now moved aside with a `.corrupt` suffix and reported, instead of being read as an empty list - which made it look like another Emacs had removed every project, so the merge dropped the session's projects too and overwrote the file. Projectile also strips text properties when saving, since a propertized string whose properties don't read back is how the file gets corrupted in the first place. diff --git a/doc/modules/ROOT/pages/configuration.adoc b/doc/modules/ROOT/pages/configuration.adoc index ee9b3ac7a..1a184a019 100644 --- a/doc/modules/ROOT/pages/configuration.adoc +++ b/doc/modules/ROOT/pages/configuration.adoc @@ -540,6 +540,13 @@ suppress them: (setq projectile-verbose nil) ---- +What this covers is the things Projectile says *without being asked* - caching a +file you have just opened, a background index that had something to report, a +session file it had to skip, a search path entry that no longer exists, a +project whose watches it gave up on. Commands you invoke still report what they +did either way, so turning this off makes Projectile quieter rather than mute: +`projectile-invalidate-cache` still confirms that it ran. + === Menu bar Projectile adds a menu to the Emacs menu bar by default. To disable it: diff --git a/projectile.el b/projectile.el index 5da2bd5bc..fe8abd4be 100644 --- a/projectile.el +++ b/projectile.el @@ -468,7 +468,13 @@ is set to `alien'." :package-version '(projectile . "3.1.0")) (defcustom projectile-verbose t - "Echo messages that are not errors." + "Whether to echo the messages Projectile emits without being asked. + +This covers what Projectile says as a side effect of something else - +caching a file you just opened, a background index that had something to +report, a session file it had to skip. Commands you invoke still say +what they did whatever this is set to: turning it off makes Projectile +quieter, not mute." :group 'projectile :type 'boolean :package-version '(projectile . "0.12.0")) @@ -2348,9 +2354,10 @@ PROJECT-ROOT defaults to the current project." ;; UI immediately after the new file was created. (when (projectile-persistent-cache-p) (projectile--schedule-cache-flush current-project))) - (message "File %s added to project %s cache." - (propertize current-file 'face 'font-lock-keyword-face) - (propertize current-project 'face 'font-lock-keyword-face))))))) + (when (or projectile-verbose (called-interactively-p 'interactive)) + (message "File %s added to project %s cache." + (propertize current-file 'face 'font-lock-keyword-face) + (propertize current-project 'face 'font-lock-keyword-face)))))))) ;; cache opened files automatically to reduce the need for cache invalidation (defun projectile-cache-files-find-file-hook (&optional project-root) @@ -2645,7 +2652,8 @@ discover projects there." (let ((dir (projectile--known-project-root (projectile-project-root directory)))) (unless (member dir projectile-known-projects) (projectile-add-known-project dir))))) - (message "Project search path directory %s doesn't exist" directory))) + (when (or projectile-verbose (called-interactively-p 'interactive)) + (message "Project search path directory %s doesn't exist" directory)))) (defvar projectile--search-path-discovered nil "Non-nil once `projectile-project-search-path' has been auto-discovered. @@ -3734,7 +3742,7 @@ Only text sent to standard output is taken into account." ;; Non-zero exit but we still got a listing: trust it. Only ;; mention it (quietly) when there was stderr worth seeing. (files - (when had-stderr + (when (and had-stderr projectile-verbose) (message "Projectile: `%s' exited with code %d but produced output; using it (see *projectile-files-errors*)" full-command exit-code))) ;; Non-zero exit and nothing on stdout: a real failure. @@ -16395,9 +16403,13 @@ with a message, so the user learns why nothing was restored." projectile-session--format-version) data)) ((and (consp data) (plist-member data :projectile-session-version)) - (message "Ignoring session file %s: format version %s (expected %s)" - file (plist-get data :projectile-session-version) - projectile-session--format-version) + ;; `projectile-session-restore-all' can walk a directory full of these + ;; at startup, so this is exactly the kind of thing that shouldn't + ;; announce itself once per file. + (when projectile-verbose + (message "Ignoring session file %s: format version %s (expected %s)" + file (plist-get data :projectile-session-version) + projectile-session--format-version)) nil)))) (defun projectile-session--read (root) diff --git a/test/projectile-core-test.el b/test/projectile-core-test.el index 098dc842a..ab61f8aed 100644 --- a/test/projectile-core-test.el +++ b/test/projectile-core-test.el @@ -427,4 +427,102 @@ (let ((projectile-sort-order 'no-such-order)) (expect (projectile-sort-files '("b" "a")) :to-equal '("b" "a"))))) +(describe "projectile-verbose" + ;; The line it draws: Projectile stays quiet about what it does off its + ;; own bat, and still answers for what you asked it to do. + + (describe "caching the file you just opened" + (it "says nothing when the find-file hook did it" + (projectile-test-with-project (("main.el" . ";; x")) + (let ((projectile-verbose nil) + (projectile-enable-caching t) + (root (projectile-project-root))) + (puthash root '("other.el") projectile-projects-cache) + (spy-on 'message) + (with-temp-buffer + (setq buffer-file-name (expand-file-name "main.el" root)) + (projectile-cache-current-file root)) + ;; it still cached the file... + (expect (gethash root projectile-projects-cache) :to-contain "main.el") + ;; ...without announcing it + (expect 'message :not :to-have-been-called)))) + + (it "still answers when you invoke it yourself" + (projectile-test-with-project (("main.el" . ";; x")) + (let ((projectile-verbose nil) + (projectile-enable-caching t) + (root (projectile-project-root))) + (puthash root '("other.el") projectile-projects-cache) + (spy-on 'message) + (spy-on 'called-interactively-p :and-return-value t) + (with-temp-buffer + (setq buffer-file-name (expand-file-name "main.el" root)) + (projectile-cache-current-file root)) + (expect 'message :to-have-been-called))))) + + (describe "a search path entry that doesn't exist" + (it "says nothing during the automatic scan" + (let ((projectile-verbose nil)) + (spy-on 'message) + (projectile-discover-projects-in-directory "/no/such/directory/") + (expect 'message :not :to-have-been-called))) + + (it "still says so when you run the command" + (let ((projectile-verbose nil)) + (spy-on 'message) + (spy-on 'called-interactively-p :and-return-value t) + (projectile-discover-projects-in-directory "/no/such/directory/") + (expect 'message :to-have-been-called)))) + + (describe "an indexing command that exits non-zero with output" + (it "keeps the note to itself, but still uses the output" + (projectile-test-with-sandbox + (projectile-test-with-files ("project/" "project/.projectile") + (let ((default-directory (projectile-test-project-root)) + (projectile-verbose nil)) + (spy-on 'message) + (expect (projectile-files-via-ext-command + default-directory "printf 'a.el\\0'; echo boom >&2; exit 1") + :to-equal '("a.el")) + (expect 'message :not :to-have-been-called))))) + + (it "mentions it when asked to be verbose" + (projectile-test-with-sandbox + (projectile-test-with-files ("project/" "project/.projectile") + (let ((default-directory (projectile-test-project-root)) + (projectile-verbose t)) + (spy-on 'message) + (projectile-files-via-ext-command + default-directory "printf 'a.el\\0'; echo boom >&2; exit 1") + (expect 'message :to-have-been-called)))))) + + (describe "a session file from another format version" + (it "is skipped silently, since restore-all can meet a directory of them" + (projectile-test-with-temp-files ((file ".eld")) + (let ((projectile-verbose nil)) + (projectile-serialize '(:projectile-session-version 0 :tabs nil) file) + (spy-on 'message) + (expect (projectile-session--read-file file) :to-be nil) + (expect 'message :not :to-have-been-called)))) + + (it "says which file it skipped when asked to be verbose" + (projectile-test-with-temp-files ((file ".eld")) + (let ((projectile-verbose t)) + (projectile-serialize '(:projectile-session-version 0 :tabs nil) file) + (spy-on 'message) + (expect (projectile-session--read-file file) :to-be nil) + (expect 'message :to-have-been-called))))) + + (describe "what it deliberately does not cover" + (it "leaves a command's own answer alone" + ;; `projectile-invalidate-cache' exists to be invoked; saying nothing + ;; would leave you wondering whether it ran. + (projectile-test-with-project (("main.el" . ";; x")) + (let ((projectile-verbose t) + (root (projectile-project-root))) + (puthash root '("main.el") projectile-projects-cache) + (spy-on 'message) + (projectile-invalidate-cache nil) + (expect 'message :to-have-been-called)))))) + ;;; projectile-core-test.el ends here