retach 0.10.0

Persistent terminal sessions with native scrollback passthrough
Documentation
//! Trait abstraction for headless terminal emulation.

use super::cell::Row;
use super::grid::{CursorShape, TerminalModes};
use super::style::{Style, StyleId};

/// A terminal emulator that processes byte streams and maintains a cell grid.
///
/// This is the primary abstraction for headless terminal emulation.
/// Feed bytes via [`process`](Self::process), then read the grid state
/// via row iterators, cursor position, and style resolution.
pub trait TerminalEmulator {
    /// Feed raw bytes (from SSH, PTY, etc.) through the VTE parser.
    fn process(&mut self, bytes: &[u8]);

    /// Resize the terminal grid to new dimensions.
    ///
    /// Implementations must clamp dimensions to a sane range; [`Screen`]
    /// clamps to `1..=MAX_DIMENSION` (4096) on both axes.
    ///
    /// [`Screen`]: crate::screen::Screen
    fn resize(&mut self, cols: u16, rows: u16);

    /// Number of columns in the terminal grid.
    fn cols(&self) -> u16;

    /// Number of visible rows in the terminal grid.
    fn rows(&self) -> u16;

    /// The visible row at `y` (0-based). The primary, allocation-free row
    /// accessor; `O(1)` for [`Screen`].
    ///
    /// # Panics
    ///
    /// Panics if `y >= rows()`.
    ///
    /// [`Screen`]: crate::screen::Screen
    fn visible_row(&self, y: u16) -> &Row;

    /// The scrollback row at `i` (0-based, oldest first). `O(1)` for
    /// [`Screen`].
    ///
    /// # Panics
    ///
    /// Panics if `i >= scrollback_len()`.
    ///
    /// [`Screen`]: crate::screen::Screen
    fn scrollback_row(&self, i: usize) -> &Row;

    /// Iterate over visible rows (the current screen content).
    ///
    /// Convenience over [`visible_row`](Self::visible_row); boxes the
    /// iterator for object safety. Hot loops should prefer index access.
    fn visible_rows(&self) -> Box<dyn Iterator<Item = &Row> + '_> {
        Box::new((0..self.rows()).map(move |y| self.visible_row(y)))
    }

    /// Iterate over scrollback rows (history above the visible screen),
    /// oldest first.
    ///
    /// Convenience over [`scrollback_row`](Self::scrollback_row); boxes the
    /// iterator for object safety. Hot loops should prefer index access.
    fn scrollback_rows(&self) -> Box<dyn Iterator<Item = &Row> + '_> {
        Box::new((0..self.scrollback_len()).map(move |i| self.scrollback_row(i)))
    }

    /// Number of scrollback rows currently stored.
    fn scrollback_len(&self) -> usize;

    /// Current cursor position as `(x, y)`, both 0-based.
    fn cursor_position(&self) -> (u16, u16);

    /// Whether the cursor is currently visible (DECTCEM).
    fn cursor_visible(&self) -> bool;

    /// Resolve a cell's interned style ID to a full [`Style`].
    fn resolve_style(&self, id: StyleId) -> Style;

    /// Whether the terminal is in alternate screen mode (e.g. vim, htop).
    fn in_alt_screen(&self) -> bool;

    /// Take pending responses that should be written back to the PTY/SSH stdin
    /// (e.g. DA, DSR query replies).
    fn take_responses(&mut self) -> Vec<Vec<u8>>;

    /// Current window title (set by OSC 0/2).
    fn title(&self) -> &str;

    /// DECSCUSR cursor shape.
    fn cursor_shape(&self) -> CursorShape;

    /// Current scroll region as `(top, bottom)`, both 0-based.
    fn scroll_region(&self) -> (u16, u16);

    /// Terminal mode flags (autowrap, mouse, charset, etc.).
    fn modes(&self) -> &TerminalModes;

    /// Take pending OSC passthrough sequences to forward to the outer terminal.
    fn take_passthrough(&mut self) -> Vec<Vec<u8>>;

    /// Drain queued desktop notifications (OSC 9/777/99).
    fn take_queued_notifications(&mut self) -> Vec<Vec<u8>>;

    /// Drain scrollback rows produced since the last call (advances the
    /// pending cursor). Used by renderers that inject scrollback into the
    /// outer terminal, and by frontends consuming scrollback incrementally.
    ///
    /// # Building a scrolling frontend
    ///
    /// Drain pending rows into your own history each tick; your viewport is a
    /// slice of that history plus [`visible_rows`](Self::visible_rows):
    ///
    /// ```rust
    /// use retach::screen::{Row, Screen, TerminalEmulator};
    ///
    /// let mut screen = Screen::new(10, 3, 1000);
    /// let mut history: Vec<Row> = Vec::new();
    ///
    /// screen.process(b"1\r\n2\r\n3\r\n4\r\n5");   // rows scroll out of view
    /// history.extend(screen.take_pending_scrollback());
    ///
    /// assert_eq!(history.len(), 2);                // "1" and "2" scrolled off
    /// assert_eq!(history[0].text(), "1");
    /// // viewport at scroll offset k: &history[history.len()-k..] + visible_rows()
    /// ```
    fn take_pending_scrollback(&mut self) -> Vec<Row>;
}