Files
Adam Jeniski 93931f5811
SCIP Index / index (push) Successful in 1m43s
Trigger CI for cross-repo fix
2026-02-03 19:02:49 -05:00

235 lines
5.3 KiB
Markdown

# Clojure TUI
A terminal user interface framework for Clojure, inspired by [Bubbletea](https://github.com/charmbracelet/bubbletea) (Go). Build interactive CLI applications using the Elm Architecture with Hiccup-style views.
![Counter Example](assets/counter.gif)
## Features
- **Elm Architecture** - Predictable state management with `init`, `update`, and `view`
- **Hiccup Views** - Declarative UI with familiar Clojure syntax
- **Async Commands** - Timers, batched operations, and custom async functions
- **Rich Styling** - Colors (16, 256, true color), bold, italic, underline, and more
- **Layout System** - Rows, columns, and boxes with borders
- **Input Handling** - Full keyboard support including arrows, function keys, and modifiers
- **Babashka Compatible** - Fast startup with Babashka or full Clojure
## Quick Start
### Installation
Add to your `deps.edn`:
```clojure
{:deps {io.github.yourname/clojure-tui {:git/tag "v0.1.0" :git/sha "..."}}}
```
### Hello World
```clojure
(ns myapp.core
(:require [tui.core :as tui]))
(defn update-fn [model msg]
(if (tui/key= msg "q")
[model tui/quit]
[model nil]))
(defn view [model _size]
[:col
[:text {:fg :cyan :bold true} "Hello, TUI!"]
[:text {:fg :gray} "Press q to quit"]])
(tui/run {:init {}
:update update-fn
:view view})
```
Output:
```
Hello, TUI!
Press q to quit
```
### Counter Example
```clojure
(ns myapp.counter
(:require [tui.core :as tui]))
(defn update-fn [model msg]
(cond
(tui/key= msg "q") [model tui/quit]
(tui/key= msg "k") [(inc model) nil]
(tui/key= msg "j") [(dec model) nil]
(tui/key= msg "r") [0 nil]
:else [model nil]))
(defn view [model _size]
[:col
[:box {:border :rounded :padding [0 2]}
[:text {:fg :yellow :bold true} (str "Count: " model)]]
[:text {:fg :gray} "j/k: change r: reset q: quit"]])
(tui/run {:init 0
:update update-fn
:view view})
```
Output:
```
╭──────────────╮
│ Count: 0 │
╰──────────────╯
j/k: change r: reset q: quit
```
## Examples
### Counter
Simple counter demonstrating basic Elm Architecture.
![Counter](assets/counter.gif)
```bash
bb counter
```
### Timer
Countdown timer with pause/resume - demonstrates async commands.
![Timer](assets/timer.gif)
```bash
bb timer
```
### List Selection
Multi-select list with cursor navigation.
![List Selection](assets/list.gif)
```bash
bb list
```
### Spinner
Animated loading spinners with multiple styles.
![Spinner](assets/spinner.gif)
```bash
bb spinner
```
### Views
State machine pattern with multiple views and navigation.
![Views](assets/views.gif)
```bash
bb views
```
### HTTP
Async HTTP requests with loading states.
![HTTP](assets/http.gif)
```bash
bb http
```
### Running Examples
Run examples with Babashka (recommended for fast startup):
```bash
bb counter
bb timer
```
Or with full Clojure:
```bash
clojure -A:dev -M -m examples.counter
```
## Documentation
- [Getting Started](docs/getting-started.md) - Installation, first app, core concepts
- [Hiccup Views](docs/hiccup-views.md) - View elements, styling, and layout
- [API Reference](docs/api-reference.md) - Complete API documentation
- [Examples](docs/examples.md) - Annotated example applications
## Architecture
```
View (hiccup) → Render (ANSI string) → Terminal (raw mode I/O)
↑ │
│ v
Model ←──────── Update ←─────────── Input (key parsing)
```
The application follows the Elm Architecture:
1. **Model** - Your application state (any Clojure data structure)
2. **Update** - A pure function `(fn [model msg] [new-model cmd])` that handles messages
3. **View** - A pure function `(fn [model size] hiccup)` that renders the UI, where `size` is `{:width w :height h}`
## Hiccup View Elements
| Element | Description | Example |
|---------|-------------|---------|
| `:text` | Styled text | `[:text {:fg :red :bold true} "Error"]` |
| `:row` | Horizontal layout | `[:row "Left" "Right"]` |
| `:col` | Vertical layout | `[:col "Line 1" "Line 2"]` |
| `:box` | Bordered container | `[:box {:border :rounded} "Content"]` |
| `:space` | Empty space | `[:space {:width 5}]` |
## Commands
Commands are returned from your `update` function to trigger side effects:
| Command | Description |
|---------|-------------|
| `tui/quit` | Exit the application |
| `(tui/after ms msg)` | Send `msg` after ms milliseconds |
| `(tui/batch c1 c2)` | Run commands in parallel |
| `(tui/sequentially c1 c2)` | Run commands sequentially |
| `(fn [] msg)` | Custom async function returning a message |
## 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 # Main runtime (Elm architecture + async commands)
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
```
## License
MIT