Buffer system
This module provides the core buffer abstraction for managing terminal screen content and efficient rendering.
Types
Cell = object symbol*: string style*: Style hyperlink*: string
- Represents a single character cell in the terminal
DirtyRegion = object isDirty*: bool minX*, minY*: int maxX*, maxY*: int
- Tracks the rectangular region of changed cells
Procs
proc `[]=`(buffer: var Buffer; pos: Position; cell: Cell) {.inline, ...raises: [], tags: [], forbids: [].}
- Set cell at position
proc `[]=`(buffer: var Buffer; x, y: int; newCell: Cell) {....raises: [], tags: [], forbids: [].}
-
Set cell at coordinates.
Maintains wide-character consistency:
- If overwriting the shadow cell of a wide character at (x-1), the orphaned lead at (x-1) is replaced with a space (preserving its style; the hyperlink, if any, is dropped because the link anchor is gone).
- If the old cell at (x, y) was a wide-character lead, its shadow at (x+1) is replaced with a space (preserving the lead's style).
Writes with an empty symbol (shadow cells) skip the left-side cleanup so that sequential wide-character writes (lead followed by shadow) do not erase the lead just written.
NOTE: This is a low-level single-cell write. When newCell is a wide character (width == 2) the matching shadow at (x+1) is NOT placed automatically — callers must write the shadow themselves or use setCell / setString which handle it. Likewise, writing a wide character at the rightmost column leaves the buffer in an inconsistent state (no room for a shadow); use setCell, which performs the right-edge check.
proc cell(symbol: char; style: Style = defaultStyle(); hyperlink: string = ""): Cell {. inline, ...raises: [], tags: [], forbids: [].}
- Create a new Cell from a character
proc clearDirty(buffer: var Buffer) {.inline, ...raises: [], tags: [], forbids: [].}
- Clear the dirty region after rendering Should be called after buffer has been successfully rendered
proc diff(old, new: Buffer): seq[tuple[pos: Position, cell: Cell]] {....raises: [], tags: [], forbids: [].}
-
Calculate differences between two buffers with dirty region optimization Returns a sequence of changes needed to transform old into new
This optimized implementation uses the dirty region tracking to avoid scanning unchanged portions of the buffer, providing significant performance improvements for typical use cases (e.g., single character edits, cursor movement).
Performance characteristics:
- No changes: O(1) - immediate return
- Small changes (1-100 cells): O(dirty region size) - highly optimized
- Large changes (>2000 cells): O(width × height) - fallback to full scan
proc getDirtyRegionSize(buffer: Buffer): int {....raises: [], tags: [], forbids: [].}
- Get the number of cells in the dirty region Returns 0 if no changes have been made
proc isShadow(cell: Cell): bool {.inline, ...raises: [], tags: [], forbids: [].}
- True when this cell is a shadow (right half) of a wide character. Shadow cells carry an empty symbol; a blank cell holds " " instead. Alias of isEmpty — the predicates share an implementation because a shadow cell is, by definition, the only legitimate empty cell.
proc isValidPos(buffer: Buffer; pos: Position): bool {.inline, ...raises: [], tags: [], forbids: [].}
- Check if position is within buffer bounds
proc isValidPos(buffer: Buffer; x, y: int): bool {.inline, ...raises: [], tags: [], forbids: [].}
- Check if coordinates are within buffer bounds
proc markDirtyRect(buffer: var Buffer; rect: Rect) {.inline, ...raises: [], tags: [], forbids: [].}
- Mark a rectangular area as dirty More efficient than marking individual cells when multiple cells change
proc runesWidth(runes: seq[Rune]): int {....raises: [], tags: [], forbids: [].}
- Calculate total display width of a sequence of runes
proc runeWidth(r: Rune): int {....raises: [], tags: [], forbids: [].}
- Get the display width of a rune using Unicode standard width detection
proc setCell(buffer: var Buffer; pos: Position; rune: Rune; width: int; style: Style = defaultStyle(); hyperlink: string = "") {.inline, ...raises: [], tags: [], forbids: [].}
- Position + Rune overload for setCell.
proc setCell(buffer: var Buffer; pos: Position; symbol: string; width: int; style: Style = defaultStyle(); hyperlink: string = "") {.inline, ...raises: [], tags: [], forbids: [].}
- Position overload for setCell.
proc setCell(buffer: var Buffer; x, y: int; rune: Rune; width: int; style: Style = defaultStyle(); hyperlink: string = "") {.inline, ...raises: [], tags: [], forbids: [].}
- Rune overload — converts to string once, then delegates.
proc setCell(buffer: var Buffer; x, y: int; symbol: string; width: int; style: Style = defaultStyle(); hyperlink: string = "") {.inline, ...raises: [], tags: [], forbids: [].}
-
Set a single character cell at (x, y) with a known display width. Unlike setString, this skips UTF-8 parsing and width calculation. For wide characters (width=2), the next cell is automatically marked empty. If there is not enough room for the shadow cell, the character is not written. Caller is responsible for providing correct width.
Wide-character consistency is maintained via []= (see its doc).
proc setString(buffer: var Buffer; area: Rect; text: string; style: Style = defaultStyle(); hAlign: HAlign = hLeft; vAlign: VAlign = vTop; hyperlink: string = "") {....raises: [], tags: [], forbids: [].}
- Set a string within the given area with alignment Text is clipped to the area boundaries. Handles Unicode characters and wide characters properly for alignment calculation
proc setString(buffer: var Buffer; pos: Position; runes: seq[Rune]; style: Style = defaultStyle(); hyperlink: string = "") {.inline, ...raises: [], tags: [], forbids: [].}
- Alias for setRunes for convenience
proc setString(buffer: var Buffer; pos: Position; text: string; style: Style = defaultStyle(); hyperlink: string = "") {.inline, ...raises: [], tags: [], forbids: [].}
- Set a string starting at the given position
proc setString(buffer: var Buffer; x, y: int; runes: seq[Rune]; style: Style = defaultStyle(); hyperlink: string = "") {.inline, ...raises: [], tags: [], forbids: [].}
- Alias for setRunes for convenience
proc setString(buffer: var Buffer; x, y: int; text: string; style: Style = defaultStyle(); hyperlink: string = "") {. ...raises: [], tags: [], forbids: [].}
- Set a string starting at the given coordinates Handles Unicode characters and wide characters properly If hyperlink is provided, the text becomes a clickable link (OSC 8)