rabbita_tui

    Native terminal UI toolkit built around The Elm Architecture

    tui
    terminal
    tea
    native
    cli
    Download zip
    Version
    0.1.2
    License
    Apache-2.0
    Last updated
    3 days ago
    Downloads
    360

    Dependencies

    #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 an executable, resume
    Cmd::exec_process_with_argsrun an executable with argv-style arguments
    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",
    code => emit(ProcessExited(code)),
    )
    Cmd::exec_process_with_args(
    "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)
    let result = app.run_with_timeout_result(options, 5000)

    CancelToken is useful when an outer task owns cancellation:

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

    Use the *_result helpers when the caller needs to distinguish ProgramQuit, ProgramCancelled, and ProgramTimedOut.

    #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::exec_process_with_args

    fn Cmd::exec_process_with_args(command : String, args : Array[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_cancel_token_result

    async fn[Model, Msg] Program::run_with_cancel_token_result(self : Program[Model, Msg], options : ProgramOptions, token : CancelToken) -> ProgramRuntimeResult[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_result

    async fn[Model, Msg] Program::run_with_options_result(self : Program[Model, Msg], options : ProgramOptions) -> ProgramRuntimeResult[Model] 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::run_with_timeout_result

    async fn[Model, Msg] Program::run_with_timeout_result(self : Program[Model, Msg], options : ProgramOptions, milliseconds : Int) -> ProgramRuntimeResult[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

    ProgramExit

    pub(all) enum ProgramExit {
    ProgramQuit
    ProgramCancelled
    ProgramTimedOut
    } derive(Eq,
    Debug
    )

    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
    }

    ProgramRuntimeResult

    pub(all) struct ProgramRuntimeResult[Model] {
    model : Model
    exit : ProgramExit
    } derive(Eq,
    Debug
    )

    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]