Subject: 29.0.90; project.el can't dynamically populate the project list

From: Spencer Baugh <[email-masked]>
Date: Sat, 03 Jun 2023 07:55:10 -0400

project.el wants a list of known projects, for project-switch-project
and prompting in project-current.

Currently this list is maintained in two ways:
- automatically, by remembering any project the user runs project
  commands within
- manually by the user with project-remember-project and other functions

In both cases, this list is persisted so that it stays around through
Emacs restarts.  All this is good.

But, I often clone new repositories outside of Emacs, and I also have
scripts outside Emacs which make new clones automatically.  It would be
nice for project.el to know about those clones, so I can switch to them
with project-switch-project right away.  Instead, today I usually
manually navigate to those projects the first time, and I'm only able to
use project-switch-project on subsequent times.

These new repos are created in relatively predictable places, so I can
write code which discovers them all.  But I don't have a way to tell
project.el about them.

I could run code on a timer to project-remember-project these projects,
since I create them in predictable locations.  But that would mean
there's a delay between cloning the repo and being able to use it with
project-switch-project, which is annoying especially when I manually
cloned the repo and want to use it immediately.

The new projects are created while Emacs is running, so just remembering
them all at startup doesn't work either.

I'd like a customization point where I can supply a function (or list of
functions) which project-known-project-roots should run to produce an
additional list of project root directories, which should then be
appended to project--list.

I don't need project.el to specifically remember these projects; they'll
be remembered automatically as users use them, and completing-read will
nicely deduplicate the project roots anyway.

Does that seem reasonable?  It would be something like this:

diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el
index 04c67710d71..cc05cf460ef 100644
--- a/lisp/progmodes/project.el
+++ b/lisp/progmodes/project.el
@@ -1679,11 +1679,21 @@ project-prompt-project-name
       (let ((proj (assoc pr-name choices)))
         (if (stringp proj) proj (project-root (cdr proj)))))))
 
+(defcustom project-dynamic-roots '()
+  "List of functions to call to dynamically find projects.
+
+Each is called with no arguments and should return a list of
+project root dirs."
+  :type '(repeat function)
+  :group 'project
+  :version "30.1")
+
 ;;;###autoload
 (defun project-known-project-roots ()
   "Return the list of root directories of all known projects."
   (project--ensure-read-project-list)
-  (mapcar #'car project--list))
+  (flatten-tree (cons (mapcar #'car project--list)
+                      (mapcar #'funcall project-dynamic-roots))))
 
 ;;;###autoload
 (defun project-execute-extended-command ()