1use std::sync::{Arc, OnceLock};
28
29use lattice_protocol::position::{Position, Range as NativeRange};
30use lattice_syntax::{Lang, SyntaxSnapshot};
31use streaming_iterator::StreamingIterator;
32use tree_sitter::{Node, Point, Query, QueryCursor, Tree};
33
34pub struct TreeSnapshotResource {
36 snapshot: Arc<SyntaxSnapshot>,
37}
38
39pub struct NodeResource {
61 snapshot: Arc<SyntaxSnapshot>,
62 path: Vec<u32>,
63 meta: OnceLock<NodeMeta>,
64 children: OnceLock<Arc<Children>>,
65}
66
67struct Children {
73 all: Vec<ChildMeta>,
74 named: Vec<u32>,
75}
76
77#[derive(Clone)]
79struct NodeMeta {
80 kind: Arc<str>,
81 range: NativeRange,
82 is_named: bool,
83 is_error: bool,
84}
85
86struct ChildMeta {
88 index: u32,
90 field: Option<Arc<str>>,
91 meta: NodeMeta,
92}
93
94fn meta_of(node: Node<'_>) -> NodeMeta {
95 NodeMeta {
96 kind: Arc::from(node.kind()),
97 range: NativeRange {
98 start: position_of(node.start_position()),
99 end: position_of(node.end_position()),
100 },
101 is_named: node.is_named(),
102 is_error: node.is_error(),
103 }
104}
105
106pub struct QueryResource {
110 query: Query,
111 lang: Lang,
112}
113
114pub struct CursorResource {
119 snapshot: Arc<SyntaxSnapshot>,
120 path: Vec<u32>,
121}
122
123fn point_of(pos: Position) -> Point {
124 Point {
125 row: pos.line as usize,
126 column: pos.byte as usize,
127 }
128}
129
130fn position_of(p: Point) -> Position {
131 Position {
132 line: p.row as u32,
133 byte: p.column as u32,
134 }
135}
136
137fn point_le(a: Point, b: Point) -> bool {
139 (a.row, a.column) <= (b.row, b.column)
140}
141
142fn resolve<'t>(tree: &'t Tree, path: &[u32]) -> Option<Node<'t>> {
145 let mut node = tree.root_node();
146 for &i in path {
147 node = node.child(i)?;
148 }
149 Some(node)
150}
151
152fn path_of(node: Node) -> Vec<u32> {
157 let mut path = Vec::new();
158 let mut cur = node;
159 while let Some(parent) = cur.parent() {
160 let mut idx = 0u32;
161 for i in 0..parent.child_count() {
162 if parent.child(i as u32).map(|c| c.id()) == Some(cur.id()) {
163 idx = i as u32;
164 break;
165 }
166 }
167 path.push(idx);
168 cur = parent;
169 }
170 path.reverse();
171 path
172}
173
174fn descend_to_point(tree: &Tree, point: Point) -> Option<Vec<u32>> {
185 let root = tree.root_node();
186 if !(point_le(root.start_position(), point) && point_le(point, root.end_position())) {
187 return None;
188 }
189 let mut cursor = tree.walk();
190 let mut path = Vec::new();
191 while let Some(idx) = cursor.goto_first_child_for_point(point) {
192 let child = cursor.node();
193 if !(point_le(child.start_position(), point) && point_le(point, child.end_position())) {
195 break;
196 }
197 path.push(idx as u32);
198 }
199 Some(path)
200}
201
202impl TreeSnapshotResource {
203 pub fn new(snapshot: Arc<SyntaxSnapshot>) -> Self {
205 Self { snapshot }
206 }
207
208 pub fn has_tree(&self) -> bool {
211 self.snapshot.tree().is_some()
212 }
213
214 fn node_at_path(&self, path: Vec<u32>) -> NodeResource {
216 NodeResource {
217 snapshot: Arc::clone(&self.snapshot),
218 path,
219 meta: OnceLock::new(),
220 children: OnceLock::new(),
221 }
222 }
223
224 pub fn root(&self) -> NodeResource {
226 self.node_at_path(Vec::new())
227 }
228
229 pub fn language(&self) -> String {
231 self.snapshot.lang().name().to_string()
232 }
233
234 pub fn node_at(&self, pos: Position) -> Option<NodeResource> {
238 let tree = self.snapshot.tree()?;
239 let mut path = descend_to_point(tree, point_of(pos))?;
240 while !path.is_empty() {
241 let node = resolve(tree, &path)?;
242 if node.is_named() {
243 break;
244 }
245 path.pop();
246 }
247 Some(self.node_at_path(path))
248 }
249
250 pub fn enclosing(&self, pos: Position, kinds: &[String]) -> Option<NodeResource> {
255 let tree = self.snapshot.tree()?;
256 let mut path = self.node_at(pos)?.path;
257 loop {
258 let node = resolve(tree, &path)?;
259 let matched = if kinds.is_empty() {
260 node.is_named()
261 } else {
262 kinds.iter().any(|k| k == node.kind())
263 };
264 if matched {
265 return Some(self.node_at_path(path));
266 }
267 if path.is_empty() {
268 return None;
269 }
270 path.pop();
271 }
272 }
273
274 pub fn compile_query(&self, source: &str) -> Result<QueryResource, String> {
278 let lang = self.snapshot.lang();
279 let language = self
280 .snapshot
281 .registry()
282 .tree_sitter_language(lang.name())
283 .ok_or_else(|| format!("no tree-sitter grammar for language '{}'", lang.name()))?;
284 let query = Query::new(&language, source).map_err(|e| e.to_string())?;
285 Ok(QueryResource { query, lang })
286 }
287
288 pub fn run_query(
294 &self,
295 query: &QueryResource,
296 within: Option<NativeRange>,
297 ) -> Vec<(String, NodeResource)> {
298 let Some(tree) = self.snapshot.tree() else {
299 return Vec::new();
300 };
301 if query.lang != self.snapshot.lang() {
302 return Vec::new();
303 }
304 let source = self.snapshot.source();
305 let names = query.query.capture_names();
306 let mut cursor = QueryCursor::new();
307 if let Some(r) = within {
308 cursor.set_point_range(point_of(r.start)..point_of(r.end));
309 }
310 let mut matches = cursor.matches(&query.query, tree.root_node(), source);
311 let mut out = Vec::new();
312 while let Some(m) = matches.next() {
313 for cap in m.captures {
314 let name = names
315 .get(cap.index as usize)
316 .copied()
317 .unwrap_or_default()
318 .to_string();
319 out.push((
320 name,
321 NodeResource {
322 snapshot: Arc::clone(&self.snapshot),
323 path: path_of(cap.node),
324 meta: OnceLock::from(meta_of(cap.node)),
325 children: OnceLock::new(),
326 },
327 ));
328 }
329 }
330 out
331 }
332
333 pub fn run_query_ranges(
347 &self,
348 query: &QueryResource,
349 within: Option<NativeRange>,
350 ) -> Vec<(String, u32, NativeRange)> {
351 let Some(tree) = self.snapshot.tree() else {
352 return Vec::new();
353 };
354 if query.lang != self.snapshot.lang() {
355 return Vec::new();
356 }
357 let source = self.snapshot.source();
358 let names = query.query.capture_names();
359 let mut cursor = QueryCursor::new();
360 if let Some(r) = within {
361 cursor.set_point_range(point_of(r.start)..point_of(r.end));
362 }
363 let mut matches = cursor.matches(&query.query, tree.root_node(), source);
364 let mut out = Vec::new();
365 let mut match_index: u32 = 0;
369 while let Some(m) = matches.next() {
370 for cap in m.captures {
371 let name = names
372 .get(cap.index as usize)
373 .copied()
374 .unwrap_or_default()
375 .to_string();
376 out.push((
377 name,
378 match_index,
379 NativeRange {
380 start: position_of(cap.node.start_position()),
381 end: position_of(cap.node.end_position()),
382 },
383 ));
384 }
385 match_index = match_index.saturating_add(1);
386 }
387 out
388 }
389}
390
391impl NodeResource {
392 fn tree(&self) -> Option<&Tree> {
393 self.snapshot.tree()
394 }
395
396 pub fn path(&self) -> &[u32] {
399 &self.path
400 }
401
402 fn with_path(&self, path: Vec<u32>) -> NodeResource {
403 NodeResource {
404 snapshot: Arc::clone(&self.snapshot),
405 path,
406 meta: OnceLock::new(),
407 children: OnceLock::new(),
408 }
409 }
410
411 fn with_path_and_meta(&self, path: Vec<u32>, meta: NodeMeta) -> NodeResource {
414 NodeResource {
415 snapshot: Arc::clone(&self.snapshot),
416 path,
417 meta: OnceLock::from(meta),
418 children: OnceLock::new(),
419 }
420 }
421
422 fn meta(&self) -> Option<&NodeMeta> {
424 if let Some(m) = self.meta.get() {
425 return Some(m);
426 }
427 let node = self.tree().and_then(|t| resolve(t, &self.path))?;
428 Some(self.meta.get_or_init(|| meta_of(node)))
429 }
430
431 fn children(&self) -> &Children {
433 self.children.get_or_init(|| {
434 let Some(node) = self.tree().and_then(|t| resolve(t, &self.path)) else {
435 return Arc::new(Children {
436 all: Vec::new(),
437 named: Vec::new(),
438 });
439 };
440 let mut cursor = node.walk();
441 let mut all: Vec<ChildMeta> = Vec::with_capacity(node.child_count());
442 let mut named: Vec<u32> = Vec::with_capacity(node.named_child_count());
443 if cursor.goto_first_child() {
444 let mut index = 0u32;
445 loop {
446 let meta = meta_of(cursor.node());
447 if meta.is_named {
448 named.push(all.len() as u32);
449 }
450 all.push(ChildMeta {
451 index,
452 field: cursor.field_name().map(Arc::from),
453 meta,
454 });
455 index = index.saturating_add(1);
456 if !cursor.goto_next_sibling() {
457 break;
458 }
459 }
460 }
461 Arc::new(Children { all, named })
462 })
463 }
464
465 pub fn kind(&self) -> String {
468 self.meta().map(|m| m.kind.to_string()).unwrap_or_default()
469 }
470
471 pub fn is_named(&self) -> bool {
472 self.meta().map(|m| m.is_named).unwrap_or(false)
473 }
474
475 pub fn is_error(&self) -> bool {
476 self.meta().map(|m| m.is_error).unwrap_or(false)
477 }
478
479 pub fn byte_range(&self) -> NativeRange {
482 let z = Position { line: 0, byte: 0 };
483 self.meta()
484 .map(|m| m.range)
485 .unwrap_or(NativeRange { start: z, end: z })
486 }
487
488 pub fn parent(&self) -> Option<NodeResource> {
490 if self.path.is_empty() {
491 return None;
492 }
493 let mut path = self.path.clone();
494 path.pop();
495 Some(self.with_path(path))
496 }
497
498 pub fn named_child_count(&self) -> u32 {
499 self.children().named.len() as u32
500 }
501
502 pub fn named_child(&self, index: u32) -> Option<NodeResource> {
505 let children = self.children();
506 let at = *children.named.get(index as usize)? as usize;
507 let child = children.all.get(at)?;
508 let mut path = self.path.clone();
509 path.push(child.index);
510 Some(self.with_path_and_meta(path, child.meta.clone()))
511 }
512
513 pub fn child_by_field(&self, name: &str) -> Option<NodeResource> {
515 let child = self
516 .children()
517 .all
518 .iter()
519 .find(|c| c.field.as_deref() == Some(name))?;
520 let mut path = self.path.clone();
521 path.push(child.index);
522 Some(self.with_path_and_meta(path, child.meta.clone()))
523 }
524
525 pub fn next_named_sibling(&self) -> Option<NodeResource> {
526 self.named_sibling(true)
527 }
528
529 pub fn prev_named_sibling(&self) -> Option<NodeResource> {
530 self.named_sibling(false)
531 }
532
533 fn named_sibling(&self, forward: bool) -> Option<NodeResource> {
536 let (&last, parent_path) = self.path.split_last()?;
537 let tree = self.tree()?;
538 let parent = resolve(tree, parent_path)?;
539 let cur = last as usize;
540 let candidate = if forward {
541 (cur + 1..parent.child_count()).find(|&i| {
542 parent
543 .child(i as u32)
544 .map(|c| c.is_named())
545 .unwrap_or(false)
546 })
547 } else {
548 (0..cur).rev().find(|&i| {
549 parent
550 .child(i as u32)
551 .map(|c| c.is_named())
552 .unwrap_or(false)
553 })
554 };
555 candidate.map(|i| {
556 let mut path = parent_path.to_vec();
557 path.push(i as u32);
558 self.with_path(path)
559 })
560 }
561
562 pub fn walk(&self) -> CursorResource {
564 CursorResource {
565 snapshot: Arc::clone(&self.snapshot),
566 path: self.path.clone(),
567 }
568 }
569}
570
571impl CursorResource {
572 fn tree(&self) -> Option<&Tree> {
573 self.snapshot.tree()
574 }
575
576 pub fn current_node(&self) -> NodeResource {
578 NodeResource {
579 snapshot: Arc::clone(&self.snapshot),
580 path: self.path.clone(),
581 meta: OnceLock::new(),
582 children: OnceLock::new(),
583 }
584 }
585
586 pub fn current_field(&self) -> Option<String> {
589 let (&last, parent_path) = self.path.split_last()?;
590 let tree = self.tree()?;
591 let parent = resolve(tree, parent_path)?;
592 parent.field_name_for_child(last).map(str::to_string)
593 }
594
595 pub fn goto_first_named_child(&mut self) -> bool {
597 let Some(tree) = self.tree() else {
598 return false;
599 };
600 let Some(node) = resolve(tree, &self.path) else {
601 return false;
602 };
603 for i in 0..node.child_count() {
604 if node.child(i as u32).map(|c| c.is_named()).unwrap_or(false) {
605 self.path.push(i as u32);
606 return true;
607 }
608 }
609 false
610 }
611
612 pub fn goto_next_named_sibling(&mut self) -> bool {
614 let Some((&last, parent_path)) = self.path.split_last() else {
615 return false;
616 };
617 let Some(tree) = self.tree() else {
618 return false;
619 };
620 let Some(parent) = resolve(tree, parent_path) else {
621 return false;
622 };
623 for i in (last as usize + 1)..parent.child_count() {
624 if parent
625 .child(i as u32)
626 .map(|c| c.is_named())
627 .unwrap_or(false)
628 {
629 let plen = self.path.len();
630 self.path[plen - 1] = i as u32;
631 return true;
632 }
633 }
634 false
635 }
636
637 pub fn goto_parent(&mut self) -> bool {
639 if self.path.is_empty() {
640 return false;
641 }
642 self.path.pop();
643 true
644 }
645
646 pub fn reset(&mut self, node: &NodeResource) {
648 self.reset_to_path(node.path.clone());
649 }
650
651 pub fn reset_to_path(&mut self, path: Vec<u32>) {
655 self.path = path;
656 }
657}
658
659#[cfg(test)]
660mod tests {
661 #![allow(clippy::unwrap_used, clippy::panic)]
662
663 use super::*;
664 use lattice_syntax::{Lang, Syntax};
665
666 fn rust_snapshot(src: &str) -> Arc<SyntaxSnapshot> {
667 let mut syntax = Syntax::for_language(Lang::Rust).unwrap().unwrap();
668 syntax.parse(src);
669 Arc::new(syntax.snapshot_owned())
670 }
671
672 fn pos(line: u32, byte: u32) -> Position {
673 Position { line, byte }
674 }
675
676 #[test]
677 fn root_is_the_source_file_and_language_is_rust() {
678 let snap = rust_snapshot("fn main() {}\n");
679 let ts = TreeSnapshotResource::new(snap);
680 assert!(ts.has_tree());
681 assert_eq!(ts.language(), "rust");
682 assert_eq!(ts.root().kind(), "source_file");
683 assert!(ts.root().is_named());
684 assert!(ts.root().parent().is_none());
685 }
686
687 #[test]
688 fn node_at_resolves_the_smallest_named_node() {
689 let src = "fn main() { let x = 1; }\n";
691 let ts = TreeSnapshotResource::new(rust_snapshot(src));
692 let x_col = src.find('x').unwrap() as u32;
693 let node = ts.node_at(pos(0, x_col)).unwrap();
694 assert_eq!(node.kind(), "identifier");
696 assert!(node.is_named());
697 let r = node.byte_range();
698 assert_eq!(r.start, pos(0, x_col));
699 assert_eq!(r.end, pos(0, x_col + 1));
700 }
701
702 #[test]
703 fn enclosing_finds_the_named_scope_by_kind() {
704 let src = "fn main() { let x = 1; }\n";
706 let ts = TreeSnapshotResource::new(rust_snapshot(src));
707 let x_col = src.find('x').unwrap() as u32;
708 let block = ts.enclosing(pos(0, x_col), &["block".to_string()]).unwrap();
709 assert_eq!(block.kind(), "block");
710 let r = block.byte_range();
711 assert_eq!(r.start, pos(0, src.find('{').unwrap() as u32));
713 assert_eq!(r.end, pos(0, (src.rfind('}').unwrap() + 1) as u32));
714 }
715
716 #[test]
717 fn enclosing_with_no_matching_kind_is_none() {
718 let src = "fn main() {}\n";
719 let ts = TreeSnapshotResource::new(rust_snapshot(src));
720 assert!(
721 ts.enclosing(pos(0, 3), &["nonexistent_kind".to_string()])
722 .is_none()
723 );
724 }
725
726 #[test]
727 fn enclosing_empty_kinds_is_the_nearest_named() {
728 let src = "fn main() { let x = 1; }\n";
729 let ts = TreeSnapshotResource::new(rust_snapshot(src));
730 let x_col = src.find('x').unwrap() as u32;
731 let node = ts.enclosing(pos(0, x_col), &[]).unwrap();
732 assert_eq!(node.kind(), "identifier");
733 }
734
735 #[test]
740 fn named_child_indexes_past_anonymous_children() {
741 let ts = TreeSnapshotResource::new(rust_snapshot("fn main() {}\n"));
745 let func = ts.root().named_child(0).unwrap();
746
747 let named: Vec<String> = (0..func.named_child_count())
748 .filter_map(|i| func.named_child(i))
749 .map(|n| n.kind())
750 .collect();
751 assert_eq!(named, vec!["identifier", "parameters", "block"]);
752
753 let by_field = func.child_by_field("name").unwrap();
756 let by_index = func.named_child(0).unwrap();
757 assert_eq!(by_field.kind(), by_index.kind());
758 assert_eq!(by_field.byte_range(), by_index.byte_range());
759 assert_eq!(by_field.path(), by_index.path());
760
761 assert_eq!(by_index.parent().unwrap().kind(), "function_item");
764 }
765
766 #[test]
770 fn cached_answers_match_a_fresh_resolve() {
771 let src = "fn a() { let x = 1; }\nfn b(y: u32) -> u32 { y }\n";
772 let ts = TreeSnapshotResource::new(rust_snapshot(src));
773 let root = ts.root();
774 for i in 0..root.named_child_count() {
775 let warmed = root.named_child(i).unwrap();
776 let (kind, range) = (warmed.kind(), warmed.byte_range());
778 let fresh = ts.node_at_path(warmed.path().to_vec());
779 assert_eq!(kind, fresh.kind());
780 assert_eq!(range, fresh.byte_range());
781 assert_eq!(warmed.is_named(), fresh.is_named());
782 assert_eq!(warmed.is_error(), fresh.is_error());
783 assert_eq!(warmed.named_child_count(), fresh.named_child_count());
784 }
785 }
786
787 #[test]
794 #[ignore = "diagnostic probe; prints a scaling table rather than asserting"]
795 fn node_api_scaling() {
796 for n in [50usize, 100, 200, 400, 800] {
797 let src: String = (0..n)
798 .map(|i| format!("fn f{i}() {{ let x = {i}; }}\n"))
799 .collect();
800 let ts = TreeSnapshotResource::new(rust_snapshot(&src));
801 let root = ts.root();
802 let started = std::time::Instant::now();
803 let count = root.named_child_count();
804 let mut matched = 0;
805 for i in 0..count {
806 let Some(child) = root.named_child(i) else {
807 continue;
808 };
809 if child.kind() == "function_item" {
810 matched += 1;
811 }
812 let _ = child.byte_range();
813 let _ = child.child_by_field("name");
814 }
815 println!(
816 " children={count:<5} -> {:>12?} ({matched} matched)",
817 started.elapsed()
818 );
819 }
820 }
821
822 #[test]
823 fn named_child_navigation_and_count() {
824 let ts = TreeSnapshotResource::new(rust_snapshot("fn main() {}\n"));
826 let root = ts.root();
827 assert_eq!(root.named_child_count(), 1);
828 let func = root.named_child(0).unwrap();
829 assert_eq!(func.kind(), "function_item");
830 assert!(root.named_child(1).is_none());
831 assert_eq!(func.parent().unwrap().kind(), "source_file");
833 }
834
835 #[test]
836 fn child_by_field_resolves_grammar_fields() {
837 let ts = TreeSnapshotResource::new(rust_snapshot("fn main() {}\n"));
839 let func = ts.root().named_child(0).unwrap();
840 let name = func.child_by_field("name").unwrap();
841 assert_eq!(name.kind(), "identifier");
842 assert_eq!(name.byte_range().start, pos(0, 3));
843 assert!(func.child_by_field("no_such_field").is_none());
844 }
845
846 #[test]
847 fn named_siblings_walk_in_both_directions() {
848 let src = "fn m() { let a = 1; let b = 2; }\n";
850 let ts = TreeSnapshotResource::new(rust_snapshot(src));
851 let a_col = src.find('a').unwrap() as u32;
852 let first = ts
854 .enclosing(pos(0, a_col), &["let_declaration".to_string()])
855 .unwrap();
856 let second = first.next_named_sibling().unwrap();
857 assert_eq!(second.kind(), "let_declaration");
858 let b_col = src.find('b').unwrap() as u32;
860 assert!(second.byte_range().start.byte <= b_col);
861 let back = second.prev_named_sibling().unwrap();
863 assert_eq!(back.byte_range().start, first.byte_range().start);
864 assert!(first.prev_named_sibling().is_none());
865 }
866
867 #[test]
868 fn compile_and_run_query_returns_predicate_filtered_captures() {
869 let src = "fn alpha() {}\nfn beta() {}\n";
870 let ts = TreeSnapshotResource::new(rust_snapshot(src));
871 let q = ts
872 .compile_query("(function_item name: (identifier) @fname)")
873 .expect("valid query compiles");
874 let caps = ts.run_query(&q, None);
875 assert_eq!(caps.len(), 2, "both functions captured");
876 assert!(caps.iter().all(|(name, _)| name == "fname"));
877 assert_eq!(caps[0].1.kind(), "identifier");
879 let starts: Vec<u32> = caps
880 .iter()
881 .map(|(_, n)| n.byte_range().start.byte)
882 .collect();
883 assert_eq!(starts, vec![3, 3]); }
885
886 #[test]
887 fn run_query_honors_a_text_predicate() {
888 let src = "fn alpha() {}\nfn beta() {}\n";
889 let ts = TreeSnapshotResource::new(rust_snapshot(src));
890 let q = ts
892 .compile_query("((function_item name: (identifier) @fname) (#eq? @fname \"beta\"))")
893 .expect("valid predicated query compiles");
894 let caps = ts.run_query(&q, None);
895 assert_eq!(caps.len(), 1, "the #eq? predicate is evaluated host-side");
896 assert_eq!(caps[0].1.byte_range().start.line, 1);
897 }
898
899 #[test]
905 fn run_query_ranges_agrees_with_run_query_extents() {
906 let src = "fn alpha() {}\nfn beta() {}\n";
907 let ts = TreeSnapshotResource::new(rust_snapshot(src));
908 let q = ts
909 .compile_query("(function_item name: (identifier) @fname)")
910 .expect("valid query compiles");
911
912 let nodes = ts.run_query(&q, None);
913 let ranges = ts.run_query_ranges(&q, None);
914
915 assert_eq!(ranges.len(), nodes.len());
916 for ((nname, node), (rname, _, range)) in nodes.iter().zip(ranges.iter()) {
917 assert_eq!(nname, rname);
918 let nr = node.byte_range();
919 assert_eq!(
920 (range.start.line, range.start.byte),
921 (nr.start.line, nr.start.byte)
922 );
923 assert_eq!((range.end.line, range.end.byte), (nr.end.line, nr.end.byte));
924 }
925 }
926
927 #[test]
933 fn run_query_ranges_groups_captures_by_match() {
934 let src = "fn alpha() {\n let x = 1;\n}\nfn beta() {}\n";
935 let ts = TreeSnapshotResource::new(rust_snapshot(src));
936 let q = ts
937 .compile_query("(function_item name: (identifier) @fname body: (_) @fbody)")
938 .expect("valid two-capture query compiles");
939
940 let caps = ts.run_query_ranges(&q, None);
941 assert_eq!(caps.len(), 4, "two functions x two captures");
942
943 let alpha: Vec<&(String, u32, NativeRange)> =
945 caps.iter().filter(|c| c.1 == caps[0].1).collect();
946 assert_eq!(alpha.len(), 2);
947 let mut names: Vec<&str> = alpha.iter().map(|c| c.0.as_str()).collect();
948 names.sort();
949 assert_eq!(names, vec!["fbody", "fname"]);
950
951 assert!(
953 caps.iter().any(|c| c.1 != caps[0].1),
954 "the second match must not share the first match's index"
955 );
956 }
957
958 #[test]
959 fn compile_query_rejects_a_malformed_query() {
960 let ts = TreeSnapshotResource::new(rust_snapshot("fn m() {}\n"));
961 let result = ts.compile_query("(this is not a valid query");
962 assert!(
963 matches!(&result, Err(msg) if !msg.is_empty()),
964 "malformed query is a typed error"
965 );
966 }
967
968 #[test]
969 fn cursor_walks_the_tree() {
970 let src = "fn m() { let x = 1; }\n";
971 let ts = TreeSnapshotResource::new(rust_snapshot(src));
972 let mut cursor = ts.root().walk();
973 assert_eq!(cursor.current_node().kind(), "source_file");
974 assert!(cursor.goto_first_named_child());
976 assert_eq!(cursor.current_node().kind(), "function_item");
977 assert_eq!(cursor.current_field(), None);
978 assert!(cursor.goto_parent());
980 assert_eq!(cursor.current_node().kind(), "source_file");
981 assert!(!cursor.goto_parent());
982 cursor.reset(&ts.root());
984 assert!(cursor.goto_first_named_child());
985 assert!(!cursor.goto_next_named_sibling(), "one top-level item");
986 }
987
988 #[test]
989 fn cursor_current_field_reports_the_grammar_field() {
990 let ts = TreeSnapshotResource::new(rust_snapshot("fn m() {}\n"));
991 let func = ts.root().named_child(0).unwrap();
993 let name = func.child_by_field("name").unwrap();
994 let mut cursor = name.walk();
995 assert_eq!(cursor.current_field(), Some("name".to_string()));
996 cursor.goto_parent();
997 assert_eq!(cursor.current_node().kind(), "function_item");
998 }
999
1000 #[test]
1001 fn run_query_for_a_different_language_is_empty() {
1002 let ts = TreeSnapshotResource::new(rust_snapshot("fn m() {}\n"));
1007 let q = ts.compile_query("(identifier) @id").unwrap();
1008 assert!(!ts.run_query(&q, None).is_empty());
1009 }
1010
1011 #[test]
1012 fn no_tree_snapshot_reports_absent() {
1013 let snap = Arc::new(
1016 Syntax::for_language(Lang::Rust)
1017 .unwrap()
1018 .unwrap()
1019 .snapshot_owned(),
1020 );
1021 let ts = TreeSnapshotResource::new(snap);
1022 assert!(!ts.has_tree());
1023 assert!(ts.node_at(pos(0, 0)).is_none());
1024 }
1025}