109 lines
5.0 KiB
Markdown
109 lines
5.0 KiB
Markdown
# Chapter 20 — Guest TUI framework (`ctx.tui`)
|
|
|
|
Guest scripts (`async function run(ctx, argv)`), `/bin` utilities, and guest Pear apps can build full-screen terminal apps **without `import` or `require`**. The booter attaches **`ctx.tui`** (and **`ctx.sdk`**) after the REPL session exists, unless **`BARE_OS_TUI=0`**.
|
|
|
|
Source is first-party raw JS under [`lib/tui/src/`](../lib/tui/src/) concatenated to [`/lib/bare-os/tui.js`](../kernel/lib/bare-os/tui.js). It is **not** Holepunch **`ctx.bare.bareTui`** (that bundle still uses native TTY fds and `require`).
|
|
|
|
API reference: [docs/reference/ctx-tui.md](../docs/reference/ctx-tui.md). Inspector: **`tui`** / **`man tui`**.
|
|
|
|
## Minimal app
|
|
|
|
```js
|
|
async function run(ctx) {
|
|
if (!ctx.tui || !ctx.tui.isTTY()) {
|
|
ctx.console.error('needs a TTY')
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
await ctx.tui.run({
|
|
init: function () {
|
|
this.n = 0
|
|
return null
|
|
},
|
|
update: function (msg) {
|
|
if (ctx.tui.key.matches(msg, 'q', 'ctrl+c')) return [this, ctx.tui.quit]
|
|
if (ctx.tui.key.matches(msg, 'up', 'k')) this.n++
|
|
if (ctx.tui.key.matches(msg, 'down', 'j')) this.n--
|
|
return [this, null]
|
|
},
|
|
view: function () {
|
|
return ctx.tui
|
|
.style()
|
|
.border(ctx.tui.style.borders.rounded)
|
|
.padding(1, 2)
|
|
.render('count: ' + this.n + '\n\n↑/↓ change · q quit')
|
|
}
|
|
})
|
|
}
|
|
```
|
|
|
|
Repo examples: [`examples/tui-counter/`](../examples/tui-counter/), [`examples/tui-form/`](../examples/tui-form/), [`examples/tui-dashboard/`](../examples/tui-dashboard/), [`examples/tui-irc/`](../examples/tui-irc/), [`examples/tui-summon/`](../examples/tui-summon/). Live guest: **`tui demo`**.
|
|
|
|
## TEA contract
|
|
|
|
- **`init()`** → first command or `null`
|
|
- **`update(msg)`** → `[model, cmd]` (or a bare model)
|
|
- **`view()`** → string (pure; do not start I/O here)
|
|
|
|
Commands: **`ctx.tui.quit`**, **`tick`**, **`every`**, **`batch`**, **`sequence`**, **`suspend`**.
|
|
|
|
The Program suspends Fish (`suspendReplForSubprocess`), enters the alternate screen, and always restores the terminal in `try/finally` (including thrown `update`).
|
|
|
|
For editors and floating UI, pass **`{ buffer: 'cell' }`**. The base `view()` stays a fixed-size screen; return **`overlay({ width, height })`** or **`overlays(...)`** as `{ row, col, text }` so a modal does not push later rows down. `ctx.tui.modal` uses this when the Program is in cell mode.
|
|
|
|
## Mapping from Holepunch `bare-tui`
|
|
|
|
| Holepunch | Bare OS guest |
|
|
| -------------------------- | --------------------------------------------- |
|
|
| `require('bare-tui')` | `ctx.tui` |
|
|
| `new Program(model).run()` | `ctx.tui.run(model)` |
|
|
| `list.create(...)` | `ctx.tui.list.create(...)` |
|
|
| `style()` | `ctx.tui.style()` |
|
|
| host `bare-fs` filepicker | `ctx.tui.filepicker.create({ vfs: ctx.vfs })` |
|
|
|
|
## Testing
|
|
|
|
Inject streams; **`fps: 0`** renders synchronously:
|
|
|
|
```js
|
|
await ctx.tui.run(model, {
|
|
input,
|
|
output,
|
|
isTTY: true,
|
|
fps: 0,
|
|
width: 80,
|
|
height: 24
|
|
})
|
|
```
|
|
|
|
Assert on `ctx.tui.style.stripAnsi(model.view())`, not only the write capture (the renderer is a diff stream).
|
|
|
|
## `ctx.sdk`
|
|
|
|
Same object as **`ctx.tui`** at **`ctx.sdk.tui`**. Extra namespaces for app authors:
|
|
|
|
| Namespace | Role |
|
|
| --------------- | ----------------------------------------------------- |
|
|
| **`sdk.tty`** | `isTTY`, `size`, `acquire` / `release`, `withSession` |
|
|
| **`sdk.theme`** | `name`, `tokens`, `apply(name)` → `bareOsApplyTheme` |
|
|
| **`sdk.env`** | `get` / `has` / `term` / `colorDepth` / `noColor` |
|
|
| **`sdk.vfs`** | `list`, `readText`, `writeText` |
|
|
| **`sdk.proc`** | `read(name)` under `/proc/bare_os/` |
|
|
| **`sdk.ipc`** | `pushJson` / `takeJson` when `ctx.bareOsIpc` exists |
|
|
| **`sdk.app`** | `run`, `confirm`, `prompt`, `select`, `form` |
|
|
|
|
## Stock utilities
|
|
|
|
TTY mode for **`dhttop`**, **`swarmmap`**, **`routeview`**, **`holepunch-view`**, **`swarmtop`**, **`chat`**, **`edit`**, **`nano`**, **`baretop`**, **`btop`**, **`irc`**, and **`summon`** calls **`ctx.tui.run`** when the SDK is attached (`edit`/`nano`/`baretop`/`irc`/`summon` use **`buffer: 'cell'`**). **`agent --setup`** uses **`ctx.tui.form`**. All of these keep the pre-SDK path if **`BARE_OS_TUI=0`**. **`irc`** defaults to Libera.Chat TLS (`irc.libera.chat:6697`); see [irc-client.md](../docs/reference/irc-client.md). **`summon`** is the text web browser; see [summon.md](../docs/reference/summon.md).
|
|
|
|
## Environment
|
|
|
|
- **`BARE_OS_TUI=0`** — omit `ctx.tui` / `ctx.sdk`
|
|
- **`BARE_OS_TUI_NO_ALTSCREEN`** — full clear instead of `?1049h`
|
|
- **`BARE_OS_TUI_DEBUG`** — reserved
|
|
- **`NO_COLOR`**, **`BARE_OS_COLOR_DEPTH`**, **`BARE_OS_THEME`** — style / theme
|
|
|
|
---
|
|
|
|
[← Apps beyond the shell](07-apps-beyond-the-shell.md) · [Testing →](08-testing-and-debugging.md)
|