wijjit.terminal.screen_buffer.ScreenBuffer
- class wijjit.terminal.screen_buffer.ScreenBuffer(width, height)[source]
2D buffer of terminal cells with dirty region tracking.
This class manages a 2D array of Cell objects representing the terminal screen. It tracks which regions have changed (dirty regions) to enable efficient diff rendering.
- Parameters:
- Variables:
Notes
Wide-character model. A width-2 glyph (most CJK, many emoji) is stored as two cells: a head cell holding the glyph and a continuation cell whose
charis the empty string (seewijjit.terminal.cell.is_continuation()) carrying the head’s style. Zero-width combining marks (e.g. an NFD-decomposed accent) are folded onto the base glyph as a single multi-code-pointchar. The standard text path –wijjit.rendering.paint_context.PaintContext.write_text()plus the diff and full-render emitters here – is column-correct: it skips continuation cells and advances the diff cursor by each glyph’s true column width, so wide glyphs no longer overflow borders or desync the cursor.Element rendering also goes through this path now: all elements paint via the clipped, wide-aware
PaintContextwrite APIs (write_text,write_cell,write_cells/write_cells_vertical); directbuffer.set_cellloops were migrated out (review items 2.1/2.11) and a ratchet test keeps them out. Remaining gap (roadmap):wijjit.rendering.ansi_adapter.ansi_string_to_cells()– used for pre-rendered ANSI content such as Rich-rendered tables withcontent_type="ansi"– still maps one code point per cell.Examples
Create a buffer and write a cell:
>>> buffer = ScreenBuffer(80, 24) >>> buffer.set_cell(0, 0, Cell('A', fg_color=(255, 0, 0))) >>> buffer.get_cell(0, 0).char 'A'
Track dirty regions:
>>> buffer.mark_dirty(0, 0, 10, 1) >>> dirty = buffer.get_dirty_regions() >>> (0, 0, 10, 1) in dirty True
Methods
__init__(width, height)clear()Clear the entire buffer to empty cells.
Clear all dirty region tracking.
copy_from(other)Make this buffer's cells reference
other's, row by row.Stop recording coverage and return the cells painted this frame.
fill_rect(x, y, width, height, cell)Fill a rectangular region with the same cell.
get_cell(x, y)Get the cell at the specified position.
Get all dirty regions.
Get dirty regions collapsed to full-width rows.
Mark the entire buffer as dirty.
mark_dirty(x, y, width, height)Mark a rectangular region as needing update.
Snapshot the cells painted so far this frame, without ending tracking.
record_coverage_rect(x, y, width, height)Record a rectangular region as painted, without touching any cell.
reset()Reset to a freshly-constructed state: all blank, no dirty regions.
resize(new_width, new_height)Resize the buffer, preserving content where possible.
set_cell(x, y, cell)Set a cell at the specified position and mark it dirty.
set_cells_horizontal(x, y, cells)Set multiple cells horizontally in a single operation.
set_cells_vertical(x, y, cells)Set multiple cells vertically in a single operation.
start_tracking(damage)Begin recording paint coverage for one frame.
Convert buffer to plain text string for debugging.
to_text()Convert buffer to plain text (for testing/debugging).
Attributes
The shared blank cell (see module-level _BLANK_CELL).
- blank_cell: Cell = Cell(char=' ', fg_color=None, bg_color=None, bold=False, italic=False, underline=False, reverse=False, dim=False, _style_mask=0)
The shared blank cell (see module-level _BLANK_CELL). Exposed on the instance so callers (e.g. the incremental renderer blanking a vacated cell) can reference it without importing the module constant.
- start_tracking(damage)[source]
Begin recording paint coverage for one frame.
- Parameters:
damage (
bool) – When True, bulk writes also change-detect per cell (used on the incremental paint path where the buffer starts as a copy of the previous frame). When False, coverage is recorded but bulk writes keep their unconditional fast path (used to capture coverage on the full-repaint path without altering its output).- Return type:
None
- peek_coverage()[source]
Snapshot the cells painted so far this frame, without ending tracking.
Returns a copy so the caller can compare it against coverage recorded by later writes (e.g. the incremental path snapshots the frame-border pass’s coverage before the element loop, to keep elements that overlap a border from being skipped). Empty when tracking is not active.
- record_coverage_rect(x, y, width, height)[source]
Record a rectangular region as painted, without touching any cell.
Used by the incremental paint path when an unchanged element is skipped (its cells are already correct in the copied-in baseline): the element’s previously-painted region must still be counted as coverage so the vacated-cell blanking does not erase it. The rect is clipped to the buffer and packed as
y * width + xints, matchingset_cell(). A no-op when coverage tracking is not active.
- reset()[source]
Reset to a freshly-constructed state: all blank, no dirty regions.
Unlike
clear(), this does not mark the buffer dirty - it reproduces exactly what__init__yields, so a pooled buffer can be reused for a full repaint without the differ treating every cell as changed.- Return type:
None
- copy_from(other)[source]
Make this buffer’s cells reference
other’s, row by row.A shallow per-row copy: each row becomes a fresh list of references to
other’s cell objects (cells are immutable and replaced wholesale, so sharing references is safe). Used by the incremental paint path to start a frame from the previous frame’s content, so thatset_cell’s change-detection then dirties only the cells that actually change. Dirty regions and coverage are reset.- Parameters:
other (ScreenBuffer)
- Return type:
None
- set_cell(x, y, cell)[source]
Set a cell at the specified position and mark it dirty.
- Parameters:
- Return type:
None
Notes
Bounds checking is performed. Out-of-bounds coordinates are silently ignored. The cell region is automatically marked dirty if the cell content differs from the current cell.
- set_cells_horizontal(x, y, cells)[source]
Set multiple cells horizontally in a single operation.
This is optimized for performance, avoiding individual cell comparisons and marking the entire region dirty once instead of cell-by-cell.
- Parameters:
- Return type:
None
Notes
Out-of-bounds cells are clipped. The entire region is marked dirty regardless of whether individual cells changed, which is more efficient than checking each cell when most cells are expected to change.
- set_cells_vertical(x, y, cells)[source]
Set multiple cells vertically in a single operation.
This is optimized for performance, avoiding individual cell comparisons and marking the entire region dirty once instead of cell-by-cell.
- Parameters:
- Return type:
None
Notes
Out-of-bounds cells are clipped. The entire region is marked dirty regardless of whether individual cells changed, which is more efficient than checking each cell when most cells are expected to change.
- fill_rect(x, y, width, height, cell)[source]
Fill a rectangular region with the same cell.
This is highly optimized for filling regions with a single character, such as backgrounds or padding areas.
- Parameters:
- Return type:
None
Notes
The entire region is marked dirty once. This is much more efficient than individual set_cell() calls for filling large areas.
- mark_dirty(x, y, width, height)[source]
Mark a rectangular region as needing update.
- Parameters:
- Return type:
None
Notes
Dirty regions are used by the diff renderer to identify which parts of the screen need updating. Multiple overlapping regions are automatically merged during rendering.
- mark_all_dirty()[source]
Mark the entire buffer as dirty.
Notes
Used when a full redraw is needed, such as after a terminal resize or when first rendering.
- Return type:
None
- get_merged_dirty_regions()[source]
Get dirty regions collapsed to full-width rows.
- Returns:
One
(x, y, width, height)tuple per dirty row: each spans the full buffer width with height 1, i.e.(0, row, width, 1).- Return type:
Notes
Rather than computing minimal bounding rectangles, every touched row is returned as a single full-width strip. This is cheaper to compute and, for the common case of scattered single-cell changes spread over a few rows, keeps the number of regions the diff renderer processes small.
- clear_dirty()[source]
Clear all dirty region tracking.
Notes
Should be called after rendering is complete to reset dirty state for the next frame.
- Return type:
None
- clear()[source]
Clear the entire buffer to empty cells.
Notes
Fills all cells with the shared blank (space, no styling) - the same no-per-cell-allocation fill used at construction (see _BLANK_CELL) - and marks the entire buffer dirty.
- Return type:
None
- to_text()[source]
Convert buffer to plain text (for testing/debugging).
- Returns:
Plain text representation with newlines
- Return type:
Notes
This converts the cell buffer to plain text by extracting character data and joining with newlines. Useful for test assertions and debugging output.
Examples
>>> buffer = ScreenBuffer(10, 3) >>> buffer.set_cell(0, 0, Cell('H')) >>> buffer.set_cell(1, 0, Cell('i')) >>> text = buffer.to_text() >>> 'Hi' in text True