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.
Summary
Functions
Translates a buffer line number to a visible line number.
Returns the number of active folds.
Returns true if the fold map has no folds.
Adds a fold range. Returns the updated fold map, or the original if the range overlaps with an existing fold.
Folds all provided ranges (non-overlapping only).
Returns the fold range containing the given line, or :none.
Returns true if the given buffer line is the start (summary) line of a fold.
Returns true if the given buffer line is hidden by a fold (inside but not the start line).
Returns the list of active fold ranges.
Creates a fold map from a list of fold ranges. Removes overlaps by keeping earlier ranges.
Finds the innermost (smallest) fold range containing the given line.
Creates an empty fold map.
Returns the next visible buffer line after the given buffer line.
Returns the previous visible buffer line before the given buffer line.
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.
Removes all folds.
Removes the fold containing the given buffer line. Returns the updated fold map. No-op if the line isn't in any fold.
Unfolds any fold that contains a line in the given list.
Returns the total number of visible lines given the buffer's total line count.
Translates a visible line number to a buffer line number.
Types
@type t() :: %MingaEditor.FoldMap{folds: [Minga.Editing.Fold.Range.t()]}
The fold map: a sorted list of non-overlapping, currently-folded ranges.
Functions
@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.
@spec count(t()) :: non_neg_integer()
Returns the number of active folds.
Returns true if the fold map has no folds.
@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.
@spec fold_all(t(), [Minga.Editing.Fold.Range.t()]) :: t()
Folds all provided ranges (non-overlapping only).
@spec fold_at(t(), non_neg_integer()) :: {:ok, Minga.Editing.Fold.Range.t()} | :none
Returns the fold range containing the given line, or :none.
@spec fold_start?(t(), non_neg_integer()) :: boolean()
Returns true if the given buffer line is the start (summary) line of a fold.
@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).
@spec folds(t()) :: [Minga.Editing.Fold.Range.t()]
Returns the list of active fold ranges.
@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.
@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.
@spec new() :: t()
Creates an empty fold map.
@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.
@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.
@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.
Removes all folds.
@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.
@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.
@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.
@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.