pub struct VisualMap {
pub rows: Vec<VRow>,
pub content_start: usize,
pub tables: Vec<TableInfo>,
pub code_blocks: Vec<CodeBlockInfo>,
pub media: Vec<MediaInfo>,
pub directives: Vec<DirectiveInfo>,
/* private fields */
}Expand description
The rendered document plus the offset⇄position mapping the caret rides on.
Fields§
§rows: Vec<VRow>The document’s default monospace rendering — one VRow of glyphs
per visual line, tables spelled with box-drawing borders (│ ─ ┌┬┐…) and
cells padded to whole character-cell columns. Any monospace surface can
draw these verbatim, so a consumer gets a working view for free: the TUI
paints them as-is, and a five-line plain-text dump would too.
It’s a default, not the only truth. A frontend with its own geometry —
a proportional GUI — lays text out in its own units, and for a table
skips the box-drawn rows named by TableInfo::rows_span and draws from
the structural TableInfo instead. The box glyphs live here rather than
in a frontend precisely because they are a renderable default: unlike a
colour (a role each surface must map to its own palette — see
crate::style), ┌─┐ is finished text that needs no interpretation.
content_start: usizeThe first source offset that is actually rendered — the caret floor for
the WYSIWYG view. Non-zero when a leading metadata block (YAML/TOML
frontmatter) is skipped: the frontmatter is preserved in the source and
editable in the source view, but hidden and unreachable here, so the
caret and selection can’t wander into it (and copy won’t grab it).
tables: Vec<TableInfo>Every table in the document, in order, described structurally rather than
drawn — see TableInfo for why both exist.
code_blocks: Vec<CodeBlockInfo>Every fenced/indented code block, in order, as the range of rows it
occupies — a frontend draws one bordered, tinted box around each and
scrolls it horizontally rather than wrapping. Derived from the per-row
VRow::code flag once the rows are final (so it survives incremental
row reuse), the same way [collect_stops] derives the stop table.
media: Vec<MediaInfo>Every block-level image in the document, in order — one per placeholder
row a frontend replaces with a real picture. Derived from the per-row
VRow::media mark once the rows are final (so it survives incremental
row reuse), the same way code_blocks is
derived from VRow::code.
directives: Vec<DirectiveInfo>Every leaf directive in the document, in order — one per placeholder
row a frontend may replace with whatever the host app’s vocabulary makes
of it. Derived from the per-row VRow::leaf_directive mark once the
rows are final, exactly as media is.
Implementations§
Source§impl VisualMap
impl VisualMap
pub fn num_rows(&self) -> usize
Sourcepub fn row_width(&self, row: usize) -> usize
pub fn row_width(&self, row: usize) -> usize
The width of row in display columns — the rightmost column its caret
can occupy, and so what a goal column is clamped to on the way in.
Sourcepub fn pos_of_offset(&self, off: usize) -> (usize, usize)
pub fn pos_of_offset(&self, off: usize) -> (usize, usize)
The screen (row, col) for a source offset — where to draw the caret:
the nearest stop at or past off. Snaps a hidden offset (inside a
delimiter) to the next visible glyph, and never resolves onto decoration
(a table border, a cell’s padding), which is drawn but holds no caret.
“Nearest” rather than “the first one found” because a table’s wrapped cells put rows slightly out of offset order: scanning top to bottom, the second line of column 1 comes after the first line of column 2 but holds smaller offsets. Where rows are in order the two rules agree.
A soft wrap is the one place two rows want the same offset: the row above ends where the row below opens, the space the wrap ate being drawn on the row above and the offset past it being the row below’s first character. It resolves downstream, to the row that character is on — the row above’s last column is a phantom, a place the caret can be drawn but never sent, and resolving upstream into it is what pinned Down at the first wrap of a paragraph: it aimed at the row below’s column 0, landed on the offset it already had, and read that back as the row above’s end.
Sourcepub fn row_range_for(&self, range: Range<usize>) -> (usize, usize)
pub fn row_range_for(&self, range: Range<usize>) -> (usize, usize)
The rows a source range occupies, inclusive: (first, last).
A different question from pos_of_offset, which
is why it can’t be spelled with two calls to it. That one answers “where
does the caret go”, and for a caret its forward snap is right — an offset
inside a hidden delimiter has no column of its own, so the caret belongs
at the next visible glyph, wherever that turns out to be. This one asks
“which rows does this block cover”, and there the snap is a trap: a
footnote whose body ends in a link ([^2]: [title](url)) has a last
byte inside the hidden destination, so pos_of_offset(end - 1) walked
clean off the note’s row and landed on the next note’s — and a peek
slicing first..=last out of the frame drew two notes where the reader
asked for one. Every block ending in a link, an image, or any trailing
hidden markup had the same fault; only a block ending in visible text
(which is what the tests happened to use) did not.
row.end_src is no help either: it is where the rendered text of a row
ends, not how far into the source the block reaches, and redefining it
would move every end-of-line caret.
So the last row is found by asking which rows open before the range
does, rather than by mapping its last byte: a row belongs to the range
when its first caret stop lies before range.end. Decoration is skipped
(a drawn gap between blocks is not part of either), and the answer is
never shorter than one row — a range whose every byte is hidden still
covers the row it started on.
Sourcepub fn task_box_at(&self, row: usize, col: usize) -> Option<usize>
pub fn task_box_at(&self, row: usize, col: usize) -> Option<usize>
The source offset of the task checkbox drawn at (row, col), or None
when that cell holds no box — the hit-test a frontend runs on a click
before treating it as a tick rather than a caret placement.
Only the box’s own cells answer. Clicking an item’s text places the
caret like any other click, so the box is a target aimed at rather than
something tripped over while editing — which is also why this is a
separate question from offset_of_pos instead of
a flag on the offset it returns.
Sourcepub fn task_box_at_glyph(&self, row: usize, glyph: usize) -> Option<usize>
pub fn task_box_at_glyph(&self, row: usize, glyph: usize) -> Option<usize>
task_box_at keyed by glyph index rather than
display column — for a frontend that shapes its own rows (the GUI) and so
resolves a click to a glyph before it ever has a column.
Sourcepub fn offset_of_pos(&self, row: usize, col: usize) -> usize
pub fn offset_of_pos(&self, row: usize, col: usize) -> usize
The source offset for a screen (row, col) — where a click or a
visual-space move lands the caret. Clicking decoration maps through its
src, which points at the text it decorates, so a click on a border or
on a cell’s padding lands in that cell.
The inverse of pos_of_offset, which it has to
agree with: col is a display column, and the one it names may be the
far cell of a wide glyph — VRow::glyph_at_col is where that lands.
Sourcepub fn block_media_stop(&self, off: usize) -> Option<(MediaStop, Range<usize>)>
pub fn block_media_stop(&self, off: usize) -> Option<(MediaStop, Range<usize>)>
Which of a block media’s two caret homes off is, or None for every
other offset in the document.
block_media gives a block-level image, video, or
audio exactly two stops — one in front of it and one just past it — and
nothing inside the markup. Both are ordinary offsets to everything else in
core, but they are the two places where inserting text would dissolve the
picture:  with anything typed against it is no longer a block
image but a paragraph with an inline one, and the frontend that was
painting a photo there paints a text run instead. A caller that is about to
insert asks this so it can open a paragraph first — see
Doc::insert.
An inline image reports None: it has no placeholder row and no stops of
its own, and typing beside one is ordinary editing.
Answers with the media’s own source span as well, since a caller that has
to keep the picture whole usually has to address it — Doc::backspace
takes the picture out in one piece rather than nibbling a byte off its
markup, which is the same dissolution from the other side.
Sourcepub fn table_end_stop(&self, off: usize) -> bool
pub fn table_end_stop(&self, off: usize) -> bool
Whether off is a table’s trailing caret stop — the one home past a
table’s last cell, at the block’s own end (TableInfo::end_src).
The table’s peer of block_media_stop’s
After: text inserted at that offset joins the table’s last source
line, and a line glued under a table is a row of it (| 1 | 2 |x), so
a caller about to insert there opens a paragraph first — see
Doc::insert. Nothing else about the offset is
special: it is where Down from the last row lands and where a click in
the blank space under a trailing table lands.
Sourcepub fn snap_to_stop(&self, off: usize) -> usize
pub fn snap_to_stop(&self, off: usize) -> usize
Snap off to the nearest caret stop — the funnel a frontend that
hit-tests pixels straight to a source offset must run its result through.
A click or drag can land in the blank gap a paragraph break is drawn with,
or inside a hidden delimiter; both are offsets the caret can’t rest at, so
resting there would draw the caret in one place and type in another. This
settles it on a real caret home instead. Idempotent on an offset that is
already a stop — the (row, col) click path already snaps this way inside
offset_of_pos, and this gives the pixel path the
same guarantee. Returns off unchanged only for an empty document (no
stops at all).
Sourcepub fn snap_to_glyph_stop(&self, off: usize) -> usize
pub fn snap_to_glyph_stop(&self, off: usize) -> usize
The glyph stop nearest off — nearest_stop
for a walk that pairs stops with characters, which a mark’s content
end has none of. A caret resting on one resolves to the glyph stop
drawn at the same spot, the one just past the hidden delimiter, so the
text a system input is shown from there and the steps it counts agree.
Sourcepub fn caret_stop_after(&self, off: usize) -> Option<usize>
pub fn caret_stop_after(&self, off: usize) -> Option<usize>
The next place the caret rests past off — the next glyph stop or the
next mark’s content end, whichever comes first. What Right walks:
leaving **bold** from the d is two presses, one onto the end of the
bold (still bold, the toolbar lit) and one past its delimiter, at the
same spot on screen. stop_after is the walk that
skips the first, for every caller that pairs stops with characters.
Sourcepub fn caret_stop_before(&self, off: usize) -> Option<usize>
pub fn caret_stop_before(&self, off: usize) -> Option<usize>
The previous place the caret rests before off — the mirror of
caret_stop_after, what Left walks.
Whether the caret can occupy row at all: decoration rows (a table’s
border rules) are stepped over by vertical motion.
Sourcepub fn row_start(&self, row: usize) -> Option<usize>
pub fn row_start(&self, row: usize) -> Option<usize>
The first offset the caret can rest at on row — its first stop, or the
row’s own end when it holds no text (an empty paragraph). None for a
decoration row, which holds no caret at all.
Not offset_of_pos(row, 0): column 0 of a quoted or listed row is the
gutter, and a gutter’s src points at the block it opens, so the stop
nearest it is the one on the block’s first row rather than on this one.
Which is right for a click — the gutter decorates the whole block — and
wrong for Home, whose whole question is where this row starts.
The nearest row above row the caret can occupy, skipping decoration.
The nearest row below row the caret can occupy, skipping decoration.
Sourcepub fn stop_before(&self, off: usize) -> Option<usize>
pub fn stop_before(&self, off: usize) -> Option<usize>
The caret stop just before off — one press of Left. None at the
first stop in the document.
Runs of decoration (a table border, a cell’s alignment padding) are stepped over in a single press: they hold no stop, so they aren’t in the table to land on.
Sourcepub fn stop_after(&self, off: usize) -> Option<usize>
pub fn stop_after(&self, off: usize) -> Option<usize>
The caret stop just after off — one press of Right. None at the last
stop in the document.
Sourcepub fn stop_at_or_after(&self, off: usize) -> Option<usize>
pub fn stop_at_or_after(&self, off: usize) -> Option<usize>
The first caret stop at or past off — where the caret at a hidden
offset is drawn, and so where a rightward walk over the rendered text
starts from.
Sourcepub fn stop_at_or_before(&self, off: usize) -> Option<usize>
pub fn stop_at_or_before(&self, off: usize) -> Option<usize>
The last caret stop at or before off — where a leftward walk starts
from. Snapping the way the walk is headed, rather than always forward,
is what keeps a leftward motion from ever moving the caret right.
Sourcepub fn is_stop(&self, off: usize) -> bool
pub fn is_stop(&self, off: usize) -> bool
Whether the caret may rest at off — the invariant every motion in this
view has to leave standing. A glyph stop, a row’s end, or a hidden
mark’s content end (mark_ends).
Sourcepub fn visible_text(&self, from: usize, to: usize) -> String
pub fn visible_text(&self, from: usize, to: usize) -> String
The visible text a caret crosses walking rightward from from up to
(but not including) to — UITextInput.text(in:)’s [from, to) in
this view. A hidden inline-mark delimiter (**, `, _, an
escape backslash) never got a glyph in the first place — see
[push_text]/[synth] — so it contributes nothing; what’s left is
what’s drawn on screen for that span, one character per caret stop.
Exactly one character per stop is the contract, and it is the
system text input’s, not a nicety: UITextInput’s tokenizer reads a
window of this text around a tap, indexes into it by the integer
offset(from:to:) reports (distance_offset in leaf-ffi, a count of
stop_after hops), finds a word boundary at some
character index, and hands the delta back through
position(from:offset:), which hops stops again. If the text ever
spends a character on something that is not a stop, or a stop on
nothing, every index past that point is off by one and the word the
reader double-tapped comes back shifted — into the header row of a
table, or one letter short. So a stop that draws a glyph is spelled
as that glyph, and a stop that draws none is spelled '\n':
- a row’s own end stop (
VRow::end_src) — the caret home past a paragraph’s, heading’s, list item’s, or code line’s last glyph. This is also what keeps two blocks’ words apart: without it the last word of one paragraph and the first of the next read as one run of letters ("…edb\n\nhello\n"came back as"edbhello"), and the tokenizer selected across the boundary. A list item’s end is a row end like any other, though no blank gap row follows it. - a table cell’s end, which
push_table_rowdraws as the gutter space before the next│so the caret has somewhere to stand past the cell’s last character. To a reader of this text a cell ends a line: spelled as a space, a touch surface that lands a tap at a word’s end past the space that follows it stepped into the next cell — or the next row, from the last column.
A hidden mark’s content end (mark_ends) is a place
the caret rests but not a stop the walks above count, so it has no
character here either; from is snapped to the glyph stop drawn at
the same spot first, exactly as snap_to_glyph_stop does for those
walks. to is left as given, so a stop landing exactly on it is still
excluded — the same half-open range distance_offset’s loop counts.
Sourcepub fn visible_utf16_len(&self, from: usize, to: usize) -> usize
pub fn visible_utf16_len(&self, from: usize, to: usize) -> usize
The UTF-16 length of visible_text(from, to) — what an NSRange
location into that text is, without building the string.
AppKit’s NSTextInputClient and NSAccessibility speak in UTF-16
units of the text as the system sees it, which for leaf is the visible
text — delimiters hidden. A frontend reporting its selection to the
system converts each end with this and gets back an index into the
string visible_text(0, end) returns, which is exactly what the system
will index into.
Sourcepub fn offset_at_visible_utf16(&self, to: usize, index: usize) -> Option<usize>
pub fn offset_at_visible_utf16(&self, to: usize, index: usize) -> Option<usize>
The inverse of visible_utf16_len(0, ·): the source offset of the
visible character a UTF-16 index into visible_text(0, to) lands on.
An index inside a surrogate pair resolves to the character that owns
it; one at or past the end of the text returns None, so a caller can
substitute the document’s end stop. The \n a row’s or a cell’s end
is spelled with resolves to that end stop — a caret home, so a caller
placing a caret there needs no snap.