Overriding project.el project root in Emacs

 

I've recently been experimenting with replacing Projectile with the built-in project.el, and so far it has impressed me. Not only are all of Projectile's useful features available, but for me project.el runs significantly faster in large repositories. If you're not familiar, both of these packages provide functions to search and operate on files and directories in the same project. If you're using Git, a "project" is probably synonymous with a repository.

Unfortunately, project detection is not always as easy as looking for a .git/ nearby, and sometimes Emacs gets it wrong. Projectile solves this by also looking for a .projectile file, which overrides detection and says "this is the project root". This happens to be one of the features missing in project.el.


Update

Since the writing of this post, Emacs 29 has been released and introduces a variable which solves this same issue in a cleaner way! Unfortunately it doesn't appear to work for me. Still it may be worth a shot for you: set project-vc-extra-root-markers to a list of file names or glob patterns which mark a project's root in addition to the default ".git", ".hg" and other common markers.

So, the cleaner equivalent to the rest of the post, if it works, is a simple

(setq project-vc-extra-root-markers '(".project.el" ".projectile" ))

If, however, you're like me and can't seem to get this to do anything, the original post still works:


Original post

Luckily, we can provide our own function to project.el which looks for a file like this in the current and parent directories. Even better, the excellent Emacs community has already jumped on this, and a splendid solution was provided by Michael Stapelberg.

Alas, Michael couldn't have forseen that Emacs would change the project root data format in Emacs 29, so the provided function only works in earlier versions. However, adding in forward compatibility isn't much trouble. And while we're at it, we can also provide support for anyone else moving from Projectile like I am, by allowing .projectile to serve as a project root marker alongside Michael's .project.el.

(defun project-root-override (dir)
  "Find DIR's project root by searching for a '.project.el' file.

If this file exists, it marks the project root. For convenient compatibility
with Projectile, '.projectile' is also considered a project root marker.

https://blog.jmthornton.net/p/emacs-project-override"
  (let ((root (or (locate-dominating-file dir ".project.el")
                  (locate-dominating-file dir ".projectile")))
        (backend (ignore-errors (vc-responsible-backend dir))))
    (when root (if (version<= emacs-version "28")
                    (cons 'vc root)
                  (list 'vc backend root)))))

;; Note that we cannot use :hook here because `project-find-functions' doesn't
;; end in "-hook", and we can't use this in :init because it won't be defined
;; yet.
(use-package project
  :config
  (add-hook 'project-find-functions #'project-root-override))

Now we can use touch .project.el in any directory, and project.el with recognize it as the project root!

By the way, the snippet above makes use of use-package which provides fantastic package configuration and loading ability. John Wiegley is currently working on adding it into Emacs itself, so it shouldn't be long before this code snippet is fully native!

One note, in an ideal world, I'd prefer the root marker to be just .project instead of .project.el, but this is already widely used by other tools like Eclipse and I'd rather not cause conflicts. If you'd like to use this in your own Emacs, obviously you can change the function to check for anything you want.