TermUI.Renderer.Diff (TermUI v0.2.0)
View SourceDifferential rendering algorithm for terminal UI.
Compares current and previous buffers to produce minimal render operations. The algorithm identifies changed cells, groups them into spans, and generates operations for cursor movement, style changes, and text output.
Usage
operations = Diff.diff(current_buffer, previous_buffer)
# => [{:move, 1, 5}, {:style, style}, {:text, "Hello"}, ...]Operation Types
{:move, row, col}- Move cursor to position{:style, style}- Set text style (colors, attributes){:text, string}- Output text at current cursor position:reset- Reset all style attributes
Algorithm
- Iterate rows in order (row-major for efficient terminal output)
- For each row, find spans of changed cells
- Optimize spans by merging small gaps
- Generate render operations for each span
- Track style to emit deltas only
Summary
Functions
Compares two buffers and returns a list of render operations.
Compares a single row and returns render operations for changed spans.
Finds spans of changed cells within a row.
Merges adjacent spans when the gap is smaller than cursor move cost.
Converts a span to render operations.
Checks if a cell contains a wide character (display width > 1).
Types
@type operation() :: {:move, pos_integer(), pos_integer()} | {:style, TermUI.Renderer.Style.t()} | {:text, String.t()} | :reset
@type span() :: %{ row: pos_integer(), start_col: pos_integer(), end_col: pos_integer(), cells: [TermUI.Renderer.Cell.t()] }
Functions
@spec diff(TermUI.Renderer.Buffer.t(), TermUI.Renderer.Buffer.t()) :: [operation()]
Compares two buffers and returns a list of render operations.
The current buffer contains the new frame to render, and the previous buffer contains the last rendered frame. Only differences are output.
Examples
{:ok, current} = Buffer.new(24, 80)
{:ok, previous} = Buffer.new(24, 80)
Buffer.write_string(current, 1, 1, "Hello")
operations = Diff.diff(current, previous)
# => [{:move, 1, 1}, {:style, %Style{}}, {:text, "Hello"}]
@spec diff_row( TermUI.Renderer.Buffer.t(), TermUI.Renderer.Buffer.t(), pos_integer(), pos_integer() ) :: [operation()]
Compares a single row and returns render operations for changed spans.
@spec find_changed_spans( [{pos_integer(), TermUI.Renderer.Cell.t()}], [{pos_integer(), TermUI.Renderer.Cell.t()}], pos_integer() ) :: [span()]
Finds spans of changed cells within a row.
Returns a list of spans, where each span contains contiguous changed cells.
Merges adjacent spans when the gap is smaller than cursor move cost.
This reduces cursor movements by including unchanged cells in the output when it's cheaper than moving the cursor around them.
The current_cells_map is used to fill gaps with actual cell content from the current buffer, rather than empty cells.
Converts a span to render operations.
Generates move, style, and text operations for the span. Splits on style changes to minimize SGR sequence overhead.
@spec wide_char?(TermUI.Renderer.Cell.t()) :: boolean()
Checks if a cell contains a wide character (display width > 1).