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

Tracks which buffer ranges are currently folded (collapsed) in a window.

Pure data structure, no GenServer. Owned by each `Window`, so different
windows viewing the same buffer can have independent fold states (like
Neovim's per-window folds).

Internally stores a sorted list of non-overlapping `FoldRange` structs.
Lookups are linear scans over the sorted list. When the list is empty,
all translation functions are O(1) via guard clauses (zero overhead
for buffers without folds). This is adequate for typical fold counts
(tens, not thousands). The planned `DisplayMap` (#522) will replace
this module with an interval-tree-backed unified coordinate mapping.

## Coordinate translation

The fold map translates between two coordinate systems:

- **Buffer lines**: the actual line numbers in the document (0-indexed).
- **Visible lines**: the line numbers as displayed on screen, with folded
  regions collapsed to a single summary line.

A folded range `{start_line, end_line}` contributes exactly one visible
line (the start/summary line). The `end_line - start_line` hidden lines
are skipped.

## Design

Mirrors `WrapMap` in philosophy: a rendering concern, not a buffer
concern. Pure functions, stateless computation, per-window ownership.

# `t`

```elixir
@type t() :: %MingaEditor.FoldMap{folds: [Minga.Editing.Fold.Range.t()]}
```

The fold map: a sorted list of non-overlapping, currently-folded ranges.

# `buffer_to_visible`

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

Translates a buffer line number to a visible line number.

Each folded range hides `end_line - start_line` lines (the start line
remains visible as the summary). Lines before the first fold are
unchanged. Returns the buffer line number minus the total hidden lines
from folds that end before or contain this line.

For hidden lines (inside a fold), returns the visible line of the fold's
start line.

# `count`

```elixir
@spec count(t()) :: non_neg_integer()
```

Returns the number of active folds.

# `empty?`

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

Returns true if the fold map has no folds.

# `fold`

```elixir
@spec fold(t(), Minga.Editing.Fold.Range.t()) :: t()
```

Adds a fold range. Returns the updated fold map, or the original if the
range overlaps with an existing fold.

# `fold_all`

```elixir
@spec fold_all(t(), [Minga.Editing.Fold.Range.t()]) :: t()
```

Folds all provided ranges (non-overlapping only).

# `fold_at`

```elixir
@spec fold_at(t(), non_neg_integer()) :: {:ok, Minga.Editing.Fold.Range.t()} | :none
```

Returns the fold range containing the given line, or :none.

# `fold_start?`

```elixir
@spec fold_start?(t(), non_neg_integer()) :: boolean()
```

Returns true if the given buffer line is the start (summary) line of a fold.

# `folded?`

```elixir
@spec folded?(t(), non_neg_integer()) :: boolean()
```

Returns true if the given buffer line is hidden by a fold (inside but not the start line).

# `folds`

```elixir
@spec folds(t()) :: [Minga.Editing.Fold.Range.t()]
```

Returns the list of active fold ranges.

# `from_ranges`

```elixir
@spec from_ranges([Minga.Editing.Fold.Range.t()]) :: t()
```

Creates a fold map from a list of fold ranges. Removes overlaps by keeping earlier ranges.

# `innermost_range`

```elixir
@spec innermost_range([Minga.Editing.Fold.Range.t()], non_neg_integer()) ::
  Minga.Editing.Fold.Range.t() | nil
```

Finds the innermost (smallest) fold range containing the given line.

When multiple ranges contain the same line (e.g., a `def` inside a
`defmodule`), returns the one with the smallest span. This ensures
`za` folds the nearest enclosing block, not the outermost one.

# `new`

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

Creates an empty fold map.

# `next_visible`

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

Returns the next visible buffer line after the given buffer line.

Skips over folded regions. If the line is inside a fold, jumps to
the line after the fold's end.

# `prev_visible`

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

Returns the previous visible buffer line before the given buffer line.

Skips over folded regions. If the previous line is inside a fold,
jumps to the fold's start line.

# `toggle`

```elixir
@spec toggle(t(), non_neg_integer(), [Minga.Editing.Fold.Range.t()]) :: t()
```

Toggles the fold at the given buffer line. If the line is inside a fold,
removes it. If the line is the start of a foldable range in the provided
available ranges, adds the fold.

# `unfold_all`

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

Removes all folds.

# `unfold_at`

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

Removes the fold containing the given buffer line. Returns the updated
fold map. No-op if the line isn't in any fold.

# `unfold_containing`

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

Unfolds any fold that contains a line in the given list.

Used by search to auto-unfold matches.

# `visible_line_count`

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

Returns the total number of visible lines given the buffer's total line count.

# `visible_to_buffer`

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

Translates a visible line number to a buffer line number.

Inverse of `buffer_to_visible/2`. Walks through folds, adding back
the hidden lines to map from visible coordinates to buffer coordinates.

---

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