Say hello to Joyride - The Clojure REPL in VS Code

In case you’ve managed to miss it: @borkdude and I have cooked up something pretty exciting. (I know, biased, but you can bring it to the bank that I am excited about it.)

Joyride is an extension for Visual Studio Code that brings Clojure Interactive Programming powers over VS Code and its Extensions into user space (compare with Emacs and ELisp). Now things that otherwise takes a full blown extension to do, now can be done interactively in the editor itself. By you as a user of VS Code, while you are using it. Yes, we are leveraging on SCI to create this thing!

Joyride is very young. First baby steps, if there ever were any such, anywhere. But it is already quite useful: Some key features:

  • Full access to the dynamic parts of the VS Code extension API
  • Interactive Programming
  • User and Workspace scripts, these can be run at will, and bound to keyboard shortcuts.
  • User and Workspace activate.cljs scripts. These run at startup, i.e. when a new VS Code window is opened.
  • A command for running arbitrary Clojure code. Can be run at will, and bound to keyboard shortcuts.

Example: Type code fast!

Here’s a non-contrived example: When recording a demo video of Joyride I needed something so that the audience don’t start thinking of ways to kill themselves while I slowly type my Clojure code. So I tried to use the MacOS built in text-substitution mechanism. It is exactly what I need for this. Then I discovered that for some reason the feature does not work in VS Code!

Now what? I recalled that some year ago I released an extension that pastes text from the clipboard applying whatever replacements you configure. The extension is Paste Replaced and it is not a block buster, if I put it that way. (It had 35 downloads when I checked earlier today). But it was quite close to the tool I needed right now. So. I configured some replacers for it. But it was a bit too awkward to type some text, select it, copy it, then do paste-replaced. Having spent some time googling and asking around for a solution … face palm … I can use Joyride for it!

Demo video of Type code fast!

What’s involved

The ingredients in this solutions are:

  1. Paste Replace (that other extension)
    1. Settings for this extension
  2. Joyride
    1. Two functions in my my-lib Joyride User script namespace. (This namespace is required in my User activate.cljs script.)
    2. Two keyboard shortcuts

Here are the settings for Paste Replaced:

    "paste-replaced.replacers": [
        [
            [ "\"", "\\\"", "g" ],
            [ " +", " ", "g" ],
            [ "\n", "\\n", "g" ],
            [
                "^!r7$",
                "(repeat 7 \"I am using the REPL! 💪\")",
            ],
            [
                "^!hw2$",
                "(p/let [choice (vscode/window.showInformationMessage \"Be a Joyrider 🎸\" \"Yes\" \"Of course!\")]\n (if choice\n (.appendLine (joyride/output-channel) (str \"You choose: \" choice \" 🎉\"))\n (.appendLine (joyride/output-channel) \"You just closed it? 😭\")))"
            ],
            [
                "^!hw$",
                "(vscode/window.showInformationMessage \"Hello World!\")"
            ],
        ]
    ],

Here are the two functions in <joyride user scripts>/my-lib.cljs:

(defn replace-all-text []
  (p/do (vscode/commands.executeCommand "editor.action.selectAll")
        (vscode/commands.executeCommand "execCopy")
        (vscode/commands.executeCommand "paste-replaced.paste")))

(defn replace-last-word []
  (p/do (vscode/commands.executeCommand "cursorWordLeftSelect")
        (vscode/commands.executeCommand "execCopy")
        (vscode/commands.executeCommand "paste-replaced.paste")))

The keybindings:

    {
        "key": "cmd+f4",
        "command": "joyride.runCode",
        "args": "(my-lib/replace-all-text)",
        "when": "!editorTextFocus"
    },
    {
        "key": "cmd+f4",
        "command": "joyride.runCode",
        "args": "(my-lib/replace-last-word)",
        "when": "editorTextFocus"
    },

The reason I need two functions and two keybinding definitions is that the in the text editor, I can’t replace all text, it should just be the just typed ”word”. And outside the text editor (input prompts) I don’t know how to select just a word, but selecting all text works. The when clause takes care of letting the same keys work for both commands.

More examples

Please roam the examples in the Joyride repository for more ways to use it. And please consider contributing with how you use it!

It would be fantastic if you would like to try this out. Please don’t hesitate to ask for help here, in the Discussion on the repo, or in the #joyride channel at the Clojurians Slack.

9 Likes

@PEZ, this is pretty cool. Now you guys need a MELPA for Joyride and and a built-in package-install library


A couple of questions:

  • I’m curious why you have to embed an nrepl? Wouldn’t it be possible to eval script the cljs code directly within VSCode (as you can with elisp)?
  • Is it possible to pull in dependencies (from npm/babashka)?
1 Like

Thanks! Fabulous questions!

:smile:

Seriously, we’ll probably (if Joyride gets any popular) do something for sharing around solutions at some point.

It’s a matter of convenience and leveraging existing tools. nREPL means Calva can be utilized as a REPL client (or CIDER, or any nREPL client). I’m not too familiar with ELisp and the usage patterns there, so can’t really compare.

You can evaluate the script pretty easily. And also evaluate arbitrary code with Joyride, so you don’t need to start the Joyride nREPL server. (Which could be tricky in a Clojure project, because of some Calva limitations.) You can use Joyride to make it more convenient than selecting code and pasting in the Joyride: Run Clojure Code… input box. E.g. if you have this in some library you load from your activate.cljs script:

(defn evaluate-selection []
  (p/let [selection vscode/window.activeTextEditor.selection
          document vscode/window.activeTextEditor.document
          code (.getText document selection)
          result (vscode/commands.executeCommand "joyride.runCode" code)]
    result))

Then bind a key to it:

    {
        "key": "cmd+ctrl+enter",
        "command": "joyride.runCode",
        "args": "(my-lib/evaluate-selection)",
    },

Now you can hit cmd+ctrl+enter with any selection. And have it evaluated. Say you have this Rich comment:

(comment
  (->> 2
       (+ 40)
       (str "Th|e answer: ")
       (vscode/window.showInformationMessage (str "The question?")))
  )

(The vertical bar being the cursor.) Then press: ctrl+alt+w, space, cmd+ctrl+enter to get:

(ctrl+alt+w, space are default Calva bindings for selecting the current top level form.)

We will be adding some API to Calva that will make it more powerful to leverage its structural commands programmatically. But already you could give yourself an Evaluate Top Level Form command by binding some key to:

(defn evaluate-top-level-form []
  (p/let [_ (vscode/commands.executeCommand "paredit.rangeForDefun")]
    (evaluate-selection)))

You can’t use just any dependencies. The built in ones you can just require. Placing libraries in the classpath (which is either the user or workspace script directories) you can use them as long as they are compatible with SCI and how Joyride uses SCI. (It should even be possible to create your own m2 directory there and write some Joyride code to populate it, afaiut.) I think we need to make some updates to Joyride to get all built-in sci-configs goodness that nbb users enjoy. @borkdude knows more about that. As also the question about npm dependencies. I think that in theory you should be able to do that. It might require some changes on Joyride to enable it. Let’s see what Michiel says about it!.

1 Like

Sweet. Thanks for the write up. I never noticed it before but wow, the p/let macro is super handy.

Elisp is built into Emacs so you can open up a scratch, type (+ 1 2 3) or any elisp form and then use C-x C-e to eval.

It’s basically what your solution for Evaluate Top Level Form command does.

1 Like

Indeed! Even if I was abusing it in that second function :smile: Let’s fix that:

(defn evaluate-top-level-form []
  (p/do! (vscode/commands.executeCommand "paredit.rangeForDefun")
         (evaluate-selection)))

Calva just got an updated, that relates to Joyride because it adds and API for evaluating code through Calva’s REPL connection.

I find that my mind boggles a bit when using this and I have to pay a lot of attention to where which code is being evaluated.

Will be cool to see what people do with it! I don’t have a use case myself right now, but I know @seancorfield has! :smile:

My VS Code / Calva setup repo has just been updated with two Joyride scripts that leverage the new Calva Extension API – see this commit: add joyride scripts · seancorfield/vscode-calva-setup@6090ed2 (github.com)

This implements the two “missing” commands from my previous VS Code / Clover setup where I could highlight a symbol or expression, hit a key binding, and have the appropriate JavaDoc or ClojureDocs page opened directly inside VS Code using the built-in Simple Browser.

The JavaDoc code evaluates the selected code from the editor in your connected Clojure nREPL to get the underly Java class name and constructs the javadoc-url from that and tidies up the result so it will open in a browser. Joyride gets the selected text from the editor and submits it for that evaluation to get the nREPL result (a string) which is then read as EDN to get the URL string (because nREPL returns "\"https://...\""), and then uses VS Code’s executeCommand to show the Simple Browser with that URL.

The ClojureDocs code is simpler (since it only works with a symbol): it resolves the symbol (using (str (symbol #'<symbol>)) via your connected Clojure nREPL) and builds a clojuredocs.org URL, tidied up for a browser. As above, Joyride drives that evaluation to get the result, reads it as EDN, and shows the Simple Browser.

1 Like

Latest Calva, v2.0.278, has some added Extension API to make it easier to get at text from things like the current form, and current top level form. (Easier, for instance, than with some of the examples I’ve posted above.) See above for link to the Calva Extension API docs.

While we figure out how VS Code extensions can distribute utility Joyride libraries, I’ve added an example z-joylib.calva-api namespace here:

New Joyride version out, v0.0.15. Here’s the changelog since we started:

[0.0.15] - 2022-06-01

[0.0.14] - 2022-05-31

  • Add some logging when Joyride starts and finishes activating

[0.0.13] - 2022-05-30

[0.0.12] - 2022-05-18

[0.0.11] - 2022-05-15

[0.0.10] - 2022-05-15

[0.0.9] - 2022-05-11

[0.0.8] - 2022-05-09

[0.0.7] - 2022-05-06

[0.0.6] - 2022-05-06

[0.0.5] - 2022-05-06

[0.0.4] - 2022-05-05

[0.0.3] - 2022-05-02

[0.0.2] - 2022-04-27

[0.0.1] - 2022-04-27