Joyride Examples
July 21, 2026 · View on GitHub
Demonstrating some ways to Joyride VS Code. You'll find the examples in the examples/.joyride folder of this repository.
See also: Joyride @ London Clojurians, Nov 29 2022
- The meetup was about how to hack Visual Studio Code like it was Emacs, using the VS Code Extension Joyride.
- Recording: https://www.youtube.com/watch?v=ObjIR08t3lg
Activation scripts
One for the Workspace and one on he User level:
user_activate.cljs
A User user_activate.cljs script that shows:
- How to write to, and show, the Joyride output channel.
- A re-runnable recipe for registering VS Code disposables as with the Workspace
activate.cljsexample. - A way to safely require VS Code extensions from
activate.cljsscripts, where the extensions might yet not be activated.
NB: This script will be automatically installed for you if you do not have a User activate.cljs script already.
workspace_activate.cljs
A Workspace activate.cljs script that registers a vscode/workspace.onDidOpenTextDocument event handler. Demonstrates:
- Using the
joyride.core/extension-contextto push disposables on itssubscriptionsarray. Making VS Code dispose of them when Joyride is deactivated. - A re-runnable recipe to avoid re-registering the event handler. (By disposing it and then re-register.)
Fuzzy git history search menu
A quick-pick menu for fuzzy searching your git history
- Instant preview of diffs
- Button for copying the commit id
- Button for opening the file

Source: examples/.joyride/src/git_fuzzy.cljs
Keybinding Command Palette
A personal command palette for your VS Code keybindings. Reads your keybindings.json (JSONC-aware, so comments are fine), filters entries that have a "title" field, renders key combos with Unicode symbols (⌘⌥⇧⌃⏎↑↓…), and executes the selected command — including commands with arguments.

Source: examples/.joyride/src/keybinding_palette.cljs
Installation
- Install the npm dependency:
cd ~/.config/joyride && npm install jsonc-parser - Copy the source file to
~/.config/joyride/src/keybinding_palette.cljs - Add this keybinding to your
keybindings.json:{ "title": "Keybinding Command Palette", "key": "ctrl+alt+j ctrl+alt+j", "command": "joyride.runCode", "args": "(require '[keybinding-palette :as kp] :reload) (kp/show-palette!+)" } - Add
"title"fields to your other keybindings — these become the searchable labels in the palette. Keybindings without titles are filtered out.
Platform note: The keybindings-path function constructs a macOS-specific path. For Linux or Windows, adapt the path construction to match your VS Code config directory.
Note: You can ask Copilot to help you with all this. It knows what to do if you ask it to install the keybindings-palette example for you.
Pastedown — Paste as Markdown
https://github.com/user-attachments/assets/9e2ab00d-1754-4b92-9733-126a774edf29
A DocumentPasteEditProvider that converts clipboard HTML to clean Markdown using Turndown with GFM support. When you copy rich text (from a browser, Copilot Chat, etc.) and paste it, Pastedown appears in the "Paste As…" menu offering Markdown formatting.
Features:
- Converts rich text (HTML) to clean Markdown on paste
- Full GFM support (tables, strikethrough, task lists)
- Proper nested list indentation (2 spaces for bullets, 3 for numbered)
- Converts Copilot Chat's
data-hreflinks to workspace-relative Markdown file links - Includes a
pastedown-in-chat!workaround for the chat input (which doesn't support paste providers)
Source: examples/.joyride/src/pastedown.cljs
Installation
- Install the npm dependencies:
cd ~/.config/joyride && npm install turndown turndown-plugin-gfm - Copy the source file to
~/.config/joyride/src/pastedown.cljs - Require and activate from your
user_activate.cljs:(require '[pastedown]) (pastedown/activate!) - Add keybindings to
keybindings.json:{ "key": "ctrl+alt+j ctrl+alt+v", "command": "joyride.runCode", "when": "inChatInput", "args": "(require 'pastedown :reload) (pastedown/pastedown-in-chat!)" }, { "key": "ctrl+alt+j ctrl+alt+v", "command": "editor.action.pasteAs", "when": "editorTextFocus", "args": { "kind": "pastedown" } }
Give yourself a JavaScript REPL
Evaluate code in JavaScript files, similar to how it's done with Clojure and ClojureScript.
https://github.com/BetterThanTomorrow/joyride/assets/30010/d5becc9d-d079-4371-9789-7c739ed0439f
Source, including some usage info: examples/.joyride/scripts/js_repl.cljs
Create a Hover Provider
In examples/.joyride/src/problem_hover.cljs there's a Hover Provider which can be activated from the template user_activate.cljs. It creates an extra Diagnostics item in the hover. It is registered after a significant delay (5 seconds) to make sure this hover item goes at the very top of the hover. Looks like so:

It's a workaround for this issue on Calva: https://github.com/BetterThanTomorrow/calva/issues/2001
Create a Webview
webview/example.cljs
Create a Webview. Uses scittle + reagent for the contents of the webview.
Live demo here: https://twitter.com/borkdude/status/1519607386218053632
Quick and Easy Webviews with Flares
Creating Webviews is such a common thing that Joyride has convenience API to quickly create them: Flares. Flares can be created as views in the editor area, as well as sidebar panel views. Flares support using Replicant Hiccup.
See more fancy example in .joyride/src/flares_examples.cljs
(require '[joyride.flare :as flare])
;; Display HTML in a Webview
(flare/flare!+ {:html [:h1 "Hello, Joyride Flares!"]
:title "Greeting"
:key "example"})
;; Close the flare
(flare/close! "example")
;; SVG visualization
(flare/flare!+ {:html [:svg {:height 100 :width 100}
[:circle {:r 40 :cx 50 :cy 50 :fill "blue"}]]
:title :blue})
;; Custom tab icon
(flare/flare!+ {:html [:img {:src "https://raw.githubusercontent.com/sindresorhus/awesome/refs/heads/main/media/logo.png"}]
:title "Awesome"
:icon "https://raw.githubusercontent.com/sindresorhus/awesome/refs/heads/main/media/logo.png"})
;; Display in sidebar slots (1-5 available)
(flare/flare!+ {:html [:div
[:h2 "Sidebar View"]
[:p "This appears in Joyride sidebar slot 1"]]
:title "Sidebar View"
:key :sidebar-1})
;; Use different slots for multiple sidebars
(flare/flare!+ {:file "assets/something.html"
:title "Sidebar 2"
:key :sidebar-2})
The :key parameter allows reusing the same panel for updates.
Terminal
.joyride/scripts/terminal.cljs
Create a Terminal, send text to it and show it.

Fontsize
.joyride/scripts/fontsize.cljs
Manipulates the editor font size. NB: This changes the global/User font size, and if you have configured editor.fontSize in the Workspace, nothing will appear to happen, you might be in for a surprise when opening other workspaces.
Live demo: https://twitter.com/borkdude/status/1519709769157775360
Structural Editing
.joyride/scripts/ignore_form.cljs
Adds a command for (un)ignoring (Clojure-wise) the current enclosing form.
Depends on that the Calva extension is installed, because it is what
helps us find out of the current list in order to insert, or remove, the ignore
tag (#_).
If you want to use this script, you can setup a VSCode key binding for it by
editing VSCode's keybindings JSON and adding the following. Note that this
overrides the default comment-keyboard-shortcut on macOS. The result is that
pressing CMD-/ in a Clojure file will use the (un)ignore script when there is no
selection, and keep using the default comment action (prepend the line with ;;
when there is a selection). For Windows you probably want to change the key to
the default Windows comment keyboard shortcut.
{
"key": "cmd+/",
"command": "joyride.runWorkspaceScript",
"args": "ignore_form.cljs",
"when": "!editorHasSelection && editorTextFocus && !editorReadOnly && editorLangId =~ /clojure|scheme|lisp/"
}
- Video here: https://www.youtube.com/watch?v=V1oTf-1EchU
- Tweet to like/comment/retweet: https://twitter.com/pappapez/status/1519825664177807363
Structural Editing via rewrite-clj
.joyride/scripts/port_arrow_form.cljs
Converts a midje arrow clause into a clojure.test/is form, when selected. Shows how to use rewrite-clj
to for parsing, getting S-Expressions, as well as how to use syntax-quote for easy form building.
(+ 2 2) => 4
;; is changed to
(is (= 4 (+ 2 2)))
{
"key": "f3",
"command": "joyride.runWorkspaceScript",
"args": "port_arrow_form.cljs",
"when": "editorHasSelection && editorTextFocus && !editorReadOnly && editorLangId == 'clojure'"
}
Toggle between interface and implementation when using Polylith
.joyride/scripts/toggle_between_interface_and_impl.cljs
When using Polylith, you have interface files and implementation files in your
components. This script helps you navigate between them: when you're in the
interface, it will switch to the implementation and vice versa. Note: not all
possible cases are implemented in this example, specifically when you're in
interface.clj, this script does not know how to handle that. See also the
documentation in the script file.
Joyride API
Joyride comes with the joyride.core namespace, giving you access to things as the extension context, the Joyride output channel, and some info about the evaluation environment.
And, you can also script Joyride with Joyride using its Extension API.
Example script: .joyride/scripts/joyride_api.cljs
See also: the Joyride API docs
Opening a file
The open_document.cljs script asks if you want to open one of the examples and then opens a random .cljs file from the scripts folder.
Joyride API used:
VS Code APIs used:
vscode/window.showInformationMessagevscode/workspace.findFilesvscode/workspace.openTextDocumentvscode/window.showTextDocument
Build Your Own Joyride Library
Adding commands to your workflow can be implemented in several ways, including:
- Define a
joyride.runCodekeyboard shortcut with some code. - Make a Joyride script, optionally binding a keyboard shortcut to it
- Define a function in a namespace that you know is required and call the function from a
joyride.runCodekeyboard shortcut.
You will probably be using all three ways. And even combos of them. Here we'll explore the third option a bit.
Start with creating a User script my_lib.cljs with this content:
(ns my-lib
(:require ["vscode" :as vscode]
[promesa.core :as p]))
Make sure it is required from User activate.cljs:
(ns activate
(:require ...
[my-lib]
...))
Here are some my-lib examples. NB: These examples are included as Getting started User scripts content. See your user scripts, or assets/getting-started-content/user/my_lib.cljs in this repository.
Lookup current symbol in Clojuredocs
See .joyride/scripts/clojuredocs.cljs for @seancorfield's script for looking up the current symbol on clojuredocs.org.
If you require that script in your User activation.cljs, you can then define a keyboard shortcut like so for easy lookup:
{
"command": "joyride.runCode",
"args": "(clojuredocs/lookup-current-form-or-selection)",
"key": "ctrl+alt+c d",
}
Note that Calva has several facilities for looking up things in Clojuredocs, but if you like to have it in an inline browser view, you can have it with this script.
Find-in-file with regexp toggled on
VS Code does not provide a way to reliably start a find-in-file with regular expressions toggled on. You can add a function to your my-lib namespace that does it. Like this one:
(defn find-with-regex-on []
(let [selection vscode/window.activeTextEditor.selection
selectedText (vscode/window.activeTextEditor.document.getText selection)
regexp-chars (js/RegExp. #"[.?+*^$\\|(){}[\]]" "g")
newline-chars (js/RegExp. #"\n" "g")
escapedText (-> selectedText
(.replace regexp-chars "\\$&")
(.replace newline-chars "\\n?$&"))]
(vscode/commands.executeCommand "editor.actions.findWithArgs" #js {:isRegex true
:searchString escapedText})))
This function takes care of escaping regular expression characters in the selected text as well as prepending newline characters with \n? which mysteriously makes VS Code enable multiline regexp search.
Example shortcut definition:
{
"key": "cmd+ctrl+alt+f",
"command": "joyride.runCode",
"args": "(my-lib/find-with-regex-on)",
},
(Or use the default find-in-file shortcut if you fancy.)
https://user-images.githubusercontent.com/30010/172149989-69e87313-afeb-44fc-a71d-417a439dac32.mp4
Customize findInFiles (searching the whole workspace in the sidebar)
A similar approach works to customize the workspace finder in the sidebar. The following code will open up the sidebar with some values preset. A complete list of available parameters is here.
(ns find-in-files
(:require ["vscode" :as vscode]
[joyride.core :as joyride]))
(defn find-regexp-in-src []
(vscode/commands.executeCommand "workbench.action.findInFiles"
#js {:isRegex true
:filesToInclude "src/**/*.clj"}))
This function can be mapped to a keybinding as above.
Workspace file system
The .joyride/scripts/ws.cljs script shows two things:
- How to find the current workspace root (or the first workspace folder, really)
- How to read the contents of a file in the workspace
Both examples use a utility script: .joyride/src/util/workspace.cljs
requiring JavaScript files
You can use code written in JavaScript (or compiled to JavaScript) with Joyride by using absolute or relative paths to these scripts in your requires.
.joyride/scripts/require_js_file.cljs does this, using an ns-form require that looks like so:
(ns require-js-file
(:require ["../src/hello-world.js" :as hello-world] :reload))
It can then use the exports from .joyride/src/hello-world.js.
This means you can write your scripts using JavaScript if you like, using only a small glue script written in Clojure, that require the JS code.
By adding the :reload option to the require, the JavaScript code is reloaded when you run/evaluate the Clojure glue code, so you can enjoy some interactive programming this way. For when you have JavasScript code that will stay stable, skip the :reload option.
npm packages
You can use packages from npm in your Joyride scripts. There's an example of this, using posthtml-parser in
html_to_hiccup.cljs

To use a package from npm, Joyride needs to find it somewhere in the path from the using script and up to the system root. Some directories to consider for your Joyride node_modules are ~/.config/joyride and <workspace-root>/.joyride. Sometimes the workspace root makes sense. To try this particular example you can:
- Have the Joyride repo check out, say in a folder named
joyride $ cd joyride/examples$ npm init -y$ npm icode examples- Issue the command: Calva: Start a Joyride REPL and Connect
- Open
.joyride/scripts/html_to_hiccup.cljsand Calva: Load/Evaluate Current File - Place the cursor somewhere in the
(-> ...)form and Calva: Evaluate Top Level Form
This example also uses clojure.walk. (Somewhat unnecessary according to some, but there certainly are occasions when it can be put to great use.)
clojure.zip
clojure.walk is wonderful, and so is clojure.zip! See zippers.cljs

babashka.fs
Joyride includes babashka.fs for path helpers and temp directories. See babashka_fs.cljs for a small demo of cwd / exists? / path / file-name and with-temp-dir.
Hickory
Hickory is not included with Joyride, but that doesn't mean you can't use it! In .joyride/src/hickory there is the slightly (very slightly) modified source of hickory.select as well as the unmodified source of hickory.zip. This makes the find-in-html possible. (See npm packages about the npm dependency.)

Your example here?
Please be inspired to add functions to your my-lib namespace (and to rename it, if you fancy) to give yourself commands that VS Code and/or some extension is lacking. And please consider contributing to the examples published here.
