Skip to main content

lattice_syntax/
oneshot.rs

1//! D.3.b.2 (2026-05-29): one-shot synchronous highlight.
2//!
3//! Wraps `Syntax::for_language_with_registry` + `parse` +
4//! `snapshot().highlight_lines` into a single call so callers
5//! that don't own a long-lived `SyntaxHandle` (notably the
6//! diff overlay's deletion-block renderer, which parses
7//! an ephemeral baseline rope) can get styled spans without
8//! standing up an actor or wiring incremental state.
9//!
10//! No caching. Every call re-parses the source from scratch.
11//! The caller is expected to cache the returned spans
12//! alongside whatever revision it tracks for the source.
13//!
14//! See `docs/dev/architecture/diff-system.md` ยง6.6.
15
16use std::sync::Arc;
17
18use crate::{Lang, LangRegistry, StyledSpan, Syntax};
19
20/// Run a fresh tree-sitter parse over `source` and return
21/// per-line styled spans for lines in `start_line..end_line`.
22///
23/// Returns `None` when:
24/// - `lang` is `Lang::Plain` (no grammar to apply),
25/// - the registry has no registered grammar for `lang`,
26/// - the parse or highlight call fails for any reason
27///   (registry lookup error, malformed source, etc.) โ€” fail
28///   silently so the caller's "no syntax" fallback path
29///   (monochrome rendering) takes over without a panic.
30///
31/// `start_line` and `end_line` are inclusive-exclusive in
32/// line indices, matching
33/// [`crate::SyntaxSnapshot::highlight_lines`]. Returns one
34/// `Vec<StyledSpan>` per line in the range. Spans within
35/// each inner vec are line-relative byte offsets.
36///
37/// **Cost model.** Tree-sitter parse is O(source_len);
38/// highlight query walk is O(matches). For the diff
39/// deletion-block case where a typical baseline is hundreds
40/// to thousands of lines, both are well under 10 ms on
41/// modern hardware โ€” within
42/// `DiffOverlayRefreshTask`'s off-thread budget. Caller is
43/// expected to invoke at hunk-publish frequency (debounced
44/// at the session level), not per-frame.
45pub fn oneshot_highlight_lines(
46    lang: Lang,
47    registry: Arc<LangRegistry>,
48    source: &str,
49    start_line: u32,
50    end_line: u32,
51) -> Option<Vec<Vec<StyledSpan>>> {
52    let mut syntax = Syntax::for_language_with_registry(lang, registry)
53        .ok()
54        .flatten()?;
55    syntax.parse(source);
56    syntax.snapshot().highlight_lines(start_line, end_line).ok()
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn returns_none_for_plain_language() {
65        let registry = LangRegistry::standard().expect("standard registry");
66        let out = oneshot_highlight_lines(Lang::Plain, registry, "anything", 0, 1);
67        assert!(out.is_none());
68    }
69
70    #[test]
71    fn returns_some_for_rust_source_with_lines() {
72        let registry = LangRegistry::standard().expect("standard registry");
73        let source = "fn main() {\n    let x = 1;\n}\n";
74        let out = oneshot_highlight_lines(Lang::Rust, registry, source, 0, 3);
75        let lines = out.expect("rust grammar registered");
76        // Three source lines requested.
77        assert_eq!(lines.len(), 3);
78        // First line "fn main() {" should produce at least
79        // one styled span (the `fn` keyword).
80        assert!(!lines[0].is_empty(), "expected styled spans on line 0");
81    }
82
83    #[test]
84    fn empty_range_returns_empty_vec() {
85        let registry = LangRegistry::standard().expect("standard registry");
86        let out = oneshot_highlight_lines(Lang::Rust, registry, "fn main() {}", 0, 0).expect("ok");
87        assert!(out.is_empty());
88    }
89
90    #[test]
91    fn handles_empty_source_gracefully() {
92        // Parsing an empty string shouldn't panic; the
93        // highlight call against an empty rope returns
94        // either an empty vec or one empty line โ€” either is
95        // acceptable for the caller's fallback path. Just
96        // assert the call doesn't return None for a known
97        // language.
98        let registry = LangRegistry::standard().expect("standard registry");
99        let out = oneshot_highlight_lines(Lang::Rust, registry, "", 0, 1);
100        assert!(out.is_some());
101    }
102}