Skip to main content

syntax/
document.rs

1//! A source kept parsed across edits.
2//!
3//! [`crate::highlight`] parses the whole text on every call. A [`Document`]
4//! holds the tree, takes each edit as it happens, and reparses from the old
5//! tree, so a keystroke costs a reparse of what it touched.
6//!
7//! Spans come back the way [`crate::highlight`] paints them: of the patterns
8//! that capture one node, the first in the query wins, and a node inside
9//! another paints over it.
10//!
11//! [`Document::new`] answers `None` for a [`Grammar::Wasm`] language and for a
12//! language with an injections query.
13
14use crate::lang::{self, Grammar, Lang};
15use std::{collections::HashSet, ops::Range};
16use theme::HighlightKind;
17use tree_sitter::{InputEdit, Parser, Point, Query, QueryCursor, StreamingIterator as _, Tree};
18
19/// One text, its language, and its current tree.
20pub struct Document {
21    parser: Parser,
22    query: Query,
23    /// Per capture index, what it paints. `None` is a capture outside
24    /// [`lang::NAMES`], which paints nothing but still claims its node.
25    kinds: Vec<Option<HighlightKind>>,
26    tree: Tree,
27    source: String,
28}
29
30impl Document {
31    /// Parse `source` as the language `tag` names.
32    pub fn new(tag: &str, source: impl Into<String>) -> Option<Self> {
33        Self::with_lang(lang::resolve(tag)?, source)
34    }
35
36    pub fn with_lang(lang: &'static Lang, source: impl Into<String>) -> Option<Self> {
37        if !lang.injections.is_empty() {
38            return None;
39        }
40        let language = match &lang.grammar {
41            Grammar::Native(grammar) => tree_sitter::Language::from(*grammar),
42            Grammar::Wasm(_) => return None,
43        };
44        let query = Query::new(&language, lang.query).ok()?;
45        let kinds = query
46            .capture_names()
47            .iter()
48            .map(|name| recognized(name).map(lang::kind_of))
49            .collect();
50        let mut parser = Parser::new();
51        parser.set_language(&language).ok()?;
52        let source = source.into();
53        let tree = parser.parse(&source, None)?;
54        Some(Self {
55            parser,
56            query,
57            kinds,
58            tree,
59            source,
60        })
61    }
62
63    /// The text as of the last edit.
64    pub fn source(&self) -> &str {
65        &self.source
66    }
67
68    /// `range` of the text held became the `new_end - range.start` bytes of
69    /// `source` that start at `range.start`, and `source` is the whole text
70    /// now. Reparses from the old tree and answers the byte ranges of `source`
71    /// whose spans may have changed.
72    pub fn edit(&mut self, range: Range<usize>, new_end: usize, source: &str) -> Vec<Range<usize>> {
73        let edit = InputEdit {
74            start_byte: range.start,
75            old_end_byte: range.end,
76            new_end_byte: new_end,
77            start_position: point_at(&self.source, range.start),
78            old_end_position: point_at(&self.source, range.end),
79            new_end_position: point_at(source, new_end),
80        };
81        self.tree.edit(&edit);
82        self.source.clear();
83        self.source.push_str(source);
84        let Some(tree) = self.parser.parse(&self.source, Some(&self.tree)) else {
85            return std::iter::once(0..self.source.len()).collect();
86        };
87        let mut changed: Vec<Range<usize>> = self
88            .tree
89            .changed_ranges(&tree)
90            .map(|range| range.start_byte..range.end_byte)
91            .collect();
92        // What was typed is changed whether or not the tree's shape moved.
93        changed.push(range.start..new_end);
94        self.tree = tree;
95        merge(changed)
96    }
97
98    /// Spans over the whole text, in document order.
99    pub fn spans(&self) -> Vec<(Range<usize>, HighlightKind)> {
100        self.spans_in(0..self.source.len())
101    }
102
103    /// Spans over the part of the text `range` covers, clipped to it.
104    pub fn spans_in(&self, range: Range<usize>) -> Vec<(Range<usize>, HighlightKind)> {
105        let mut cursor = QueryCursor::new();
106        cursor.set_byte_range(range.clone());
107        let mut captures =
108            cursor.captures(&self.query, self.tree.root_node(), self.source.as_bytes());
109        // One per node: the first pattern to capture it.
110        let mut seen = HashSet::new();
111        let mut claimed: Vec<(Range<usize>, Option<HighlightKind>)> = Vec::new();
112        while let Some((found, index)) = captures.next() {
113            let capture = found.captures()[*index];
114            if seen.insert(capture.node.id()) {
115                claimed.push((
116                    capture.node.byte_range(),
117                    self.kinds[capture.index as usize],
118                ));
119            }
120        }
121        claimed.sort_by(|a, b| a.0.start.cmp(&b.0.start).then(b.0.end.cmp(&a.0.end)));
122        flatten(
123            claimed
124                .into_iter()
125                .filter_map(|(span, kind)| Some((span, kind?))),
126            range,
127        )
128    }
129}
130
131/// Nested captures, outer first at a shared start, as flat spans where the
132/// innermost wins, clipped to `within`.
133fn flatten(
134    captures: impl Iterator<Item = (Range<usize>, HighlightKind)>,
135    within: Range<usize>,
136) -> Vec<(Range<usize>, HighlightKind)> {
137    let mut spans = Vec::new();
138    let mut stack: Vec<(usize, HighlightKind)> = Vec::new();
139    let mut at = within.start;
140    let mut paint = |to: usize, stack: &[(usize, HighlightKind)], at: &mut usize| {
141        let to = to.min(within.end);
142        if to > *at {
143            if let Some((_, kind)) = stack.last() {
144                spans.push((*at..to, *kind));
145            }
146            *at = to;
147        }
148    };
149    for (span, kind) in captures {
150        while let Some(&(end, _)) = stack.last() {
151            if end > span.start {
152                break;
153            }
154            paint(end, &stack, &mut at);
155            stack.pop();
156        }
157        paint(span.start, &stack, &mut at);
158        stack.push((span.end, kind));
159    }
160    while let Some(&(end, _)) = stack.last() {
161        paint(end, &stack, &mut at);
162        stack.pop();
163    }
164    spans
165}
166
167/// The row and byte column of `offset` in `text`.
168fn point_at(text: &str, offset: usize) -> Point {
169    let before = &text.as_bytes()[..offset.min(text.len())];
170    let row = before.iter().filter(|byte| **byte == b'\n').count();
171    let column = before
172        .iter()
173        .rposition(|byte| *byte == b'\n')
174        .map_or(before.len(), |newline| before.len() - newline - 1);
175    Point { row, column }
176}
177
178/// Sorted and joined where they touch.
179fn merge(mut ranges: Vec<Range<usize>>) -> Vec<Range<usize>> {
180    ranges.sort_by_key(|range| range.start);
181    let mut merged: Vec<Range<usize>> = Vec::new();
182    for range in ranges {
183        match merged.last_mut() {
184            Some(last) if range.start <= last.end => last.end = last.end.max(range.end),
185            _ => merged.push(range),
186        }
187    }
188    merged
189}
190
191/// The entry of [`lang::NAMES`] a capture name paints as: the one with the
192/// most dot-separated parts, all of them among the capture's. The rule
193/// `tree_sitter_highlight::HighlightConfiguration::configure` applies.
194fn recognized(capture: &str) -> Option<&'static str> {
195    let parts: Vec<&str> = capture.split('.').collect();
196    // The first of the longest, as `configure` breaks a tie.
197    let mut best: Option<(&'static str, usize)> = None;
198    for name in lang::NAMES {
199        let len = name.split('.').count();
200        if name.split('.').all(|part| parts.contains(&part))
201            && best.is_none_or(|(_, longest)| len > longest)
202        {
203            best = Some((name, len));
204        }
205    }
206    best.map(|(name, _)| name)
207}