Per-window render state for incremental rendering.
Tracks which buffer lines need re-rendering and stores last-frame comparison
values so the render pipeline can detect when a full redraw is needed. The
semantic RenderModel.Window.Builder reuses retained composed rows (see the
retained_rows/retained_wrap_lines fields) for unchanged lines.
Dirty-line tracking
The dirty set uses two representations:
:allmeans every line needs re-rendering (scroll, resize, theme change, highlight update, fold toggle, or any other wholesale invalidation)- A map of specific buffer line numbers (
%{line => true}) for targeted invalidation (edits that touch a few lines)
Tracking fields
last_viewport_top, last_viewport_cache_key, last_gutter_w,
last_line_count, last_cursor_line, and last_buf_version store values
from the previous frame. The Scroll stage compares current values against
these to detect full-invalidation triggers. last_context_fingerprint
captures all per-frame render context inputs (visual selection, search
matches, syntax highlights, signs, etc.) so context changes trigger full
redraws.
Summary
Types
Context fingerprint: a term derived from the render context that captures all per-frame inputs affecting every visible line. When the fingerprint changes between frames, all lines are re-rendered.
A retained composed row plus the cheap input fingerprint that produced it.
A retained wrapped logical line: the cheap input fingerprint that produced its visual rows plus the full visual-row entry list (Rows and their wrap metadata).
Functions
Returns a cached wrapped visual row total when the key matches.
Compares the current render context fingerprint against the last frame's.
Checks current frame parameters against last-frame tracking fields and marks all lines dirty if anything requiring a full redraw has changed.
Returns true if the given buffer line needs re-rendering.
Marks specific buffer lines as needing re-render.
Marks the next retained GUI frame as a frontend-state reset.
Prepares the retained GUI content epoch for the current frame.
Replaces the retained-row map with the current frame's composed rows.
Replaces the retained wrapped-line map with the current frame's logical lines.
Stores the wrapped visual row total for the current cache key.
Returns a fresh cache with all lines dirty.
Returns a fresh cache invalidation while preserving retained-render epoch state.
Returns the {row_id => {input_hash, Row.t()}} map captured by the last
semantic content build. The Builder consults it to skip recomposing rows
whose cheap input fingerprint is unchanged.
Returns the {buf_line => {input_hash, entries}} map captured by the last
semantic content build for wrapped windows. The Builder consults it to skip
recomposing and re-wrapping logical lines whose input fingerprint is unchanged.
Snapshots tracking fields after a successful render pass.
Types
@type context_fingerprint() :: term()
Context fingerprint: a term derived from the render context that captures all per-frame inputs affecting every visible line. When the fingerprint changes between frames, all lines are re-rendered.
Built from: visual selection, search matches, highlight version, diagnostic signs, git signs, viewport left scroll, active status, and theme color structs.
@type retained_row() :: {input_hash :: non_neg_integer(), Minga.RenderModel.Window.Row.t()}
A retained composed row plus the cheap input fingerprint that produced it.
The Content stage (semantic Builder) keys these by row_id. On the next
frame it recomputes only the input fingerprint per row; when the fingerprint
matches, it reuses the cached Row.t() verbatim instead of recomposing the
text and spans. See MingaEditor.RenderModel.Window.Builder (#2287).
@type retained_wrap_line() :: {input_hash :: non_neg_integer(), entries :: [map()]}
A retained wrapped logical line: the cheap input fingerprint that produced its visual rows plus the full visual-row entry list (Rows and their wrap metadata).
The Content stage (semantic Builder) keys these by buf_line. On the next
frame, when the logical line's fingerprint (line text, highlight segments,
compose context, and content width) is unchanged, the Builder reuses the
entire visual-row set verbatim instead of recomposing the line and recomputing
its wrap points. See MingaEditor.RenderModel.Window.Builder (#2287).
@type t() :: %MingaEditor.Window.RenderCache{ content_epoch: non_neg_integer(), dirty_lines: :all | %{optional(non_neg_integer()) => true}, last_buf_version: integer(), last_context_fingerprint: context_fingerprint(), last_cursor_line: integer(), last_gutter_w: integer(), last_line_count: integer(), last_reset_fingerprint: term(), last_viewport_cache_key: integer(), last_viewport_top: integer(), reset_pending: boolean(), retained_rows: %{optional(non_neg_integer()) => retained_row()}, retained_wrap_lines: %{optional(non_neg_integer()) => retained_wrap_line()}, total_visual_rows_cache: {term(), non_neg_integer()} | nil }
Functions
@spec cached_total_visual_rows(t(), term()) :: non_neg_integer() | nil
Returns a cached wrapped visual row total when the key matches.
@spec detect_context_change(t(), context_fingerprint()) :: t()
Compares the current render context fingerprint against the last frame's.
If the fingerprint changed, marks all lines dirty. Catches changes to visual selection, search matches, syntax highlights, diagnostic signs, git signs, horizontal scroll, active/inactive status, and theme colors.
@spec detect_invalidation( t(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer() ) :: t()
Checks current frame parameters against last-frame tracking fields and marks all lines dirty if anything requiring a full redraw has changed.
Structural redraw triggers: viewport scroll, gutter width, line count, buffer version, first frame (sentinel values). Only first frame and gutter-width geometry changes request a retained-GUI epoch reset; ordinary text edits and line-count changes use row hashes without bumping the content epoch.
@spec detect_invalidation( t(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer() ) :: t()
@spec detect_invalidation( t(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer() ) :: t()
@spec dirty?(t(), non_neg_integer()) :: boolean()
Returns true if the given buffer line needs re-rendering.
@spec mark_dirty(t(), [non_neg_integer()] | :all) :: t()
Marks specific buffer lines as needing re-render.
Pass :all to force a complete redraw. Pass a list of buffer line
numbers for targeted invalidation. If already fully dirty, adding
specific lines is a no-op.
Marks the next retained GUI frame as a frontend-state reset.
@spec prepare_epoch(t(), term()) :: {t(), non_neg_integer(), boolean()}
Prepares the retained GUI content epoch for the current frame.
@spec put_retained_rows(t(), %{optional(non_neg_integer()) => retained_row()}) :: t()
Replaces the retained-row map with the current frame's composed rows.
Stored wholesale (not merged) so the map stays bounded to the visible row set and never accumulates rows that scrolled out of view.
@spec put_retained_wrap_lines(t(), %{ optional(non_neg_integer()) => retained_wrap_line() }) :: t()
Replaces the retained wrapped-line map with the current frame's logical lines.
Stored wholesale (not merged) so the map stays bounded to the visible logical lines and never accumulates lines that scrolled out of view.
@spec put_total_visual_rows(t(), term(), non_neg_integer()) :: t()
Stores the wrapped visual row total for the current cache key.
@spec reset() :: t()
Returns a fresh cache with all lines dirty.
Use after any event that invalidates render state: buffer switch, resize, theme change, etc.
Returns a fresh cache invalidation while preserving retained-render epoch state.
@spec retained_rows(t()) :: %{optional(non_neg_integer()) => retained_row()}
Returns the {row_id => {input_hash, Row.t()}} map captured by the last
semantic content build. The Builder consults it to skip recomposing rows
whose cheap input fingerprint is unchanged.
@spec retained_wrap_lines(t()) :: %{ optional(non_neg_integer()) => retained_wrap_line() }
Returns the {buf_line => {input_hash, entries}} map captured by the last
semantic content build for wrapped windows. The Builder consults it to skip
recomposing and re-wrapping logical lines whose input fingerprint is unchanged.
@spec snapshot( t(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), context_fingerprint() ) :: t()
Snapshots tracking fields after a successful render pass.
Clears the dirty set and records the current frame's parameters so the next frame can detect what changed.
@spec snapshot( t(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), context_fingerprint() ) :: t()