# `MingaEditor.Window`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga_editor/window.ex#L1)

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.
Instead of rebuilding draw commands for every visible line on every frame,
the pipeline caches draws keyed by buffer line number and only re-renders
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)

Gutter and content caches are separate because cursor movement with
relative line numbering dirties every gutter entry without changing
content. This avoids re-rendering line text when only line numbers change.

Tracking fields (`last_viewport_top`, `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.

# `id`

```elixir
@type id() :: pos_integer()
```

Unique identifier for a window.

# `t`

```elixir
@type t() :: %MingaEditor.Window{
  buffer: pid(),
  content: MingaEditor.Window.Content.t(),
  cursor: Minga.Buffer.position(),
  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()) =&gt; [{non_neg_integer(), non_neg_integer()}]
  },
  viewport: MingaEditor.Viewport.t()
}
```

# `cache_line`

```elixir
@spec cache_line(t(), non_neg_integer(), [MingaEditor.DisplayList.draw()], [
  MingaEditor.DisplayList.draw()
]) :: t()
```

Stores rendered gutter and content draws for a buffer line.

Does NOT remove the line from the dirty set; that happens in
`snapshot_after_render/5` when the full frame is complete.

# `detect_context_change`

```elixir
@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`

```elixir
@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.

# `dirty?`

```elixir
@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`

```elixir
@spec fold_all(t()) :: t()
```

Folds all available ranges.

# `fold_at`

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

Folds the range containing the given buffer line.

# `has_folds?`

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

Returns true if this window has any active folds.

# `invalidate`

```elixir
@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`

```elixir
@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.

# `new`

```elixir
@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`

```elixir
@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`

```elixir
@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.Server 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`

```elixir
@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?`

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

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

# `prev_textobject`

```elixir
@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).

# `prune_cache`

```elixir
@spec prune_cache(t(), non_neg_integer(), non_neg_integer()) :: t()
```

Prunes cache entries for buffer lines no longer in the visible range.

Keeps the cache bounded to avoid memory growth as the user scrolls
through a large file.

# `resize`

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

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

# `scroll_viewport`

```elixir
@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_fold_ranges`

```elixir
@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.

# `snapshot_after_render`

```elixir
@spec snapshot_after_render(
  t(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  MingaEditor.Window.RenderCache.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. The context fingerprint captures
all per-frame render context inputs (visual selection, search matches,
syntax highlights, signs, etc.) so context changes trigger full redraws.

# `toggle_fold`

```elixir
@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`

```elixir
@spec unfold_all(t()) :: t()
```

Unfolds all folds.

# `unfold_at`

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

Unfolds the range containing the given buffer line.

# `unfold_containing`

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

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
