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>
impl<W: Write> TerminalRenderer<W>
Sourcepub const fn with_plain_mode(writer: W, plain: bool) -> Self
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.
Sourcepub const fn plain_mode(&self) -> bool
pub const fn plain_mode(&self) -> bool
Returns whether plain mode is enabled. See set_plain_mode.
Sourcepub const fn color_support(&self) -> ColorSupport
pub const fn color_support(&self) -> ColorSupport
Returns the configured ColorSupport level. See
set_color_support.
Sourcepub const fn set_color_support(&mut self, color_support: ColorSupport)
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.
Sourcepub const fn with_color_support(self, color_support: ColorSupport) -> Self
pub const fn with_color_support(self, color_support: ColorSupport) -> Self
This renderer with color_support set. See set_color_support.
Sourcepub const fn set_plain_mode(&mut self, plain: bool)
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.
Sourcepub const fn writer_mut(&mut self) -> &mut W
pub const fn writer_mut(&mut self) -> &mut W
Returns a mutable reference to the underlying writer.
Sourcepub fn into_writer(self) -> W
pub fn into_writer(self) -> W
Consumes the renderer, returning the underlying writer.
Sourcepub const fn reset_state(&mut self)
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.
Sourcepub fn begin_synchronized_update(&mut self) -> Result<()>
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.
Sourcepub fn end_synchronized_update(&mut self) -> Result<()>
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.
Sourcepub fn draw<'a, I>(&mut self, content: I) -> Result<()>
pub fn draw<'a, I>(&mut self, content: I) -> Result<()>
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.
Sourcepub fn draw_frame<'a, I>(&mut self, content: I) -> Result<()>
pub fn draw_frame<'a, I>(&mut self, content: I) -> Result<()>
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.
Sourcepub fn end_frame(&mut self) -> Result<()>
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.
Sourcepub fn clear_screen(&mut self) -> Result<()>
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.
Sourcepub fn move_cursor_to(&mut self, position: Pos) -> Result<()>
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.
Sourcepub fn set_cursor_visible(&mut self, visible: bool) -> Result<()>
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.
Sourcepub fn set_cursor_style(&mut self, style: CursorStyle) -> Result<()>
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>
impl<W: Write + IsTerminal> TerminalRenderer<W>
Sourcepub fn auto(writer: W) -> Self
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).