///|
/// Command constructors for the Pippa TUI framework.
///|
pub fn[Msg] Cmd::none() -> Cmd[Msg] {
{ _phantom: [], actions: [] }
}
///|
/// Enqueue a message to be delivered immediately by the runtime.
pub fn[Msg] Cmd::msg(msg : Msg) -> Cmd[Msg] {
Cmd::perform(fn() { Some(msg) })
}
///|
/// Ask the runtime to quit cleanly.
pub fn[Msg] Cmd::quit() -> Cmd[Msg] {
{ _phantom: [], actions: [LifecycleAction(Quit)] }
}
///|
/// Ask the runtime to stop as interrupted.
pub fn[Msg] Cmd::interrupt() -> Cmd[Msg] {
{ _phantom: [], actions: [LifecycleAction(Interrupt)] }
}
///|
/// Ask the runtime to release and then re-acquire the terminal.
pub fn[Msg] Cmd::suspend() -> Cmd[Msg] {
{ _phantom: [], actions: [LifecycleAction(Suspend)] }
}
///|
/// Ask the runtime to deliver a resume lifecycle message.
pub fn[Msg] Cmd::resume_program() -> Cmd[Msg] {
{ _phantom: [], actions: [LifecycleAction(Resume)] }
}
///|
/// Stop the runtime with a typed runtime error result.
pub fn[Msg] Cmd::runtime_error(message : String) -> Cmd[Msg] {
{ _phantom: [], actions: [RuntimeErrorAction(message)] }
}
///|
/// Print unmanaged text above the program in inline terminal mode.
///
/// The printed text is not part of the managed view and may persist in terminal
/// scrollback across later renders. The runtime prints each newline-delimited
/// line on its own terminal line, then repaints the current view below it. When
/// the alternate screen is active the request is discarded, matching Bubble Tea's
/// `Println` / `Printf` behavior.
pub fn[Msg] Cmd::print_above(message : String) -> Cmd[Msg] {
{
_phantom: [],
actions: [
Perform(fn() {
runtime_enqueue_print_above({ message_body: message })
None
}),
],
}
}
///|
/// Print unmanaged text above the program in inline terminal mode.
///
/// This is the Pippa equivalent of Bubble Tea's `Println`: unlike writing from a
/// view, the line is outside the managed render area and is ignored while the
/// alternate screen is active.
pub fn[Msg] Cmd::println(message : String) -> Cmd[Msg] {
Cmd::print_above(message)
}
///|
fn print_above_format(template : String, args : Array[String]) -> String {
let buf = StringBuilder::new(size_hint=template.length())
let chars : Array[Char] = []
for ch in template {
chars.push(ch)
}
let mut arg_index = 0
let mut i = 0
while i < chars.length() {
let ch = chars[i]
if ch == '%' && i + 1 < chars.length() {
let next = chars[i + 1]
if next == '%' {
buf.write_char('%')
i = i + 2
continue
} else if next == 's' {
if arg_index < args.length() {
buf.write_string(args[arg_index])
arg_index = arg_index + 1
} else {
buf.write_string("%s")
}
i = i + 2
continue
}
}
buf.write_char(ch)
i = i + 1
}
buf.to_string()
}
///|
/// Format unmanaged text and print it above the program in inline terminal mode.
///
/// Pippa accepts pre-rendered string arguments and replaces `%s` placeholders in
/// `template` from left to right; `%%` emits a literal percent sign. The result is
/// then handled like `Cmd::println`, including the alt-screen no-op behavior.
pub fn[Msg] Cmd::printf(
template : String,
args? : Array[String] = [],
) -> Cmd[Msg] {
Cmd::print_above(print_above_format(template, args))
}
///|
/// Run an external process while the runtime releases the terminal.
pub fn[Msg] Cmd::exec_process(
process : ExecProcess,
to_msg : (ExecResult) -> Msg,
) -> Cmd[Msg] {
{ _phantom: [], actions: [ExecProcessAction(process, to_msg)] }
}
///|
/// Execute a runtime effect immediately after the current update cycle.
///
/// The runtime runs `f` on the event loop thread and feeds the resulting
/// message back into `update` when `Some(msg)` is returned.
pub fn[Msg] Cmd::perform(f : () -> Msg?) -> Cmd[Msg] {
{ _phantom: [], actions: [Perform(f)] }
}
///|
/// Schedule a one-shot tick — runtime calls `on_tick` when the timer fires.
///
/// Prefer `Cmd::after` when the timer should directly produce a specific
/// message. `Cmd::tick` is mainly for app-wide compatibility with `Program`'s
/// `on_tick` hook.
pub fn[Msg] Cmd::tick(interval_ms : Int) -> Cmd[Msg] {
if interval_ms > 0 {
{ _phantom: [], actions: [Tick(interval_ms)] }
} else {
{ _phantom: [], actions: [] }
}
}
///|
/// Schedule `f()` after `interval_ms` milliseconds.
/// The runtime evaluates `f` when the timer fires, not when the command is
/// created.
pub fn[Msg] Cmd::after(interval_ms : Int, f : () -> Msg) -> Cmd[Msg] {
let ms = if interval_ms > 0 { interval_ms } else { 1 }
{ _phantom: [], actions: [After(ms, f)] }
}
///|
/// Schedule `f` once on the next aligned interval boundary.
///
/// Alignment is measured against the runtime's monotonic millisecond clock:
/// the deadline is the next strict multiple of `interval_ms`. The callback
/// receives a typed elapsed-time payload, and apps that want recurrence should
/// return another `Cmd::every` from `update`.
pub fn[Msg] Cmd::every(interval_ms : Int, f : (TimerTick) -> Msg) -> Cmd[Msg] {
let ms = if interval_ms > 0 { interval_ms } else { 1 }
{ _phantom: [], actions: [Every(ms, f)] }
}
///|
/// Enter the terminal alternate screen buffer at runtime.
pub fn[Msg] Cmd::enter_alt_screen() -> Cmd[Msg] {
{ _phantom: [], actions: [EnterAltScreen] }
}
///|
/// Exit the terminal alternate screen buffer at runtime.
pub fn[Msg] Cmd::exit_alt_screen() -> Cmd[Msg] {
{ _phantom: [], actions: [ExitAltScreen] }
}
///|
/// Clear the screen and move the cursor to the home position.
pub fn[Msg] Cmd::clear_screen() -> Cmd[Msg] {
{ _phantom: [], actions: [ClearScreen] }
}
///|
/// Invalidate the renderer cache so the next render fully repaints.
pub fn[Msg] Cmd::repaint() -> Cmd[Msg] {
{ _phantom: [], actions: [Repaint] }
}
///|
/// Invalidate the renderer cache so the next render fully repaints.
pub fn[Msg] Cmd::force_redraw() -> Cmd[Msg] {
{ _phantom: [], actions: [ForceRedraw] }
}
///|
/// Hide the terminal cursor at runtime.
pub fn[Msg] Cmd::hide_cursor() -> Cmd[Msg] {
{ _phantom: [], actions: [HideCursor] }
}
///|
/// Show the terminal cursor at runtime.
pub fn[Msg] Cmd::show_cursor() -> Cmd[Msg] {
{ _phantom: [], actions: [ShowCursor] }
}
///|
/// Ask the runtime to deliver the current terminal size as a typed message.
pub fn[Msg] Cmd::request_window_size(f : (WindowSize) -> Msg) -> Cmd[Msg] {
{ _phantom: [], actions: [RequestWindowSize(f)] }
}
///|
/// Request primary device attributes (DA1) from the terminal.
pub fn[Msg] Cmd::request_device_attributes() -> Cmd[Msg] {
{ _phantom: [], actions: [RequestDeviceAttributes] }
}
///|
/// Request secondary device attributes / terminal version (DA2).
pub fn[Msg] Cmd::request_secondary_device_attributes() -> Cmd[Msg] {
{ _phantom: [], actions: [RequestSecondaryDeviceAttributes] }
}
///|
/// Request the current cursor position (DSR CPR) from the terminal.
pub fn[Msg] Cmd::request_cursor_position() -> Cmd[Msg] {
{ _phantom: [], actions: [RequestCursorPosition] }
}
///|
/// Request the current default foreground color (OSC 10).
pub fn[Msg] Cmd::request_foreground_color() -> Cmd[Msg] {
{ _phantom: [], actions: [RequestForegroundColor] }
}
///|
/// Request the current default background color (OSC 11).
pub fn[Msg] Cmd::request_background_color() -> Cmd[Msg] {
{ _phantom: [], actions: [RequestBackgroundColor] }
}
///|
/// Request a terminal capability with XTGETTCAP.
pub fn[Msg] Cmd::request_capability(name : String) -> Cmd[Msg] {
{ _phantom: [], actions: [RequestCapability(name)] }
}
///|
/// Request clipboard contents with OSC 52.
pub fn[Msg] Cmd::request_clipboard(selection : ClipboardSelection) -> Cmd[Msg] {
{ _phantom: [], actions: [RequestClipboard(selection)] }
}
///|
/// Set system clipboard contents with OSC 52.
pub fn[Msg] Cmd::set_clipboard(text : String) -> Cmd[Msg] {
Cmd::set_clipboard_for(SystemClipboard, text)
}
///|
/// Set clipboard contents for an OSC 52 selection.
pub fn[Msg] Cmd::set_clipboard_for(
selection : ClipboardSelection,
text : String,
) -> Cmd[Msg] {
{ _phantom: [], actions: [SetClipboard(selection, text)] }
}
///|
/// Enable terminal focus reporting at runtime.
pub fn[Msg] Cmd::enable_focus_reporting() -> Cmd[Msg] {
{ _phantom: [], actions: [SetFocusReporting(true)] }
}
///|
/// Disable terminal focus reporting at runtime.
pub fn[Msg] Cmd::disable_focus_reporting() -> Cmd[Msg] {
{ _phantom: [], actions: [SetFocusReporting(false)] }
}
///|
/// Enable terminal bracketed paste mode at runtime.
pub fn[Msg] Cmd::enable_bracketed_paste() -> Cmd[Msg] {
{ _phantom: [], actions: [SetBracketedPaste(true)] }
}
///|
/// Disable terminal bracketed paste mode at runtime.
pub fn[Msg] Cmd::disable_bracketed_paste() -> Cmd[Msg] {
{ _phantom: [], actions: [SetBracketedPaste(false)] }
}
///|
/// Set the terminal window title at runtime.
pub fn[Msg] Cmd::set_window_title(title : String) -> Cmd[Msg] {
{ _phantom: [], actions: [SetWindowTitle(title)] }
}
///|
/// Set the real terminal cursor shape/blink style at runtime.
pub fn[Msg] Cmd::set_cursor_style(style : CursorStyle) -> Cmd[Msg] {
{ _phantom: [], actions: [SetCursorStyle(style)] }
}
///|
/// Clear the terminal window title at runtime.
pub fn[Msg] Cmd::clear_window_title() -> Cmd[Msg] {
Cmd::set_window_title("")
}
///|
/// Enable terminal mouse press, release, wheel, and button-held drag reporting at runtime.
pub fn[Msg] Cmd::enable_mouse_cell_motion() -> Cmd[Msg] {
{ _phantom: [], actions: [SetMouseMode(ViewMouseCellMotion)] }
}
///|
/// Enable all terminal mouse motion reporting at runtime.
pub fn[Msg] Cmd::enable_mouse_all_motion() -> Cmd[Msg] {
{ _phantom: [], actions: [SetMouseMode(ViewMouseAllMotion)] }
}
///|
/// Disable terminal mouse reporting at runtime.
pub fn[Msg] Cmd::disable_mouse() -> Cmd[Msg] {
{ _phantom: [], actions: [SetMouseMode(ViewMouseOff)] }
}
///|
/// Run commands in order.
///
/// Each command starts only after the prior command's immediate effects have
/// been drained by the runtime. Delayed timers from earlier steps continue to
/// run independently once scheduled.
pub fn[Msg] Cmd::sequence(cmds : Array[Cmd[Msg]]) -> Cmd[Msg] {
let non_empty = cmds.filter(fn(cmd) { !cmd.is_none() })
match non_empty {
[] => Cmd::none()
[cmd] => cmd
_ => { _phantom: [], actions: [Sequence(non_empty)] }
}
}
///|
/// Batch multiple commands so each effect is enqueued independently.
pub fn[Msg] Cmd::batch(cmds : Array[Cmd[Msg]]) -> Cmd[Msg] {
let actions = cmds.iter().flat_map(fn(cmd) { cmd.actions.iter() }).to_array()
{ _phantom: [], actions }
}
///|
/// Returns true when the command carries no actions (equivalent to `Cmd::none()`).
pub fn[Msg] Cmd::is_none(self : Cmd[Msg]) -> Bool {
self.actions.is_empty()
}
///|
/// Map the message type of a command, transforming runtime-produced messages.
pub fn[A, B] Cmd::map(f : (A) -> B, cmd : Cmd[A]) -> Cmd[B] {
let actions : Array[CmdAction[B]] = []
for action in cmd.actions {
match action {
Perform(task) => actions.push(Perform(fn() { task().map(f) }))
Tick(interval_ms) => actions.push(Tick(interval_ms))
After(interval_ms, task) =>
actions.push(After(interval_ms, fn() { f(task()) }))
Every(interval_ms, task) =>
actions.push(Every(interval_ms, fn(tick) { f(task(tick)) }))
EnterAltScreen => actions.push(EnterAltScreen)
ExitAltScreen => actions.push(ExitAltScreen)
ClearScreen => actions.push(ClearScreen)
Repaint => actions.push(Repaint)
ForceRedraw => actions.push(ForceRedraw)
HideCursor => actions.push(HideCursor)
ShowCursor => actions.push(ShowCursor)
RequestWindowSize(task) =>
actions.push(RequestWindowSize(fn(size) { f(task(size)) }))
RequestDeviceAttributes => actions.push(RequestDeviceAttributes)
RequestSecondaryDeviceAttributes =>
actions.push(RequestSecondaryDeviceAttributes)
RequestCursorPosition => actions.push(RequestCursorPosition)
RequestForegroundColor => actions.push(RequestForegroundColor)
RequestBackgroundColor => actions.push(RequestBackgroundColor)
RequestCapability(name) => actions.push(RequestCapability(name))
RequestClipboard(selection) => actions.push(RequestClipboard(selection))
SetClipboard(selection, text) =>
actions.push(SetClipboard(selection, text))
SetFocusReporting(enabled) => actions.push(SetFocusReporting(enabled))
SetBracketedPaste(enabled) => actions.push(SetBracketedPaste(enabled))
SetMouseMode(mode) => actions.push(SetMouseMode(mode))
SetWindowTitle(title) => actions.push(SetWindowTitle(title))
SetCursorStyle(style) => actions.push(SetCursorStyle(style))
LifecycleAction(msg) => actions.push(LifecycleAction(msg))
ExecProcessAction(process, task) =>
actions.push(ExecProcessAction(process, fn(result) { f(task(result)) }))
RuntimeErrorAction(message) => actions.push(RuntimeErrorAction(message))
Sequence(cmds) =>
actions.push(Sequence(cmds.map(fn(inner) { Cmd::map(f, inner) })))
}
}
{ _phantom: [], actions }
}