Skip to main content

TerminalSession

Struct TerminalSession 

Source
pub struct TerminalSession {
    pub selection: Option<TerminalSelection>,
    /* private fields */
}
Available on crate feature terminal only.
Expand description

A single PTY-backed terminal session: PTY process, reader thread, vt100 parser, and scrollback ring buffer.

Call poll each frame tick to drain PTY output, then to_terminal to build a paint snapshot.

§Scrollback model

The vt100 parser is kept at scrollback = 0 (live view) at all times. Lines that scroll off the live screen are captured into an internal VecDeque ring. Calling scroll_up / scroll_down adjusts scroll_offset; the snapshot builder blends history rows and live rows accordingly.

Fields§

§selection: Option<TerminalSelection>

Mouse text selection, if any.

Implementations§

Source§

impl TerminalSession

Source

pub fn spawn( cols: u16, rows: u16, shell: &str, cwd: &Path, history_capacity: usize, ) -> Result<Self, Box<dyn Error>>

Spawn a new interactive shell session.

  • cols, rows — initial PTY dimensions.
  • shell — shell binary path (e.g. "/bin/bash"). Use default_shell to pick a sensible default — it reads $SHELL on Unix, and always returns "powershell.exe" on Windows.
  • cwd — working directory for the shell process.
  • history_capacity — maximum scrollback lines to retain. 0 means unlimited (use a large finite value for production).
Source

pub fn poll(&mut self) -> bool

Drain pending PTY output and feed it to the vt100 parser.

Lines that scroll off the live screen are captured into the scrollback ring buffer. Also polls child-process exit status.

Returns true when any new data was processed — the caller should trigger a repaint.

Source

pub fn write_input(&mut self, data: &[u8])

Send raw bytes as keyboard input to the shell.

The bytes are written directly to the PTY master write-end. Callers are responsible for encoding key presses into the appropriate escape sequences (see the key_to_pty_bytes helper in the tui_terminal example).

Source

pub fn send_str(&mut self, s: &str)

Send a UTF-8 string as input to the shell.

Convenience wrapper around write_input. A supervising process uses this to inject prompts programmatically.

Source

pub fn cols(&self) -> u16

Current terminal width in columns.

Source

pub fn rows(&self) -> u16

Current terminal height in rows.

Source

pub fn is_exited(&self) -> bool

true once the child process has exited.

Use exit_code() for the numeric exit status.

Source

pub fn scroll_offset(&self) -> usize

Current scroll offset (rows above the live bottom).

0 = live view; maximum = history_len(). Change via set_scroll_offset() / scroll_up() / scroll_down().

Source

pub fn exit_code(&self) -> Option<u32>

Exit code of the child process, or None while still running.

0 conventionally means success. Populated by the first poll call that observes the child exiting. Check is_exited() first if you only need a boolean; this method returns the actual numeric code.

Source

pub fn title(&self) -> Option<&str>

The most recent window title set by the child program via an OSC 0 or OSC 2 escape sequence (e.g. vim, tmux, or a shell’s own prompt hook), or None if it has never set one.

Source

pub fn take_title_changed(&mut self) -> bool

true exactly once per title change: returns whether title() has changed since the last call to this method, clearing the dirty flag as a side effect.

Call this once per poll()-driven frame; a caller (e.g. the GTK runner) can use it to cheaply gate a native window title update instead of setting it on every frame.

Source

pub fn cursor_visible(&self) -> bool

Returns true when the cursor should be rendered visible.

The cursor is suppressed when:

  • The child process has exited (exited is true), or
  • The view is scrolled into history (scroll_offset > 0).

Use this to gate cursor rendering in the app layer instead of relying on cell-level is_cursor flags alone.

Source

pub fn cursor_shape(&self) -> TerminalCursorShape

The child program’s most recent DECSCUSR (ESC [ Ps SP q) cursor shape request — Block until the child sends one. vim/zsh and other readline-style line editors switch this to Bar on entering insert mode and back to Block on returning to normal mode; see [decscusr_shape] for the full Ps → shape table.

to_terminal() stamps this onto the cursor cell’s TerminalCell::cursor_shape automatically — most callers don’t need to read this directly unless they’re also steering a real hardware cursor (Backend::set_caret_shape) outside the cell-grid paint path.

Source

pub fn cursor_blinking(&self) -> bool

Whether the child’s most recent DECSCUSR request asked for a blinking cursor variant (odd Ps) rather than steady (even Ps) — false (steady) until the child sends a DECSCUSR sequence at all. See TerminalCell::cursor_blinking’s doc for how rasterisers use this.

Source

pub fn screen_text(&self) -> String

Extract the current vt100 live screen as plain text.

Returns one line per screen row, trailing whitespace stripped. Completely blank rows at the bottom of the screen are omitted. This is the “what does the visible terminal look like right now?” getter — useful for programmatic drivers that need to scrape output (e.g. detecting a shell prompt line) without maintaining a full cell snapshot.

Source

pub fn bracketed_paste_enabled(&self) -> bool

Returns true when the child has enabled bracketed-paste mode (ESC[?2004h).

Interactive programs (e.g. claude, shells with line editors) turn this mode on once their input prompt is live and ready to accept keystrokes, so it doubles as a reliable input-readiness signal for programmatic drivers: wait for this to flip true before injecting a bracketed paste, otherwise early bytes are silently dropped. Backed by vt100’s tracking of the DEC private mode 2004.

Source

pub fn paste(&mut self, text: &str)

Send pasted text to the shell — the single paste entry point every paste source (GTK Ctrl-V/Ctrl-Shift-V, GTK middle-click PRIMARY selection, TUI crossterm bracketed paste) should call instead of hand-rolling the bracketed-paste wrap. Also the natural entry point for a future supervising process that wants to inject text programmatically, though nothing wires that up today.

Wraps text in bracketed-paste markers (ESC[200~ ... ESC[201~) when the child has enabled bracketed-paste mode (bracketed_paste_enabled), so programs that understand it (readline-based shells, vim, claude, …) can tell pasted text apart from typed text — most importantly, so pasted newlines don’t get interpreted as “run this line” one at a time. Sends text raw when the child hasn’t enabled that mode, since wrapping unconditionally would leak literal escape bytes into programs that don’t strip them (e.g. cat, less).

Also resets the scroll offset to the live view — like ordinary keystroke input — so a paste is always visible immediately even if the user had scrolled into history first.

Source

pub fn application_cursor_keys(&self) -> bool

Returns true when the child has enabled application-cursor-keys mode (DECCKM, DEC private mode ?1h / ESC [ ? 1 h).

In this mode, unmodified arrow keys and Home/End must be encoded as SS3 sequences (ESC O A…ESC O D, ESC O H, ESC O F) rather than the normal CSI sequences (ESC [ A…ESC [ 4 ~). Modifier combinations (e.g. Ctrl+Up) continue to use the CSI form regardless of this flag.

Full-TUI programs — vim, neovim, claude, htop — set DECCKM when active. Without honouring it, navigation inside those programs silently stops working. encode_key queries this flag for you each keystroke; call this directly only if you’re hand-rolling the encoding.

Backed by vt100::Screen::application_cursor.

Source

pub fn encode_key(&self, key: Key, mods: Modifiers) -> Option<Vec<u8>>

Encode a key press as PTY bytes, reading the child’s DECCKM (application-cursor-keys) state internally so callers don’t have to thread application_cursor_keys through themselves.

Returns None for keys with no meaningful PTY encoding (e.g. CapsLock) — callers should treat that as “swallow this keystroke”, not as an error.

This is the read-only half; most callers want write_key, which also writes the bytes and resets the scroll offset the same way paste does.

Source

pub fn write_key(&mut self, key: Key, mods: Modifiers) -> bool

Encode a key press with encode_key and write it to the PTY, resetting the scroll offset to the live view first (like paste and ordinary character input) so a keystroke is always visible immediately even if the user had scrolled into history. Returns true when bytes were written.

Callers embedding a full interactive shell (arrows, F-keys, modifiers, Ctrl-combos) should route UiEvent::KeyPressed here instead of hand-rolling the escape-sequence encoding — see examples/common/terminal_app.rs for the reference wiring.

Source

pub fn on_alt_screen(&self) -> bool

true when the child is currently rendering on the alternate screen (DEC private mode 1047 / 1049, or legacy 47).

Source

pub fn mouse_reporting_enabled(&self) -> bool

true when the child has enabled any xterm mouse-reporting mode (DEC private modes 1000 / 1002 / 1003). Independent of the 1006 (SGR) encoding bit, which selects the wire format but doesn’t turn reporting on or off.

Used by should_forward_wheel and forward_mouse to decide whether mouse events belong to the child rather than our local UI (selection, scrollback).

Source

pub fn should_forward_wheel(&self) -> bool

Whether wheel scroll events should be forwarded to the PTY child rather than handled locally.

Source

pub fn encode_mouse( &self, kind: TerminalMouseKind, button: MouseButton, col: u16, row: u16, modifiers: Modifiers, ) -> Option<Vec<u8>>

Encode a mouse event as SGR-1006 PTY bytes, without writing it.

Returns None when the engine has determined the event should not be reported to the child:

Callers that want to bypass the gate (e.g. for testing) can call the free function encode_mouse_sgr directly.

Source

pub fn forward_mouse( &mut self, kind: TerminalMouseKind, button: MouseButton, col: u16, row: u16, modifiers: Modifiers, ) -> bool

Forward a mouse event to the PTY child as SGR-1006 bytes, gated on the current reporting / alt-screen state.

Returns true when bytes were written. When false, the caller should fall back to local handling — wheel events scroll our own scrollback (scroll_up / scroll_down), clicks drive local selection, etc.

Source

pub fn handle_wheel( &mut self, up: bool, step: usize, col: u16, row: u16, ) -> bool

Canonical wheel-notch policy: try to forward the wheel to the PTY child, and fall back to scrolling local scrollback when it doesn’t take it.

This is the forward-vs-scrollback composition every consumer was re-deriving by hand: “is the child on the alt-screen or reporting mouse events? If so, let it paginate itself. Otherwise scroll our own scrollback by step rows.” Equivalent to:

ⓘ
let kind = if up { WheelUp } else { WheelDown };
if !sess.forward_mouse(kind, MouseButton::Left, col, row, Modifiers::default()) {
    if up { sess.scroll_up(step) } else { sess.scroll_down(step) }
}

col/row are the cell the pointer is over (0-based, clamped to the viewport by the caller). SGR-1006 always encodes the pointer position on the wire (encode_mouse_sgr), and real alt-screen consumers — tmux is the canonical example — use it to route wheel input to the pane under the cursor, so the caller must supply the real position rather than a fixed (0, 0).

Returns true when the event was written to the child (the caller must not also touch local scrollback), false when it fell back to scroll_up / scroll_down.

Source

pub fn scrollback_text(&self) -> String

Extract all captured scrollback history as plain text.

Returns one line per history row (oldest first), trailing whitespace stripped, joined by newlines. Does not include the live screen — call screen_text for that, or full_text for both together.

Source

pub fn full_text(&self) -> String

Concatenate scrollback_text and screen_text into a single string, separated by a newline when both are non-empty.

Useful for programmatic scraping: scan full_text() for a prompt or completion marker after each poll() cycle.

Source

pub fn resize(&mut self, cols: u16, rows: u16)

Resize the PTY and update the vt100 parser dimensions.

Sends SIGWINCH to the child process so running programs (e.g. vim, htop) re-layout their output.

§Preserving content across a width change

vt100 0.16’s set_size does not reflow — it truncates each row on a column shrink and pads with blanks on a widen, so a naive shrink→expand drag loses text permanently. We restore logical-line reflow via [reflow_screen] (public-API snapshot → resize → replay), gated to genuine width changes on the normal screen. This runs in the shared engine, so both the TUI and GTK backends get it for free.

§Closing the shell-redraw-vs-resize race

The shared engine also guards against a timing bug in the resize sequence:

Source

pub fn history_len(&self) -> usize

Number of scrollback rows captured so far.

Source

pub fn set_scroll_offset(&mut self, offset: usize)

Set the scroll offset.

0 = live view. history_len() = oldest available row at the top. Clamped to [0, history_len()].

Source

pub fn scroll_up(&mut self, n: usize)

Scroll up into history by n rows.

Source

pub fn scroll_down(&mut self, n: usize)

Scroll down toward the live view by n rows.

Source

pub fn scroll_reset(&mut self)

Return to the live view (scroll_offset = 0).

Source

pub fn selected_text(&self) -> Option<String>

Extract selected text, blending history and live screen correctly.

Selection coordinates (TerminalSelection) are always in display-row space (0-based from the visible top), which is the same coordinate system build_rows uses. This means the function works at any scroll_offset, including inside scrollback history.

Returns None when there is no active selection.

Source

pub fn to_terminal( &self, id: WidgetId, scrollbar: Option<TerminalScrollbar>, ) -> Terminal

Build a paint snapshot for [Backend::draw_terminal].

Pass Some(scrollbar) (e.g. from scrollbar_state) to show a scrollbar when the history is non-empty.

Source

pub fn scrollbar_state(&self, scrollbar_width: Option<u16>) -> TerminalScrollbar

Build a TerminalScrollbar reflecting the current scrollback state.

  • scrollbar_width: visual width in backend-native units. None → backend default (1 TUI cell, ~8 px GTK).

The scrollbar uses inverted = true so that offset 0 (live view) places the thumb at the track bottom, matching normal terminal UX.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more