/clover

Visual Studio code implementation of REPL-Tooling

Primary LanguageClojure

Clover

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.

Features

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 command clj -J-Dclojure.server.browser="{:port 4444 :accept cljs.server.browser/repl}"
  • ClojureCLR
  • Lumo
  • Plank
  • Joker
  • Babashka

Example:

Evaluating code

Usage:

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.

Custom Commands

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

Refresh Namespaces

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

Keybindings

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": ""
    }

Disclaimer:

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.

Known problems:

Currently there are some synchronization problems, so sometimes a restart of VSCode is necessary.

Related Projects: