Skip to main content

VisualMap

Struct VisualMap 

Source
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: usize

The 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::image] 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 images is.

Implementations§

Source§

impl VisualMap

Source

pub fn num_rows(&self) -> usize

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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: ![](p.png) 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.

Source

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).

Source

pub fn row_is_navigable(&self, row: usize) -> bool

Whether the caret can occupy row at all: decoration rows (a table’s border rules) are stepped over by vertical motion.

Source

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.

Source

pub fn navigable_above(&self, row: usize) -> Option<usize>

The nearest row above row the caret can occupy, skipping decoration.

Source

pub fn navigable_below(&self, row: usize) -> Option<usize>

The nearest row below row the caret can occupy, skipping decoration.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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) toUITextInput.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 exactly what’s drawn on screen for that span.

Built from the same stop glyphs stop_after steps across (every glyph with Glyph::stop set, i.e. one per grapheme cluster, decoration excluded) — plus one inserted '\n' for every genuine block boundary strictly inside [from, to): a run of whole decoration rows sitting between two content rows — a paragraph gap, a table rule, an image’s reserved filler rows — never an ordinary soft wrap, which puts no decoration row between the two halves of its one paragraph (only inline decoration glyphs, e.g. a table’s , live inside a single content row, and never split one).

Without that inserted break, two blocks abutting in this string were indistinguishable from one run of text: [collect_stops] gives a block boundary zero stops of its own (crossing one is a single, free hop — see the_caret_skips_the_gap_between_two_paragraphs in doc.rs’s tests, which pins that as intentional caret behaviour, a paragraph gap costing no extra Right presses, not a bug to fix here). So the last word of one paragraph and the first word of the next used to land directly adjacent with nothing between them in this string ("...edb\n\nhello\n" read back as "edbhello"), and UITextInput’s default word tokenizer then saw one unbroken run of letters and selected across the boundary — reported as double-tapping the last word on a line expanding the selection into the following paragraph(s).

This means the once-strict equality with distance_offset/ step_offset (leaf-ffi) no longer always holds: those intentionally keep costing a block boundary zero stops, while this text now spends one character on it that is never itself a stop. So the relationship is visible_text(a, b).chars().count() >= distance_offset(a, b), equality holding whenever (a, b) spans no block boundary (the common case, and the only case the previous equality was ever tested against). It can only ever be greater, never less: every character this function omits relative to a plain stop count is a stop with no glyph of its own (a hidden delimiter, or a block’s own trailing “end of row” stop), and every such omission at a block’s end is exactly paired with the one inserted separator that follows it, so nothing this function returns is ever short of what a consumer walking stops one at a time would need. That inequality is still exactly what UITextInput‘s tokenizer needs: it only ever reads this string to find a boundary and converts the character index it finds back to a position with position(from:offset:), which walks stops — an inserted separator is never handed back as one, it only keeps two paragraphs’ words apart for the tokenizer’s letter-run scan.

from is snapped to its nearest stop first, exactly as a caret asked to stand at a hidden offset is drawn at the next stop instead; to is left as given, so a stop landing exactly on it is still the walk’s last step — the same asymmetry distance_offset’s own loop has.

Trait Implementations§

Source§

impl Default for VisualMap

Source§

fn default() -> VisualMap

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.