LavaUI docs GitHub

struct VisualLayout

Swift

struct VisualLayout

Where each visual row begins and ends, in character offsets over the whole buffer.

With wrapping, a visual row is no longer a logical line, and every mapping — caret, hit test, selection, vertical movement — has to use this rather than newline positions.

Sources/LavaText/SoftWrap.swift:116

Constructors

init(rows: [Range<Int>])

Creates a layout from row ranges. An empty list becomes a single empty row, so there is always a row for the caret.

Sources/LavaText/SoftWrap.swift:122

Properties

var count: Int { get }

How many visual rows there are.

Sources/LavaText/SoftWrap.swift:283

var rows: [Range<Int>]

Character range of each visual row over the whole buffer, in order.

Sources/LavaText/SoftWrap.swift:118

Methods

func column(ofOffset offset: Int, affinity: CaretAffinity = .downstream) -> Int

The caret's column within its row: characters from the row's start to offset. affinity picks the row at a wrap boundary, as for rowIndex(ofOffset:affinity:).

Sources/LavaText/SoftWrap.swift:343

static func lineIndex(_ text: String, includingBytes: Bool = true) -> LineIndex

The same scan, also reporting where each line sits in bytes.

Character offsets are what the row table is stated in. Byte offsets are what a cheap comparison of two buffers produces, and what slicing one line out of the buffer wants — and a wrapping editor needs both on every edit, so it asks for them together rather than scanning twice. Everything else asks for logicalRows and does not pay for the second array, which on a 200,000-line log is not a rounding error.

Sources/LavaText/SoftWrap.swift:149

static func logical(_ text: String) -> VisualLayout

One row per logical line — the no-wrapping case.

Sources/LavaText/SoftWrap.swift:127

static func logicalRows(_ text: String) -> [Range<Int>]

Row boundaries for logical(_:), exposed separately so a caller (an editor leaf with wrapping off) can cache the result via TextEditingState.setVisualRows instead of recomputing this O(length) scan on every layout access — which, left uncached, is what a large buffer felt: every caret draw, hit test, and gutter row rescanned the whole document.

Sources/LavaText/SoftWrap.swift:137

func offset(row: Int, column: Int) -> Int

Offset at row/column, clamped to that row.

Sources/LavaText/SoftWrap.swift:349

func rowIndex(ofOffset offset: Int, affinity: CaretAffinity = .downstream) -> Int

Row containing offset.

At a soft-wrap boundary one offset is both the end of a row and the start of the next, and there is no way to pick from the offset alone — which is why carets carry an affinity. Upstream keeps the caret on the row that ended (where a clamped vertical move should stay); downstream puts it on the row that starts (where a click or Home should land).

Sources/LavaText/SoftWrap.swift:292