4.3 KiB
4.3 KiB
Clojure TUI
A Clojure TUI (Terminal User Interface) framework inspired by Bubbletea, using the Elm Architecture with Hiccup for views.
Architecture
┌─────────────────────────────────────┐
│ Hiccup DSL (view returns hiccup) │ ← User-facing API
├─────────────────────────────────────┤
│ Layout Engine (calculates sizes) │ ← Constraint solving
├─────────────────────────────────────┤
│ Render (hiccup → ANSI string) │ ← Colors, styles
├─────────────────────────────────────┤
│ Terminal (raw mode, input, output) │ ← Platform abstraction
└─────────────────────────────────────┘
The Elm Architecture
Every app has three parts:
;; Model - your application state
(def initial-model {:count 0})
;; Update - handle messages, return [new-model command]
(defn update-model [model msg]
(cond
(tui/key= msg "q") [model tui/quit]
(tui/key= msg :up) [(update model :count inc) nil]
:else [model nil]))
;; View - render model as hiccup
(defn view [{:keys [count]}]
[:col
[:text {:bold true} "Counter"]
[:text (str "Count: " count)]
[:text {:fg :gray} "Press up to increment, q to quit"]])
Hiccup Elements
| Element | Description | Attributes |
|---|---|---|
:text |
Styled text | :fg :bg :bold :dim :italic :underline :inverse |
:row |
Horizontal layout | :gap |
:col |
Vertical layout | :gap |
:box |
Bordered container | :border :title :padding |
:space |
Empty space | :width :height |
Colors
:fg and :bg accept: :black :red :green :yellow :blue :magenta :cyan :white :gray and bright variants.
Borders
:border accepts: :rounded :single :double :heavy :ascii
Padding
:padding accepts: n (all sides), [v h] (vertical, horizontal), or [top right bottom left]
Running Examples
With Clojure CLI
# Counter - basic Elm architecture
clojure -A:dev -M -m examples.counter
# Timer - async commands (tick)
clojure -A:dev -M -m examples.timer
# List selection - cursor navigation, multi-select
clojure -A:dev -M -m examples.list-selection
# Spinner - animated loading
clojure -A:dev -M -m examples.spinner
# Views - multi-screen state machine
clojure -A:dev -M -m examples.views
# HTTP - async HTTP requests
clojure -A:dev -M -m examples.http
With Babashka (limited)
The full async runtime requires core.async. For Babashka, use tui.simple:
(require '[tui.simple :as tui])
;; Same API, but no async commands (tick, http, etc.)
(tui/run {:init initial-model
:update update-model
:view view})
Built-in Commands
| Command | Description |
|---|---|
tui/quit |
Exit the program |
(tui/tick ms) |
Send :tick message after ms |
(tui/batch cmd1 cmd2) |
Run commands in parallel |
(tui/sequentially cmd1 cmd2) |
Run commands in sequence |
(fn [] msg) |
Custom async command |
Key Matching
(tui/key= msg "q") ;; Character
(tui/key= msg :enter) ;; Special key
(tui/key= msg :up) ;; Arrow
(tui/key= msg [:ctrl \c]) ;; Control combo
(tui/key= msg [:alt \x]) ;; Alt combo
Project Structure
src/
tui/
core.clj # Full async runtime (core.async)
simple.clj # Simple sync runtime (Babashka-compatible)
render.clj # Hiccup → ANSI
terminal.clj # Raw mode, input/output
input.clj # Key parsing
ansi.clj # ANSI codes, colors
examples/
counter.clj
timer.clj
list_selection.clj
spinner.clj
views.clj
http.clj
Differences from Bubbletea
| Bubbletea (Go) | Clojure TUI |
|---|---|
| String views | Hiccup views |
| Lipgloss styling | Inline :fg :bold attrs |
tea.Cmd functions |
Vector commands [:tick 100] |
| Imperative builder | Declarative data |
License
MIT