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