Terminal Widgets (require feature terminal)
tui-lipan = { version = "*", features = ["terminal"] }ManagedTerminal
The recommended starting point: a complete PTY terminal with automatic lifecycle management. No manual wiring needed.
| Prop | Type | Description |
|---|---|---|
config | TerminalPtyConfig | Shell/cwd/env configuration |
scrollback | usize | Scrollback buffer size in lines (default: 2000) |
initial_cols | u16 | Initial columns (default: 120) |
initial_rows | u16 | Initial rows (default: 24) |
auto_start | bool | Start PTY on init (default: true) |
placeholder | Option<Arc<str>> | Text shown before PTY is ready |
forward_mouse | bool | Forward mouse events to PTY (default: true) |
scroll_wheel | bool | Mouse wheel for scrollback (default: true) |
style | Style | Terminal content style |
focusable | bool | Accept focus (default: true) |
width | Length | Width (default: Flex(1)) |
height | Length | Height (default: Flex(1)) |
on_status | Callback<ManagedTerminalStatus> | Status change callback |
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):
| Variant | Meaning |
|---|---|
Starting | PTY is being initialized |
Ready | PTY is ready and accepting input |
Exited(i32) | Shell exited with status code |
Error(Arc<str>) | Error occurred |
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.
| Prop | Type | Description |
|---|---|---|
snapshot | TerminalRenderSnapshot | Current screen snapshot |
style | Style | Container style |
focusable | bool | Accept focus |
scroll_wheel | bool | Mouse wheel scrollback |
selection_style | Style | Text selection style |
selection | Option<TerminalSelection> | Controlled selection |
border | bool | Show border |
border_style | BorderStyle | Border style |
padding | impl Into<Padding> | Padding |
width | Length | Width |
height | Length | Height |
on_input | Callback<TerminalInputEvent> | Keyboard/paste input from user |
on_resize | Callback<TerminalViewport> | Viewport size changed |
on_scroll_to | Callback<usize> | Scrollback offset changed |
on_mouse_forward | Callback<Vec<u8>> | Mouse event bytes for PTY |
on_selection | Callback<TerminalSelection> | Selection changed |
on_key | KeyHandler | Low-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.
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.
// 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 terminalTerminalRenderSnapshot fields:
| Field | Type | Description |
|---|---|---|
text | Vec<Vec<char>> | Screen text grid |
color_lines | Vec<Vec<CellStyle>> | Per-cell styles |
cursor | (u16, u16) | Cursor position (col, row) |
cursor_visible | bool | Whether cursor is shown |
scrollback_offset | usize | Current scrollback offset |
total_scrollback_rows | usize | Total history rows |
mouse_mode | bool | PTY has mouse tracking enabled |
Manual Wiring Pattern
For advanced use cases (multiple terminals, custom input routing):
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).
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.