Skip to main content

SyntaxSnapshot

Struct SyntaxSnapshot 

Source
pub struct SyntaxSnapshot { /* private fields */ }
Expand description

Read-only view of one parse result.

Holds everything downstream consumers (renderer / folds / completion / picker) need to compute highlights, walk the tree, or query symbols – and nothing they don’t. Cheap to clone (every non-trivial field is Arc-shareable: Tree is internally Arc’d by tree-sitter; source is Arc<[u8]>; registry is Arc<LangRegistry>).

Lives behind an ArcSwap<SyntaxSnapshot> inside crate::SyntaxHandle so the render thread reads the latest parse at hardware-floor speed while a worker task runs the next reparse off the UI thread (paramount goal #1).

Implementations§

Source§

impl SyntaxSnapshot

Source

pub fn lang(&self) -> Lang

The document language this snapshot was built for.

Source

pub fn tree(&self) -> Option<&Tree>

Latest parse result. None until the first parse has run.

Source

pub fn source(&self) -> &[u8] ⓘ

Cached source bytes that produced Self::tree.

Source

pub fn registry(&self) -> &LangRegistry

Shared language registry. Query-driven consumers (compute_syntax_folds, future textobjects / indents) look up per-language compiled queries here.

Source

pub fn text_version(&self) -> u64

Document text_version this snapshot was built from. Used by the async handle to skip republishing identical state and by consumers that want to compare freshness against a DocumentSnapshot::text_version.

Source

pub fn changed_lines(&self) -> Option<&[(u32, u32)]>

H.2 (2026-06-04): inclusive source-line ranges whose syntax tree changed between Self::reparsed_from_version and this snapshot, from Tree::changed_ranges. None ⇒ full parse / unknown (treat the whole file as dirty). The cells worker rebuilds only the intersecting rows on a reparse-completion republish, gated on its cached matrix’s syntax version matching reparsed_from_version.

Source

pub fn reparsed_from_version(&self) -> u64

The text_version this snapshot’s tree was reparsed FROM — the baseline Self::changed_lines is the delta against. Meaningful only when changed_lines() is Some.

Source

pub fn parsed_text_version(&self) -> u64

The text_version this snapshot’s tree is a completed parse of. See Self::parsed_text_version for why this is not any of the other three version fields.

Source

pub fn render_version(&self) -> u64

The stamp a render cache should key on: it changes when either the text or the tree behind this snapshot changes.

text_version alone is not it, and that is not a subtlety — it is the stale-highlight bug. One edit produces TWO publishes from the syntax worker (slice C.2): an intermediate whose byte ranges are shifted to track the edit but whose tree shape is pre-parse, then the completed reparse. Both carry the same text_version. A cache keyed on text_version therefore cannot tell “shifted, not yet coloured” from “parsed, colours ready”: it invalidates on the intermediate, rebuilds without highlights, and the completed parse moves nothing — so the buffer stays at default colours until something drops the cache entirely (<C-l>).

parsed_text_version alone is not it either: it does not move on the intermediate, and the intermediate is what makes unchanged content paint at correct positions immediately. Both publishes must invalidate.

Summing them gives a value that is strictly increasing across intermediate → parsed → next intermediate → … and changes on every publish. It is a cache key, not a version anyone should compare for ordering — ask Self::tree_reflects when the question is “can I trust this tree”.

Source

pub fn tree_reflects(&self, text_version: u64) -> bool

Whether this snapshot’s tree is a completed parse of text_version.

The question every consumer that wants to trust the tree’s structure is actually asking. A byte-shifted intermediate snapshot answers false here even though it carries the right text_version, which is the whole reason this predicate exists rather than a bare comparison at each call site.

Source

pub fn cursor_in_string_scope(&self, cursor_byte: usize) -> bool

True when the byte position cursor_byte falls inside a string-literal node according to the cached tree. Walks ancestors from the deepest descendant covering the position and matches their kind() against a hardcoded set of string-shaped node names that span the v1 language coverage (rust / python / javascript). Returns false when no parse is cached, when the position falls outside the source bytes, or when no ancestor matches.

Used by the host’s gen:path insert-completion source (Phase 4.2.g.6 (2/2)) – the spec triggers path-completion inside string literals so file-path text is the only place where / opens the popup.

Source

pub fn collect_symbols(&self) -> Vec<String>

Run the language’s symbols.scm query against the cached tree and return the deduped list of @symbol-captured names (definition-position identifiers). Empty when: no parse yet, language has no symbols query, or the tree contains no matches.

Phase 4.2.g.6 (1/2): the host-orchestrated gen:tree-sitter-symbol insert-completion source calls this once per popup-trigger; cost is O(tree-size) for the cursor walk, which is sub-millisecond even on large source files.

Source

pub fn collect_symbol_locations(&self) -> Vec<(String, u32, u32)>

Like Self::collect_symbols but also reports each symbol’s location – (name, line, byte_column) with 0-based line and 0-based utf-8 byte column. Used by the picker’s :picker outline source so accept can jump directly to the symbol definition. Dedup keys on (name, line, col) to keep redundant captures (function name appearing in both @name and @definition captures of the same query) from doubling up.

Source

pub fn scope_at_cursor( &self, line: u32, col_byte: u32, capture_suffix: &str, ) -> Option<Range>

Find the innermost tree-sitter scope containing the cursor whose textobjects.scm capture name ends with capture_suffix (e.g. "function.outer", "class.outer", "block.outer") and return its inclusive 0-based (start_line, end_line) source rows.

“Innermost” = smallest byte span among the matching captures that contain the cursor byte, so a cursor inside a closure nested in a function resolves the closure for "function.outer", and a cursor on a statement resolves the tightest enclosing brace block for "block.outer". Returns None when no parse is cached, the language ships no textobjects.scm, the cursor line is out of range, or no matching capture contains the cursor.

line / col_byte are 0-based; col_byte is a utf-8 byte offset within the line (the snapshot’s position convention). Powers narrow-mode’s tree-sitter targets (:narrow-function / :narrow-class / :narrow-block, N.1.3); the plain (u32, u32) return keeps multibuffer / narrow types out of this crate (dependency direction: lattice-multibuffer -> lattice-syntax).

Source

pub fn scope_toward( &self, line: u32, col_byte: u32, suffix: &str, dir: NavDir, boundary: NavBoundary, count: u32, ) -> Option<Position>

The count-th node whose textobjects.scm capture name ends with suffix, scanning in dir, targeting the node’s boundary. Backs the structural motions (]f/[c/…, TSM.4) via [lattice_grammar::ScopeResolver::scope_toward].

Respects the enclosing-object rule (treesitter-motions.md §4.1): (Forward, Start) and (Backward, End) skip the object the cursor is currently inside (candidates strictly past the cursor byte); (Backward, Start) and (Forward, End) may land on the current object’s own boundary (candidates at-or-past the cursor byte), so e.g. jumping backward to a function start from inside its body lands on that function’s own fn keyword rather than skipping past it.

NavBoundary::End returns end_position (one past the last byte), matching Self::scope_at_cursor’s half-open convention – the operator’s inclusive-end handling adds the final byte back for d]F-style deletes.

Returns None gracefully (heuristic #5, no-op) when: there is no cached tree, the language ships no textobjects.scm, the cursor line is out of range, or there are fewer than count matching candidates in dir – never panics.

line / col_byte are 0-based, col_byte a utf-8 byte offset within the line (the snapshot’s position convention, same as Self::scope_at_cursor).

Source

pub fn highlight_lines( &self, start_line: u32, end_line: u32, ) -> Result<Vec<Vec<StyledSpan>>, SyntaxError>

Compute styled spans for each line in [start_line, end_line). start_line and end_line are 0-based and clamped to the source’s line count.

Returns one Vec<StyledSpan> per line in the requested range. Spans use line-relative byte offsets (consistent with the renderer’s existing assumption).

As of Step 4 this is a thin pass-through to the hand-rolled native pipeline (Self::highlight_lines_native); the legacy tree_sitter_highlight::Highlighter-based path was removed when its dependency was dropped from the workspace.

Source

pub fn highlight_lines_native( &self, start_line: u32, end_line: u32, ) -> Result<Vec<Vec<StyledSpan>>, SyntaxError>

Hand-rolled highlighter that runs highlights.scm directly against Self::tree() via tree_sitter::QueryCursor, bypassing tree_sitter_highlight::Highlighter. This is the Step 3 deliverable of the Option B migration: one parse per keystroke (the parser already feeds folds, future textobjects, indents, etc.) instead of the streaming highlighter’s parallel reparse.

As of Step 3b this method also recursively highlights ranges captured by injections.scm: markdown’s block→inline path (so **bold** inside a paragraph picks up Bold styling) and fenced-code blocks (so ```rust ... ``` inside a markdown buffer reuses the rust highlights). Recursion is bounded (one level deep per call site – markdown_inline has no further injections we honour today).

Trait Implementations§

Source§

impl Clone for SyntaxSnapshot

Source§

fn clone(&self) -> SyntaxSnapshot

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SyntaxSnapshot

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl ScopeResolver for SyntaxSnapshot

N.1.4b (2026-06-10): bridge the snapshot into the grammar dispatcher’s tree-sitter text-object resolution. The grammar crate defines the ScopeResolver trait (cursor -> enclosing scope row range) and stays tree-sitter-agnostic; this impl forwards to the snapshot’s existing SyntaxSnapshot::scope_at_cursor (N.1.0). The host coerces Arc<SyntaxSnapshot> to Arc<dyn ScopeResolver + Send + Sync> and threads it through Document::dispatch_with_scope_resolver so daf / yic etc. resolve against the live syntax tree off the UI thread (paramount #1: the snapshot is immutable, the query is bounded to the cursor’s 1-byte window).

Source§

fn scope_at(&self, line: u32, col_byte: u32, suffix: &str) -> Option<Range>

The innermost node at (line, col_byte) whose capture name ends with suffix (e.g. "function.outer"), as a byte-precise half-open [start, end) [ProtoRange] — byte columns, not just rows, so intra-line objects like aa / ia are charwise-accurate. End is exclusive, matching tree-sitter node ranges and the operator slice convention. None when there is no parse or no match.
Source§

fn scope_toward( &self, line: u32, col_byte: u32, suffix: &str, dir: NavDir, boundary: NavBoundary, count: u32, ) -> Option<Position>

The count-th node whose capture name ends with suffix, in dir, targeting the node’s boundary. Respects the enclosing-object rule (see treesitter-motions.md §4.1): (Forward, Start) / (Backward, End) skip the object the cursor is inside; (Backward, Start) / (Forward, End) may land on the current object’s own boundary. Returns the target position, or None (no tree / no match / fewer than count candidates).

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more