153 lines
4.3 KiB
Markdown
153 lines
4.3 KiB
Markdown
# Clojure TUI
|
|
|
|
A Clojure TUI (Terminal User Interface) framework inspired by [Bubbletea](https://github.com/charmbracelet/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:
|
|
|
|
```clojure
|
|
;; 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
|
|
|
|
```bash
|
|
# 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`:
|
|
|
|
```clojure
|
|
(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
|
|
|
|
```clojure
|
|
(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
|