nrepl

onetom 2025-06-05T08:15:03.818639Z

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?

oyakushev 2025-06-05T08:15:50.430139Z

To be sure, which nrepl version?

onetom 2025-06-05T08:17:09.548119Z

Cursive is still using a very old version:

nrepl.core/version
=> {:major 0, :minor 8, :incremental 3, :qualifier nil, :version-string "0.8.3"}

oyakushev 2025-06-05T08:18:25.507479Z

Yeah, that's a/the problem

onetom 2025-06-05T08:19:05.886879Z

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*

oyakushev 2025-06-05T08:19:40.312559Z

I believe it should. I fixed this particular issue somewhere along the way to 1.3

šŸ™ 1
onetom 2025-06-05T08:41:42.252199Z

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 šŸ˜ž

oyakushev 2025-06-05T10:22:48.379359Z

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

šŸ˜„ 1
cfleming 2025-06-05T10:40:51.427379Z

Ok, I'll upgrade Cursive for the next release and see if anything breaks...

onetom 2025-06-05T10:51:26.841949Z

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.

onetom 2025-06-05T10:52:33.519929Z

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 set

oyakushev 2025-06-05T11:00:26.704289Z

Alright, I'll take another look

oyakushev 2025-06-05T11:01:13.541679Z

Ah, sorry, I haven't really fixed it for *complete-fn*.

oyakushev 2025-06-05T11:02:12.583829Z

For the time being, you can .bindRoot either nrepl.middleware.completion/*complete-fn* or nrepl.middleware.completion/completions .

onetom 2025-06-05T11:08:46.226669Z

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.

onetom 2025-06-05T11:09:38.440119Z

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.

oyakushev 2025-06-05T11:13:41.472209Z

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.

onetom 2025-06-05T11:25:57.309079Z

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!

šŸŽ‰ 1
oyakushev 2025-06-05T11:29:50.561609Z

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 ā¤ļø

onetom 2025-06-05T11:30:57.633569Z

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.

oyakushev 2025-06-05T11:31:17.899849Z

It was probably just on Slack, no ticket has been filed back then

onetom 2025-06-05T12:31:57.296709Z

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 screenshot

onetom 2025-06-05T12:34:09.185199Z

if 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.

onetom 2025-06-09T09:52:39.957489Z

https://github.com/nrepl/nrepl/discussions/373

bozhidar 2025-06-09T06:41:19.244799Z

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)

šŸ‘ 1
James Diacono 2025-06-05T11:14:41.197519Z

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.

bozhidar 2025-06-18T14:15:39.414059Z

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)

James Diacono 2025-06-10T07:21:46.559019Z

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.

bozhidar 2025-06-10T09:10:22.130749Z

I think CIDER expects the .nrepl-port to be in the project root directory, but I'll have to double check this.

bozhidar 2025-06-10T09:12:25.133209Z

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))")))

James Diacono 2025-06-10T11:35:26.331109Z

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) .

bozhidar 2025-06-09T10:00:27.121159Z

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.

bozhidar 2025-06-09T10:01:32.663369Z

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.

James Diacono 2025-06-10T02:05:01.827369Z

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.

bozhidar 2025-06-10T04:39:08.814139Z

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.

bozhidar 2025-06-10T04:59:22.647789Z

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.

James Diacono 2025-06-10T05:01:06.530339Z

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.

James Diacono 2025-06-10T05:03:35.981089Z

I agree that CIDER seems to do an awful lot.

bozhidar 2025-06-10T05:05:34.725119Z

https://docs.cider.mx/cider/platforms/overview.html

bozhidar 2025-06-10T05:10:48.705819Z

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.