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
16const 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
35pub(crate) struct AppendContext<'a> {
37 pub grammars: &'a mut Grammars,
38 pub stats: &'a mut SyntaxWorkStats,
39 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 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 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 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
397fn 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}