Joyride

repository·master·Indexed 20 days ago

https://github.com/betterthantomorrow/joyride

A VS Code extension that makes the editor hackable by allowing users to extend and script it using ClojureScript or JavaScript via the Small Clojure Interpreter (SCI). It provides direct access to the VS Code API for interactive customization and automation through User and Workspace scopes, activation scripts, and a convenience API for Webviews called Flares.

Tokens
24.9K
Snippets
69
Records
124
Agent score
67%

What's inside joyride

  1. Overview of Joyride

    master
    Joyride is a VS Code extension that enables runtime extensibility using ClojureScript and JavaScript. It allows users to execute code via a REPL or keyboard shortcuts, providing full access to the VS Code API. It follows an Emacs-inspired model where users can automate workflows and extend the editor without writing traditional VS Code extensions.
  2. Understand Joyride Activation Scripts

    master

    Joyride uses activation scripts to run code at different scopes within VS Code. There are two primary types:

    1. User-level (user_activate.cljs): Runs for all workspaces. This is the place to register global behaviors, such as custom keybindings, global output channels, or universal command palettes.
    2. Workspace-level (workspace_activate.cljs): Runs only for the current workspace. This is ideal for project-specific automation, such as registering event handlers for specific file types or workspace-specific tools.

    Key Patterns:

    • Disposables: Use joyride.core/extension-context to push disposables onto its subscriptions array. This ensures VS Code automatically cleans up your registered events/commands when Joyride is deactivated.
    • Re-runnability: When writing scripts that register event handlers (like vscode/workspace.onDidOpenTextDocument), always dispose of the previous registration before re-registering to avoid duplicate handlers when the script is re-evaluated.
  3. Configure script activation lifecycle

    master

    You can execute code automatically when Joyride activates by providing activation scripts. These scripts are run in the following order:

    1. <User scripts directory>/user_activate.cljs
    2. <Workspace scripts directory>/workspace_activate.cljs
  4. Implement VS Code and Node.js interfaces in Joyride SCI

    master

    Because Joyride uses SCI, you cannot use deftype to implement interfaces. Instead, use a plain JavaScript object (#js) containing the required functions. Use these objects wherever an instance of that interface is expected.

    ;; Instead of:
    ;; (deftype Foo [] (bar [x y] ...))
    
    ;; Use:
    #js {:bar (fn [x y] ...)}
  5. JavaScript Interop: Objects and Destructuring

    master

    Since Joyride runs in a JS environment, you frequently interact with JS objects and interfaces.

    Creating JS Objects: Use the #js literal to create native JavaScript objects. This is required for passing options to VS Code APIs that expect JS objects.

    #js {:onClick (fn [event] (println "Clicked!"))}

    Destructuring JS Objects: To work with JS objects in a Clojure-idiomatic way, use js->clj with :keywordize-keys true to convert them to Clojure maps.

    ;; Creating JS objects
    #js {:onClick (fn [event] (println "Clicked!"))
         :onHover (fn [event] (println "Hovered!"))}
    
    ;; Destructuring JS Objects
    (let [{:keys [uri fsPath]} (js->clj workspace-folder :keywordize-keys true)]
      (println "Workspace at:" fsPath))
  6. Understand the Joyride classpath

    master

    Joyride searches for your source files in a fixed order of directories. The first match found is used. The search order is:

    1. <workspace-root>/.joyride/src
    2. <workspace-root>/.joyride/scripts
    3. <user-home>/.config/joyride/src
    4. <user-home>/.config/joyride/scripts
  7. Organize User and Workspace scripts

    master

    Joyride automatically discovers scripts based on their location in your file system. Use the following directory structures to organize your scripts:

    User Scripts

    Store global scripts in your user configuration directory: <user home>/.config/joyride/scripts/**/*.cljs

    Workspace Scripts

    Store project-specific scripts within your workspace root: .joyride/scripts/**/*.cljs

  8. Manage state and disposables in Joyride

    master

    Joyride uses a central application state and a disposable pattern to manage resources and cleanup.

    • Central State: The application state is managed via the !app-db atom, which tracks :disposables, :extension-context, :workspace-root-path, and the :invoked-script.
    • Disposables: To prevent resource leaks, use push-disposable! to register cleanup tasks and clear-disposables! to execute them.
    ;; Central app state
    (defonce !app-db (atom {:disposables []
                            :extension-context nil
                            :workspace-root-path nil
                            :invoked-script nil}))
  9. Communicate between Extension and Webview

    master

    Joyride supports bidirectional communication between your extension logic and the flare's webview.

    1. Extension $\rightarrow$ Webview: Use post-message!+ to send a serialized JSON message to a specific flare.
    2. Webview $\rightarrow$ Extension: Provide a :message-handler function when calling flare!+. This function will be invoked whenever the webview sends a message.
    ;; Extension $\rightarrow$ Webview
    (post-message!+ :my-flare {:action "update" :data {...}})
    
    ;; Webview $\rightarrow$ Extension
    (flare!+ {:key :my-flare
              :html "..."
              :message-handler (fn [msg]
                                (js/console.log "Received:" msg))})
  10. Manage resources and disposables in reloadable scripts

    master

    To prevent event handlers and UI elements (like status bar items) from piling up every time you re-run a script, use a pattern for managing disposables. This ensures that old resources are cleaned up before new ones are created.

    Recommended Pattern:

    1. Maintain an atom of !disposables.
    2. Create a clear-disposables! function to iterate and call .dispose() on everything in the atom.
    3. Create a register-disposable! function that both adds the item to your atom and pushes it to the joyride/extension-context subscriptions.
    4. Call clear-disposables! at the start of your main function.
    ;; Pattern for reloadable scripts that create disposables
    (defonce !disposables (atom []))
    
    (defn clear-disposables! []
      "Dispose all existing disposables and clear the list"
      (run! #(.dispose %) @!disposables)
      (reset! !disposables []))
    
    (defn register-disposable! [disposable]
      "Register a disposable for cleanup and with VS Code"
      (swap! !disposables conj disposable)
      (.push (.-subscriptions (joyride/extension-context)) disposable))
    
    (defn main []
      ;; Clear any existing disposables first (makes script reloadable)
      (clear-disposables!)
    
      ;; Now create new disposables
      (register-disposable! 
        (vscode/workspace.onDidOpenTextDocument my-handler))
    
      ;; Create status bar button (will be recreated on re-run)
      (register-disposable! 
        (doto (vscode/window.createStatusBarItem vscode/StatusBarAlignment.Left)
          (aset "text" "My Button")
          (aset "command" "my.command")
          (.show))))
    
    ;; Use in activation scripts and regular scripts
    (when (= (joyride/invoked-script) joyride/*file*)
      (main))
  11. Workspace vs User: Decision Guide

    master

    Decide where to place your Joyride code based on its scope and intended audience:

    CriterionWorkspace (.joyride/)User (~/.config/joyride/)
    Applies toThis project onlyAll workspaces
    ShareableYes — commit to repoNo — personal setup
    OverridesWins over user-level codeProvides defaults
    Typical useProject tooling, build helpersPersonal editor customizations
    Activationworkspace_activate.cljsuser_activate.cljs

    Rule of thumb: If it's useful in every workspace, put it in user. If it's project-specific or team-shareable, put it in workspace.