A Clojure Socket REPL package for Visual Studio Code and Visual Studio Codium
PLEASE NOTICE: this repository is only kept for historical purposes, newer code will ONLY be published at GitLab: https://gitlab.com/clj-editors/clover. There's a good reason for this change, even if you don't agree with it.
For now, it is possible to connect on a Clojure (and Clojure-ish) socket REPL and evaluate forms. You can load the current file, there's autocomplete, and support for multiple ClojureScript REPLs (with the notable exception being that Figwheel is missing - it only supports Shadow-CLJS and "pure ClojureScript socket REPLs" like Lumo, Plank, clj
with some additional code, etc).
For now, the following Clojure implementations were tested and are known to work:
- Clojure with lein/boot/clj
- ClojureScript with Shadow-CLJS (multi-target)
- ClojureScript with
clj
and the commandclj -J-Dclojure.server.browser="{:port 4444 :accept cljs.server.browser/repl}"
- ClojureCLR
- Lumo
- Plank
- Joker
- Babashka
Fire up a clojure REPL with Socket REPL support. With shadow-cljs
, when you watch
some build ID it'll give you a port for nREPL and Socket REPL. With lein
, invoke it in a folder where you have project.clj
and you can use JVM_OPTS
environment variable like:
JVM_OPTS='-Dclojure.server.myrepl={:port,5555,:accept,clojure.core.server/repl}' lein repl
On Shadow-CLJS' newer versions, when you start a build with shadow-cljs watch <some-id>
, it doesn't shows the Socket REPL port on the console, but it does create a file with the port number on .shadow-cljs/socket-repl.port
. You can read that file to see the port number (Clover currently uses this file to mark the port as default).
With clj
, you can run the following from any folder:
clj -J'-Dclojure.server.repl={:port,5555,:accept,clojure.core.server/repl}'
Or have it in :aliases
in deps.edn
. (For an example with port 50505 see https://github.com/seancorfield/dot-clojure/blob/master/deps.edn, then you can run clj -A:socket
.)
Then, you connect Clover with the port using the command Connect Clojure Socket REPL. This package works with lumo too, but you'll need to run Connect ClojureScript Socket REPL.
When connected, it'll try to load compliment
(for autocomplete, falling back to a "simpler" autocomplete if not present). Then you can evaluate code on it.
Exactly as in Chlorine, there's support for Custom Commands. Please follow the link for more explanation, but the idea is that, after connecting to a REPL, you run the command Clover: Open config file"
and then register your custom commands there. Please notice that "custom tags" is not supported, and probably never will unless VSCode makes its API better - the only custom tags supported are :div/clj
and :div/ansi
(the first accepts a Clojure data structure and uses the Clover renderer to render it, the second accepts a string with ANSI codes and color then accordingly).
Because of limitations of VSCode, you will not see custom commands on the command palette - they are registered as Tasks. So you'll run the command "Tasks: Run Task" and then choose "Clover". There, the custom commands will be presented. Probably VSCode will ask you if you want to "Scan output of task". It's safe to say "Never scan the output for any Clover task".
The reason that Clover uses tasks is because you can set keybindings to tasks - to register a keybinding to a task, you need to run the command "Preferences: Open Keyboard Shortcuts (JSON)". Be aware that you need to edit keybindings via JSON. There, you'll define a keybinding for the command workbench.action.tasks.runTask
and the args will be exactly the name that appears on the task menu - case sensitive.
So, supposed you did add a custom command on your config (one that just prints "Hello, World" to the console:
(defn hello-world []
(println "Hello, World!"))
Then, you'll see that the task registered will be named "Clover: Hello world". You can register, for example, ctrl+h
with the following JSON fragment:
{
"key": "ctrl+h",
"command": "workbench.action.tasks.runTask",
"args": "Clover: Hello world"
}
Clover does not have the command to use clojure.tools.namespace
to refresh, but it's easy to add your own version with custom commands:
(def ^:private refresh-needs-clear? (atom true))
(defn- refresh-full-command []
(if @refresh-needs-clear?
'(do
(clojure.tools.namespace.repl/clear)
(clojure.tools.namespace.repl/refresh-all))
'(do
(clojure.tools.namespace.repl/refresh))))
(defn refresh-namespaces []
(p/let [_ (p/catch (editor/eval {:text "(clojure.core/require '[clojure.tools.namespace.repl])"})
(constantly nil))
cmd (refresh-full-command)
cmd (list 'let ['res cmd]
'(if (= res :ok)
{:html [:div "Refresh Successful"]}
{:html [:div.error [:div/clj res]]}))
res (editor/eval-interactive {:text (pr-str cmd)
:range [[0 0] [0 0]]
:namespace "user"})]
(if (->> res :result pr-str (re-find #":div/clj"))
(reset! refresh-needs-clear? true)
(reset! refresh-needs-clear? false))))
To avoid conflicts, this plug-in does not register any keybindings. You can define your own on keybindings.json
. One possible suggestion is:
{
"key": "ctrl+enter",
"command": "clover.evaluate-top-block",
"when": "!editorHasSelection"
},
{
"key": "shift+enter",
"command": "clover.evaluate-block",
"when": ""
},
{
"key": "ctrl+enter",
"command": "clover.evaluate-selection",
"when": "editorHasSelection"
},
{
"key": "ctrl+shift+c",
"command": "clover.connectSocketRepl",
"when": ""
},
{
"key": "ctrl+shift+l",
"command": "clover.clear-console",
"when": ""
}
This plug-in should be considered "alpha state". Its still in the beginning, and the only reason it works for evaluation, autocomplete, and supports lots of Clojure implementations is because it re-uses most of the code from Chlorine.
Currently there are some synchronization problems, so sometimes a restart of VSCode is necessary.