Phosphor::Buffer
A 2D grid of Cells sized to a Frame.
Buffer is the shared canvas that all widgets write into during a render pass.
Widgets call set_string at frame-relative positions; the Renderer diffs two
buffers to find changed cells and writes only those to the terminal.
Cells are stored in row-major order: position (col, row) maps to flat index
row * frame.width + col. The stored frame lets the renderer translate those
positions into absolute terminal coordinates when emitting ANSI cursor moves.
Constants
Matches ANSI CSI escape sequences (e.g. \e[31m).
Stripped from text in set_string — styling must go through Style.
Constructors
dark_background resolves any AdaptiveColor written via set_string
— defaults to true so existing call sites that don't care about
light/dark resolution (most specs) don't need to change. color_profile
downsamples colors to what the terminal actually supports — defaults to
TrueColor (no downsampling) for the same reason.
Instance methods
Returns the Cell at local (col, row) — the same buffer-local,
zero-based coordinate space #copy_region accepts. Read counterpart
to #set_cell.
Copies a width×height rectangular region of src, starting at
local (src_col, src_row), into self at local (dest_col,
dest_row). Coordinates are buffer-local and zero-based — the same
space #full_redraw/#diff walk internally — not the absolute,
origin-shifted space #set_string accepts.
Transition subclasses (e.g. Transition::Slide) use this to
composite two independently rendered Buffers — one per screen —
into the shared transition buffer, without needing direct access to
either buffer's cell storage. Both buffers must share the same
Frame dimensions; the region must fit within both.
Compares self against other cell by cell and returns only the changed cells.
Wide characters and their Cell::CONTINUATION slot are treated as an atomic pair:
if either half differs between the two buffers, both slots appear in the result.
Rewriting only one half of a wide character pair corrupts terminal output.
Returns an empty array when both buffers are identical. Positions in the returned
Diff values are absolute terminal coordinates (frame origin applied).
Returns every cell in self as a Diff, unconditionally — a genuine
full redraw, unlike #diff against a fresh blank Buffer.
Renderer#draw needs this after Renderer#invalidate: diffing
against a synthetic blank buffer only catches cells that are
non-default in self. Any cell self also leaves at its own
default (untouched) — e.g. background rows the new screen doesn't
paint — would compare equal to that blank buffer and never appear in
the diff, even though the real terminal still shows whatever the
previous screen actually drew there. Emitting every cell sidesteps
that comparison entirely.
Writes cell at local (col, row), the same coordinate space
#copy_region accepts. Transition subclasses that composite two
buffers cell by cell rather than region by region (e.g.
Transition::Fade) use this alongside #cell_at instead of
#copy_region.
Writes text into the buffer starting at column x, row y — in the
same frame-relative coordinate space every MockWidget renders in
(frame.origin_col + col, frame.origin_row + row; see CLAUDE.md's
"All rendering is frame-relative" rule). x/y are translated to
this buffer's own local cell array by subtracting @frame.origin_col/
@frame.origin_row — the exact inverse of what #diff/#full_redraw
add back when producing absolute terminal positions. This is what
makes a non-zero-origin buffer (an Inline session's block, or any
nested sub-frame) address the same coordinates its widgets compute,
rather than requiring every widget to special-case its own origin.
Walks grapheme clusters via the \X regex, honoring Unicode display widths
through Width.of. Wide characters (CJK, emoji) occupy 2 cells; the second
slot receives Cell::CONTINUATION. Truncation stops at max_width cells
without ever splitting a wide character. Out-of-bounds writes are silently
clipped — safety net, not a contract. Raw ANSI escape sequences in text
are stripped before processing; styling is applied exclusively through style.
style is merged over whatever style the target cell already carries
(via Style#merge), not written in place of it — an attribute style
doesn't set (e.g. a plain-text Style.new with no bg) falls back to
the existing cell rather than erasing it. This is what lets
Renderer#draw's full-frame background fill survive underneath text a
Screen/widget draws on top of it. A caller that wants to explicitly
erase a cell's background back to the terminal's own default sets
Color::Default rather than leaving bg unset — Style#merge treats
Color::Default as an explicit value, distinct from nil.