|
| 1 | += Project-specific Configuration |
| 2 | +:experimental: |
| 3 | + |
| 4 | +A pretty common question about CIDER is how to handle project-specific configuration. |
| 5 | +There are many reasons for wanting to do something like this, but probably the first |
| 6 | +that comes to mind is running Leiningen with some specific profile or adding "-A:fig" |
| 7 | +to the jack-in command when using the Clojure CLI (a.k.a. `tools.deps`). |
| 8 | + |
| 9 | +TIP: If you simply need to edit a jack-in command on the fly you're probably better off |
| 10 | +prefixing the command with kbd:[C-u] (e.g. kbd:[C-u C-c C-x j j]), which will |
| 11 | +allow you to edit the entire command string in the minibuffer. |
| 12 | + |
| 13 | +CIDER doesn't have any special provisions for project-specific configuration, as this |
| 14 | +is something well supported in Emacs itself. Unfortunately the functionality in Emacs has the |
| 15 | +slight weird name "dir-local variables", which is probably not the thing people would |
| 16 | +start googling for. On the bright side - the Emacs functionality is much more generic |
| 17 | +than dealing with project-specific configuration. |
| 18 | + |
| 19 | +Very simply put, all you need to do is to create in the root of your project a file named |
| 20 | +`.dir-locals.el` which should look something like: |
| 21 | + |
| 22 | +[source,emacs-lisp] |
| 23 | +---- |
| 24 | +((clojurescript-mode |
| 25 | + (cider-clojure-cli-global-options . "-A:fig") |
| 26 | + (eval . (cider-register-cljs-repl-type 'super-cljs "(do (foo) (bar))")) |
| 27 | + (cider-default-cljs-repl . super-cljs))) |
| 28 | +---- |
| 29 | + |
| 30 | +The structure of the file is a mapping of major modes and some variables |
| 31 | +that need to be set in them. As CIDER is not a major mode most of the time you'll |
| 32 | +probably be setting variables in `clojure-mode` or `clojurescript-mode`. Note that |
| 33 | +`clojurescript-mode` derives from `clojure-mode`, so whatever applies to `clojure-mode` |
| 34 | +will apply to `clojurescript-mode` as well. You can also evaluate code by using |
| 35 | +`eval` as the variable name in the variable to value mapping, but that's something |
| 36 | +you'll rarely need in practice. |
| 37 | + |
| 38 | +Normally, you'd simply create the `.dir-locals.el` manually and edit it like any other |
| 39 | +Emacs Lisp code. If you, however, feel |
| 40 | +overwhelmed by its syntax you can simply do `M-x add-dir-local-variable` and |
| 41 | +you'll be able to select the major-mode, the variable and its value |
| 42 | +interactively. One small problem with this approach is that the resulting |
| 43 | +`.dir-local.el` will be created in the current directory, which may be a problem |
| 44 | +depending on what you're trying to do. Users of |
| 45 | +https://github.com/bbatsov/projectile[Projectile] may leverage the |
| 46 | +project-aware `projectile-edit-dir-locals` command instead. |
| 47 | + |
| 48 | +Here's one slightly more complex `.dir-locals.el`: |
| 49 | + |
| 50 | +[source,emacs-lisp] |
| 51 | +---- |
| 52 | +((emacs-lisp-mode |
| 53 | + (bug-reference-url-format . "https://github.com/clojure-emacs/cider/issues/%s") |
| 54 | + (bug-reference-bug-regexp . "#\\(?2:[[:digit:]]+\\)") |
| 55 | + (indent-tabs-mode . nil) |
| 56 | + (fill-column . 80) |
| 57 | + (sentence-end-double-space . t) |
| 58 | + (emacs-lisp-docstring-fill-column . 75) |
| 59 | + (checkdoc-symbol-words . ("top-level" "major-mode" "macroexpand-all" "print-level" "print-length")) |
| 60 | + (checkdoc-package-keywords-flag) |
| 61 | + (checkdoc-arguments-in-order-flag) |
| 62 | + (checkdoc-verb-check-experimental-flag) |
| 63 | + (elisp-lint-indent-specs . ((if-let* . 2) |
| 64 | + (when-let* . 1) |
| 65 | + (let* . defun) |
| 66 | + (nrepl-dbind-response . 2) |
| 67 | + (cider-save-marker . 1) |
| 68 | + (cider-propertize-region . 1) |
| 69 | + (cider-map-repls . 1) |
| 70 | + (cider--jack-in . 1) |
| 71 | + (cider--make-result-overlay . 1) |
| 72 | + ;; need better solution for indenting cl-flet bindings |
| 73 | + (insert-label . defun) ;; cl-flet |
| 74 | + (insert-align-label . defun) ;; cl-flet |
| 75 | + (insert-rect . defun) ;; cl-flet |
| 76 | + (cl-defun . 2) |
| 77 | + (with-parsed-tramp-file-name . 2) |
| 78 | + (thread-first . 1) |
| 79 | + (thread-last . 1))))) |
| 80 | +---- |
| 81 | + |
| 82 | +Did you manage to guess what it is? That's CIDER's own `.dir-locals.el`, which |
| 83 | +ensures that all people hacking on the Elisp codebase are going to be using some |
| 84 | +common code style settings. That's why everything's scoped to `emacs-lisp-mode`. |
| 85 | + |
| 86 | +For a Clojure-centric example let's take a look at ``cider-nrepl``'s `.dir-locals.el`: |
| 87 | +
|
| 88 | +[source,emacs-lisp] |
| 89 | +---- |
| 90 | +((clojure-mode |
| 91 | + (clojure-indent-style . :always-align) |
| 92 | + (indent-tabs-mode . nil) |
| 93 | + (fill-column . 80))) |
| 94 | +---- |
| 95 | +
|
| 96 | +Here the point is to ensure everyone working on the Clojure codebase using Emacs would |
| 97 | +be sharing the same code style settings. |
| 98 | +
|
| 99 | +Often in the wild you'll see dir-local entries with `nil` as the major mode there. |
| 100 | +This odd looking notation simply means that the configuration specified there will |
| 101 | +be applied to every buffer regardless of its major mode. Use this approach sparingly, as there's |
| 102 | +rarely a good reason to do this. |
| 103 | +
|
| 104 | +Another thing to keep in mind is that you can have multiple `.dir-locals.el` files in your project. |
| 105 | +Their overall effect will be cumulative with the innermost file taking precedence for any files |
| 106 | +in the directories beneath it. I've never needed this in practice, but I can imagine it being |
| 107 | +useful for people who have multiple projects in a mono repo, or people who apply different conventions |
| 108 | +to "real" code and its tests. |
| 109 | +
|
| 110 | +You might be wondering when do changes to `.dir-locals.el` get reflected in the Emacs buffers |
| 111 | +affected by them. The answer is to this question is "when the buffers get created". If you change |
| 112 | +something in `.dir-locals.el` you'll normally have to re-create the related buffers. |
| 113 | +Or you can do in the hacker way and apply https://emacs.stackexchange.com/questions/13080/reloading-directory-local-variables[a bit of Elisp magic]. |
| 114 | +
|
| 115 | +TIP: There are more aspects to dir-locals, but they are beyond the scope of this article. |
| 116 | +If you're curious for all the gory details you should check out https://www.gnu.org/software/emacs/manual/html_node/emacs/Directory-Variables.html[the official Emacs documentation on dir-locals]. |
0 commit comments