Subject: bug#77566: [PATCH] Add option for automatically delete non-existent projects.

From: Elijah Gabe Pérez <[email-masked]>
Date: Wed, 09 Apr 2025 13:28:05 -0600

Dmitry Gutov <[email-masked]> writes:

> On 09/04/2025 07:23, Elijah Gabe Pérez wrote:
>> Instead what about purge it once after emacs startup?
>
> I think a concern was voiced that during a long-running session directories can get removed at any time.
>
> +        (project-forget-zombie-projects nil t))))
>
> Are we sure that we want to suppress the messages about the removals in this case? Personally, I'd just print it in any case (if some were removed anyway).

It would display the message after `startup-echo-area-message'.
I think it would be intrusive.

I've changed it to `inhibit-message' instead, which will include
the message into *Messages* buffer but will not be displayed.

> +(defun project-forget-zombie-projects (&optional no-remote no-message)
>> +  "Forget all known projects that don't exist any more.
>> +If NO-REMOTE is non-nil, don't forget remote projects.
>
> Maybe NO-REMOTE should be called INCLUDE-REMOTE instead?
>
> Right now it sounds like the opposite of its actual meaning.

I think EXCLUDE-REMOTES would be a better name,
the INCLUDE-REMOTE name would give the impression that it does not
delete remote projects by default.  `project-forget-zombie-projects' by
default includes remote and no remote projects.

>From 0feaabcf32428c97afc7967e4a51adaab88c290a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?El=C3=ADas=20Gabriel=20P=C3=A9rez?= <[email-masked]>
Date: Tue, 8 Apr 2025 22:30:28 -0600
Subject: [PATCH] Add option for automatically delete non-existent projects. 
 (bug#77566)

* etc/NEWS: Announce changes.
* lisp/progmodes/project.el (project-prune-zombie-projects):
New user option.
(project-forget-zombie-projects): Add new function argument for
exclude projects that are remotes.
(project--ensure-read-project-list, project-prompt-project-dir)
(project-prompt-project-name): Call `project-forget-zombie-projects'
but inhibit its message.
---
 etc/NEWS                  |  9 +++++++++
 lisp/progmodes/project.el | 30 +++++++++++++++++++++++++-----
 2 files changed, 34 insertions(+), 5 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index cc63d03eafe..bb71ac0b80b 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -362,6 +362,15 @@ invoked standalone or from the 'project-switch-commands' dispatch menu.
 This user option describes projects that should always be skipped by
 'project-remember-project'.
 
+---
+*** New user option 'project-prune-zombie-projects'.
+This user option automatically removes projects from 'project-list-file'
+that cannot be accessed when switching projects.
+
+*** New argument EXCLUDE-REMOTES for 'project-forget-zombie-projects'.
+This function argument tells 'project-forget-zombie-projects' to
+exclude remote projects from being forgotten.
+
 ---
 *** New command 'project-save-some-buffers' bound to 'C-x p C-x s'.
 This is like 'C-x s', but only for this project's buffers.
diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el
index e2cd5bfa231..34788a34954 100644
--- a/lisp/progmodes/project.el
+++ b/lisp/progmodes/project.el
@@ -1551,6 +1551,13 @@ project-ignore-buffer-conditions
   :group 'project
   :package-version '(project . "0.8.2"))
 
+(defcustom project-prune-zombie-projects nil
+  "Automatically remove from project list projects that were deleted.
+If non-nil, remove deleted projects only when switching projects."
+  :type 'boolean
+  :version "31.1"
+  :group 'project)
+
 (defun project--read-project-buffer ()
   (let* ((pr (project-current t))
          (current-buffer (current-buffer))
@@ -1888,7 +1895,11 @@ project--read-project-list
 (defun project--ensure-read-project-list ()
   "Initialize `project--list' if it isn't already initialized."
   (when (eq project--list 'unset)
-    (project--read-project-list)))
+    (project--read-project-list)
+    (if-let* (project-prune-zombie-projects
+              ((consp project--list))
+              (inhibit-message t))
+        (project-forget-zombie-projects))))
 
 (defun project--write-project-list ()
   "Save `project--list' in `project-list-file'."
@@ -1963,6 +1974,9 @@ project-prompt-project-dir
 It's also possible to enter an arbitrary directory not in the list.
 When PROMPT is non-nil, use it as the prompt string."
   (project--ensure-read-project-list)
+  (if-let* (project-prune-zombie-projects
+            (inhibit-message t))
+      (project-forget-zombie-projects))
   (let* ((dir-choice "... (choose a dir)")
          (choices
           ;; XXX: Just using this for the category (for the substring
@@ -1992,6 +2006,9 @@ project-prompt-project-name
 see `project-list-file'.
 It's also possible to enter an arbitrary directory not in the list.
 When PROMPT is non-nil, use it as the prompt string."
+  (if-let* (project-prune-zombie-projects
+            (inhibit-message t))
+      (project-forget-zombie-projects))
   (let* ((dir-choice "... (choose a dir)")
          project--name-history
          (choices
@@ -2117,11 +2134,14 @@ project-remember-projects-under
                          count) count))
     count))
 
-(defun project-forget-zombie-projects ()
-  "Forget all known projects that don't exist any more."
-  (interactive)
+(defun project-forget-zombie-projects (&optional exclude-remotes)
+  "Forget all known projects that don't exist any more.
+If EXCLUDE-REMOTES is non-nil, exclude remote projects from being
+forgotten."
+  (interactive "P")
   (dolist (proj (project-known-project-roots))
-    (unless (file-exists-p proj)
+    (when (or (and exclude-remotes (not (file-remote-p proj)) (not (file-exists-p proj)))
+              (not (file-exists-p proj)))
       (project-forget-project proj))))
 
 (defun project-forget-projects-under (dir &optional recursive)
-- 
2.49.0

-- 
                                          - E.G via GNU Emacs and Org.