nandi/jolt-nativepublic Fork 0
e98a184f21222aa11f115f428f758f6b5d9b6cfa
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

README.md · 106 lines · 4.9 KBmarkdown Blame HistoryRaw
Paint the same tree into a terminal, for the machines with no window a7f6202 nandi 17d ago1# jolt-tui
2
3glimmer's **terminal** backend, behind the retained-tree C ABI — `libjolttui.so`.
4
5[glimmer-tui](https://github.com/jolt-lang/glimmer-tui) is the design this
6follows: the same tags, the same props, the same keyboard, and the same rule
7that painting goes through a grid so a test needs no terminal. What moves is
8where the widget layer lives. There it is jolt over ncurses; here it is Rust
9behind the ABI `libvidya` already exports, so the jolt side picks a GPU window
10or a terminal by naming a different shared object and changing nothing else.
11
12```
13 glimmer (reactive cells, components, the reconciler)
14
15 ┌────────────────┴────────────────┐
16 libvidya.so libjolttui.so
17 egui, a GPU window a terminal, in cells
18```
19
20## Why the tree is down here
21
22A reconciler needs widgets to patch. A terminal has none — it has a grid you
23overwrite — so the same problem egui poses turns up again, and gets the same
24answer: the node arena lives in Rust, the caller mutates it by integer handle,
25and nothing is drawn until `tui_frame` walks the whole tree at once.
26
27Two things follow, both of them the reason for the arrangement rather than
28accidents of it:
29
30* **FFI traffic tracks edits, not frames.** A screen that is not changing costs
31 no crossings; only what the reconciler actually changed is sent.
32* **Painting is testable.** Everything above `term.rs` writes into a `Screen`
33 a grid of styled cells — and only `term.rs` emits an escape sequence.
34 `tui_headless` opens a session with no terminal at all, `tui_feed_key` types
35 into it, and `tui_screen_line` reads back what was painted. That is not a
36 second implementation for tests; it is the same code path with the writer
37 taken off the end.
38
39**Handlers do not cross the boundary.** A node reports that it was clicked and
40the caller looks up whose `:on-click` that was, exactly as with libvidya.
41
42## The ABI
43
44[`include/jolttui.h`](include/jolttui.h) is the reference: tags, props, events,
45key names, and the borrowed-string rule. In outline:
46
47| | |
48|---|---|
49| session | `tui_open` / `tui_headless` / `tui_close`, `tui_should_close`, `tui_quit` |
50| loop | `tui_tick(timeout_ms)` handles input, `tui_frame` lays out, paints and flushes |
51| nodes | `tui_node_new` / `_free` / `_append` / `_remove` / `_insert_after` / `_replace` |
52| props | `tui_node_set_str` / `_num` / `_bool`, and the matching reads |
53| reading back | `tui_node_tag`, `tui_node_child_at`, `tui_tree_dump`, `tui_screen_line` |
54| events | `tui_tree_poll_event` and the four accessors |
55| input by hand | `tui_feed_key` / `_click` / `_wheel`, `tui_focus` |
56
57## Keyboard
58
59Focus is a ring in paint order over the widgets that can take it — buttons,
60checkbuttons, entries, listboxes. Tab and Shift-Tab walk it, `:autofocus`
61claims it on the first frame, and a widget that is unmounted or turned
62insensitive gives it up rather than stranding the focus on nothing.
63
64| | |
65|---|---|
66| Enter, Space | activate the focused widget |
67| arrows, `j`/`k`, Page Up/Down, `g`/`G` | move a list's cursor |
68| Ctrl-A/E, Ctrl-W, Ctrl-U/K, Alt-B/F, Home/End | readline editing in an entry |
69| Esc | closes the topmost `:overlay` |
70| Ctrl-C, Ctrl-Q | quit |
71
72Anything nothing here wanted comes out as a `key` event on the focused node,
73named the way it is fed in — `"ctrl+u"`, `"page-down"`, `"f5"`. Bubbling it to a
74container's `:on-key` belongs to the caller: it holds the handlers.
75
76## Layout
77
78Every node answers two sizes — natural and minimum. A box hands out the natural
79ones when there is room, shrinks them proportionally toward the minimums when
80there is not, and gives the surplus to whoever set `:hexpand` / `:vexpand`.
81`:width-request` and `:height-request` are a floor on both, so asking for four
82rows gets four rows even when space is short.
83
84## Building
85
86```bash
87cargo build -p jolt-tui --release # target/release/libjolttui.so
88cargo test -p jolt-tui # the whole widget layer, headless
89cargo run -p jolt-tui --example showcase # every tag, in a terminal
90```
91
92`--no-default-features` drops crossterm and with it `tui_open` — the tree, the
93layout, the painter and `tui_headless` all still work, which is what makes this
94crate buildable and checkable on a machine with no terminal crate and no TTY.
95
96## Limits
97
98* **One session per process, on one thread.** The session lives in thread-local
99 storage, so a call from another thread is inert rather than unsound — the
100 same rule libvidya keeps.
101* **One cell per character.** A wide CJK glyph or an emoji is measured as one
102 column and will crowd its neighbour. Widths are a table lookup away; nothing
103 in the design is in the way of adding one.
104* **No `:table`, `:paginator` or `:help` yet.** glimmer-tui has them; a table is
105 a keyed listbox of rows here for now. Each is widget-layer work in
106 `src/paint.rs` plus a measure in `src/layout.rs` and a tag.