Fork me on GitHub
Daniel Slutsky07:04:40

Hi @bozhidar, following recent discussions of middleware, What I am wondering about is whether we could somehow avoid the need of user setup altogether in such a case (like the Clay tool), where all we need is to listen to user evaluations as they go through nREPL.


Well, you can also inject the middleware dynamically at runtime, but this would still require some setup on the client-side. In general I can't think of an arrangement that would require 0 setup unless either clients setup this (via configuration or runtime injection) or it's part of the standard middleware bundled with nREPL.

Daniel Slutsky08:04:29

Thanks! Good to know. 🙏


In Calva, for some features, we look for some ”contributions” (or whatever it should be called) among dependencies. @U02EMBDU2JU can describe it better than I can. Maybe we could do something like that for middleware... So if we find, say, a file named .editor-config/nrepl-middleware in the library, we can pick that up and use it to inject the middleware at runtime. Clay would be a dependency of the app, right?

👍 1
Daniel Slutsky09:04:28

Right. That sounds great. 🙏

Lukas Domagala12:04:01

@U0ETXRFEW do we have the capability to inject nrepl-middleware into a running process in Calva? Isn’t that the “side-loader” bit that hasn’t been finished yet?


Could be, could be. If so we will have a use case here. 😃 But idk, tbh. Would it be done via side-loading, @bozhidar?

Daniel Slutsky12:04:21

Thanks. IIUC, there is an (experimental) option of dynamic loading, where an nREPL client can ask an nREPL server to change its middleware: That is something that Calva & CIDER can use at runtime. The situation with a tool such as Clay, which is just a Clojure library, is maybe a bit different: even though it is running in the same JVM Clojure process as the nREPL server, it does not have a connection to the nREPL server and thus does not know how to send operations to it -- "the camel does not see its own hump". (Or am I missing something?)

Lukas Domagala12:04:24

You are right @U066L8B18, but calva does have the connection and we could add the middleware for you, similar to the I put into Calva. Adding the Calva side wouldn't be that hard. Not sure about the dynamic middleware loading bit, I'd have to look deeper into that bit.

Lukas Domagala12:04:50

It would be helpful to have an example of the calls Calva would have to make to add the middleware, not sure if it's on use anywhere yet.

Daniel Slutsky12:04:35

Thanks! I'll look into it later or tomorrow, to figure out the necessary nREPL ops.

👍 1
Daniel Slutsky22:04:17

Hi @U02EMBDU2JU! Here is the dynamic middleware example I promised a few days ago, thanks for your patience. 😊

(ns scratch
  (:require [nrepl.server]
            [nrepl.core :as nrepl]))

(defonce server
  (nrepl.server/start-server :port 1234))

;; Adding the Clay middleware to the nREPL server using an op sent by an nREPL client:

(with-open [conn (nrepl/connect :port 1234)]
  (-> (nrepl/client conn 1000)
      (nrepl/message {:op "add-middleware"
                      :middleware ["scicloj.clay.v1.nrepl/middleware"]})

;; To test it:

(require '[scicloj.clay.v1.api :as clay]
         '[ :as tools])

(clay/restart! {:tools [tools/scittle]})

;; Open the browser at localhost:1971

(with-open [conn (nrepl/connect :port 1234)]
  (-> (nrepl/client conn 1000)
      (nrepl/message {:op "eval"
                      :code "(+ 1 2)"})

;; You should see "3" printed in the browser,
;; which means the Clay middleware is working.

👍 1
❤️ 1
Daniel Slutsky22:04:23

To make the above example work, we need Clay as a dependency.

Daniel Slutsky16:05:19

Following the various setup issues, and differences between Calva and CIDER, Lein and CLI, etc., I am wondering if I should abandon the nREPL-middleware approach and rely on tap> . This would also require editor-specific setup, but it looks like the tap> approach gets more community attention and would thus be more unsurprising and in harmony with other tools & habits. Both CIDER and Calva offer ways to comfortably tap> all evaluated values. I'll explore that a bit. 🙏

Daniel Slutsky16:05:51

> You are right @U066L8B18, but calva does have the connection and we could add the middleware for you, similar to the I put into Calva. Adding the Calva side wouldn't be that hard. Not sure about the dynamic middleware loading bit, I'd have to look deeper into that bit. @U02EMBDU2JU, following this kind suggestion above -- maybe it is better to wait a bit (unless you find it useful in general) -- since I am exploring the tap> alternative.

Lukas Domagala17:05:50

I’m drowning in stuff anyway, so it’s going to take some time from my side 😛 (I should have taken the clojurists together funding for something calva/lsp related 🙂 ) I’d love to get dynamic middleware into calva anyway, but I like your tap> idea. Basing everything on nrepl seems fine, since CIDER + Calva sit on top of it, but if it’s possible to also support cursive or socket repls it would be great

🙏 1

Cursive uses nREPL, doesn't it?

Lukas Domagala17:05:14

I thought cursive does mostly static analysis stuff and works with any repl? I’ve never been a cursive user though, @U0ETXRFEW immediately pulled me to the dark side 🙂


Well, any repl would include nREPL?

Lukas Domagala17:05:14

True. My thinking was more along the lines of: Calva is probably happy to add some extra nREPL config stuff to enable sideloading and CIDER already has it, but if the editor isn’t nrepl based it probably wouldn’t want to add extra support for such things

👍 1
Lukas Domagala17:05:08

And if the same effect thing can be done without extra middleware, it’s going to be compatible in more situations.

👍 1
Daniel Slutsky17:05:41

Also with the middleware approach indeed we noticed some slightly different behaviors across different editor setups. E.g. Sometime some special eval ops are being sent. Also, could not make the eval listener work in Conjure, not sure why. Maybe relying on tapping would provide a more standardized behavior, as you said.

Lukas Domagala17:05:08

If tap> doesn’t do everything you want, it might be a good “base” that works for everyone and we can have a richer experience in editors that support it or that are based on nrepl. Kind of like a website that works without js but has more features if you enable it.

👍 1
Daniel Slutsky21:05:35

You were right, the tap> case is indeed less informative -- we get the result value, but not the original code. Passing the code itself from the editor to Clojure would require some care of escaping, etc.

Daniel Slutsky22:05:07

(.. but it looks like it will be ok, at least in Emacs)


I'm on vacation these days and I don't have much time for Slack, but I'm certain that the middleware route is the better approach for most nREPL clients.


Obviously we can get far with eval, but that's not without problems as well, especially when we factor in different Clojure implementations.


E.g. recently people were complaining that CIDER was evaluating some Clojure code here and there and this caused issues with different runtimes (e.g. bbn).

Daniel Slutsky10:05:07

Thanks so much, @bozhidar, wishing you a good vacation. The important realization was that we needed some editor-specific setup (as you wrote in the beginning of this thread). Given that, the tap> way can provide for our very basic needs at the moment, but it will be great to explore more of the nREPL way and its benefits.