Add Any Directory to project.el's List of Projects

I am using the built-in project.el in Emacs to find files in various git-backed projects. But I also have a shared folder of blog post drafts with Sascha at zettelkasten.de that does not have any version control backing. I couldn’t get that folder to show up in project.el’s list because of that.

Until now.

My own attempts to make something like this work came to a halt when I encountered the generic project-root function. That stuff goes over my head. Finding a project marker file worked okay, but that isn’t enough, apparently

;; This does not suffice!

(defun ct/dir-contains-project-marker (dir)
  "Checks if `.project' file is present in directory at DIR path."
  (let ((project-marker-path (file-name-concat dir ".project")))
    (when (file-exists-p project-marker-path)
       dir)))
(customize-set-variable 'project-find-functions
                        (list #'project-try-vc
                              #'ct/dir-contains-project-marker))

Update 2022-07-13: Changed (concat dir "/" ".project") to (file-name-concat dir ".project"), thanks to a hint from grtcdr!

With only that, the function correctly reports a directory path as being a project, but something fails in project-root:

cl-no-applicable-method: No applicable method: project-root, "~/path/to/drafts/"

Didn’t know how to fix that.

From Karthik Chikmagalur’s (better known as @karthink) GPL’ed project-x package, I took the functions and variables that were used to mark a directory path at project-indexable by adding a .project file to the root. The rest, like window restoration, I left out. It’s quite simple, actually, and I am printing the source below:

;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; For a full copy of the GNU General Public License
;; see <http://www.gnu.org/licenses/>.

(defgroup project-local nil
  "Local, non-VC-backed project.el root directories."
  :group 'project)

(defcustom project-local-identifier ".project"

You can specify a single filename or a list of names."
  :type '(choice (string :tag "Single file")
                 (repeat (string :tag "Filename")))
  :group 'project-local)

(cl-defmethod project-root ((project (head local)))
  "Return root directory of current PROJECT."
  (cdr project))

(defun project-local-try-local (dir)
  "Determine if DIR is a non-VC project.
DIR must include a file with the name determined by the
variable `project-local-identifier' to be considered a project."
  (if-let ((root (if (listp project-local-identifier)
                     (seq-some (lambda (n)
                                 (locate-dominating-file dir n))
                               project-local-identifier)
                   (locate-dominating-file dir project-local-identifier))))
      (cons 'local root)))

(customize-set-variable 'project-find-functions
                        (list #'project-try-vc
                              #'project-local-try-local))

So my own approach was missing a definition like this:

(cl-defmethod project-root ((project (head local)))
  "Return root directory of current PROJECT."
  (cdr project))

Can’t explain how the cl-defgeneric and cl-defmethod stuff works at all; I can only highlight that this is all that was necessary. How this differs from the couple of built-in project-root method definitions, and how Elisp figures out which one to use? I have no clue.

Now I merely have to add a .project file to a directory and project.el will remember this directory in my list of project paths, so I can quickly jump to a draft from anywhere quickly.