Skip to main content

clankerdiff_syntax/
incremental.rs

1use crate::{
2    SyntaxError,
3    language::resolve_language,
4    spans::{Injection, Span},
5};
6use std::{
7    borrow::Cow,
8    collections::{BTreeMap, HashMap},
9    fmt,
10    sync::Arc,
11};
12use tree_sitter::{
13    InputEdit, Language, Node, Parser, Point, Query, QueryCursor, StreamingIterator, Tree,
14};
15
16/// Bytes handed to Tree-sitter per input callback. `parser_input_bytes` counts
17/// these requests, so smaller chunks measure re-lexing more precisely.
18const PARSER_CHUNK_BYTES: usize = 64;
19
20#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
21pub struct SyntaxWorkStats {
22    pub parser_input_bytes: usize,
23    pub queried_bytes: usize,
24    pub full_parses: usize,
25    pub incremental_parses: usize,
26    pub projected_bytes: usize,
27    pub projected_lines: usize,
28    pub reused_lines: usize,
29    pub compared_nodes: usize,
30}
31
32#[derive(Default)]
33pub(crate) struct Grammars(HashMap<String, Arc<Grammar>>);
34
35/// Shared state for one append across a document and its injections.
36pub(crate) struct AppendContext<'a> {
37    pub grammars: &'a mut Grammars,
38    pub stats: &'a mut SyntaxWorkStats,
39    /// Line starts of the root stream, for Tree-sitter edit positions.
40    pub line_starts: &'a [usize],
41}
42
43pub(crate) struct IncrementalDocument {
44    parser: Option<Parser>,
45    tree: Option<Tree>,
46    grammar: Arc<Grammar>,
47    length: usize,
48    spans: BTreeMap<usize, Vec<Span>>,
49    injections: BTreeMap<usize, Vec<InjectedDocument>>,
50    boundaries: BTreeMap<usize, usize>,
51}
52
53impl Grammars {
54    /// Creates an empty document, or `None` when no grammar is bundled.
55    pub(crate) fn document(
56        &mut self,
57        language: &str,
58    ) -> Result<Option<IncrementalDocument>, SyntaxError> {
59        let grammar = if let Some(grammar) = self.0.get(language) {
60            Arc::clone(grammar)
61        } else {
62            let Some((language_fn, highlights, injections)) = grammar_spec(language) else {
63                return Ok(None);
64            };
65            let highlights = if language == "solidity" {
66                Cow::Owned(highlights.replace(
67                    "(struct_expression type: ((expression (identifier)) @type .))",
68                    "(struct_expression type: (expression (identifier)) @type .)",
69                ))
70            } else {
71                Cow::Borrowed(highlights)
72            };
73            let compile = |source| {
74                Query::new(&language_fn, source).map_err(|source| SyntaxError::Query {
75                    language: language.to_owned(),
76                    source,
77                })
78            };
79            let query_source = format!("{highlights}\n{injections}");
80            let highlights = compile(&highlights)?;
81            let injections = compile(injections)?;
82            let non_local = [&highlights, &injections].into_iter().any(|query| {
83                (0..query.pattern_count()).any(|index| query.is_pattern_non_local(index))
84            });
85            let grammar = Arc::new(Grammar {
86                highlights,
87                injections,
88                language: language_fn,
89                non_local,
90                query_source,
91            });
92            self.0.insert(language.to_owned(), Arc::clone(&grammar));
93            grammar
94        };
95        Ok(Some(IncrementalDocument {
96            parser: None,
97            tree: None,
98            grammar,
99            length: 0,
100            spans: BTreeMap::new(),
101            injections: BTreeMap::new(),
102            boundaries: BTreeMap::new(),
103        }))
104    }
105}
106
107impl IncrementalDocument {
108    /// Appends the bytes of `source` beyond the previous length. `base` is the
109    /// document's offset within the root stream.
110    pub(crate) fn append(
111        &mut self,
112        source: &str,
113        base: usize,
114        depth: usize,
115        cx: &mut AppendContext<'_>,
116    ) -> Result<usize, SyntaxError> {
117        if source.len() == self.length && self.tree.is_some() {
118            return Ok(source.len());
119        }
120        let tree = self.parse_tree(source, base, cx)?;
121        let mut start = if self.tree.is_none() || self.grammar.non_local {
122            0
123        } else {
124            self.length
125        };
126        if let Some(previous) = &self.tree {
127            for change in previous.changed_ranges(&tree) {
128                let refined = if change.start_byte == 0 {
129                    self.grammar
130                        .error_prefix(previous.root_node(), tree.root_node(), cx.stats)
131                } else {
132                    None
133                };
134                start = start.min(refined.unwrap_or(change.start_byte));
135            }
136        }
137        // A capture ending exactly at the edit point may be an unterminated
138        // token that the appended text extends, so widen to it as well.
139        loop {
140            let earlier = self
141                .boundaries
142                .range(start..)
143                .map(|(_, &begin)| begin)
144                .min()
145                .unwrap_or(start);
146            if earlier >= start {
147                break;
148            }
149            start = earlier;
150        }
151        let (spans, injections) = loop {
152            let result = query(&self.grammar, &tree, source, start, cx.stats);
153            let capture_start = result
154                .0
155                .iter()
156                .map(|span| span.start as usize)
157                .chain(result.1.iter().map(|injection| injection.start as usize))
158                .min()
159                .unwrap_or(start);
160            if capture_start < start {
161                start = capture_start;
162            } else {
163                break result;
164            }
165        };
166        self.boundaries.split_off(&start.saturating_add(1));
167        for (from, to) in spans.iter().map(|span| (span.start, span.end)).chain(
168            injections
169                .iter()
170                .map(|injection| (injection.start, injection.end)),
171        ) {
172            self.boundaries
173                .entry(to as usize)
174                .and_modify(|begin| *begin = (*begin).min(from as usize))
175                .or_insert(from as usize);
176        }
177        self.spans.split_off(&start);
178        for span in spans {
179            self.spans
180                .entry(span.start as usize)
181                .or_default()
182                .push(span);
183        }
184        self.update_injections(source, base, start, injections, depth, cx)?;
185        self.tree = Some(tree);
186        self.length = source.len();
187        Ok(start)
188    }
189
190    fn parse_tree(
191        &mut self,
192        source: &str,
193        base: usize,
194        cx: &mut AppendContext<'_>,
195    ) -> Result<Tree, SyntaxError> {
196        if self.parser.is_none() {
197            let mut parser = Parser::new();
198            parser.set_language(&self.grammar.language)?;
199            self.parser = Some(parser);
200        }
201        if let Some(tree) = &mut self.tree {
202            let old_end = end_point(cx.line_starts, base, self.length);
203            tree.edit(&InputEdit {
204                start_byte: self.length,
205                old_end_byte: self.length,
206                new_end_byte: source.len(),
207                start_position: old_end,
208                old_end_position: old_end,
209                new_end_position: end_point(cx.line_starts, base, source.len()),
210            });
211            cx.stats.incremental_parses += 1;
212        } else {
213            cx.stats.full_parses += 1;
214        }
215        let bytes = source.as_bytes();
216        let stats = &mut *cx.stats;
217        self.parser
218            .as_mut()
219            .expect("initialized parser")
220            .parse_with_options(
221                &mut |offset, _| {
222                    let end = offset.saturating_add(PARSER_CHUNK_BYTES).min(bytes.len());
223                    let input = &bytes[offset.min(bytes.len())..end];
224                    stats.parser_input_bytes += input.len();
225                    input
226                },
227                self.tree.as_ref(),
228                None,
229            )
230            .ok_or(SyntaxError::NoTree)
231    }
232
233    fn update_injections(
234        &mut self,
235        source: &str,
236        base: usize,
237        start: usize,
238        injections: Vec<Injection>,
239        depth: usize,
240        cx: &mut AppendContext<'_>,
241    ) -> Result<(), SyntaxError> {
242        let mut previous_injections = self.injections.split_off(&start);
243        if depth > 0 {
244            for injection in injections {
245                let from = injection.start as usize;
246                let to = injection.end as usize;
247                let Some(text) = source.get(from..to).filter(|text| !text.is_empty()) else {
248                    continue;
249                };
250                let language = resolve_language(injection.language.as_str(), text)
251                    .unwrap_or(&injection.language);
252                let previous = previous_injections.get_mut(&from).and_then(|entries| {
253                    entries
254                        .iter()
255                        .position(|entry| {
256                            entry.language == language && text.len() >= entry.document.length
257                        })
258                        .map(|index| entries.swap_remove(index))
259                });
260                let mut entry = match previous {
261                    Some(entry) => entry,
262                    None => match cx.grammars.document(language)? {
263                        Some(document) => InjectedDocument {
264                            language: language.to_owned(),
265                            document,
266                        },
267                        None => continue,
268                    },
269                };
270                entry.document.append(text, base + from, depth - 1, cx)?;
271                self.injections.entry(from).or_default().push(entry);
272            }
273        }
274        Ok(())
275    }
276
277    pub(crate) fn spans_from(&self, start: usize) -> Vec<Span> {
278        let mut result: Vec<_> = self
279            .spans
280            .range(start..)
281            .flat_map(|(_, spans)| spans.iter().cloned())
282            .collect();
283        for (&offset, injections) in self.injections.range(start..) {
284            for injection in injections {
285                for mut span in injection.document.spans_from(0) {
286                    span.start += u32::try_from(offset).expect("source limit fits u32");
287                    span.end += u32::try_from(offset).expect("source limit fits u32");
288                    result.push(span);
289                }
290            }
291        }
292        result
293    }
294}
295
296impl Clone for IncrementalDocument {
297    fn clone(&self) -> Self {
298        Self {
299            parser: None,
300            tree: self.tree.clone(),
301            grammar: Arc::clone(&self.grammar),
302            length: self.length,
303            spans: self.spans.clone(),
304            injections: self.injections.clone(),
305            boundaries: self.boundaries.clone(),
306        }
307    }
308}
309
310impl fmt::Debug for IncrementalDocument {
311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312        f.debug_struct("IncrementalDocument")
313            .field("length", &self.length)
314            .finish_non_exhaustive()
315    }
316}
317
318#[derive(Clone)]
319struct InjectedDocument {
320    language: String,
321    document: IncrementalDocument,
322}
323
324struct Grammar {
325    language: Language,
326    highlights: Query,
327    injections: Query,
328    non_local: bool,
329    query_source: String,
330}
331
332impl Grammar {
333    fn error_prefix(
334        &self,
335        before: Node<'_>,
336        after: Node<'_>,
337        stats: &mut SyntaxWorkStats,
338    ) -> Option<usize> {
339        if self.non_local
340            || self.query_source.contains("ERROR")
341            || self.query_source.contains("(_ ")
342            || self.query_source.contains("(_\n")
343        {
344            return None;
345        }
346        let before = self.error_root(before)?;
347        let after = self.error_root(after)?;
348        let mut end = before.start_byte().min(after.start_byte());
349        let mut left = before.walk();
350        let mut right = after.walk();
351        for (before, after) in before.children(&mut left).zip(after.children(&mut right)) {
352            if !same_subtree(before, after, stats) {
353                break;
354            }
355            end = before.end_byte().min(after.end_byte());
356        }
357        Some(end)
358    }
359
360    fn error_root<'a>(&self, node: Node<'a>) -> Option<Node<'a>> {
361        if node.is_error() {
362            Some(node)
363        } else if !self.query_source.contains(node.kind()) && node.child_count() == 1 {
364            node.child(0).filter(Node::is_error)
365        } else {
366            None
367        }
368    }
369}
370
371fn same_subtree(before: Node<'_>, after: Node<'_>, stats: &mut SyntaxWorkStats) -> bool {
372    stats.compared_nodes += 1;
373    if before.byte_range() != after.byte_range() || before.has_changes() || after.has_changes() {
374        return false;
375    }
376    if before.id() == after.id() {
377        return true;
378    }
379    if before.kind_id() != after.kind_id() || before.child_count() != after.child_count() {
380        return false;
381    }
382    let mut left = before.walk();
383    let mut right = after.walk();
384    before
385        .children(&mut left)
386        .zip(after.children(&mut right))
387        .enumerate()
388        .all(|(index, (left, right))| {
389            let Ok(index) = u32::try_from(index) else {
390                return false;
391            };
392            before.field_name_for_child(index) == after.field_name_for_child(index)
393                && same_subtree(left, right, stats)
394        })
395}
396
397/// Row and column of byte `len` within a document that starts at `base` in
398/// the root stream whose line starts are `line_starts`.
399fn end_point(line_starts: &[usize], base: usize, len: usize) -> Point {
400    let first = line_starts.partition_point(|&start| start <= base);
401    let last = line_starts.partition_point(|&start| start <= base + len);
402    let column = line_starts[first..last]
403        .last()
404        .map_or(len, |&start| base + len - start);
405    Point::new(last - first, column)
406}
407
408fn query(
409    grammar: &Grammar,
410    tree: &Tree,
411    source: &str,
412    start: usize,
413    stats: &mut SyntaxWorkStats,
414) -> (Vec<Span>, Vec<Injection>) {
415    let mut cursor = QueryCursor::new();
416    cursor.set_byte_range(start..source.len());
417    stats.queried_bytes += source.len() - start;
418    let mut matches = cursor.matches(&grammar.highlights, tree.root_node(), source.as_bytes());
419    let mut spans = Vec::new();
420    while let Some(found) = matches.next() {
421        for capture in found.captures() {
422            let name = grammar.highlights.capture_names()[capture.index as usize];
423            if name.starts_with('_') || name.starts_with("injection.") {
424                continue;
425            }
426            spans.push(Span {
427                start: u32::try_from(capture.node.start_byte()).expect("source limit fits u32"),
428                end: u32::try_from(capture.node.end_byte()).expect("source limit fits u32"),
429                capture: name.to_owned(),
430                pattern_index: u32::try_from(found.pattern_index).expect("query pattern fits u32"),
431            });
432        }
433    }
434    stats.queried_bytes += source.len() - start;
435    let mut matches = cursor.matches(&grammar.injections, tree.root_node(), source.as_bytes());
436    let mut injections = Vec::new();
437    while let Some(found) = matches.next() {
438        let mut content = None;
439        let mut language = None;
440        for property in grammar.injections.property_settings(found.pattern_index) {
441            if property.key.as_ref() == "injection.language" {
442                language = property.value.as_deref().map(str::to_owned);
443            }
444        }
445        for capture in found.captures() {
446            match grammar.injections.capture_names()[capture.index as usize] {
447                "injection.content" => content = Some(capture.node),
448                "injection.language" if language.is_none() => {
449                    language = capture
450                        .node
451                        .utf8_text(source.as_bytes())
452                        .ok()
453                        .map(str::to_owned);
454                }
455                _ => {}
456            }
457        }
458        if let (Some(node), Some(language)) = (content, language) {
459            injections.push(Injection {
460                start: u32::try_from(node.start_byte()).expect("source limit fits u32"),
461                end: u32::try_from(node.end_byte()).expect("source limit fits u32"),
462                language,
463            });
464        }
465    }
466    (spans, injections)
467}
468
469fn grammar_spec(language: &str) -> Option<(Language, &'static str, &'static str)> {
470    macro_rules! grammar {
471        ($module:ident) => {{
472            use $module as grammar;
473            (
474                grammar::language().into(),
475                &grammar::HIGHLIGHTS_QUERY,
476                &grammar::INJECTIONS_QUERY,
477            )
478        }};
479    }
480    Some(match language {
481        "asm" => grammar!(arborium_asm),
482        "bash" => grammar!(arborium_bash),
483        "batch" => grammar!(arborium_batch),
484        "c" => grammar!(arborium_c),
485        "c-sharp" => grammar!(arborium_c_sharp),
486        "clojure" => grammar!(arborium_clojure),
487        "cmake" => grammar!(arborium_cmake),
488        "commonlisp" => grammar!(arborium_commonlisp),
489        "cpp" => grammar!(arborium_cpp),
490        "css" => grammar!(arborium_css),
491        "dart" => grammar!(arborium_dart),
492        "diff" => grammar!(arborium_diff),
493        "dockerfile" => grammar!(arborium_dockerfile),
494        "elixir" => grammar!(arborium_elixir),
495        "erlang" => grammar!(arborium_erlang),
496        "fish" => grammar!(arborium_fish),
497        "go" => grammar!(arborium_go),
498        "graphql" => grammar!(arborium_graphql),
499        "haskell" => grammar!(arborium_haskell),
500        "hcl" => grammar!(arborium_hcl),
501        "html" => grammar!(arborium_html),
502        "ini" => grammar!(arborium_ini),
503        "java" => grammar!(arborium_java),
504        "javascript" => grammar!(arborium_javascript),
505        "json" => grammar!(arborium_json),
506        "just" => grammar!(arborium_just),
507        "kotlin" => grammar!(arborium_kotlin),
508        "lua" => grammar!(arborium_lua),
509        "make" => grammar!(arborium_make),
510        "markdown" => grammar!(arborium_markdown),
511        "meson" => grammar!(arborium_meson),
512        "ninja" => grammar!(arborium_ninja),
513        "nix" => grammar!(arborium_nix),
514        "objc" => grammar!(arborium_objc),
515        "ocaml" => grammar!(arborium_ocaml),
516        "perl" => grammar!(arborium_perl),
517        "php" => grammar!(arborium_php),
518        "powershell" => grammar!(arborium_powershell),
519        "proto" => grammar!(arborium_proto),
520        "python" => grammar!(arborium_python),
521        "r" => grammar!(arborium_r),
522        "rego" => grammar!(arborium_rego),
523        "ruby" => grammar!(arborium_ruby),
524        "rust" => grammar!(arborium_rust),
525        "scala" => grammar!(arborium_scala),
526        "scheme" => grammar!(arborium_scheme),
527        "scss" => grammar!(arborium_scss),
528        "solidity" => grammar!(arborium_solidity),
529        "sql" => grammar!(arborium_sql),
530        "starlark" => grammar!(arborium_starlark),
531        "svelte" => grammar!(arborium_svelte),
532        "swift" => grammar!(arborium_swift),
533        "toml" => grammar!(arborium_toml),
534        "tsx" => grammar!(arborium_tsx),
535        "typescript" => grammar!(arborium_typescript),
536        "vue" => grammar!(arborium_vue),
537        "x86asm" => grammar!(arborium_x86asm),
538        "xml" => grammar!(arborium_xml),
539        "yaml" => grammar!(arborium_yaml),
540        "zig" => grammar!(arborium_zig),
541        "zsh" => grammar!(arborium_zsh),
542        _ => return None,
543    })
544}