neorepl 0.5: CIDER in a Terminal
neorepl 0.5 is out! When I announced neorepl last month, I wrote that I’d always wanted a terminal REPL that felt like CIDER, without the Emacs part. A few releases later I think neorepl is mostly there, so this one went out with “CIDER in a terminal” for a tagline. It also has everything from the “What’s next” list in the 0.4 post, which almost never happens with my plans.
A line editor that knows Clojure

That’s where you spend most of your time in a REPL, so that’s where most of the work went:
- The arglists of the function you’re calling show up under the input, the
way eldoc shows them in CIDER, with the parameter the argument at the
cursor goes into in bold, and the arities that can’t take that many
arguments faint. Macros get matched by their shape, so the docstring of a
defnlines up withdoc-string?. The lookups happen in the background now, so typing never waits on the server. - Enter in an unfinished form starts a line indented the way cljfmt would indent it.
- The delimiter that pairs with the one at the cursor gets highlighted, and a closer that pairs with nothing, or with the wrong kind, shows up in red. JLine’s own idea of paren matching was to move the cursor to the opener for half a second, which always made me think I’d pressed the wrong key.
- C-x Enter evaluates only the form at the cursor and gives you your input back, which is great for checking a piece of a bigger expression without retyping it. And after an evaluation the prompt shows at its right whether it worked, and how long it took. I borrowed both ideas from clojure-cli.repl, and I’m not sorry at all.
- C-x C-e opens what you’ve typed in your
$EDITOR, as it does in bash, for when a form outgrows the line editor. set editing-mode viin~/.inputrcgets you vi’s keys. neorepl takes the readline settings JLine knows from there, and what’s under$if neoreplis for neorepl alone.- Commands get highlighted as you type them, and a command that doesn’t exist shows up in red before you press Enter.
Not everyone likes that much going on around the cursor, so ,set lists
the REPL’s settings and turns them on and off, C-x C-h, C-x C-p and C-x C-q
toggle eldoc, pretty printing and quiet mode, and
~/.config/neorepl/config.edn says what they start as:
{:eldoc false
:print-length 100
:autoreload true}
Finding your way around

,ns without a name opens a full-screen browser of the project’s
namespaces, or every loaded one after you press a. Type / and part of a
name to narrow them down, → to see a namespace’s public vars with their
docs, Enter to switch to it and e to open it in your editor. ,spec does
the same for the spec registry: a spec’s form, the specs it names one key
away, and x for an example generated from it, as long as test.check is on
the server’s classpath. (With cider-nrepl, x only works since
0.5.1, which came
out today as well. I found that bug recording the GIF above.)
,refs lists the functions that use a var, and ,deps the ones a function
uses, courtesy of cider-nrepl’s fn-refs and fn-deps. They come in handy
before you change a function’s arguments, and even more so for coding
agents, which get them as neorepl refs and the MCP refs tool. They only
look through the project’s code that’s loaded, mind you.
Reloading and friends

,autoreload has the REPL reload the namespaces whose files changed before
each evaluation, and before ,test, ,inspect and ,ns, so you’re never
calling yesterday’s version of a function. It’s
clj-reload under cider-nrepl, which
reloads the namespaces that depend on the changed ones too, in the right
order, and keeps what defonce defined. When a reload fails you get its
error, rather than an evaluation against half-reloaded code.
Coding agents need this even more than people do, as they edit files all
day long and forget to load them about as often. neorepl eval --reload and
the reload option of the MCP eval tool take care of that.

A few more things CIDER users will recognize:
- Values sent to
tap>show up above the prompt, and,tapopens the last one in the inspector, with cider-nrepl 0.64 or newer. - What the server’s own threads print, like a web server’s handlers, shows up above the prompt as well, instead of getting lost in the server’s terminal, with nREPL 1.5 or newer and babashka.
,profiletimes every call of the functions you name, or of a whole namespace, and without a name shows what it measured: how many calls each function had, and how long they took, on average, in all, at the shortest and the longest.neorepl profiledoes the same from the shell.
Beside your editor
The CLI was meant for scripts from day one, and it turns out an editor
doesn’t need a plugin to use it. neorepl eval -f FILE --line N:COL -
evaluates the top-level form at the cursor in the file’s namespace, with
the buffer’s text on standard input, so unsaved changes count too, and
neorepl eval --ns NS - does the same for a selection.
Helix can’t send a buffer to a command, so there
the keys save the file first, and that’s all it takes:
[keys.normal."+"]
e = [":write", ":sh neorepl eval -s editor -f %{buffer_name} --line %{cursor_line}:%{cursor_column}"]
l = [":write", ":sh neorepl load %{buffer_name}"]
doc/editors.md
has complete setups for Neovim, Emacs and Helix. (Yes, Emacs. Please don’t
tell anyone.) The REPL sees whatever the editor evaluates, as they share a
server, so with the REPL in a split next to your editor you get a pretty
decent setup out of a few lines of config. The setups evaluate in a session
of their own, called editor, so ,sessions editor takes the REPL there,
and ,pst shows the last error your editor ran into.
More servers, tested for real
neorepl’s tests run against fakes that behave like cider-nrepl, which says
nothing about the next cider-nrepl, or the next babashka. So now CI also
runs neorepl against the real thing: nREPL with cider-nrepl, started by the
Clojure CLI and by Leiningen, babashka’s server, ClojureScript on Node
through piggieback and through shadow-cljs, nbb and Basilisp. That turned
up a fair share of bugs on both sides of the wire. Running a ClojureScript
namespace’s tests through piggieback failed with a circular dependency, a
ClojureScript test’s error came with the line NaN, code that threw on nbb
didn’t fail neorepl eval, and source, macroexpand and apropos didn’t
work on Basilisp. All fixed now.
neorepl needs babashka 1.13.227 or newer these days, as that’s the first release with all the JLine and socket classes it used to work around.
Making the neighbours better, round three
- I filed a bunch of issues for nbb’s nREPL server, about errors, sessions
sharing state and
in-ns, and Michiel fixed them within a day or two. They’ll be in the next nbb release. Thanks again, Michiel! On the babashka side, the test output going to the wrong message andinfoknowing nothing about namespaces are fixed on master. - Building on cider-nrepl’s profiler and taps turned up a few rough edges in its ops for them, all filed by now.
- The nREPL fixes I wrote about last time are merged and waiting for the next nREPL release, and I found a few more ways for an interrupted evaluation to leave a session in a bad state. Interrupts are hard.
- The piggieback changes are still in review. No news there, I’m afraid.
What’s next
0.6 is about getting neorepl to more people:
- neorepl on Clojars, which also lets you install it as a Clojure tool and run it inside your project’s JVM, plus an uberjar and Homebrew
- proper Windows support, at long last
- a documentation site, as the README is getting rather long
- the frames of ClojureScript errors, from the JavaScript stack, which have been “next” since 0.2
After that comes 1.0, where the CLI’s exit codes, its edn and JSON output and the MCP tools become a contract I promise to keep.
Try it
$ bbin install io.github.nrepl/neorepl
$ cd my-project
$ neorepl
The CHANGELOG has everything I skipped here, and the README has the details. Please keep the issues and ideas coming!
Keep hacking!