TermUI.Runtime (TermUI v1.0.0)
View SourceThe central runtime orchestrator for TermUI applications.
The runtime implements The Elm Architecture dispatch loop:
- Receive event from terminal
- Deliver it to the single root Elm module
- Call the root's event_to_msg
- Call the root's update with the resulting message
- Collect commands from update
- Mark component dirty
- On render timer, call view and render
The components field in runtime state currently contains only the reserved
:root entry. TermUI.ComponentServer, TermUI.EventRouter, and the other
process-oriented component services are separate lower-level APIs; they are
not mounted or consulted by this runtime loop.
Usage
# Start with a root component
{:ok, runtime} = Runtime.start_link(root: MyApp.Root)
# Send events (usually from terminal input)
Runtime.send_event(runtime, Event.key(:enter))
# Shutdown gracefully
Runtime.shutdown(runtime)
Summary
Functions
Gets the current backend mode.
Gets the detected terminal capabilities.
Returns a child specification for starting the runtime in a supervisor.
Delivers a command result back to the runtime.
Marks the runtime dirty and performs an immediate render.
Gets the current runtime state (for testing/debugging).
Starts the runtime and blocks until it shuts down.
Sends an event to the runtime for processing.
Sends a message to a runtime component ID.
Initiates graceful shutdown of the runtime.
Starts the runtime with the given options.
Synchronously waits for all pending events and messages to be processed.
Types
@type option() :: {:root, module()} | {:name, GenServer.name()} | {:render_interval, pos_integer()} | {:backend, :auto | :raw | :tty | module() | {module(), keyword()}} | {:skip_terminal, boolean()} | {:use_input_handler, boolean()} | {:iex_mode, boolean()}
Functions
@spec backend_mode() :: TermUI.Runtime.State.backend_mode()
Gets the current backend mode.
Returns the node-global local backend context: :raw, :tty, :skip, or
nil. Explicit custom/SSH runtimes do not publish a mode here; query their
host/session state instead.
Examples
:raw = Runtime.backend_mode()
:tty = Runtime.backend_mode()
@spec capabilities() :: TermUI.Runtime.State.capabilities() | nil
Gets the detected terminal capabilities.
Returns a map with keys:
:colors- Color depth (:true_color,:color_256,:color_16,:monochrome):unicode- Boolean indicating Unicode support:dimensions-{rows, cols}tuple ornil:terminal- Boolean indicating terminal presence
Returns the node-global local Raw/TTY capability map, or nil when no local
context is published. Explicit custom/SSH runtimes keep capabilities in
their session/backend state and do not publish them here.
Examples
%{colors: :true_color, unicode: true} = Runtime.capabilities()
@spec child_spec([option()]) :: Supervisor.child_spec()
Returns a child specification for starting the runtime in a supervisor.
Options
Same as start_link/1:
:root- The root component module (required):name- GenServer name (optional):render_interval- Milliseconds between renders (default: 16):backend-:auto,:raw,:tty, a backend module, or{backend_module, backend_opts}:skip_terminal- Skip terminal initialization (default: false):use_input_handler- Use theTermUI.Inputhandler path (default: false)
Examples
children = [
{TermUI.Runtime, root: MyApp.Root, name: :my_runtime}
]
Supervisor.start_link(children, strategy: :one_for_one)
@spec command_result(GenServer.server(), term(), term(), term()) :: :ok
Delivers a command result back to the runtime.
@spec force_render(GenServer.server()) :: :ok
Marks the runtime dirty and performs an immediate render.
@spec get_state(GenServer.server()) :: TermUI.Runtime.State.t()
Gets the current runtime state (for testing/debugging).
Starts the runtime and blocks until it shuts down.
This is the main entry point for running a TUI application. It starts the runtime, takes over the terminal, and blocks the calling process until the application exits (e.g., user presses quit key).
Options
Same as start_link/1.
Example
# In your application entry point:
TermUI.Runtime.run(root: MyApp.Root)
# This blocks until the app exits
@spec send_event(GenServer.server(), TermUI.Event.t()) :: :ok
Sends an event to the runtime for processing.
@spec send_message(GenServer.server(), term(), term()) :: :ok
Sends a message to a runtime component ID.
In the 1.0 runtime, only :root is registered. Messages sent to any other ID
are ignored.
@spec shutdown(GenServer.server()) :: :ok
Initiates graceful shutdown of the runtime.
@spec start_link([option()]) :: GenServer.on_start()
Starts the runtime with the given options.
Options
:root- The root component module (required):name- GenServer name (optional):render_interval- Milliseconds between renders (default: 16):backend-:auto(default),:raw,:tty, a backend module, or{backend_module, backend_opts}:skip_terminal- Skip terminal initialization (default: false, for testing):use_input_handler- Use theTermUI.Inputhandler path instead of the legacy local input reader (default: false)
Backend Selection
The :backend option controls which terminal backend is used:
:auto(default) - Attempts raw mode first, falls back to TTY if unavailable:raw- Forces raw mode (requires OTP 28+, errors if unavailable):tty- Forces TTY mode (line-based input, no raw mode attempt)
When the caller is an IEx evaluator, :auto resolves directly to :tty so
the existing shell keeps ownership of the terminal. An explicit :raw or
custom backend selection is preserved.
Examples
# Auto-detect backend (default behavior)
{:ok, runtime} = Runtime.start_link(root: MyApp.Root)
# Force TTY mode
{:ok, runtime} = Runtime.start_link(root: MyApp.Root, backend: :tty)
# Query backend mode at runtime
:raw = Runtime.backend_mode()
# Query capabilities (useful for TTY mode)
%{colors: :true_color, unicode: true} = Runtime.capabilities()
@spec sync(GenServer.server(), timeout()) :: :ok
Synchronously waits for all pending events and messages to be processed.
This is primarily useful for testing to avoid race conditions from Process.sleep. It processes all queued messages and returns when complete.
Example
Runtime.send_event(runtime, Event.key(:up))
Runtime.send_event(runtime, Event.key(:up))
Runtime.sync(runtime) # Wait for both events to be processed
state = Runtime.get_state(runtime)
assert state.root_state.count == 2