https://nrepl.org/nrepl/usage/misc.html#code-completion says i can do
(set! nrepl.middleware.completion/*complete-fn* my.namespace/my-completion)
but when i tried it (through a Cursive remote REPL run config), i got
Execution error (IllegalStateException) at ...
Can't change/establish root binding of: *complete-fn* with set
How can I make this set! operation work?
Is this an issue with Cursive, so I should raise it with @cfleming?To be sure, which nrepl version?
Cursive is still using a very old version:
nrepl.core/version
=> {:major 0, :minor 8, :incremental 3, :qualifier nil, :version-string "0.8.3"}Yeah, that's a/the problem
would it work, if Cursive would use the latest nREPL?
the only binding i see in the latest nrepl.server NS is dynamic-loader/*state*
I believe it should. I fixed this particular issue somewhere along the way to 1.3
dang, i've just realized that we were not even using the nrepl version we declared:
(-> (System/getProperty "java.class.path")
(clojure.string/split #":")
(->> (filter (partial re-find #"nrepl"))))
=>
("/Users/onetom/Library/Application Support/JetBrains/IntelliJIdea2025.1/plugins/clojure-plugin/lib/nrepl-0.8.3.jar"
"/Users/onetom/.m2/repository/cider/cider-nrepl/0.55.7/cider-nrepl-0.55.7.jar"
"/Users/onetom/.m2/repository/nrepl/nrepl/1.3.1/nrepl-1.3.1.jar")
we even start our own custom nrepl server in our remotely deployed environments, but locally Cursive old 0.8.3 nREPL takes precendence šTry deleting this file and see what happens š«
/Users/onetom/Library/Application Support/JetBrains/IntelliJIdea2025.1/plugins/clojure-plugin/lib/nrepl-0.8.3.jar
Ok, I'll upgrade Cursive for the next release and see if anything breaks...
i tried this:
mv "/Users/onetom/Library/Application Support/JetBrains/IntelliJIdea2025.1/plugins/clojure-plugin/lib/nrepl-0.8.3.jar"{,.bak}
cp "/Users/onetom/.m2/repository/nrepl/nrepl/1.3.1/nrepl-1.3.1.jar" "/Users/onetom/Library/Application Support/JetBrains
/IntelliJIdea2025.1/plugins/clojure-plugin/lib/nrepl-0.8.3.jar"
then i started our usual local cursive repl and i see the newer nrepl version:
nrepl.version/version
=> {:major 1, :minor 3, :incremental 1, :qualifier nil, :version-string "1.3.1"}
and i was able to run our test suite and connect to remote datomic cloud dbs without any issues.the problem persists though:
nrepl.middleware.completion/*complete-fn*
=> #function [nrepl.util.completion/completions]
(set! nrepl.middleware.completion/*complete-fn*
nrepl.middleware.completion/*complete-fn*)
Execution error (IllegalStateException) at ...
Can't change/establish root binding of: *complete-fn* with setAlright, I'll take another look
Ah, sorry, I haven't really fixed it for *complete-fn*.
For the time being, you can .bindRoot either nrepl.middleware.completion/*complete-fn* or nrepl.middleware.completion/completions .
my actual use-cases are the following:
1. (set! puget.printer/*options* (puget.printer/merge-options puget.printer/*options* {:width 120}))
2. have some shared repl namespaces, e.g. project.repl, with pre-baked expressions for admin tasks. multiple people should be able to use these within the same jvm process, without any interference with each other. e.g. we would have (defonce ^dynamic $user nil) and they would (set! project.repl/$user [:project.user/email " from within a rich-comment, or (set! $some-entity (d/q ...)) to pull some data to work with.
i don't really need to change nrepl.middleware.completion/*complete-fn*.
i was just bringing that up as an example, since it was mentioned in the official documentation.
You can bind such dynamic variable before starting nrepl server (if you start if from the code that you control). Otherwise, you can hack into (:session nrepl.middleware.interruptible-eval/*msg*) to append your dynamic variable there mutably.
indeed!
(-> puget.printer/*options* :width)
=> 80
(-> nrepl.middleware.interruptible-eval/*msg* :session
(swap! assoc #'puget.printer/*options*
(puget.printer/merge-options
puget.printer/*options*
{:width 120}))
(get #'puget.printer/*options*)
:width)
=> 120
(-> puget.printer/*options* :width)
=> 120
and from another repl connection to the same local process, i do see an independent value too!I think we already had requests to be able to bind new dynamic variables that weren't bound when nrepl started. It indeed would be a useful feature, but I haven't decided yet what the best API for that would be. If you have ideas how you personally would like it to look, please submit a ticket to https://github.com/nrepl/nrepl/issues ā¤ļø
i remember reading some discussions about such a feature too, but i couldn't find it now, that's why i haven't referenced it.
It was probably just on Slack, no ticket has been filed back then
if we would have some standardized mechanism / recommendation for bifurcating repl user states, then i could really think of REPLs as 1st-class, *developer end-user* UI / UX paradigm. 1. less pretty and less constrained than TUIs, but much simpler to program 2. can avoid stringification and escaping issues of a command line interface a little bit of interactivity can also be introduced extremely simply, if we can accept the ancient "print numbered menu items; type menu item index; submit with enter" approach. i crystallized it into this util fn so far:
(defn choose [elem->item coll]
(let [menu (->> coll
(map-indexed
(fn elem->colored-numbered-menu-item-arr [idx elem]
(-> [(symbol (format "%2d." (inc idx)))]
(into (elem->item elem))
(->> (mapv puget/cprint-str))))))]
(run! (partial apply println) menu) ; display menu options
(print "==> ") (flush) ; display prompt
(let [choice (-> (read-line) parse-long dec)] ; read choice
(->> choice (nth menu) (apply println)) ; print chosen menu item, just to be clear
(->> choice (nth coll)) ; return chosen collection element
)))
not sure how to call this UI paradigm, but it's streaming output-friendly, like the Cursive REPL tool window, because it's not relying on positioning the output cursor backwards, only forwards and down:
(defonce ^:dynamic $chosen nil)
(-> nrepl.middleware.interruptible-eval/*msg* :session
(swap! assoc #'$chosen nil))
(comment
(->> [{:a "frobulator"}
{:a "blob"}]
(choose (fn [{:keys [a]}] [a (list (count a))]))
(set! $chosen))
:-)
which yields the output shown on the screenshotif i would have a project.repl NS, whenever i would load it, i would start with a (set! $user (choose (comp vector :user/email) users)), as a form of authentication.
I'm not a bad idea to start at least GH Discussions on the nREPL repo on such topics, so they could be discovered more easily and by more people. Even as a Slack user I'm struggling to keep track of everything interesting that gets discussed here. (too many workspaces/channels)
Does anybody here use https://github.com/sanel/monroe or https://github.com/Sasanidas/Rail as an Emacs nREPL client? I'm having trouble using monroe-interaction-mode successfully. I wrote up my problem on StackOverflow: https://stackoverflow.com/questions/79654318/unable-to-eval-region-using-monroe-nrepl. It's probably something to do with my almost complete ignorance of Emacs.
The :out-subscribe is something specific to cider-nrepl, so it's not normally needed. (it exists to address a peculiarity in the Clojure nREPL implementation)
Oh wow, I had not realised that you were the creator of CIDER. I've got CIDER working pretty well now, thanks for the link.
> In practice this translates to CIDER evaluating Clojure code in some cases to power its functionality.
Do you know of a way to prevent CIDER evaluating things like (require 'clojure.stacktrace) at the start of a session?
I just have one more question if you have time. M-x cider-connect seems to ignore the .nrepl-port file in the current working directory, so I always need to type in the port manually. Is that expected behavior? I can see that there is code in the CIDER codebase that checks .nrepl-port but it doesn't seem to be called.
I think CIDER expects the .nrepl-port to be in the project root directory, but I'll have to double check this.
As for your other question. Probably the easiest would be to redefine those two functions in your own Emacs config:
(defun cider-default-err-eval-handler ()
"Display the last exception without middleware support."
(cider--handle-err-eval-response
(cider-nrepl-sync-request:eval
"(clojure.stacktrace/print-cause-trace *e)")))
(defun cider-default-err-eval-print-handler ()
"Display the last exception without middleware support.
When clojure.stracktrace is not present."
(cider--handle-err-eval-response
(cider-nrepl-sync-request:eval
"(println (ex-data *e))")))Thanks, I ended up removing the Clojure init code by adding the following to ~/.emacs:
(custom-set-variables
'(cider-repl-init-code ""))
When you say "project", do you mean Clojure project specifically?
I have an almost passable integration now, except that :out responses that occur outside of evaluation never appear in the REPL buffer. This is because CIDER never sends the :out-subscribe request due to (cider-runtime-clojure-p) being false.
However, I can force the :out-subscribe request by running M-: (cider--subscribe-repl-to-server-out) after connecting, and even register this as a hook like (add-hook 'cider-connected-hook #'cider--subscribe-repl-to-server-out) .I obviously don't use them, but I'd go with Rail, as Monroe seems to be dead these days and doesn't even implement the full nREPL protocol.
A long time ago I planned to extract CIDER's own nREPL client (which Monroe kind of forked), but I never got to doing this.
I did try Rail, but it had the same problem (which makes sense since Rail is a fork of Monroe). Why did you want to extract CIDER's nREPL client? I tried CIDER first, but it did not seem to want to evaluate non-Clojure languages.
Yeah, CIDER is focused on Clojure (and Clojure-like languages). I wanted to extract CIDERās nREPL client mostly so people wouldnāt have to copy and tweak it themselves. E.g. Monroe is based on a pretty old version of CIDERās nREPL code that didnāt include certain performance improvements and bug-fixes. I also felt that a standalone nREPL client would probably make CIDERās codebase a bit cleaner, but in the end of the day I just didnāt have much incentive to work on this, as the rise of LSP made it less likely that nREPL (as a protocol) would gain broader adoption.
As for your particular problem - Iād suggest adding a breakpoint here https://github.com/sanel/monroe/blob/master/monroe.el#L434 and stepping through the function to see whatās going wrong there. Seems to me that getting the REPL buffer is unconditional, so itās not obvious how it can be nil.
Thanks for the tip. I've actually gotten CIDER working now, although it spews out a few errors initially as it automatically evals a few Clojure expressions at the start of the session.
I agree that CIDER seems to do an awful lot.
This might be useful, but yeah - when I created CIDER I didnāt think much of alternative use-cases, otherwise I would have done the connection management differently. Still, even today itās not that much work to make it play nice with other similar languages.