Skip to main content

TerminalRenderer

Struct TerminalRenderer 

Source
pub struct TerminalRenderer<W> { /* private fields */ }
Expand description

A generic ANSI/SGR cell-diff renderer.

Converts Tile content into standard ANSI/CSI escape sequences and writes them to a caller-supplied std::io::Write sink W. Tracks cursor position and the last-emitted foreground/background/attribute state across calls to draw so it only emits the escape codes needed to move to changed cells and change state.

This type has no knowledge of how its output bytes reach a display (stdout, a String buffer for JS, a test harness) or how input arrives: it is a pure Tile stream -> ANSI bytes transform, reused by every terminal-family Backend implementor.

§Examples

Driving the renderer over a Vec<u8> sink and asserting on the emitted ANSI bytes: no real terminal is needed, since W here is just an in-memory buffer.

use retroglyph_core::backend::DrawCell;
use retroglyph_core::color::{AnsiColor, Color};
use retroglyph_core::grid::Pos;
use retroglyph_core::color::Style;
use retroglyph_core::tile::Tile;
use retroglyph_terminal::TerminalRenderer;

let mut renderer = TerminalRenderer::new(Vec::new());
let tile = Tile::new('X', Style::new().fg(Color::Ansi(AnsiColor::Red)));
renderer.draw(core::iter::once(DrawCell::new(Pos { x: 0, y: 0 }, &tile)))?;
renderer.flush()?;

let out = String::from_utf8(renderer.into_writer()).expect("renderer only writes ASCII/UTF-8");
// `\x1b[1;1H` moves the cursor to row 1, col 1 (1-indexed); `\x1b[31;49m` sets red
// foreground with the default background.
assert_eq!(out, "\x1b[1;1H\x1b[31;49mX");

Implementations§

Source§

impl<W: Write> TerminalRenderer<W>

Source

pub const fn new(writer: W) -> Self

Creates a new renderer writing to writer.

Source

pub const fn with_plain_mode(writer: W, plain: bool) -> Self

Creates a new renderer writing to writer, with plain-mode set explicitly.

See set_plain_mode for what plain mode changes; prefer TerminalRenderer::auto when W implements std::io::IsTerminal and the mode should be picked automatically instead of hardcoded.

Source

pub const fn plain_mode(&self) -> bool

Returns whether plain mode is enabled. See set_plain_mode.

Source

pub const fn color_support(&self) -> ColorSupport

Returns the configured ColorSupport level. See set_color_support.

Source

pub const fn set_color_support(&mut self, color_support: ColorSupport)

Sets the ColorSupport level. See the crate-level “RGB color fallback on 256-color terminals” doc section for the full contract.

Source

pub const fn with_color_support(self, color_support: ColorSupport) -> Self

This renderer with color_support set. See set_color_support.

Source

pub const fn set_plain_mode(&mut self, plain: bool)

Enables or disables plain mode.

In plain mode, draw and the synchronized-update markers stop emitting ANSI/CSI escape sequences (cursor moves, color/SGR codes, \x1b[?2026h/l) entirely. Cell text is written as plain text instead, with row changes turned into \n and gaps between non-adjacent cells on the same row padded with spaces, so a full-grid draw call degrades to a readable ASCII rendering of that frame.

This is modeled on Python’s blessed, which does the same thing when its output stream isn’t a TTY: piping or redirecting output (myapp > log.txt) shouldn’t leave a file full of unreadable escape codes. Because this renderer only ever draws changed cells, repeated draw calls in plain mode append each frame’s diff as more plain text rather than overwriting previous output in place: there is no cursor-addressable terminal to overwrite when the sink is a file or pipe, so this is a lossy degradation intended for logging/debugging, not for reproducing the exact interactive frame sequence.

Source

pub const fn writer(&self) -> &W

Returns a reference to the underlying writer.

Source

pub const fn writer_mut(&mut self) -> &mut W

Returns a mutable reference to the underlying writer.

Source

pub fn into_writer(self) -> W

Consumes the renderer, returning the underlying writer.

Source

pub const fn reset_state(&mut self)

Resets tracked cursor/style state without touching the writer.

Call this after an external clear (e.g. \x1b[2J) so the next draw doesn’t skip a MoveTo/color/attribute escape under the assumption the terminal is still in the last-known state.

Source

pub fn begin_synchronized_update(&mut self) -> Result<()>

Begins a synchronized update (\x1b[?2026h).

Terminals that support this hold rendering until the matching end_synchronized_update, avoiding visible tearing mid-frame. Terminals that don’t understand the sequence ignore it.

A no-op in plain mode: synchronized updates are themselves a control code with nothing to synchronize once cell output has already degraded to plain text.

§Errors

Returns an error if the writer fails.

Source

pub fn end_synchronized_update(&mut self) -> Result<()>

Ends a synchronized update (\x1b[?2026l). See begin_synchronized_update.

A no-op in plain mode; see begin_synchronized_update.

§Errors

Returns an error if the writer fails.

Source

pub fn draw<'a, I>(&mut self, content: I) -> Result<()>
where I: Iterator<Item = DrawCell<'a>>,

Draws changed cells, emitting only the escape sequences needed to move the cursor and change color/attribute state versus what was last drawn.

Mirrors Output::draw’s contract: content is a stream of (Pos, &Tile, Option<&str>) items to render, the last being the tile’s full grapheme text when it has one. As with Output::draw, that trailing Option<&str> is only ever Some when this crate’s egc feature is enabled; without egc it is always None. Does not flush; call flush after.

§Errors

Returns an error if the writer fails.

Source

pub fn flush(&mut self) -> Result<()>

Flushes the underlying writer.

§Errors

Returns an error if the writer fails to flush.

Source

pub fn draw_frame<'a, I>(&mut self, content: I) -> Result<()>
where I: Iterator<Item = DrawCell<'a>>,

Begins a synchronized update and draws content, without flushing.

Combines begin_synchronized_update and draw into the single call every Output::draw_layers implementor in this workspace needs: call end_frame afterward to close the synchronized update and flush.

§Errors

Returns an error if the writer fails.

Source

pub fn end_frame(&mut self) -> Result<()>

Ends a synchronized update and flushes the underlying writer.

Combines end_synchronized_update and flush into the single call every Output::flush implementor in this workspace needs; pairs with draw_frame.

§Errors

Returns an error if the writer fails.

Source

pub fn clear_screen(&mut self) -> Result<()>

Erases the whole screen and resets tracked state.

Emits a full SGR reset (\x1b[0m) before the erase (\x1b[2J): most terminals implement erase-display via background color erase (BCE), painting erased cells with whatever background is currently active in the pen rather than the terminal’s true default, so a colored cell drawn just before this call would otherwise leave a stale tint across the whole screen. Also homes the cursor (\x1b[H) and, after writing, flushes the underlying writer and calls reset_state: the terminal-side state (cursor position, last color/attrs) is now stale versus what’s actually on screen, so the next draw must re-emit full escape sequences instead of skipping them under the assumption the terminal is still in the last-known state.

§Errors

Returns an error if the writer fails to write or flush.

Source

pub fn move_cursor_to(&mut self, position: Pos) -> Result<()>

Moves the cursor to position (CUP, CSI row;col H, 1-indexed), without flushing.

The real cursor is now wherever position says, not wherever the last drawn glyph left it, so this also resets tracked cursor position: otherwise the next draw could skip a move for a changed cell that happens to match the now-stale tracked coordinates. Color/attribute tracking is untouched: a bare cursor move doesn’t change what’s in the pen, so an unchanged style still skips its escape on the next draw.

§Errors

Returns an error if the writer fails.

Source

pub fn set_cursor_visible(&mut self, visible: bool) -> Result<()>

Shows or hides the cursor (DECTCEM, CSI ?25 h/CSI ?25 l), without flushing.

§Errors

Returns an error if the writer fails.

Source

pub fn set_cursor_style(&mut self, style: CursorStyle) -> Result<()>

Sets the cursor’s shape (DECSCUSR, CSI Ps SP q), without flushing.

§Errors

Returns an error if the writer fails.

Source§

impl<W: Write + IsTerminal> TerminalRenderer<W>

Source

pub fn auto(writer: W) -> Self

Creates a new renderer writing to writer, auto-detecting plain mode from whether writer is a TTY.

Equivalent to TerminalRenderer::with_plain_mode(writer, !writer.is_terminal()). W must implement std::io::IsTerminal for this to be callable: std::io::Stdout, std::io::Stdin, std::io::Stderr, std::fs::File, and their *Lock variants all do; an in-memory sink like Vec<u8> does not, so use with_plain_mode directly for those.

This mirrors how retroglyph-crossterm would typically wire up pipe-safe output: check once at startup whether the real destination is an interactive terminal, and fall back to plain text for everything else (files, pipes, > log.txt redirection, CI runners).

Trait Implementations§

Source§

impl<W: Debug> Debug for TerminalRenderer<W>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<W> Freeze for TerminalRenderer<W>
where W: Freeze,

§

impl<W> RefUnwindSafe for TerminalRenderer<W>
where W: RefUnwindSafe,

§

impl<W> Send for TerminalRenderer<W>
where W: Send,

§

impl<W> Sync for TerminalRenderer<W>
where W: Sync,

§

impl<W> Unpin for TerminalRenderer<W>
where W: Unpin,

§

impl<W> UnsafeUnpin for TerminalRenderer<W>
where W: UnsafeUnpin,

§

impl<W> UnwindSafe for TerminalRenderer<W>
where W: UnwindSafe,

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> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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.