1use crate::{SyntaxError, language::resolve_language};
2use arborium_highlight::{Injection, Span};
3use arborium_tree_sitter::{
4 InputEdit, Language, Node, Parser, Point, Query, QueryCursor, StreamingIterator, Tree,
5};
6use std::{
7 collections::{BTreeMap, HashMap},
8 fmt,
9 sync::Arc,
10};
11
12const PARSER_CHUNK_BYTES: usize = 64;
15
16#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
17pub struct SyntaxWorkStats {
18 pub parser_input_bytes: usize,
19 pub queried_bytes: usize,
20 pub full_parses: usize,
21 pub incremental_parses: usize,
22 pub projected_bytes: usize,
23 pub projected_lines: usize,
24 pub reused_lines: usize,
25 pub compared_nodes: usize,
26}
27
28#[derive(Default)]
29pub(crate) struct Grammars(HashMap<String, Arc<Grammar>>);
30
31pub(crate) struct AppendContext<'a> {
33 pub grammars: &'a mut Grammars,
34 pub stats: &'a mut SyntaxWorkStats,
35 pub line_starts: &'a [usize],
37}
38
39pub(crate) struct IncrementalDocument {
40 parser: Option<Parser>,
41 tree: Option<Tree>,
42 grammar: Arc<Grammar>,
43 length: usize,
44 spans: BTreeMap<usize, Vec<Span>>,
45 injections: BTreeMap<usize, Vec<InjectedDocument>>,
46 boundaries: BTreeMap<usize, usize>,
47}
48
49impl Grammars {
50 pub(crate) fn document(
52 &mut self,
53 language: &str,
54 ) -> Result<Option<IncrementalDocument>, SyntaxError> {
55 let grammar = if let Some(grammar) = self.0.get(language) {
56 Arc::clone(grammar)
57 } else {
58 let Some((language_fn, highlights, injections)) = grammar_spec(language) else {
59 return Ok(None);
60 };
61 let compile = |source| {
62 Query::new(&language_fn, source).map_err(|source| SyntaxError::Query {
63 language: language.to_owned(),
64 source,
65 })
66 };
67 let query_source = format!("{highlights}\n{injections}");
68 let highlights = compile(highlights)?;
69 let injections = compile(injections)?;
70 let non_local = [&highlights, &injections].into_iter().any(|query| {
71 (0..query.pattern_count()).any(|index| query.is_pattern_non_local(index))
72 });
73 let grammar = Arc::new(Grammar {
74 highlights,
75 injections,
76 language: language_fn,
77 non_local,
78 query_source,
79 });
80 self.0.insert(language.to_owned(), Arc::clone(&grammar));
81 grammar
82 };
83 Ok(Some(IncrementalDocument {
84 parser: None,
85 tree: None,
86 grammar,
87 length: 0,
88 spans: BTreeMap::new(),
89 injections: BTreeMap::new(),
90 boundaries: BTreeMap::new(),
91 }))
92 }
93}
94
95impl IncrementalDocument {
96 pub(crate) fn append(
99 &mut self,
100 source: &str,
101 base: usize,
102 depth: usize,
103 cx: &mut AppendContext<'_>,
104 ) -> Result<usize, SyntaxError> {
105 if source.len() == self.length && self.tree.is_some() {
106 return Ok(source.len());
107 }
108 let tree = self.parse_tree(source, base, cx)?;
109 let mut start = if self.tree.is_none() || self.grammar.non_local {
110 0
111 } else {
112 self.length
113 };
114 if let Some(previous) = &self.tree {
115 for change in previous.changed_ranges(&tree) {
116 let refined = if change.start_byte == 0 {
117 self.grammar
118 .error_prefix(previous.root_node(), tree.root_node(), cx.stats)
119 } else {
120 None
121 };
122 start = start.min(refined.unwrap_or(change.start_byte));
123 }
124 }
125 loop {
128 let earlier = self
129 .boundaries
130 .range(start..)
131 .map(|(_, &begin)| begin)
132 .min()
133 .unwrap_or(start);
134 if earlier >= start {
135 break;
136 }
137 start = earlier;
138 }
139 let (spans, injections) = loop {
140 let result = query(&self.grammar, &tree, source, start, cx.stats);
141 let capture_start = result
142 .0
143 .iter()
144 .map(|span| span.start as usize)
145 .chain(result.1.iter().map(|injection| injection.start as usize))
146 .min()
147 .unwrap_or(start);
148 if capture_start < start {
149 start = capture_start;
150 } else {
151 break result;
152 }
153 };
154 self.boundaries.split_off(&start.saturating_add(1));
155 for (from, to) in spans.iter().map(|span| (span.start, span.end)).chain(
156 injections
157 .iter()
158 .map(|injection| (injection.start, injection.end)),
159 ) {
160 self.boundaries
161 .entry(to as usize)
162 .and_modify(|begin| *begin = (*begin).min(from as usize))
163 .or_insert(from as usize);
164 }
165 self.spans.split_off(&start);
166 for span in spans {
167 self.spans
168 .entry(span.start as usize)
169 .or_default()
170 .push(span);
171 }
172 self.update_injections(source, base, start, injections, depth, cx)?;
173 self.tree = Some(tree);
174 self.length = source.len();
175 Ok(start)
176 }
177
178 fn parse_tree(
179 &mut self,
180 source: &str,
181 base: usize,
182 cx: &mut AppendContext<'_>,
183 ) -> Result<Tree, SyntaxError> {
184 if self.parser.is_none() {
185 let mut parser = Parser::new();
186 parser.set_language(&self.grammar.language)?;
187 self.parser = Some(parser);
188 }
189 if let Some(tree) = &mut self.tree {
190 let old_end = end_point(cx.line_starts, base, self.length);
191 tree.edit(&InputEdit {
192 start_byte: self.length,
193 old_end_byte: self.length,
194 new_end_byte: source.len(),
195 start_position: old_end,
196 old_end_position: old_end,
197 new_end_position: end_point(cx.line_starts, base, source.len()),
198 });
199 cx.stats.incremental_parses += 1;
200 } else {
201 cx.stats.full_parses += 1;
202 }
203 let bytes = source.as_bytes();
204 let stats = &mut *cx.stats;
205 self.parser
206 .as_mut()
207 .expect("initialized parser")
208 .parse_with_options(
209 &mut |offset, _| {
210 let end = offset.saturating_add(PARSER_CHUNK_BYTES).min(bytes.len());
211 let input = &bytes[offset.min(bytes.len())..end];
212 stats.parser_input_bytes += input.len();
213 input
214 },
215 self.tree.as_ref(),
216 None,
217 )
218 .ok_or(SyntaxError::NoTree)
219 }
220
221 fn update_injections(
222 &mut self,
223 source: &str,
224 base: usize,
225 start: usize,
226 injections: Vec<Injection>,
227 depth: usize,
228 cx: &mut AppendContext<'_>,
229 ) -> Result<(), SyntaxError> {
230 let mut previous_injections = self.injections.split_off(&start);
231 if depth > 0 {
232 for injection in injections {
233 let from = injection.start as usize;
234 let to = injection.end as usize;
235 let Some(text) = source.get(from..to).filter(|text| !text.is_empty()) else {
236 continue;
237 };
238 let language = resolve_language(injection.language.as_str(), text)
239 .unwrap_or(&injection.language);
240 let previous = previous_injections.get_mut(&from).and_then(|entries| {
241 entries
242 .iter()
243 .position(|entry| {
244 entry.language == language && text.len() >= entry.document.length
245 })
246 .map(|index| entries.swap_remove(index))
247 });
248 let mut entry = match previous {
249 Some(entry) => entry,
250 None => match cx.grammars.document(language)? {
251 Some(document) => InjectedDocument {
252 language: language.to_owned(),
253 document,
254 },
255 None => continue,
256 },
257 };
258 entry.document.append(text, base + from, depth - 1, cx)?;
259 self.injections.entry(from).or_default().push(entry);
260 }
261 }
262 Ok(())
263 }
264
265 pub(crate) fn spans_from(&self, start: usize) -> Vec<Span> {
266 let mut result: Vec<_> = self
267 .spans
268 .range(start..)
269 .flat_map(|(_, spans)| spans.iter().cloned())
270 .collect();
271 for (&offset, injections) in self.injections.range(start..) {
272 for injection in injections {
273 for mut span in injection.document.spans_from(0) {
274 span.start += u32::try_from(offset).expect("source limit fits u32");
275 span.end += u32::try_from(offset).expect("source limit fits u32");
276 result.push(span);
277 }
278 }
279 }
280 result
281 }
282}
283
284impl Clone for IncrementalDocument {
285 fn clone(&self) -> Self {
286 Self {
287 parser: None,
288 tree: self.tree.clone(),
289 grammar: Arc::clone(&self.grammar),
290 length: self.length,
291 spans: self.spans.clone(),
292 injections: self.injections.clone(),
293 boundaries: self.boundaries.clone(),
294 }
295 }
296}
297
298impl fmt::Debug for IncrementalDocument {
299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300 f.debug_struct("IncrementalDocument")
301 .field("length", &self.length)
302 .finish_non_exhaustive()
303 }
304}
305
306#[derive(Clone)]
307struct InjectedDocument {
308 language: String,
309 document: IncrementalDocument,
310}
311
312struct Grammar {
313 language: Language,
314 highlights: Query,
315 injections: Query,
316 non_local: bool,
317 query_source: String,
318}
319
320impl Grammar {
321 fn error_prefix(
322 &self,
323 before: Node<'_>,
324 after: Node<'_>,
325 stats: &mut SyntaxWorkStats,
326 ) -> Option<usize> {
327 if self.non_local
328 || self.query_source.contains("ERROR")
329 || self.query_source.contains("(_ ")
330 || self.query_source.contains("(_\n")
331 {
332 return None;
333 }
334 let before = self.error_root(before)?;
335 let after = self.error_root(after)?;
336 let mut end = before.start_byte().min(after.start_byte());
337 let mut left = before.walk();
338 let mut right = after.walk();
339 for (before, after) in before.children(&mut left).zip(after.children(&mut right)) {
340 if !same_subtree(before, after, stats) {
341 break;
342 }
343 end = before.end_byte().min(after.end_byte());
344 }
345 Some(end)
346 }
347
348 fn error_root<'a>(&self, node: Node<'a>) -> Option<Node<'a>> {
349 if node.is_error() {
350 Some(node)
351 } else if !self.query_source.contains(node.kind()) && node.child_count() == 1 {
352 node.child(0).filter(Node::is_error)
353 } else {
354 None
355 }
356 }
357}
358
359fn same_subtree(before: Node<'_>, after: Node<'_>, stats: &mut SyntaxWorkStats) -> bool {
360 stats.compared_nodes += 1;
361 if before.byte_range() != after.byte_range() || before.has_changes() || after.has_changes() {
362 return false;
363 }
364 if before.id() == after.id() {
365 return true;
366 }
367 if before.kind_id() != after.kind_id() || before.child_count() != after.child_count() {
368 return false;
369 }
370 let mut left = before.walk();
371 let mut right = after.walk();
372 before
373 .children(&mut left)
374 .zip(after.children(&mut right))
375 .enumerate()
376 .all(|(index, (left, right))| {
377 let Ok(index) = u32::try_from(index) else {
378 return false;
379 };
380 before.field_name_for_child(index) == after.field_name_for_child(index)
381 && same_subtree(left, right, stats)
382 })
383}
384
385fn end_point(line_starts: &[usize], base: usize, len: usize) -> Point {
388 let first = line_starts.partition_point(|&start| start <= base);
389 let last = line_starts.partition_point(|&start| start <= base + len);
390 let column = line_starts[first..last]
391 .last()
392 .map_or(len, |&start| base + len - start);
393 Point::new(last - first, column)
394}
395
396fn query(
397 grammar: &Grammar,
398 tree: &Tree,
399 source: &str,
400 start: usize,
401 stats: &mut SyntaxWorkStats,
402) -> (Vec<Span>, Vec<Injection>) {
403 let mut cursor = QueryCursor::new();
404 cursor.set_byte_range(start..source.len());
405 stats.queried_bytes += source.len() - start;
406 let mut matches = cursor.matches(&grammar.highlights, tree.root_node(), source.as_bytes());
407 let mut spans = Vec::new();
408 while let Some(found) = matches.next() {
409 for capture in found.captures {
410 let name = grammar.highlights.capture_names()[capture.index as usize];
411 if name.starts_with('_') || name.starts_with("injection.") {
412 continue;
413 }
414 spans.push(Span {
415 start: u32::try_from(capture.node.start_byte()).expect("source limit fits u32"),
416 end: u32::try_from(capture.node.end_byte()).expect("source limit fits u32"),
417 capture: name.to_owned(),
418 pattern_index: u32::try_from(found.pattern_index).expect("query pattern fits u32"),
419 });
420 }
421 }
422 stats.queried_bytes += source.len() - start;
423 let mut matches = cursor.matches(&grammar.injections, tree.root_node(), source.as_bytes());
424 let mut injections = Vec::new();
425 while let Some(found) = matches.next() {
426 let mut content = None;
427 let mut language = None;
428 let mut include_children = false;
429 for property in grammar.injections.property_settings(found.pattern_index) {
430 match property.key.as_ref() {
431 "injection.language" => language = property.value.as_deref().map(str::to_owned),
432 "injection.include-children" => include_children = true,
433 _ => {}
434 }
435 }
436 for capture in found.captures {
437 match grammar.injections.capture_names()[capture.index as usize] {
438 "injection.content" => content = Some(capture.node),
439 "injection.language" if language.is_none() => {
440 language = capture
441 .node
442 .utf8_text(source.as_bytes())
443 .ok()
444 .map(str::to_owned);
445 }
446 _ => {}
447 }
448 }
449 if let (Some(node), Some(language)) = (content, language) {
450 injections.push(Injection {
451 start: u32::try_from(node.start_byte()).expect("source limit fits u32"),
452 end: u32::try_from(node.end_byte()).expect("source limit fits u32"),
453 language,
454 include_children,
455 });
456 }
457 }
458 (spans, injections)
459}
460
461fn grammar_spec(language: &str) -> Option<(Language, &'static str, &'static str)> {
462 macro_rules! grammar {
463 ($module:ident) => {{
464 use arborium::$module as grammar;
465 (
466 grammar::language().into(),
467 &grammar::HIGHLIGHTS_QUERY,
468 &grammar::INJECTIONS_QUERY,
469 )
470 }};
471 }
472 Some(match language {
473 "asm" => grammar!(lang_asm),
474 "bash" => grammar!(lang_bash),
475 "batch" => grammar!(lang_batch),
476 "c" => grammar!(lang_c),
477 "c-sharp" => grammar!(lang_c_sharp),
478 "clojure" => grammar!(lang_clojure),
479 "cmake" => grammar!(lang_cmake),
480 "commonlisp" => grammar!(lang_commonlisp),
481 "cpp" => grammar!(lang_cpp),
482 "css" => grammar!(lang_css),
483 "dart" => grammar!(lang_dart),
484 "diff" => grammar!(lang_diff),
485 "dockerfile" => grammar!(lang_dockerfile),
486 "elixir" => grammar!(lang_elixir),
487 "erlang" => grammar!(lang_erlang),
488 "fish" => grammar!(lang_fish),
489 "go" => grammar!(lang_go),
490 "graphql" => grammar!(lang_graphql),
491 "haskell" => grammar!(lang_haskell),
492 "hcl" => grammar!(lang_hcl),
493 "html" => grammar!(lang_html),
494 "ini" => grammar!(lang_ini),
495 "java" => grammar!(lang_java),
496 "javascript" => grammar!(lang_javascript),
497 "json" => grammar!(lang_json),
498 "just" => grammar!(lang_just),
499 "kotlin" => grammar!(lang_kotlin),
500 "lua" => grammar!(lang_lua),
501 "make" => grammar!(lang_make),
502 "markdown" => grammar!(lang_markdown),
503 "meson" => grammar!(lang_meson),
504 "ninja" => grammar!(lang_ninja),
505 "nix" => grammar!(lang_nix),
506 "objc" => grammar!(lang_objc),
507 "ocaml" => grammar!(lang_ocaml),
508 "perl" => grammar!(lang_perl),
509 "php" => grammar!(lang_php),
510 "powershell" => grammar!(lang_powershell),
511 "proto" => grammar!(lang_proto),
512 "python" => grammar!(lang_python),
513 "r" => grammar!(lang_r),
514 "rego" => grammar!(lang_rego),
515 "ruby" => grammar!(lang_ruby),
516 "rust" => grammar!(lang_rust),
517 "scala" => grammar!(lang_scala),
518 "scheme" => grammar!(lang_scheme),
519 "scss" => grammar!(lang_scss),
520 "solidity" => grammar!(lang_solidity),
521 "sql" => grammar!(lang_sql),
522 "starlark" => grammar!(lang_starlark),
523 "svelte" => grammar!(lang_svelte),
524 "swift" => grammar!(lang_swift),
525 "toml" => grammar!(lang_toml),
526 "tsx" => grammar!(lang_tsx),
527 "typescript" => grammar!(lang_typescript),
528 "vue" => grammar!(lang_vue),
529 "x86asm" => grammar!(lang_x86asm),
530 "xml" => grammar!(lang_xml),
531 "yaml" => grammar!(lang_yaml),
532 "zig" => grammar!(lang_zig),
533 "zsh" => grammar!(lang_zsh),
534 _ => return None,
535 })
536}