Skip to content

Terminal Widgets (require feature terminal)

toml
tui-lipan = { version = "*", features = ["terminal"] }

ManagedTerminal

The recommended starting point: a complete PTY terminal with automatic lifecycle management. No manual wiring needed.

PropTypeDescription
configTerminalPtyConfigShell/cwd/env configuration
scrollbackusizeScrollback buffer size in lines (default: 2000)
initial_colsu16Initial columns (default: 120)
initial_rowsu16Initial rows (default: 24)
auto_startboolStart PTY on init (default: true)
placeholderOption<Arc<str>>Text shown before PTY is ready
forward_mouseboolForward mouse events to PTY (default: true)
scroll_wheelboolMouse wheel for scrollback (default: true)
styleStyleTerminal content style
focusableboolAccept focus (default: true)
widthLengthWidth (default: Flex(1))
heightLengthHeight (default: Flex(1))
on_statusCallback<ManagedTerminalStatus>Status change callback
rust
use tui_lipan::prelude::*;

// Simple usage - starts shell in current directory
ManagedTerminal::new()
    .on_status(ctx.link().callback(Msg::TerminalStatus))

// Custom shell and working directory
ManagedTerminal::new()
    .config(
        TerminalPtyConfig::default()
            .shell("/bin/bash")
            .cwd("/home/user/projects")
            .env("MY_VAR", "value")
    )
    .scrollback(5000)
    .initial_size(120, 40)
    .on_status(ctx.link().callback(Msg::TerminalStatus))

Status events (ManagedTerminalStatus):

VariantMeaning
StartingPTY is being initialized
ReadyPTY is ready and accepting input
Exited(i32)Shell exited with status code
Error(Arc<str>)Error occurred
rust
match status {
    ManagedTerminalStatus::Ready => ctx.state.terminal_ready = true,
    ManagedTerminalStatus::Exited(code) => { /* handle exit */ }
    ManagedTerminalStatus::Error(msg) => { /* handle error */ }
    _ => {}
}

Terminal (Low-Level)

The low-level terminal viewport widget. Use when you need custom PTY handling, multiple terminals, or specialized input routing.

PropTypeDescription
snapshotTerminalRenderSnapshotCurrent screen snapshot
styleStyleContainer style
focusableboolAccept focus
scroll_wheelboolMouse wheel scrollback
selection_styleStyleText selection style
selectionOption<TerminalSelection>Controlled selection
borderboolShow border
border_styleBorderStyleBorder style
paddingimpl Into<Padding>Padding
widthLengthWidth
heightLengthHeight
on_inputCallback<TerminalInputEvent>Keyboard/paste input from user
on_resizeCallback<TerminalViewport>Viewport size changed
on_scroll_toCallback<usize>Scrollback offset changed
on_mouse_forwardCallback<Vec<u8>>Mouse event bytes for PTY
on_selectionCallback<TerminalSelection>Selection changed
on_keyKeyHandlerLow-level key handler

Use scrollbar_config to configure layout variant, gap, and thumb for the vertical scrollbar.


TerminalPty

PTY spawner and I/O bridge. Used internally by ManagedTerminal.

rust
use tui_lipan::prelude::*;

let config = TerminalPtyConfig::default()
    .shell("/bin/zsh")
    .cwd("/home/user")
    .env("TERM", "xterm-256color");

// Spawn the PTY with an event callback
let pty = TerminalPty::spawn(config, move |event| {
    match event {
        TerminalPtyEvent::Output(bytes) => link.send(Msg::Output(bytes)),
        TerminalPtyEvent::Exited(code) => link.send(Msg::Exited(code)),
        TerminalPtyEvent::Error(msg) => link.send(Msg::Error(msg)),
    }
})?;

// Send input to the PTY
pty.write(b"ls -la\r")?;

// Resize the PTY
pty.resize(cols, rows)?;

PTY env defaults: TERM=xterm-256color, COLORTERM=truecolor (overridable via .env(...)).


TerminalScreen

VT100/VT220 screen emulator (wraps alacritty_terminal). Maintains scrollback buffer.

rust
// Create a screen with given dimensions and scrollback
let mut screen = TerminalScreen::new(rows, cols, scrollback_lines);

// Process PTY output
screen.process_bytes(&bytes);

// Drain terminal responses (e.g., device queries from TUI apps like fzf)
for response in screen.drain_responses() {
    pty.write(&response)?;
}

// Get a render snapshot for the Terminal widget
let snapshot = screen.render_snapshot();

// Scrollback control
screen.set_scrollback(offset);    // 0 = live view, >0 = history
screen.scrollback_offset()        // Current offset
screen.total_scrollback_rows()    // Total scrollback rows available
screen.resize(new_rows, new_cols) // Resize the terminal

TerminalRenderSnapshot fields:

FieldTypeDescription
textVec<Vec<char>>Screen text grid
color_linesVec<Vec<CellStyle>>Per-cell styles
cursor(u16, u16)Cursor position (col, row)
cursor_visibleboolWhether cursor is shown
scrollback_offsetusizeCurrent scrollback offset
total_scrollback_rowsusizeTotal history rows
mouse_modeboolPTY has mouse tracking enabled

Manual Wiring Pattern

For advanced use cases (multiple terminals, custom input routing):

rust
pub struct MyState {
    screen: TerminalScreen,
    snapshot: TerminalRenderSnapshot,
    pty: Option<TerminalPty>,
    cols: u16,
    rows: u16,
}

// In update():
Msg::PtyOutput(bytes) => {
    ctx.state.screen.process_bytes(&bytes);
    // Forward device query responses back to PTY
    if let Some(pty) = &ctx.state.pty {
        for response in ctx.state.screen.drain_responses() {
            let _ = pty.write(&response);
        }
    }
    ctx.state.snapshot = ctx.state.screen.render_snapshot();
    Update::full()
}
Msg::TerminalInput(input) => {
    if let Some(pty) = &ctx.state.pty {
        let _ = pty.write(&input.bytes);
        // Snap to live view when user types
        if ctx.state.screen.scrollback_offset() > 0 {
            ctx.state.screen.set_scrollback(0);
            ctx.state.snapshot = ctx.state.screen.render_snapshot();
            return Update::full();
        }
    }
    Update::none()
}
Msg::Resize { cols, rows } => {
    ctx.state.cols = cols;
    ctx.state.rows = rows;
    if let Some(pty) = &ctx.state.pty {
        let _ = pty.resize(cols, rows);
    }
    ctx.state.screen.resize(rows, cols);
    ctx.state.snapshot = ctx.state.screen.render_snapshot();
    Update::full()
}

// In view():
Terminal::new()
    .snapshot(ctx.state.snapshot.clone())
    .focusable(true)
    .scroll_wheel(true)
    .on_input(ctx.link().callback(Msg::TerminalInput))
    .on_resize(ctx.link().callback(|v: TerminalViewport| Msg::Resize {
        cols: v.cols, rows: v.rows
    }))
    .on_scroll_to(ctx.link().callback(Msg::ScrollTo))
    .on_mouse_forward(ctx.link().callback(Msg::MouseForward))
    .into()

Scrollback

Mouse wheel scrolls through scrollback history when scroll_wheel(true) (default in ManagedTerminal).

Use on_scroll_to to receive the new offset and call screen.set_scrollback(offset).

rust
Msg::ScrollTo(offset) => {
    ctx.state.screen.set_scrollback(offset);
    ctx.state.snapshot = ctx.state.screen.render_snapshot();
    Update::full()
}

The cursor is hidden while scrolled into history. Typing input snaps back to live view (set scrollback to 0).

TerminalScreen::process_bytes() automatically preserves the user's scrollback position when new output arrives while scrolled up - the offset is adjusted for newly added rows.

MIT OR Apache-2.0