retach 0.10.0

Persistent terminal sessions with native scrollback passthrough
Documentation
use super::cache::RenderCache;
use super::core::{render_emulator_impl, render_line_with};
use crate::screen::cell::Row;
use crate::screen::traits::TerminalEmulator;

/// ANSI escape sequence renderer.
///
/// Renders terminal emulator state as ANSI escape sequences suitable
/// for output to a real terminal. Uses dirty-tracking for incremental updates.
pub struct AnsiRenderer {
    cache: RenderCache,
}

impl Default for AnsiRenderer {
    fn default() -> Self {
        Self::new()
    }
}

impl AnsiRenderer {
    /// Create a new ANSI renderer.
    pub fn new() -> Self {
        Self {
            cache: RenderCache::new(),
        }
    }

    /// Invalidate the cache, forcing a full redraw on next render.
    pub fn invalidate(&mut self) {
        self.cache.invalidate();
    }

    /// Render emulator state as ANSI. `full` forces a complete redraw;
    /// otherwise an incremental dirty-tracked update is produced.
    pub fn render<E: TerminalEmulator + ?Sized>(&mut self, emu: &E, full: bool) -> Vec<u8> {
        render_emulator_impl(emu, &[], full, &mut self.cache)
    }

    /// Drain pending scrollback / passthrough / notifications and render in
    /// one step. Returns `(render_data, passthrough)`. Call under one lock hold.
    /// Composes the public [`render`](Self::render) /
    /// [`render_with_scrollback`](Self::render_with_scrollback) primitives.
    pub fn take_and_render<E: TerminalEmulator + ?Sized>(
        &mut self,
        emu: &mut E,
    ) -> (Vec<u8>, Vec<Vec<u8>>) {
        let pending = emu.take_pending_scrollback();
        let scrollback_lines: Vec<Vec<u8>> = pending
            .iter()
            .map(|row| render_line_with(row, |id| emu.resolve_style(id)))
            .collect();
        let mut passthrough = emu.take_passthrough();
        passthrough.extend(emu.take_queued_notifications());
        let render_data = if scrollback_lines.is_empty() {
            self.render(&*emu, false)
        } else {
            self.render_with_scrollback(&*emu, &scrollback_lines)
        };
        (render_data, passthrough)
    }

    /// Render a full update with the given pre-rendered scrollback lines
    /// injected into the outer terminal's native scrollback.
    ///
    /// Note: a full redraw is emitted even when `scrollback` is empty (unlike
    /// [`take_and_render`](Self::take_and_render), which falls back to an
    /// incremental update).
    pub fn render_with_scrollback<E: TerminalEmulator + ?Sized>(
        &mut self,
        emu: &E,
        scrollback: &[Vec<u8>],
    ) -> Vec<u8> {
        render_emulator_impl(emu, scrollback, true, &mut self.cache)
    }

    /// Render already-drained scrollback rows as standalone ANSI lines.
    /// (Used by the server's reconnect handshake which drains under a
    /// separate lock hold.)
    ///
    /// Takes `&self` — pre-drained rows bypass the dirty-tracking cache.
    pub fn render_rows<E: TerminalEmulator + ?Sized>(&self, emu: &E, rows: &[Row]) -> Vec<Vec<u8>> {
        rows.iter()
            .map(|row| render_line_with(row, |id| emu.resolve_style(id)))
            .collect()
    }
}