GitHub - ertugrulcetin/code-bubble

3 min read Original article ↗

Clojars Project

Disclaimer — this project is AI-generated.

A Clojure dev-time UI inspired by Code Bubbles: open vars as draggable bubbles on a shared canvas, click cross-references to fan out the call graph, auto-arrange when it gets busy.

code-bubble screenshot


Install

Leiningen (project.clj)

:profiles {:dev {:dependencies [[io.github.ertugrulcetin/code-bubble "0.2.1"]]}}

tools.deps (deps.edn)

{:aliases
 {:bubbles
  {:extra-deps {io.github.ertugrulcetin/code-bubble
                {:mvn/version "0.2.1"}}}}}

clj -A:bubbles -M -r to enter a REPL with code-bubble available.


Quickstart

(require '[code-bubble.core :as bubbles])

(bubbles/show!)                          ; open the canvas
(bubbles/show! 'clojure.string/blank?)   ; + add a bubble

Hover any identifier in a bubble — the cursor switches to HAND on navigable references; click to open them.


Pre-load libraries on startup

Drop a Maven coord onto the classpath and into the namespace browser in one call. Two shapes:

;; Already at the REPL:
(bubbles/load-lib! '[dev.weavejester/medley "1.10.0"])

;; Or as part of opening the window:
(bubbles/show-with-libs! '[[dev.weavejester/medley "1.10.0"]
                            [org.clj-commons/humanize "1.1"]])

This (a) resolves the lib via tools.deps, (b) eagerly requires every namespace it ships, (c) refreshes the browser so they appear in the left column.

If the lib is already on the classpath (e.g. you put it in -Sdeps), this works in any context — no interactive REPL needed. If it's not yet loaded, load-lib! falls back to clojure.repl.deps/add-libs which requires Clojure 1.12+ and an interactive REPL.

A complete shell one-liner:

clj -Sdeps '{:deps {io.github.ertugrulcetin/code-bubble {:mvn/version "0.2.1"}
                    dev.weavejester/medley            {:mvn/version "1.10.0"}}}' \
    -M -e '(require (quote [code-bubble.core :as b])) (b/show-with-libs! (quote [[dev.weavejester/medley "1.10.0"]]))'

Navigating

Gesture Effect
Click-drag (within 50 ms) Pan canvas
Click-and-hold then drag on a bubble Move bubble / select text / resize
Click empty canvas Drop focus from any bubble
Wheel over empty canvas / unfocused bubble Pan
Wheel after clicking into a bubble's body Scroll that bubble
Shift + wheel Pan horizontally
Two-finger pinch (macOS) Zoom (cursor-anchored)

Cursor is the only affordance for navigable refs — no underline. Pinch needs Apple's com.apple.eawt.event classes (most modern macOS JDKs ship them).


Keyboard

Shortcut Action
⌘K Open Function…
⌘T New tab
⌘W Hide window
⌘⇧K Clear active workspace
⌘⇧A Auto-arrange (Sugiyama tree layout)
⌘L Toggle connectors
⌘J Toggle browser
⌘= ⌘- ⌘0 Zoom in / out / reset
⌘, Preferences…

on macOS, Ctrl elsewhere.


Workspaces

Each tab is an independent canvas — its own bubbles, edges, zoom, focus. Double-click a tab title to rename. Closing the last tab spawns a fresh one.


Namespace browser

Left sidebar (⌘J to toggle). Two filterable lists side-by-side: every loaded namespace, then every var in the selected namespace. Filters do fuzzy subsequence matching with score-based ranking. Double-click a var to open it as a bubble in the active workspace.


Anchored connectors

Click a navigable identifier: a connector arrow is drawn from the line containing the click (highlighted in soft amber) straight to the new child bubble. The new bubble is positioned at the same y as that row — arrows stay short and roughly horizontal.


Public API

code-bubble.core/…:

fn what
show! [] / [sym] open window, optionally with a bubble
show-with-libs! [coords] open window after loading libs
load-lib! [coord] / [lib version] load a lib + refresh browser
close! [sym] / clear! / dispose!
align! toggle-connectors!
zoom-in! zoom-out! reset-zoom!
toggle-browser! show-preferences!
set-font! [family size] apply font globally
new-tab! [] / [title] / close-tab! tabs
open? bool

Caveats

  • add-libs (used by load-lib! when the jar isn't on the classpath) is Clojure 1.12+ and REPL-only. Preloading via -Sdeps sidesteps both.
  • Trackpad pinch needs Apple's gesture extension classes; ⌘= / ⌘- / ⌘0 always work as fallback.

License

MIT.