-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e02d5f2
commit acc94db
Showing
21 changed files
with
168 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
+++ | ||
title = "Setting Up Emacs LSP-Mode For Clojure" | ||
author = ["Sophie"] | ||
date = 2024-09-17T22:42:00+02:00 | ||
tags = ["emacs"] | ||
draft = false | ||
+++ | ||
|
||
I use [Clojure](https://clojure.org/) on the backend [where I work](https://www.ardoq.com/). I think it's really fun, especially in combination with Emacs, since Emacs is built with Emacs Lisp and Clojure is a Lisp dialect. So many of Emacs' default keybindings started clicking for me once I started working with a language in the same tradition, editing structurally and by semantic units rather than line-by-line or character-by-character. I'm not alone in loving both and there's clearly a lot of overlap between the two communities, which is evident from the number of Clojure packages for Emacs. | ||
|
||
The probably most famous and popular among these is the excellent package [CIDER](https://cider.mx/), which provides a full-fledged development environment for Emacs with support for interactive development. CIDER has a ton of features for evaluating, debugging, and testing your code. However, you might want to supplement CIDER with other packages for things such as peeking function argument lists, navigating your code, and finding references/implementations. Emacs has some built-in documentation (`eldoc`), project management (`project`) and reference-finding capabilities (`xref`). But when you need more, [clojure-lsp](https://clojure-lsp.io/) is your friend and the LSP client for Emacs [lsp-mode](https://emacs-lsp.github.io/lsp-mode/) integrates pretty well with the aforementioned built-ins. | ||
|
||
|
||
## Basic Setup {#basic-setup} | ||
|
||
With `clojure-lsp` installed, you can use your preferred package manager to get the Emacs package `lsp-mode`. I use `use-package`. `lsp-mode` is pretty good at integrating with the built-in Emacs features, so I'll let it autoconfigure, which it does by default. I'll defer the package loading to shave off some precious milliseconds at Emacs' startup. | ||
|
||
```emacs-lisp | ||
(use-package lsp-mode | ||
:defer t) | ||
``` | ||
|
||
Let's add some hooks to start up `lsp-mode` when I'm programming in Clojure. I'll also enable the [which-key](https://github.com/justbur/emacs-which-key) integration. | ||
|
||
```emacs-lisp | ||
(use-package lsp-mode | ||
:defer t | ||
:hook ((clojure-mode . lsp) | ||
(clojurec-mode . lsp) | ||
(lsp-mode . lsp-enable-which-key-integration))) | ||
``` | ||
|
||
|
||
## Custom Bindings {#custom-bindings} | ||
|
||
Next, I'll bind some keys. The LSP server can suggest code actions and to execute these quickly, I'll bind the command `lsp-execute-code-action` to `M-<return>`. `xref` works pretty well in Clojure and lets you jump to the definition of a symbol with `M-.` (and jump back by popping the ref off the stack with `M-,`), so to find references instead of the implementation, I'll bind `lsp-find-references` to `C-M-.`. | ||
|
||
```emacs-lisp | ||
(use-package lsp-mode | ||
:defer t | ||
:hook ((clojure-mode . lsp) | ||
(clojurec-mode . lsp) | ||
(lsp-mode . lsp-enable-which-key-integration)) | ||
:bind (:map lsp-mode-map | ||
("M-<return>" . lsp-execute-code-action) | ||
("C-M-." . lsp-find-references) | ||
("C-c r" . lsp-rename))) | ||
``` | ||
|
||
Then, `M-<return>` gives me a minibuffer with available code actions. | ||
[![](/img/lsp-code-actions.png)](/img/lsp-code-actions.png) | ||
|
||
And `C-M-.` pops up an `xref` buffer where I can see all references to a function, in which I can select an entry and jump to the call site. | ||
|
||
{{< figure src="/img/lsp-xref.png" link="/img/lsp-xref.png" >}} | ||
|
||
I can also press `C-c r` to rename a symbol as you'd expect. | ||
|
||
{{< figure src="/img/lsp-rename.png" link="/img/lsp-rename.png" >}} | ||
|
||
|
||
## Diagnostics {#diagnostics} | ||
|
||
LSP mode automatically tries to figure out which diagnostics tool you're using, but I ran into some strange problem with the `:auto` setting when using [Flycheck](https://www.flycheck.org/en/latest/), so I set it to use Flycheck manually. | ||
|
||
```emacs-lisp | ||
(use-package lsp-mode | ||
:defer t | ||
:hook ((clojure-mode . lsp) | ||
(clojurec-mode . lsp) | ||
(lsp-mode . lsp-enable-which-key-integration)) | ||
:bind (:map lsp-mode-map | ||
("M-<return>" . lsp-execute-code-action) | ||
("C-M-." . lsp-find-references) | ||
("C-c r" . lsp-rename)) | ||
:config | ||
(setq lsp-diagnostics-provider :flycheck)) | ||
``` | ||
|
||
This gives me the regular Flycheck indicators (the arrows) that something's wrong, but also information in the echo area and in a popup childframe on mouse hover. | ||
|
||
{{< figure src="/img/lsp-unused-var.png" link="/img/lsp-unused-var.png" >}} | ||
|
||
|
||
## Customisation {#customisation} | ||
|
||
From this point onwards, the LSP server works really well on my machine for Clojure. However, `lsp-mode` comes with a few more bells and whistles than I'd like, so I'll finish by deactivating some features. A tour of on-by-default features and guide on how to disable them can be found [here](https://emacs-lsp.github.io/lsp-mode/tutorials/how-to-turn-off/). | ||
|
||
```emacs-lisp | ||
(use-package lsp-mode | ||
:defer t | ||
:hook ((clojure-mode . lsp) | ||
(clojurec-mode . lsp) | ||
(lsp-mode . lsp-enable-which-key-integration)) | ||
:bind (:map lsp-mode-map | ||
("M-<return>" . lsp-execute-code-action) | ||
("C-M-." . lsp-find-references) | ||
("C-c r" . lsp-rename)) | ||
:config | ||
(setq lsp-diagnostics-provider :flycheck) | ||
;; Disable visual features | ||
(setq lsp-headerline-breadcrumb-enable nil ;; No breadcrumbs | ||
lsp-ui-sideline-enable nil ;; No sideline | ||
lsp-lens-enable nil ;; No lenses | ||
;; Disable all mode line features, since I use a custom mode line | ||
lsp-modeline-code-actions-enable nil | ||
lsp-modeline-diagnostics-enable nil | ||
;; Limit raising of the echo area to show docs | ||
lsp-signature-doc-lines 3)) | ||
``` | ||
|
||
With all this, I get a lot of nice LSP features. In conjunction with CIDER, this is starting to feel really smooth. | ||
|
||
For example, the function's docstring in the echo area when it's at point. | ||
|
||
{{< figure src="/img/lsp-lookup-signature.png" link="/img/lsp-lookup-signature.png" >}} | ||
|
||
LSP-mode also plays nicely with my completion system, here using [corfu](https://github.com/minad/corfu). | ||
|
||
{{< figure src="/img/lsp-corfu.png" link="/img/lsp-corfu.png" >}} | ||
|
||
|
||
## Performance Improvements {#performance-improvements} | ||
|
||
And finally, LSP-mode works a little bit faster if we set it to use plists for serialisation. We can set this in the `:init`-block of the `use-package` declaration. | ||
|
||
```emacs-lisp | ||
:init (setq lsp-use-plists t) | ||
``` | ||
|
||
Note that if you want to do this, you also need to add `(setenv "LSP_USE_PLISTS" "true")` to your `early-init.el`. See [the performance part](https://emacs-lsp.github.io/lsp-mode/page/performance/) of the `lsp-mode` documentation for more information. | ||
|
||
|
||
## Final Configuration & Further Reading {#final-configuration-and-further-reading} | ||
|
||
And here's the final configuration that I'm using for Clojure as of September 2024. | ||
|
||
```emacs-lisp | ||
(use-package lsp-mode | ||
:defer t | ||
:init (setq lsp-use-plists t) | ||
:hook ((clojure-mode . lsp) | ||
(clojurec-mode . lsp) | ||
(lsp-mode . lsp-enable-which-key-integration)) | ||
:bind (:map lsp-mode-map | ||
("M-<return>" . lsp-execute-code-action) | ||
("C-M-." . lsp-find-references) | ||
("C-c r" . lsp-rename)) | ||
:config | ||
(setq lsp-diagnostics-provider :flycheck) | ||
;; Disable visual features | ||
(setq lsp-headerline-breadcrumb-enable nil ;; No breadcrumbs | ||
lsp-ui-sideline-enable nil ;; No sideline | ||
lsp-lens-enable nil ;; No lenses | ||
;; Disable all mode line features, since I use a custom mode line | ||
lsp-modeline-code-actions-enable nil | ||
lsp-modeline-diagnostics-enable nil | ||
;; Limit raising of the echo area to show docs | ||
lsp-signature-doc-lines 3)) | ||
``` | ||
|
||
The next thing I would consider to enhance my Clojure coding experience even further, is incorporating structural editing. Clojure, being a LISP dialect and therefore pretty close to an abstract syntax tree, is especially well-suited to structural editing. [paredit](https://paredit.org/) is the classic and what I've used previously. Now, I use a combination of paredit and [lispy](https://github.com/abo-abo/lispy), which has many more functions than paredit, with paredit-style keybindings. [Smartparens](https://github.com/Fuco1/smartparens) is a paredit-alternative. [puni](https://github.com/AmaiKinono/puni) is the most language-agnostic alternative, as far as I can tell. | ||
|
||
Thanks for reading! I welcome feedback, comments, or questions. You can reach me via email or GitHub. |
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+497 Bytes
static/ltximg/org-ltximg_07da2783635e7dff6c2fd450e549f5908e8805ae.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+497 Bytes
static/ltximg/org-ltximg_133d1818d6c8c1f41565dbd386ee3fb23345cfb8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+707 Bytes
static/ltximg/org-ltximg_3b324ef18f4dd7309d961e5059d69eed20c15e90.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+707 Bytes
static/ltximg/org-ltximg_6450d6eda73a5efda11a734cb825bfb276a72b9c.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+170 Bytes
static/ltximg/org-ltximg_762ff6c44645b2e07013d9707208b474dcb33714.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+170 Bytes
static/ltximg/org-ltximg_a26b96d10b178db76febc71026791dae6b523217.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+475 Bytes
static/ltximg/org-ltximg_b3e1bfc27d1656255d97a26d9f559f2668d9a96e.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+374 Bytes
static/ltximg/org-ltximg_b8779a5f08f1b94a6e708406f92c0ffdd7c20a0e.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+475 Bytes
static/ltximg/org-ltximg_d7eed9e74816fd7c37417b9eba76d820b5d69f45.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+374 Bytes
static/ltximg/org-ltximg_ff7217361c2aaa2d1ce2adf76ad10aaef7b2789f.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.