rabbita_tui

Native terminal UI toolkit built around The Elm Architecture

tui
terminal
tea
native
cli
moon add moonbit-community/rabbita_tui@0.1.0
Download zip
Version
0.1.0
License
Apache-2.0
Last updated
3 months ago
Downloads
315

Dependencies

README

#moonbit-community/rabbita_tui

Warning: moonbit-community/rabbita_tui is experimental. Public APIs, package layout, widget names, and runtime behavior may change while it converges with Rabbita.

moonbit-community/rabbita_tui is a native terminal UI toolkit for MoonBit. It follows The Elm Architecture:

terminal event / command result | v Msg ------> update(emit, msg, model) -> (Cmd, Model) ^ | | v Cmd <----------------------------- view(model)

The public API is intentionally close to Rabbita's current shape:

  • Emit[Msg] turns a message into a monomorphic Cmd.
  • Cmd is not parameterized by Msg.
  • cell owns Model, update, view, and subscriptions.
  • Dispatch is kept only as a deprecated alias for Emit.

The first backend targets POSIX terminals on MoonBit's native backend. Windows keeps the API surface but returns UnsupportedPlatform for interactive terminal startup.

#Install

Add the module and native async runtime dependency:

{ "deps": { "moonbit-community/rabbita_tui": "0.1.0", "moonbitlang/async": "0.17.0" }, "preferred-target": "native" }

In the package that uses the TUI:

import {
"moonbit-community/rabbita_tui" @tui,
"moonbit-community/rabbita_tui/widgets" @widgets,
"moonbitlang/async",
}

supported_targets = "+native"

options(
"is-main": true,
)

Run with:

moon run --target native path/to/your/package

#A Complete Small App

This is the smallest useful shape for an interactive program.

///|
using @tui {
border,
cell,
pad,
text,
type Cmd,
type Edge,
type Emit,
type Frame,
type Key,
type Node,
type ProgramOptions,
type Size,
type Style,
type Sub,
vstack,
}

///|
using @widgets {status_line}

///|
enum Msg {
KeyPressed(Key)
Resize(Size)
Quit
} derive(Eq, Debug)

///|
struct Model {
count : Int
width : Int
height : Int
} derive(Eq, Debug)

///|
fn initial_model() -> Model {
{ count: 0, width: 80, height: 24 }
}

///|
fn update(_emit : Emit[Msg], msg : Msg, model : Model) -> (Cmd, Model) {
match msg {
KeyPressed(key) =>
match key {
Up => (Cmd::none(), { ..model, count: model.count + 1 })
Down => (Cmd::none(), { ..model, count: model.count - 1 })
Char("q") | Ctrl("c") => (Cmd::quit(), model)
_ => (Cmd::none(), model)
}
Resize(size) =>
(Cmd::none(), { ..model, width: size.width, height: size.height })
Quit => (Cmd::quit(), model)
}
}

///|
fn view(model : Model) -> Node {
let title = text(style=Style::default().bold().fg(Ansi(39)), "Counter")
let body = text("count = \{model.count}")
let help = text("up/down change q quit")
let card = vstack(gap=1) [
title, body, help,
]
vstack [
status_line(
left="counter",
right="\{model.width}x\{model.height}",
width=model.width,
),
vstack(style=Style::default().padding(Edge::all(1)).border()) [
card,
],
]
}

///|
fn subscriptions(_model : Model) -> Sub[Msg] {
Sub::batch([
Sub::keys(key => KeyPressed(key)),
Sub::resize(size => Resize(size)),
])
}

///|
async fn main {
cell(model=initial_model(), update~, view~, subscriptions~).run_with_options(
ProgramOptions::inline(),
) catch {
NotATty(_) =>
println(
Frame::from_node(view(initial_model()), { width: 80, height: 24 }).to_string(),
)
NativeError(message) => println("terminal error: \{message}")
UnsupportedPlatform => println("native POSIX terminal required")
}
}

#Core Concepts

#Model

Keep all application state in a plain struct. Store terminal size in the model when layout depends on it.

///|
using @vector {type Vector}

///|
using @widgets {type TextInput}

///|
struct Model {
input : TextInput
items : Vector[String]
selected : Int
width : Int
height : Int
}

#Msg

Messages describe what happened. Prefer semantic messages over raw terminal events in the rest of your app.

///|
enum Msg {
Typed(Key)
Submitted
Resized(Size)
Loaded(Result[String, IOError])
Quit
}

#Update

update is the state transition function. It receives:

  • emit : Emit[Msg], used to create commands that send messages later.
  • msg : Msg, the event to handle.
  • model : Model, the current state.

It returns (Cmd, Model).

///|
fn update(emit : Emit[Msg], msg : Msg, model : Model) -> (Cmd, Model) {
match msg {
Submitted =>
(delay(emit(Loaded(Ok("done"))), 300), { ..model, status: "loading" })
Loaded(result) =>
match result {
Ok(value) => (Cmd::none(), { ..model, status: value })
Err(_) => (Cmd::none(), { ..model, status: "failed" })
}
Quit => (Cmd::quit(), model)
_ => (Cmd::none(), model)
}
}

For large apps, keep update as the top-level message router and split by domain:

///|
fn update(emit : Emit[Msg], msg : Msg, model : Model) -> (Cmd, Model) {
match msg {
KeyPressed(key) => update_key(emit, key, model)
JobFinished(id) => update_job_finished(id, model)
Resize(size) => (Cmd::none(), resize_model(size, model))
}
}

#View

view(model) returns a Node. Rendering is declarative: you describe the frame, and the runtime diffs frames at repaint time.

///|
fn view(model : Model) -> Node {
vstack [
text(style=Style::default().bold(), "Tasks"),
border(model.input.view(width=model.width - 4)),
status_line(left="enter submit", right="q quit", width=model.width),
]
}

#Subscriptions

Subscriptions translate terminal input into your Msg type.

///|
fn subscriptions(_model : Model) -> Sub[Msg] {
Sub::batch([
Sub::keys(key => KeyPressed(key)),
Sub::mouse_events(mouse => MouseSeen(mouse)),
Sub::paste(value => Pasted(value)),
Sub::focus_changes(focused => FocusChanged(focused)),
Sub::resize(size => Resized(size)),
Sub::tick(100, Tick),
])
}

Subscriptions should produce messages directly. Ignore irrelevant input in update instead of putting filtering logic inside subscription callbacks.

#Commands

Commands describe work the runtime should perform after update.

CommandUse it for
Cmd::none()no side effect
emit(msg)queue a message immediately
Cmd::batch([...])run commands concurrently
Cmd::sequence([...])run commands in order
delay(cmd, ms) / Cmd::delay(ms, cmd)run a command later
Cmd::log / Cmd::err_loglog safely above the live TUI
Cmd::exec_processtemporarily restore terminal mode, run a shell command, resume
Cmd::suspendrelease the terminal around async work
Cmd::quitstop the program
Cmd::repaintmark the frame dirty

Example: delayed follow-up message.

///|
fn update(emit : Emit[Msg], msg : Msg, model : Model) -> (Cmd, Model) {
match msg {
StartTimer => (delay(emit(TimerDone), 1000), { ..model, status: "waiting" })
TimerDone => (Cmd::none(), { ..model, status: "done" })
}
}

Example: external command.

Cmd::exec_process(
"git status --short",
code => emit(ProcessExited(code)),
)

Example: safe output that does not corrupt the current frame.

Cmd::sequence([
Cmd::log("saved configuration"),
Cmd::err_log("warning: using default profile"),
])

#Runtime Options

Use ProgramOptions to configure terminal behavior.

ProgramOptions::default()
.alternate_screen(true)
.mouse(MouseButtonMotion)
.bracketed_paste(true)
.focus_events(true)
.hide_cursor(true)
.fps(30)
.max_messages_per_frame(128)
.resize_poll_millis(50)

Common presets:

  • ProgramOptions::default() uses the alternate screen.
  • ProgramOptions::inline() renders in the current terminal scrollback.
  • .without_renderer() keeps subscriptions and commands running without drawing frames.
  • .fps(n) limits repaint cadence when commands or subscriptions produce many messages.

Run helpers:

app.run()
app.run_with_options(ProgramOptions::inline())
app.run_returning_model()
app.run_with_cancel_token(options, token)
app.run_with_timeout(options, 5000)

CancelToken is useful when an outer task owns cancellation:

let token = CancelToken::new()
// pass token to the running program
token.cancel()

#Layout and Styling

The layout API is intentionally small and composable.

FunctionPurpose
text(style?, value)text node
vstack(gap?, style?) <| nodes / hstack(gap?, style?) <| nodesvertical or horizontal layout
fragment(style?) <| nodesrender a sequence of child nodes
pad(edge=..., style?, node)add padding
border(kind?, style?, node)add a border
sized(width?, height?, style?, node)force a size
clip(size=..., style?, node)clip to a size
align(horizontal?, vertical?, style?, node)align inside available space
fill(style?, value)repeated fill

Styles support foreground, background, and text attributes:

let style = Style::default()
.fg(Ansi(16))
.bg(Ansi(250))
.bold()
.underline()

text(style~, "Ready")

For everyday view code, layout can live in Style as well, keeping wrappers flat:

vstack(
gap=1,
style=Style::default()
.fg(Ansi(250))
.padding(Edge::all(1))
.border()
.size(width=48, height=8),
) [
text(style=Style::default().bold(), "Build status"),
text("All checks passed"),
]

Colors can be Default, Ansi(index), or Rgb(r, g, b).

For terminal-safe width handling:

  • display_width(text) accounts for wide characters.
  • fit_line(text, width) clips and pads a line.
  • take_width(text, width) clips without padding.
  • wrap_text(text, width) wraps by display width.

#Widgets

Widgets live in the moonbit-community/rabbita_tui/widgets package. They are TEA submodels only when they need editing or selection state. Pure display widgets are plain functions. Widgets do not own callbacks, commands, subscriptions, or the app loop.

///|
using @widgets {
keymap,
progress,
status_line,
type TextInput,
type TextInputMsg,
}

WidgetShape
TextInputstateful: TextInput, TextInputMsg, update, view
Textareastateful: Textarea, TextareaMsg, value, update, view
Liststateful: List, ListMsg, selected_item, update, view
Viewportstateful: Viewport, ViewportMsg, update, view
CommandPalettestateful: CommandPalette, CommandPaletteMsg, items, selected_item, update, view
table / paginator / progress / timer / keymap / tabsstateless render functions
status_line / spinner / modalstateless terminal UI helpers

Stateful constructors are declared as associated constructors, for example pub fn TextInput::TextInput(...) -> TextInput, and called as TextInput(...).

Stateful widget state belongs in your app model. Route widget messages through your app's single update function. Stateless widgets are derived from the model in view and should not be stored in the model:

///|
fn initial_model() -> Model {
{ input: TextInput(placeholder="Search"), completed: 0, total: 4 }
}

///|
fn view(model : Model) -> Node {
vstack [
model.input.view(width=model.width),
progress(current=model.completed, total=model.total, width=model.width),
keymap(
bindings=[
{ keys: [Enter], help: "submit" },
{ keys: [Ctrl("c")], help: "quit" },
],
width=model.width,
),
]
}

Example input handling:

///|
fn update(_emit : Emit[Msg], msg : Msg, model : Model) -> (Cmd, Model) {
match msg {
KeyPressed(key) => {
let input_msg : TextInputMsg = TextInputKey(key)
(Cmd::none(), { ..model, input: model.input.update(input_msg) })
}
Submitted => (Cmd::none(), { ..model, saved: model.input.value })
}
}

#Keyboard and Mouse Input

The parser covers common terminal input:

  • Printable characters.
  • Control keys such as Ctrl("c").
  • Alt keys such as Alt("x").
  • Arrow/navigation/function keys.
  • Shift/Ctrl/Alt modified arrow and navigation keys.
  • Kitty keyboard protocol CSI ... u.
  • Bracketed paste.
  • SGR mouse events.
  • Focus gained/lost events.

The key type includes Modified(Key, KeyModifiers), so an app can distinguish plain Up from Modified(Up, KeyModifiers::none().shift(true)).

#Testing

Use run_headless when you want to test observable behavior without a real terminal.

///|
async test "counter increments" {
let result = app().run_headless(
events=[Key(Up), Key(Up)],
options=HeadlessOptions::default().size({ width: 40, height: 10 }),
)
@debug.assert_eq(result.model.count, 2)
@debug.assert_eq(result.frames.length() > 0, true)
}

Use Frame::from_node for snapshot-like assertions:

let frame = Frame::from_node(view(model), { width: 80, height: 24 })
assert_true(frame.to_string().contains("Ready"))

The runtime lab includes a tmux regression test that drives a real terminal:

scripts/runtime_lab_tmux_test.sh

#Examples

#examples/counter

A small counter app that demonstrates the recommended TEA shape without extra moving parts:

  • Model, Msg, update, view, and subscriptions.
  • Direct key and resize subscriptions.
  • Responsive layout.
  • Basic foreground/background styling.

Run:

moon run --target native examples/counter

#examples/codex-cli

A Codex CLI-like conversation UI. It demonstrates:

  • Responsive transcript layout.
  • Prompt editing with Textarea.
  • Prompt history.
  • Streaming command messages.
  • Diff rendering inside a chat pane.
  • Scroll behavior with PageUp/PageDown and mouse wheel.
  • Foreground/background color styling.

Run:

moon run --target native examples/codex-cli moon run --target native examples/codex-cli -- --snapshot

#examples/init-cli

A project initializer wizard. It demonstrates:

  • Multi-step form flow.
  • TextInput, List, checkbox-like toggles, progress, and summary views.
  • Delayed command scheduling.
  • Responsive split-panel layout.

Run:

moon run --target native examples/init-cli moon run --target native examples/init-cli -- --snapshot

#examples/runtime-lab

A runtime control center for learning lower-level behavior. It demonstrates:

  • Safe stdout/stderr output.
  • Cmd::exec_process.
  • Cmd::suspend.
  • Delayed jobs and timeout races.
  • Model-level cancellation patterns.
  • FPS-limited rendering.
  • Modified keyboard input.

Run:

moon run --target native examples/runtime-lab moon run --target native examples/runtime-lab -- --snapshot scripts/runtime_lab_tmux_test.sh

#Design Notes

  • Keep app state in Model; do not store terminal renderer state there.
  • Keep update as the top-level message router. For larger examples, split by domain using helpers such as update_key and update_scenario; avoid helpers whose only job is to hide command construction.
  • Use subscriptions to translate raw terminal events into semantic messages.
  • Use emit(msg) for message commands. Use Cmd::log, Cmd::suspend, and Cmd::exec_process for runtime side effects instead of manually writing to stdout or toggling raw mode.
  • Build views from Node values and widgets. The renderer remains independent from higher-level widgets so future Rabbita integration can reuse the app model/update layer.

#Current Limitations

  • Interactive startup is implemented for POSIX native terminals.
  • Windows currently returns UnsupportedPlatform for raw interactive mode.
  • The widget set is practical but intentionally smaller than Bubble Tea's full ecosystem. It covers common CLI workflows, but advanced widgets should be built as plain MoonBit structs around the same Model/Msg/update pattern.

#
TerminalError

pub(all) suberror TerminalError {
UnsupportedPlatform
NotATty(String)
NativeError(String)
} derive(Eq,
Debug
)

#
Align

pub(all) enum Align {
Start
Center
End
} derive(Eq,
Debug
)

#
Border

pub(all) enum Border {
NoBorder
Normal
Rounded
Square
Double
Thick
} derive(Eq,
Debug
)

#
CancelToken

pub(all) struct CancelToken {
cancelled : Bool
} derive(
Debug
)

#
CancelToken::cancel

fn CancelToken::cancel(self : CancelToken) -> Unit

#
CancelToken::is_cancelled

fn CancelToken::is_cancelled(self : CancelToken) -> Bool

#
CancelToken::new

#
Cmd

type Cmd

#
Cmd::attempt

fn[A, E : Error] Cmd::attempt(to_cmd : (Result[A, E]) -> Cmd, task : async () -> A raise E) -> Cmd

#
Cmd::batch

fn Cmd::batch(cmds : Array[Cmd]) -> Cmd

#
Cmd::clear_screen

fn Cmd::clear_screen() -> Cmd

#
Cmd::delay

fn Cmd::delay(milliseconds : Int, cmd : Cmd) -> Cmd

#
Cmd::disable_bracketed_paste

fn Cmd::disable_bracketed_paste() -> Cmd

#
Cmd::disable_focus_events

fn Cmd::disable_focus_events() -> Cmd

#
Cmd::disable_mouse

fn Cmd::disable_mouse() -> Cmd

#
Cmd::effect

fn Cmd::effect(task : async () -> Unit noraise) -> Cmd

#
Cmd::enable_bracketed_paste

fn Cmd::enable_bracketed_paste() -> Cmd

#
Cmd::enable_focus_events

fn Cmd::enable_focus_events() -> Cmd

#
Cmd::enable_mouse

fn Cmd::enable_mouse(mode : MouseMode) -> Cmd

#
Cmd::enter_alternate_screen

fn Cmd::enter_alternate_screen() -> Cmd

#
Cmd::err_log

fn Cmd::err_log(value : String) -> Cmd

#
Cmd::err_write

fn Cmd::err_write(value : String) -> Cmd

#
Cmd::every

fn Cmd::every(milliseconds : Int, cmd : Cmd) -> Cmd

#
Cmd::exec

fn[A] Cmd::exec(to_cmd : (A) -> Cmd, task : async () -> A noraise) -> Cmd

#
Cmd::exec_process

fn Cmd::exec_process(command : String, done : (Int) -> Cmd) -> Cmd

#
Cmd::hide_cursor

fn Cmd::hide_cursor() -> Cmd

#
Cmd::is_none

fn Cmd::is_none(self : Cmd) -> Bool

#
Cmd::leave_alternate_screen

fn Cmd::leave_alternate_screen() -> Cmd

#
Cmd::log

fn Cmd::log(value : String) -> Cmd

#
Cmd::message

#deprecated("Use emit(msg) instead.")
fn[Msg] Cmd::message(emit : Emit[Msg], msg : Msg) -> Cmd

#
Cmd::none

fn Cmd::none() -> Cmd

#
Cmd::perform

fn[A] Cmd::perform(to_cmd : (A) -> Cmd, task : async () -> A noraise) -> Cmd

#
Cmd::quit

fn Cmd::quit() -> Cmd

#
Cmd::repaint

fn Cmd::repaint() -> Cmd

#
Cmd::run

async fn Cmd::run(self : Cmd) -> Unit

#
Cmd::run_with_terminal

async fn Cmd::run_with_terminal(self : Cmd, terminal : (TerminalCommand) -> Unit) -> Unit

#
Cmd::sequence

fn Cmd::sequence(cmds : Array[Cmd]) -> Cmd

#
Cmd::set_window_title

fn Cmd::set_window_title(title : String) -> Cmd

#
Cmd::show_cursor

fn Cmd::show_cursor() -> Cmd

#
Cmd::suspend

fn Cmd::suspend(task : async () -> Cmd) -> Cmd

#
Cmd::terminal

fn Cmd::terminal(command : TerminalCommand) -> Cmd

#
Cmd::terminal_commands

fn Cmd::terminal_commands(self : Cmd) -> Array[TerminalCommand]

#
Cmd::tick

fn Cmd::tick(milliseconds : Int, cmd : Cmd) -> Cmd

#
Cmd::write

fn Cmd::write(value : String) -> Cmd

#
Color

pub(all) enum Color {
Default
Ansi(Int)
Rgb(Int, Int, Int)
} derive(Eq,
Debug
)

#
Edge

pub(all) struct Edge {
top : Int
right : Int
bottom : Int
left : Int
} derive(Eq,
Debug
)

#
Edge::all

fn Edge::all(value : Int) -> Edge

#
Edge::horizontal

fn Edge::horizontal(value : Int) -> Edge

#
Edge::trbl

fn Edge::trbl(top : Int, right : Int, bottom : Int, left : Int) -> Edge

#
Edge::vertical

fn Edge::vertical(value : Int) -> Edge

#
Edge::xy

fn Edge::xy(x : Int, y : Int) -> Edge

#
Edge::zero

fn Edge::zero() -> Edge

#
Emit

#alias(Dispatch, deprecated="Use Emit[Msg] instead.")
pub(all) struct Emit[Msg]((Msg) -> Cmd)

Message emitter used by a program.

Calling emit(msg) returns a Cmd that queues msg back into the update loop. This mirrors Rabbita's current TEA API and keeps Cmd monomorphic.

#
Emit::map

fn[A, B] Emit::map(self : Emit[A], map : (B) -> A) -> Emit[B]

#
Emit::new

fn[Msg] Emit::new(send : (Msg) -> Unit) -> Emit[Msg]

#
Emit::send

fn[Msg] Emit::send(self : Emit[Msg], msg : Msg) -> Unit

#
Event

pub(all) enum Event {
Key(Key)
Mouse(Mouse)
Resize(Size)
FocusGained
FocusLost
Paste(String)
Tick(Int64)
Quit
UnknownEvent(String)
} derive(Eq,
Debug
)

#
Frame

pub(all) struct Frame {
lines : Array[String]
} derive(Eq,
Debug
)

#
Frame::from_node

fn Frame::from_node(node : Node, size : Size) -> Frame

#
Frame::to_string

fn Frame::to_string(self : Frame) -> String

#
HeadlessOptions

pub(all) struct HeadlessOptions {
size : Size
render : Bool
max_steps : Int
} derive(Eq,
Debug
)

#
HeadlessOptions::default

#
HeadlessOptions::max_steps

fn HeadlessOptions::max_steps(self : HeadlessOptions, value : Int) -> HeadlessOptions

#
HeadlessOptions::render

fn HeadlessOptions::render(self : HeadlessOptions, enabled : Bool) -> HeadlessOptions

#
HeadlessOptions::size

#
InputDecoder

type InputDecoder derive(Eq,
Debug
)

#
Key

pub(all) enum Key {
Char(String)
Enter
Escape
Backspace
Tab
BackTab
Up
Down
Left
Right
Home
End
Delete
PageUp
PageDown
Ctrl(String)
Alt(String)
Modified(Key, KeyModifiers)
Function(Int)
Unknown(String)
} derive(Eq,
Debug
)

#
KeyModifiers

pub(all) struct KeyModifiers {
shift : Bool
alt : Bool
ctrl : Bool
super_key : Bool
hyper : Bool
meta : Bool
} derive(Eq,
Debug
)

#
KeyModifiers::alt

fn KeyModifiers::alt(self : KeyModifiers, enabled : Bool) -> KeyModifiers

#
KeyModifiers::ctrl

fn KeyModifiers::ctrl(self : KeyModifiers, enabled : Bool) -> KeyModifiers

#
KeyModifiers::hyper

fn KeyModifiers::hyper(self : KeyModifiers, enabled : Bool) -> KeyModifiers

#
KeyModifiers::meta

fn KeyModifiers::meta(self : KeyModifiers, enabled : Bool) -> KeyModifiers

#
KeyModifiers::none

#
KeyModifiers::shift

fn KeyModifiers::shift(self : KeyModifiers, enabled : Bool) -> KeyModifiers

#
KeyModifiers::super_key

fn KeyModifiers::super_key(self : KeyModifiers, enabled : Bool) -> KeyModifiers

#
Mouse

pub(all) struct Mouse {
button : MouseButton
action : MouseAction
x : Int
y : Int
} derive(Eq,
Debug
)

#
MouseAction

pub(all) enum MouseAction {
Press
Release
Drag
} derive(Eq,
Debug
)

#
MouseButton

pub(all) enum MouseButton {
Primary
Middle
Secondary
WheelUp
WheelDown
Other(Int)
} derive(Eq,
Debug
)

#
MouseMode

pub(all) enum MouseMode {
MouseOff
MouseCellMotion
MouseButtonMotion
MouseAllMotion
} derive(Eq,
Debug
)

#
Point

pub(all) struct Point {
x : Int
y : Int
} derive(Eq,
Debug
)

#
Program

#alias(Cell)
pub struct Program[Model, Msg] {
model : Model
init_fn : (Emit[Msg], Model) -> Cmd
update_fn : (Emit[Msg], Msg, Model) -> (Cmd, Model)
view_fn : (Model) -> Node
subscriptions_fn : (Model) -> Sub[Msg]
dirty : Bool
}

#
Program::handle_event

fn[Model, Msg] Program::handle_event(self : Program[Model, Msg], emit : Emit[Msg], event : Event) -> Array[Cmd]

#
Program::init

fn[Model, Msg] Program::init(self : Program[Model, Msg], emit : Emit[Msg]) -> Cmd

#
Program::is_dirty

fn[Model, Msg] Program::is_dirty(self : Program[Model, Msg]) -> Bool

#
Program::mark_clean

fn[Model, Msg] Program::mark_clean(self : Program[Model, Msg]) -> Unit

#
Program::mark_dirty

fn[Model, Msg] Program::mark_dirty(self : Program[Model, Msg]) -> Unit

#
Program::model

fn[Model, Msg] Program::model(self : Program[Model, Msg]) -> Model

#
Program::run

async fn[Model, Msg] Program::run(self : Program[Model, Msg]) -> Unit raise TerminalError

#
Program::run_headless

async fn[Model, Msg] Program::run_headless(self : Program[Model, Msg], events? : Array[Event], options? : HeadlessOptions) -> ProgramRunResult[Model]

#
Program::run_returning_model

async fn[Model, Msg] Program::run_returning_model(self : Program[Model, Msg]) -> Model raise TerminalError

#
Program::run_with_cancel_token

async fn[Model, Msg] Program::run_with_cancel_token(self : Program[Model, Msg], options : ProgramOptions, token : CancelToken) -> Model raise TerminalError

#
Program::run_with_options

async fn[Model, Msg] Program::run_with_options(self : Program[Model, Msg], options : ProgramOptions) -> Unit raise TerminalError

#
Program::run_with_options_returning_model

async fn[Model, Msg] Program::run_with_options_returning_model(self : Program[Model, Msg], options : ProgramOptions) -> Model raise TerminalError

#
Program::run_with_timeout

async fn[Model, Msg] Program::run_with_timeout(self : Program[Model, Msg], options : ProgramOptions, milliseconds : Int) -> Model raise TerminalError

#
Program::step

fn[Model, Msg] Program::step(self : Program[Model, Msg], emit : Emit[Msg], msg : Msg) -> Cmd

#
Program::subscriptions

fn[Model, Msg] Program::subscriptions(self : Program[Model, Msg]) -> Sub[Msg]

#
Program::view

fn[Model, Msg] Program::view(self : Program[Model, Msg]) -> Node

#
ProgramOptions

pub(all) struct ProgramOptions {
alternate_screen : Bool
mouse_mode : MouseMode
bracketed_paste : Bool
focus_events : Bool
hide_cursor : Bool
renderer_enabled : Bool
renderer_fps : Int
max_messages_per_frame : Int
resize_poll_millis : Int
} derive(Eq,
Debug
)

#
ProgramOptions::alternate_screen

fn ProgramOptions::alternate_screen(self : ProgramOptions, enabled : Bool) -> ProgramOptions

#
ProgramOptions::bracketed_paste

fn ProgramOptions::bracketed_paste(self : ProgramOptions, enabled : Bool) -> ProgramOptions

#
ProgramOptions::default

#
ProgramOptions::focus_events

fn ProgramOptions::focus_events(self : ProgramOptions, enabled : Bool) -> ProgramOptions

#
ProgramOptions::fps

fn ProgramOptions::fps(self : ProgramOptions, value : Int) -> ProgramOptions

#
ProgramOptions::hide_cursor

fn ProgramOptions::hide_cursor(self : ProgramOptions, enabled : Bool) -> ProgramOptions

#
ProgramOptions::inline

#
ProgramOptions::max_messages_per_frame

fn ProgramOptions::max_messages_per_frame(self : ProgramOptions, value : Int) -> ProgramOptions

#
ProgramOptions::mouse

#
ProgramOptions::renderer

fn ProgramOptions::renderer(self : ProgramOptions, enabled : Bool) -> ProgramOptions

#
ProgramOptions::resize_poll_millis

fn ProgramOptions::resize_poll_millis(self : ProgramOptions, value : Int) -> ProgramOptions

#
ProgramOptions::without_renderer

fn ProgramOptions::without_renderer(self : ProgramOptions) -> ProgramOptions

#
ProgramRunResult

pub(all) struct ProgramRunResult[Model] {
model : Model
terminal_commands : Array[TerminalCommand]
frames : Array[Frame]
quit : Bool
steps : Int
limit_reached : Bool
}

#
Size

pub(all) struct Size {
width : Int
height : Int
} derive(Eq,
Debug
)

#
Style

pub(all) struct Style {
fg : Color
bg : Color
bold : Bool
dim : Bool
italic : Bool
underline : Bool
blink : Bool
reverse : Bool
strikethrough : Bool
width : Int
height : Int
padding : Edge
border : Border
align_horizontal : Align
align_vertical : Align
align_set : Bool
clip_width : Int
clip_height : Int
} derive(Eq,
Debug
)

#
Style::align

fn Style::align(self : Style, horizontal? : Align, vertical? : Align) -> Style

#
Style::bg

fn Style::bg(self : Style, color : Color) -> Style

fn Style::blink(self : Style) -> Style

#
Style::bold

fn Style::bold(self : Style) -> Style

#
Style::border

fn Style::border(self : Style, kind? : Border) -> Style

#
Style::clip

fn Style::clip(self : Style, size : Size) -> Style

#
Style::default

fn Style::default() -> Style

#
Style::dim

fn Style::dim(self : Style) -> Style

#
Style::fg

fn Style::fg(self : Style, color : Color) -> Style

#
Style::height

fn Style::height(self : Style, height : Int) -> Style

#
Style::italic

fn Style::italic(self : Style) -> Style

#
Style::merge

fn Style::merge(self : Style, next : Style) -> Style

#
Style::padding

fn Style::padding(self : Style, edge : Edge) -> Style

#
Style::reverse

fn Style::reverse(self : Style) -> Style

#
Style::size

fn Style::size(self : Style, width? : Int, height? : Int) -> Style

#
Style::strikethrough

fn Style::strikethrough(self : Style) -> Style

#
Style::underline

fn Style::underline(self : Style) -> Style

#
Style::width

fn Style::width(self : Style, width : Int) -> Style

#
Sub

type Sub[Msg]

#
Sub::batch

fn[Msg] Sub::batch(subs : Array[Sub[Msg]]) -> Sub[Msg]

#
Sub::event

fn[Msg] Sub::event(map : (Event) -> Msg) -> Sub[Msg]

#
Sub::focus

fn[Msg] Sub::focus(map : (Event) -> Msg) -> Sub[Msg]

#
Sub::focus_changes

fn[Msg] Sub::focus_changes(map : (Bool) -> Msg) -> Sub[Msg]

#
Sub::keyboard

fn[Msg] Sub::keyboard(map : (Event) -> Msg) -> Sub[Msg]

#
Sub::keys

fn[Msg] Sub::keys(map : (Key) -> Msg) -> Sub[Msg]

#
Sub::map_event

fn[Msg] Sub::map_event(self : Sub[Msg], event : Event) -> Array[Msg]

#
Sub::mouse

fn[Msg] Sub::mouse(map : (Event) -> Msg) -> Sub[Msg]

#
Sub::mouse_events

fn[Msg] Sub::mouse_events(map : (Mouse) -> Msg) -> Sub[Msg]

#
Sub::none

fn[Msg] Sub::none() -> Sub[Msg]

#
Sub::paste

fn[Msg] Sub::paste(map : (String) -> Msg) -> Sub[Msg]

#
Sub::resize

fn[Msg] Sub::resize(map : (Size) -> Msg) -> Sub[Msg]

#
Sub::tick

fn[Msg] Sub::tick(milliseconds : Int, msg : Msg) -> Sub[Msg]

#
TerminalCommand

pub(all) enum TerminalCommand {
QuitProgram
Repaint
Print(String)
PrintLine(String)
PrintErr(String)
PrintErrLine(String)
ClearScreen
EnterAlternateScreen
LeaveAlternateScreen
HideCursor
ShowCursor
EnableMouse(MouseMode)
DisableMouse
EnableBracketedPaste
DisableBracketedPaste
EnableFocusEvents
DisableFocusEvents
SetWindowTitle(String)
} derive(Eq,
Debug
)

#
align

fn align(horizontal? : Align, vertical? : Align, style? : Style, node : Node) -> Node

#
align_line

fn align_line(line : String, width : Int, alignment : Align) -> String

#
ansi_clear_line

fn ansi_clear_line() -> String

#
ansi_clear_screen

fn ansi_clear_screen() -> String

#
ansi_disable_autowrap

fn ansi_disable_autowrap() -> String

#
ansi_disable_bracketed_paste

fn ansi_disable_bracketed_paste() -> String

#
ansi_disable_focus_events

fn ansi_disable_focus_events() -> String

#
ansi_disable_mouse

fn ansi_disable_mouse() -> String

#
ansi_enable_autowrap

fn ansi_enable_autowrap() -> String

#
ansi_enable_bracketed_paste

fn ansi_enable_bracketed_paste() -> String

#
ansi_enable_focus_events

fn ansi_enable_focus_events() -> String

#
ansi_enable_mouse

fn ansi_enable_mouse(mode : MouseMode) -> String

#
ansi_enter_alternate_screen

fn ansi_enter_alternate_screen() -> String

#
ansi_hide_cursor

fn ansi_hide_cursor() -> String

#
ansi_leave_alternate_screen

fn ansi_leave_alternate_screen() -> String

#
ansi_move_cursor

fn ansi_move_cursor(row : Int, column : Int) -> String

#
ansi_move_cursor_up

fn ansi_move_cursor_up(rows : Int) -> String

#
ansi_reset

fn ansi_reset() -> String

#
ansi_set_window_title

fn ansi_set_window_title(title : String) -> String

#
ansi_show_cursor

fn ansi_show_cursor() -> String

#
ansi_start_program

fn ansi_start_program(options : ProgramOptions, node : Node, size : Size) -> String

#
ansi_stop_program

fn ansi_stop_program(options : ProgramOptions) -> String

#
ansi_style

fn ansi_style(style : Style) -> String

#
ansi_terminal_command

fn ansi_terminal_command(command : TerminalCommand) -> String

#
attempt

fn[A, E : Error] attempt(to_cmd : (Result[A, E]) -> Cmd, task : async () -> A raise E) -> Cmd

#
batch

fn batch(cmds : Array[Cmd]) -> Cmd

#
border

fn border(kind? : Border, style? : Style, node : Node) -> Node

#
cell

fn[Model, Msg] cell(model~ : Model, init? : (Emit[Msg], Model) -> Cmd, update~ : (Emit[Msg], Msg, Model) -> (Cmd, Model), view~ : (Model) -> Node, subscriptions? : (Model) -> Sub[Msg]) -> Program[Model, Msg]

#
cell_with_dispatch

#deprecated("Use cell_with_emit instead.")
fn[Model, Msg] cell_with_dispatch(model~ : Model, init? : (Emit[Msg], Model) -> Cmd, update~ : (Emit[Msg], Msg, Model) -> (Cmd, Model), view~ : (Model) -> Node, subscriptions? : (Model) -> Sub[Msg]) -> (Emit[Msg], Program[Model, Msg])

#
cell_with_emit

fn[Model, Msg] cell_with_emit(model~ : Model, init? : (Emit[Msg], Model) -> Cmd, update~ : (Emit[Msg], Msg, Model) -> (Cmd, Model), view~ : (Model) -> Node, subscriptions? : (Model) -> Sub[Msg]) -> (Emit[Msg], Program[Model, Msg])

#
clip

fn clip(size~ : Size, style? : Style, node : Node) -> Node

#
delay

fn delay(cmd : Cmd, milliseconds : Int) -> Cmd

#
diff_ansi

fn diff_ansi(previous : Frame?, next : Frame) -> String

#
diff_node_ansi

fn diff_node_ansi(previous : Frame?, node : Node, size : Size) -> (Frame, String)

#
display_width

fn display_width(value : String) -> Int

#
effect

fn effect(task : async () -> Unit noraise) -> Cmd

#
empty

fn empty(style? : Style) -> Node

#
enter_raw_mode

fn enter_raw_mode() -> Unit raise TerminalError

#
fill

fn fill(style? : Style, value : String) -> Node

#
fit_block

fn fit_block(lines : Array[String], size : Size) -> Array[String]

#
fit_line

fn fit_line(line : String, width : Int) -> String

#
fit_lines

fn fit_lines(lines : Array[String], size : Size) -> Array[String]

#
fragment

fn fragment(style? : Style, children : Array[Node]) -> Node

#
full_render_ansi

fn full_render_ansi(node : Node, size : Size) -> String

#
hstack

fn hstack(gap? : Int, style? : Style, children : Array[Node]) -> Node

#
is_tty

fn is_tty(fd : Int) -> Bool

#
join_horizontal

fn join_horizontal(gap? : Int, style? : Style, children : Array[Node]) -> Node

#
join_vertical

fn join_vertical(gap? : Int, style? : Style, children : Array[Node]) -> Node

#
none

let none : Cmd

#
pad

fn pad(edge~ : Edge, style? : Style, node : Node) -> Node

#
pad_right

fn pad_right(line : String, width : Int) -> String

#
paint_frame_ansi

fn paint_frame_ansi(frame : Frame) -> String

#
parse_input

fn parse_input(bytes : Bytes) -> Array[Event]

#
perform

fn[A] perform(to_cmd : (A) -> Cmd, task : async () -> A noraise) -> Cmd

#
place

fn place(size~ : Size, horizontal? : Align, vertical? : Align, style? : Style, node : Node) -> Node

#
render_ansi

fn render_ansi(node : Node, size : Size) -> String

#
render_plain

fn render_plain(node : Node, size : Size) -> Array[String]

#
repeat_to_width

fn repeat_to_width(value : String, width : Int) -> String

#
restore_terminal

fn restore_terminal() -> Unit

#
simple_cell

fn[Model, Msg] simple_cell(model~ : Model, init? : (Emit[Msg], Model) -> Cmd, update~ : (Msg, Model) -> (Cmd, Model), view~ : (Model) -> Node, subscriptions? : (Model) -> Sub[Msg]) -> Program[Model, Msg]

#
sized

fn sized(width? : Int, height? : Int, style? : Style, node : Node) -> Node

#
take_width

fn take_width(value : String, width : Int) -> String

#
terminal_size

fn terminal_size() -> Size raise TerminalError

#
text

fn text(style? : Style, value : String) -> Node

#
vstack

fn vstack(gap? : Int, style? : Style, children : Array[Node]) -> Node

#
wrap_text

fn wrap_text(value : String, width : Int) -> Array[String]