Computes visual line breaks for soft word-wrapping.
Given a list of logical lines and a content width, produces a wrap map that tells the renderer how to split each logical line across multiple screen rows. All computation is pure and stateless; the wrap map is recomputed per frame for visible lines only (typically 30-50 lines), so performance is not a concern.
Design
- Break at word boundaries. Scans graphemes left to right, tracking display width. When the next grapheme would exceed the content width, breaks at the last whitespace position. If no whitespace exists in the line (a single long token), breaks at the content width exactly.
- Breakindent. Continuation rows preserve the logical line's leading whitespace, keeping indented code visually coherent.
- No buffer mutations. Wrapping is a rendering concern. The buffer stores logical lines unchanged.
Summary
Types
A wrap map: one entry per logical line in the visible range.
A single visual row within a wrapped logical line.
Wrap entry for one logical line: a list of visual rows it expands to. A non-wrapping line produces a single-element list.
Functions
Computes the wrap map for a list of logical lines.
Returns the display text for a visual row, including any breakindent prefix.
Converts a logical line index to the visual row offset from the start of the wrap map. Returns the screen row where the logical line begins.
Returns the total number of visual rows across all entries in a wrap map.
Types
@type t() :: [wrap_entry()]
A wrap map: one entry per logical line in the visible range.
@type visual_row() :: %{ text: String.t(), source_text: String.t(), byte_offset: non_neg_integer(), indent_width: non_neg_integer() }
A single visual row within a wrapped logical line.
text— the raw slice of the logical line for this visual rowsource_text— the slice of the logical line for this visual row, excluding artificial indentbyte_offset— byte offset from the start of the logical lineindent_width— artificial display columns prefixed beforesource_text
@type wrap_entry() :: [visual_row()]
Wrap entry for one logical line: a list of visual rows it expands to. A non-wrapping line produces a single-element list.
Functions
@spec compute([String.t()], pos_integer(), keyword()) :: t()
Computes the wrap map for a list of logical lines.
content_width is the number of display columns available for text
(viewport cols minus gutter width). When breakindent is true,
continuation rows are indented to match the logical line's leading
whitespace.
Returns a list with one wrap_entry per input line. Each entry is a
list of visual_row maps, one per screen row the line occupies.
@spec display_text(visual_row() | map()) :: String.t()
Returns the display text for a visual row, including any breakindent prefix.
@spec logical_to_visual(t(), non_neg_integer()) :: non_neg_integer()
Converts a logical line index to the visual row offset from the start of the wrap map. Returns the screen row where the logical line begins.
@spec visual_row_count(t()) :: non_neg_integer()
Returns the total number of visual rows across all entries in a wrap map.