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
impl SyntaxSnapshot
Sourcepub fn source(&self) -> &[u8] ⓘ
pub fn source(&self) -> &[u8] ⓘ
Cached source bytes that produced Self::tree.
Sourcepub fn registry(&self) -> &LangRegistry
pub fn registry(&self) -> &LangRegistry
Shared language registry. Query-driven consumers
(compute_syntax_folds, future textobjects / indents)
look up per-language compiled queries here.
Sourcepub fn text_version(&self) -> u64
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.
Sourcepub fn changed_lines(&self) -> Option<&[(u32, u32)]>
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.
Sourcepub fn reparsed_from_version(&self) -> u64
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.
Sourcepub fn parsed_text_version(&self) -> u64
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.
Sourcepub fn render_version(&self) -> u64
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”.
Sourcepub fn tree_reflects(&self, text_version: u64) -> bool
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.
Sourcepub fn cursor_in_string_scope(&self, cursor_byte: usize) -> bool
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.
Sourcepub fn collect_symbols(&self) -> Vec<String>
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.
Sourcepub fn collect_symbol_locations(&self) -> Vec<(String, u32, u32)>
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.
Sourcepub fn scope_at_cursor(
&self,
line: u32,
col_byte: u32,
capture_suffix: &str,
) -> Option<Range>
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).
Sourcepub fn scope_toward(
&self,
line: u32,
col_byte: u32,
suffix: &str,
dir: NavDir,
boundary: NavBoundary,
count: u32,
) -> Option<Position>
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).
Sourcepub fn highlight_lines(
&self,
start_line: u32,
end_line: u32,
) -> Result<Vec<Vec<StyledSpan>>, SyntaxError>
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.
Sourcepub fn highlight_lines_native(
&self,
start_line: u32,
end_line: u32,
) -> Result<Vec<Vec<StyledSpan>>, SyntaxError>
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
impl Clone for SyntaxSnapshot
Source§fn clone(&self) -> SyntaxSnapshot
fn clone(&self) -> SyntaxSnapshot
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for SyntaxSnapshot
impl Debug for SyntaxSnapshot
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).
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>
fn scope_at(&self, line: u32, col_byte: u32, suffix: &str) -> Option<Range>
(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>
fn scope_toward( &self, line: u32, col_byte: u32, suffix: &str, dir: NavDir, boundary: NavBoundary, count: u32, ) -> Option<Position>
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§
impl Freeze for SyntaxSnapshot
impl RefUnwindSafe for SyntaxSnapshot
impl Send for SyntaxSnapshot
impl Sync for SyntaxSnapshot
impl Unpin for SyntaxSnapshot
impl UnsafeUnpin for SyntaxSnapshot
impl UnwindSafe for SyntaxSnapshot
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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