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.