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.
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
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-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.