use tree_sitter::{Point, Range, Tree}; #[derive(Debug)] pub struct ScopeSequence(Vec); type ScopeStack = Vec<&'static str>; impl ScopeSequence { pub fn new(tree: &Tree) -> Self { let mut result = ScopeSequence(Vec::new()); let mut scope_stack = Vec::new(); let mut cursor = tree.walk(); let mut visited_children = false; loop { let node = cursor.node(); for _ in result.0.len()..node.start_byte() { result.0.push(scope_stack.clone()); } if visited_children { for _ in result.0.len()..node.end_byte() { result.0.push(scope_stack.clone()); } scope_stack.pop(); if cursor.goto_next_sibling() { visited_children = false; } else if !cursor.goto_parent() { break; } } else { scope_stack.push(cursor.node().kind()); if !cursor.goto_first_child() { visited_children = true; } } } result } pub fn check_changes( &self, other: &ScopeSequence, text: &Vec, known_changed_ranges: &Vec, ) -> Result<(), String> { if self.0.len() != text.len() { panic!( "Inconsistent scope sequence: {:?}", self.0 .iter() .zip(text.iter().map(|c| *c as char)) .collect::>() ); } assert_eq!(self.0.len(), other.0.len()); let mut position = Point { row: 0, column: 0 }; for (i, stack) in self.0.iter().enumerate() { let other_stack = &other.0[i]; if *stack != *other_stack && ![b'\r', b'\n'].contains(&text[i]) { let containing_range = known_changed_ranges .iter() .find(|range| range.start_point <= position && position < range.end_point); if containing_range.is_none() { let line = &text[(i - position.column)..] .split(|c| *c == '\n' as u8) .next() .unwrap(); return Err(format!( concat!( "Position: {}\n", "Byte offset: {}\n", "Line: {}\n", "{}^\n", "Old scopes: {:?}\n", "New scopes: {:?}\n", "Invalidated ranges: {:?}", ), position, i, String::from_utf8_lossy(line), String::from(" ").repeat(position.column + "Line: ".len()), stack, other_stack, known_changed_ranges, )); } } if text[i] == '\n' as u8 { position.row += 1; position.column = 0; } else { position.column += 1; } } Ok(()) } }