MingaEditor.Window (Minga v0.1.0)

Copy Markdown View Source

A window is a viewport into a buffer.

Each window holds a reference to a buffer process and its own independent viewport (scroll position and dimensions). Multiple windows can reference the same buffer; edits in one are visible in all.

Render cache and dirty-line tracking

Windows carry per-frame render state that enables incremental rendering. The semantic RenderModel.Window.Builder reuses retained composed rows for lines whose inputs are unchanged and only recomposes lines marked as dirty.

The dirty set uses two representations:

  • :all means every line needs re-rendering (used for scroll, resize, theme change, highlight update, and other wholesale invalidation)
  • A map of specific buffer line numbers (%{line => true}) that need re-rendering (used for edits that touch a few lines)

Tracking fields (last_viewport_top, last_viewport_cache_key, last_gutter_w, last_line_count, last_cursor_line, last_buf_version) store the values from the previous frame. The Scroll stage compares current values against these to detect full-invalidation triggers automatically.

Summary

Types

Unique identifier for a window.

t()

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 returns the window with dirty_lines: :all if anything that requires a full redraw has changed.

Returns true if the given buffer line needs re-rendering.

Folds all available ranges.

Folds the range containing the given buffer line.

Folds the outermost range containing the given buffer line and every nested range.

Returns true if this window has any active folds.

Marks all lines dirty (full redraw needed).

Marks specific buffer lines as needing re-render.

Marks the next retained GUI frame as a frontend-state reset without discarding TUI draw caches.

Creates a new window with the given id, buffer, and viewport dimensions.

Creates a new window with the given id, buffer, viewport dimensions, and cursor position.

Creates a new agent chat window.

Finds the next textobject position of the given type after (row, col).

Returns true if this window is a popup (has popup metadata attached).

Prepares the retained GUI content epoch for the current frame.

Finds the previous textobject position of the given type before (row, col).

Stores the current frame's retained composed rows for upstream reuse next frame (#2287).

Stores the current frame's retained wrapped logical lines for upstream reuse next frame (#2287).

Stores the wrapped visual row total for the current cache key.

Updates the viewport dimensions for this window, marking all lines dirty.

Returns the retained composed rows from the previous semantic content build (#2287).

Returns the retained wrapped logical lines from the previous semantic content build (#2287).

Scrolls the window horizontally by display columns.

Scrolls the window's viewport by delta lines and updates pinned state.

Updates the document symbols available for this window.

Updates the available fold ranges (from a provider). Preserves existing folds that still exist in the new ranges.

Sets whether the window should stay pinned to the bottom while content streams.

Stores the computed viewport for this window.

Toggles the fold at the given buffer line using the window's available fold ranges.

Unfolds all folds.

Unfolds the range containing the given buffer line.

Unfolds any folds that contain the given lines (used by search auto-unfold).

Unfolds every active fold inside the outermost range containing the given buffer line.

Types

id()

@type id() :: pos_integer()

Unique identifier for a window.

t()

@type t() :: %MingaEditor.Window{
  buffer: pid(),
  content: MingaEditor.Window.Content.t(),
  cursor: Minga.Buffer.position(),
  document_symbols: [Minga.Language.Symbol.t()],
  fold_map: MingaEditor.FoldMap.t(),
  fold_ranges: [Minga.Editing.Fold.Range.t()],
  id: id(),
  pinned: boolean(),
  popup_meta: MingaEditor.UI.Popup.Active.t() | nil,
  render_cache: MingaEditor.Window.RenderCache.t(),
  textobject_positions: %{
    required(atom()) => [{non_neg_integer(), non_neg_integer()}]
  },
  viewport: MingaEditor.Viewport.t()
}

Functions

cached_total_visual_rows(window, key)

@spec cached_total_visual_rows(t(), term()) :: non_neg_integer() | nil

Returns a cached wrapped visual row total when the key matches.

detect_context_change(window, fingerprint)

@spec detect_context_change(t(), MingaEditor.Window.RenderCache.context_fingerprint()) ::
  t()

Compares the current render context fingerprint against the last frame's.

If the fingerprint changed, marks all lines dirty. This catches changes to visual selection, search matches, syntax highlights, diagnostic signs, git signs, horizontal scroll, active/inactive status, and theme colors, all of which affect every visible line's draw output.

detect_invalidation(window, viewport_top, gutter_w, line_count, buf_version)

@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 returns the window with dirty_lines: :all if anything that requires a full redraw has changed.

Structural triggers (checked here): viewport scroll, gutter width, line count, buffer version, first frame (sentinel values).

Context triggers (checked separately via detect_context_change/2): visual selection, search matches, syntax highlights, diagnostic signs, git signs, viewport horizontal scroll, active status, theme colors.

detect_invalidation(window, viewport_top, gutter_w, line_count, buf_version, cursor_line)

@spec detect_invalidation(
  t(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer()
) :: t()

detect_invalidation(window, viewport_top, viewport_cache_key, gutter_w, line_count, buf_version, cursor_line)

@spec detect_invalidation(
  t(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer()
) :: t()

dirty?(window, line)

@spec dirty?(t(), non_neg_integer()) :: boolean()

Returns true if the given buffer line needs re-rendering.

Always true when dirty_lines is :all.

fold_all(window)

@spec fold_all(t()) :: t()

Folds all available ranges.

fold_at(window, line)

@spec fold_at(t(), non_neg_integer()) :: t()

Folds the range containing the given buffer line.

fold_recursive_at(window, line)

@spec fold_recursive_at(t(), non_neg_integer()) :: t()

Folds the outermost range containing the given buffer line and every nested range.

has_folds?(window)

@spec has_folds?(t()) :: boolean()

Returns true if this window has any active folds.

invalidate(window)

@spec invalidate(t()) :: t()

Marks all lines dirty (full redraw needed).

Clears all caches and resets tracking fields to sentinels so the next render pass starts from scratch. Use this when the window's buffer changes, on resize, or any other event that makes all cached draws invalid.

mark_dirty(window, lines)

@spec mark_dirty(t(), [non_neg_integer()] | :all) :: t()

Marks specific buffer lines as needing re-render.

Pass :all to force a complete redraw (scroll, resize, theme change, etc.). Pass a list of buffer line numbers for targeted invalidation (edits). If the window is already fully dirty, adding specific lines is a no-op.

mark_frontend_reset_pending(window)

@spec mark_frontend_reset_pending(t()) :: t()

Marks the next retained GUI frame as a frontend-state reset without discarding TUI draw caches.

new(id, buffer, rows, cols)

@spec new(id(), pid(), pos_integer(), pos_integer()) :: t()

Creates a new window with the given id, buffer, and viewport dimensions.

Sets both content (the polymorphic content reference) and buffer (backward-compatible pid field). During the migration, callers access window.buffer directly. Once all callers are updated to use Content.buffer_pid(window.content), the buffer field will be removed.

new(id, buffer, rows, cols, cursor)

@spec new(id(), pid(), pos_integer(), pos_integer(), Minga.Buffer.position()) :: t()

Creates a new window with the given id, buffer, viewport dimensions, and cursor position.

new_agent_chat(id, agent_buffer, rows, cols)

@spec new_agent_chat(id(), pid(), pos_integer(), pos_integer()) :: t()

Creates a new agent chat window.

The buffer field is set to the agent's *Agent* Buffer.Process pid for backward compatibility with code that reads window.buffer. The content field uses the :agent_chat tag so the render pipeline can dispatch to the agent chat renderer.

next_textobject(window, type, arg)

@spec next_textobject(t(), atom(), {non_neg_integer(), non_neg_integer()}) ::
  {non_neg_integer(), non_neg_integer()} | nil

Finds the next textobject position of the given type after (row, col).

popup?(window)

@spec popup?(t()) :: boolean()

Returns true if this window is a popup (has popup metadata attached).

prepare_render_epoch(window, reset_fingerprint)

@spec prepare_render_epoch(t(), term()) :: {t(), non_neg_integer(), boolean()}

Prepares the retained GUI content epoch for the current frame.

prev_textobject(window, type, arg)

@spec prev_textobject(t(), atom(), {non_neg_integer(), non_neg_integer()}) ::
  {non_neg_integer(), non_neg_integer()} | nil

Finds the previous textobject position of the given type before (row, col).

put_retained_rows(window, rows)

@spec put_retained_rows(t(), %{
  optional(non_neg_integer()) => MingaEditor.Window.RenderCache.retained_row()
}) :: t()

Stores the current frame's retained composed rows for upstream reuse next frame (#2287).

put_retained_wrap_lines(window, lines)

@spec put_retained_wrap_lines(
  t(),
  %{
    optional(non_neg_integer()) =>
      MingaEditor.Window.RenderCache.retained_wrap_line()
  }
) :: t()

Stores the current frame's retained wrapped logical lines for upstream reuse next frame (#2287).

put_total_visual_rows(window, key, total)

@spec put_total_visual_rows(t(), term(), non_neg_integer()) :: t()

Stores the wrapped visual row total for the current cache key.

resize(window, rows, cols)

@spec resize(t(), non_neg_integer(), non_neg_integer()) :: t()

Updates the viewport dimensions for this window, marking all lines dirty.

retained_rows(window)

@spec retained_rows(t()) :: %{
  optional(non_neg_integer()) => MingaEditor.Window.RenderCache.retained_row()
}

Returns the retained composed rows from the previous semantic content build (#2287).

retained_wrap_lines(window)

@spec retained_wrap_lines(t()) :: %{
  optional(non_neg_integer()) =>
    MingaEditor.Window.RenderCache.retained_wrap_line()
}

Returns the retained wrapped logical lines from the previous semantic content build (#2287).

scroll_horizontal(window, delta)

@spec scroll_horizontal(t(), integer()) :: t()

Scrolls the window horizontally by display columns.

scroll_viewport(window, delta, total_lines)

@spec scroll_viewport(t(), integer(), non_neg_integer()) :: t()

Scrolls the window's viewport by delta lines and updates pinned state.

Scrolling up always unpins. Scrolling down re-pins only when the viewport reaches the bottom. total_lines is the buffer's line count.

Returns the updated window.

set_document_symbols(window, symbols)

@spec set_document_symbols(t(), [Minga.Language.Symbol.t()]) :: t()

Updates the document symbols available for this window.

set_fold_ranges(window, new_ranges)

@spec set_fold_ranges(t(), [Minga.Editing.Fold.Range.t()]) :: t()

Updates the available fold ranges (from a provider). Preserves existing folds that still exist in the new ranges.

set_pinned(window, pinned?)

@spec set_pinned(t(), boolean()) :: t()

Sets whether the window should stay pinned to the bottom while content streams.

set_viewport(window, viewport)

@spec set_viewport(t(), MingaEditor.Viewport.t()) :: t()

Stores the computed viewport for this window.

snapshot_after_render(window, viewport_top, gutter_w, line_count, cursor_line, buf_version, ctx_fingerprint)

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. The context fingerprint captures all per-frame render context inputs (visual selection, search matches, syntax highlights, signs, etc.) so context changes trigger full redraws.

snapshot_after_render(window, viewport_top, viewport_cache_key, gutter_w, line_count, cursor_line, buf_version, ctx_fingerprint)

toggle_fold(window, line)

@spec toggle_fold(t(), non_neg_integer()) :: t()

Toggles the fold at the given buffer line using the window's available fold ranges.

unfold_all(window)

@spec unfold_all(t()) :: t()

Unfolds all folds.

unfold_at(window, line)

@spec unfold_at(t(), non_neg_integer()) :: t()

Unfolds the range containing the given buffer line.

unfold_containing(window, lines)

@spec unfold_containing(t(), [non_neg_integer()]) :: t()

Unfolds any folds that contain the given lines (used by search auto-unfold).

unfold_recursive_at(window, line)

@spec unfold_recursive_at(t(), non_neg_integer()) :: t()

Unfolds every active fold inside the outermost range containing the given buffer line.