As a Clojure developer one of the most important packages in my everyday Emacs usage is CIDER. There are many things to love about it, chief among them the great interactive code evaluation and a sweet integration with popular Clojure tools. I still haven’t played with its debugging facilities, but yes, I know I should.
However, there is something that has been bothering me for quite a while: REPL switching and buffer loading. I cannot pinpoint the exact moment when these seemingly simple operations have become so unreliable, but I still remember when switching to the correct REPL buffer according to the file in front of me used to work as expected and loading a buffer didn’t require dealing with sessions first.
Let me give you more details before this writing starts to look like a random
rant. My projects are usually web applications, which means I have to write both
Clojure and ClojureScript. The Clojure side can be backed by leiningen
or a
deps.edn
file, and we are set on shadow-cljs
for ClojureScript. The first thing
I normally do is jack-in with C-c C-x j j
and then bring up the dedicated
ClojureScript REPL with C-c C-x j s
. Opening the browser and navigate to
something along the lines of localhost:3000
finalises the process of setting up
the ClojureScript REPL. That’s it, another good day of coding can begin. And
soon enough frustration follows.
I tend to move from Clojure to ClojureScript files and vice versa quite a lot, and hitting C-c C-z frequently results in an unpredictable behaviour. Sometimes the REPL of the expected type pops up, sometimes the other one appears, sometimes I get a message about a missing REPL in the current session. Manually linking the current buffer to the correct REPL with C-c C-s b seems to fix the problem, but it’s only a matter of time. It takes a couple of buffer switching operations to bring the issue back. It is as if the link between the buffer and the REPL has vanished. Even worse, without that link I can forget about C-c C-k to load the buffer.
To overcome my frustration, I sat back and looked at how exactly I interact with CIDER:
- I only deal with one project at a time
- I need at most two running REPLs
- I don’t really care about firing up dedicated REPLs for other projects. If I change project, I simply close every buffer of the current one and start afresh
This made me realise that the whole CIDER session management is too much for my basic needs.
(defun mu--cider-repl-regex (type)
"Return the regexp to get the CIDER REPL based on TYPE."
(if (eq type 'clj)
"\`*cider-repl .*clj[*]"
"\`*cider-repl .*cljs"))
(defun mu--cider-repl-buffer-name (type)
"Get buffer from `buffer-list' according to TYPE."
(let ((regex (mu--cider-repl-regex type)))
(car (seq-filter (lambda (s) (string-match-p regex s))
(mapcar #'buffer-name (buffer-list))))))
(defun mu-cider-switch-to-repl (&optional type)
"Open a CIDER REPL for TYPE.
If TYPE is not passed, open a Clojure REPL."
(interactive "P")
(let ((type (or type 'clj)))
(if-let (buffer (mu--cider-repl-buffer-name type))
(pop-to-buffer buffer)
(message "No CIDER REPL available"))))
(defun mu-cider-switch-to-cljs-repl ()
"Open a CIDER REPL for ClojureScript."
(interactive)
(mu-cider-switch-to-repl 'cljs))
Note that I changed the value of nrepl-repl-buffer-name-template
to *cider-repl %j %r:%S*
, so you may have to change mu--cider-repl-regex
according to the value
in your setup.
C-c C-z
is bound to mu-cider-switch-to-repl
in clojure-mode-map
and
clojurec-mode-map
, and to mu-cider-switch-to-cljs-repl
in
clojurescript-mode-map
. This of course means that in .cljc
files I always get to
a Clojure REPL, but that’s fine. The code in there has to be tested on both
REPLs anyway, so it doesn’t matter which one comes up first.
Now, let’s fix C-c C-k as well.
(defun mu--cider-session-by-type (type)
"Return the current CIDER session by TYPE."
(let* ((regex (mu--cider-repl-regex type))
(system (sesman--system))
(sessions (sesman-current-sessions system '(buffer))))
(car
(seq-filter (lambda (s)
(string-match-p regex (buffer-name (cadr s))))
sessions))))
(defun mu--cider-load-buffer (session)
"Load the current buffer according to SESSION."
(if session
(let ((system (sesman--system))
(buf (current-buffer)))
(sesman--clear-links)
(sesman-link-session system session 'buffer buf)
(cider-load-buffer buf))
(message "No CIDER REPL available")))
(defun mu-cider-load-clj-buffer ()
"Load the current Clojure buffer."
(interactive)
(mu--cider-load-buffer (mu--cider-session-by-type 'clj)))
(defun mu-cider-load-cljc-buffer ()
"Load the current ClojureC buffer."
(interactive)
(let ((clj-session (mu--cider-session-by-type 'clj))
(cljs-session (mu--cider-session-by-type 'cljs)))
(if (and (null clj-session)
(null cljs-session))
(message "No CIDER REPL available")
(when clj-session
(mu--cider-load-buffer clj-session))
(when cljs-session
(mu--cider-load-buffer cljs-session)))))
(defun mu-cider-load-cljs-buffer ()
"Load the current ClojureScript buffer."
(interactive)
(mu--cider-load-buffer (mu--cider-session-by-type 'cljs)))
Just like for C-c C-z
, C-c C-k
is now bound to these commands according to the
mode map. For the sake of completeness, I have also disabled both C-c C-k
and
C-c C-z
in cider-mode-map
in order to avoid any kind of overshadowing by CIDER.
Note that this approach works well with my intended CIDER usage. It may not be what you are looking for if you are experiencing the same problems with REPL switching and buffer loading. Still, I have been using these commands for a while now and I am happy with them. CIDER has become my trusted Clojure IDE again.