diff --git a/etc/NEWS b/etc/NEWS index 836b16620cc..eb85baae341 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -432,6 +432,19 @@ 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 controls the automatic deletion of projects from +'project-list-file' that cannot be accessed when prompting for a +project. + +The value can be a predicate which takes one argument and should return +non-nil if the project should be removed. If set to nil, all the +inaccessible projects will not be removed automatically. + +By default this is set to 'project-prune-zombies-default' function +which removes all non-remote projects. + --- *** 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 5cf9a68a294..e515205d349 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -1562,6 +1562,24 @@ general form of conditions." :group 'project :package-version '(project . "0.8.2")) +(defcustom project-prune-zombie-projects #'project-prune-zombies-default + "Remove automatically from project list all the projects that were removed. +The value can be a predicate function which takes one argument, and +should return non-nil if the project should be removed. +If set to nil, all the inaccessible projects will not be removed automatically." + :type '(choice (const :tag "Default (remove non-remote projects)" + project-prune-zombies-default) + (const :tag "Remove any project" identity) + (function :tag "Custom function") + (const :tag "Disable auto-deletion" nil)) + :version "31.1" + :group 'project) + +(defun project-prune-zombies-default (project) + "Default function used in `project-prune-zombie-projects'. +Return non-nil if PROJECT is not a remote project." + (not (file-remote-p project))) + (defun project--read-project-buffer () (let* ((pr (project-current t)) (current-buffer (current-buffer)) @@ -1914,7 +1932,11 @@ With some possible metadata (to be decided).") (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'." @@ -1995,6 +2017,9 @@ 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." (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 @@ -2024,6 +2049,9 @@ The project is chosen among projects known from the project list, 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 @@ -2153,7 +2181,10 @@ Return the number of detected projects." "Forget all known projects that don't exist any more." (interactive) (dolist (proj (project-known-project-roots)) - (unless (file-exists-p proj) + (when (and (if project-prune-zombie-projects + (funcall project-prune-zombie-projects proj) + t) + (not (file-exists-p proj))) (project-forget-project proj)))) (defun project-forget-projects-under (dir &optional recursive)