Skip to content

Instantly share code, notes, and snippets.

@pesterhazy
Last active May 13, 2024 12:14
Show Gist options
  • Save pesterhazy/e8e445e6715f5d8bae3c62bc9db32469 to your computer and use it in GitHub Desktop.
Save pesterhazy/e8e445e6715f5d8bae3c62bc9db32469 to your computer and use it in GitHub Desktop.
Emacs, Monorepos, Eglot, Clojure-lsp and Project.el

This gist is for anyone who's trying to use emacs + eglot + monorepo (with Clojure or any other language).

When you open a file in a buffer, eglot needs to determine the scope or folder to run the language server in. By default, the folder eglot will pick as the assumed project root is the repo root (the ancestory directory containing .git).

But in a monorepo, that's rarely what you want. In a large repo, analyzing all the *.clj files with clojure-lsp could take a minute or longer. As a limiting case, imagine Google with its gargantuan monorepo. Analyzing all the source files would take an indefinite period of time.

So we need to scope eglot (and clojure-lsp) to a subfolder of the monorepo (e.g. /projects/foo). Eglot is built on top of project.el, a sort of built-in replacement for projectile, and uses its (project-current) function to determine the project root. (You can run M-x eval-expression (project-current) to see which folder it picks.)

Sadly project.el doesn't have an easy, built-in way to define what your repo root is (a strange omission for a project management tool.)

However, you can customize the behavior with elisp:

(require 'cl-extra)
(setq project-sentinels '("bb.edn" "deps.edn" "package.json" ".monorepo-project"))

(defun find-enclosing-project (dir)
  (locate-dominating-file
   dir
   (lambda (file)
     (and (file-directory-p file)
          (cl-some (lambda (sentinel)
                     (file-exists-p (expand-file-name sentinel file)))
                   project-sentinels)))))

(add-hook 'project-find-functions
          #'(lambda (d)
              (let ((dir (find-enclosing-project d)))
                (if dir (cons 'vc dir) nil))))

What this does is to instruct project.el (and thus, eglot) to pick the folder with a "dominating file" (in Emacs lingo a sentinel file found in the a file's parent directory, or its parent directory, or its parent directory etc) called deps.edn (or package.json etc) as the project root.

In Clojure, looking for deps.edn as a sentinel is not a bad assumption. Make sure to adapt the project-sentinels list to your own project.

h/t https://michael.stapelberg.ch/posts/2021-04-02-emacs-project-override/

@pesterhazy
Copy link
Author

Looks like (cons 'vc override) doesn't work anymore – something must have changed in a dependency.

Now I'm using

(add-hook 'project-find-functions
          #'(lambda (d)
              (let ((dir (find-enclosing-project d)))
                (if dir (list 'vc 'Git  dir) nil))))

@pesterhazy
Copy link
Author

This can now be done more easily with the project-vc-extra-root-markers variable

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment