Files
clojure-tui/README.md
2026-01-21 01:21:05 -05:00

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