This function (and the similar `ts_tree_cursor_goto_first_child_for_byte`) allows you to efficiently seek the tree cursor to a given position, exploiting the tree's internal balancing, without having to visit all of the preceding siblings of each node.
520 lines
17 KiB
Rust
520 lines
17 KiB
Rust
use super::helpers::edits::invert_edit;
|
|
use super::helpers::fixtures::get_language;
|
|
use crate::parse::{perform_edit, Edit};
|
|
use std::str;
|
|
use tree_sitter::{InputEdit, Parser, Point, Range, Tree};
|
|
|
|
#[test]
|
|
fn test_tree_edit() {
|
|
let mut parser = Parser::new();
|
|
parser.set_language(get_language("javascript")).unwrap();
|
|
let tree = parser.parse(" abc !== def", None).unwrap();
|
|
|
|
assert_eq!(
|
|
tree.root_node().to_sexp(),
|
|
"(program (expression_statement (binary_expression left: (identifier) right: (identifier))))"
|
|
);
|
|
|
|
// edit entirely within the tree's padding:
|
|
// resize the padding of the tree and its leftmost descendants.
|
|
{
|
|
let mut tree = tree.clone();
|
|
tree.edit(&InputEdit {
|
|
start_byte: 1,
|
|
old_end_byte: 1,
|
|
new_end_byte: 2,
|
|
start_position: Point::new(0, 1),
|
|
old_end_position: Point::new(0, 1),
|
|
new_end_position: Point::new(0, 2),
|
|
});
|
|
|
|
let expr = tree.root_node().child(0).unwrap().child(0).unwrap();
|
|
let child1 = expr.child(0).unwrap();
|
|
let child2 = expr.child(1).unwrap();
|
|
|
|
assert!(expr.has_changes());
|
|
assert_eq!(expr.start_byte(), 3);
|
|
assert_eq!(expr.end_byte(), 16);
|
|
assert!(child1.has_changes());
|
|
assert_eq!(child1.start_byte(), 3);
|
|
assert_eq!(child1.end_byte(), 6);
|
|
assert!(!child2.has_changes());
|
|
assert_eq!(child2.start_byte(), 8);
|
|
assert_eq!(child2.end_byte(), 11);
|
|
}
|
|
|
|
// edit starting in the tree's padding but extending into its content:
|
|
// shrink the content to compenstate for the expanded padding.
|
|
{
|
|
let mut tree = tree.clone();
|
|
tree.edit(&InputEdit {
|
|
start_byte: 1,
|
|
old_end_byte: 4,
|
|
new_end_byte: 5,
|
|
start_position: Point::new(0, 1),
|
|
old_end_position: Point::new(0, 5),
|
|
new_end_position: Point::new(0, 5),
|
|
});
|
|
|
|
let expr = tree.root_node().child(0).unwrap().child(0).unwrap();
|
|
let child1 = expr.child(0).unwrap();
|
|
let child2 = expr.child(1).unwrap();
|
|
|
|
assert!(expr.has_changes());
|
|
assert_eq!(expr.start_byte(), 5);
|
|
assert_eq!(expr.end_byte(), 16);
|
|
assert!(child1.has_changes());
|
|
assert_eq!(child1.start_byte(), 5);
|
|
assert_eq!(child1.end_byte(), 6);
|
|
assert!(!child2.has_changes());
|
|
assert_eq!(child2.start_byte(), 8);
|
|
assert_eq!(child2.end_byte(), 11);
|
|
}
|
|
|
|
// insertion at the edge of a tree's padding:
|
|
// expand the tree's padding.
|
|
{
|
|
let mut tree = tree.clone();
|
|
tree.edit(&InputEdit {
|
|
start_byte: 2,
|
|
old_end_byte: 2,
|
|
new_end_byte: 4,
|
|
start_position: Point::new(0, 2),
|
|
old_end_position: Point::new(0, 2),
|
|
new_end_position: Point::new(0, 4),
|
|
});
|
|
|
|
let expr = tree.root_node().child(0).unwrap().child(0).unwrap();
|
|
let child1 = expr.child(0).unwrap();
|
|
let child2 = expr.child(1).unwrap();
|
|
|
|
assert!(expr.has_changes());
|
|
assert_eq!(expr.start_byte(), 4);
|
|
assert_eq!(expr.end_byte(), 17);
|
|
assert!(child1.has_changes());
|
|
assert_eq!(child1.start_byte(), 4);
|
|
assert_eq!(child1.end_byte(), 7);
|
|
assert!(!child2.has_changes());
|
|
assert_eq!(child2.start_byte(), 9);
|
|
assert_eq!(child2.end_byte(), 12);
|
|
}
|
|
|
|
// replacement starting at the edge of the tree's padding:
|
|
// resize the content and not the padding.
|
|
{
|
|
let mut tree = tree.clone();
|
|
tree.edit(&InputEdit {
|
|
start_byte: 2,
|
|
old_end_byte: 2,
|
|
new_end_byte: 4,
|
|
start_position: Point::new(0, 2),
|
|
old_end_position: Point::new(0, 2),
|
|
new_end_position: Point::new(0, 4),
|
|
});
|
|
|
|
let expr = tree.root_node().child(0).unwrap().child(0).unwrap();
|
|
let child1 = expr.child(0).unwrap();
|
|
let child2 = expr.child(1).unwrap();
|
|
|
|
assert!(expr.has_changes());
|
|
assert_eq!(expr.start_byte(), 4);
|
|
assert_eq!(expr.end_byte(), 17);
|
|
assert!(child1.has_changes());
|
|
assert_eq!(child1.start_byte(), 4);
|
|
assert_eq!(child1.end_byte(), 7);
|
|
assert!(!child2.has_changes());
|
|
assert_eq!(child2.start_byte(), 9);
|
|
assert_eq!(child2.end_byte(), 12);
|
|
}
|
|
|
|
// deletion that spans more than one child node:
|
|
// shrink subsequent child nodes.
|
|
{
|
|
let mut tree = tree.clone();
|
|
tree.edit(&InputEdit {
|
|
start_byte: 1,
|
|
old_end_byte: 11,
|
|
new_end_byte: 4,
|
|
start_position: Point::new(0, 1),
|
|
old_end_position: Point::new(0, 11),
|
|
new_end_position: Point::new(0, 4),
|
|
});
|
|
|
|
let expr = tree.root_node().child(0).unwrap().child(0).unwrap();
|
|
let child1 = expr.child(0).unwrap();
|
|
let child2 = expr.child(1).unwrap();
|
|
let child3 = expr.child(2).unwrap();
|
|
|
|
assert!(expr.has_changes());
|
|
assert_eq!(expr.start_byte(), 4);
|
|
assert_eq!(expr.end_byte(), 8);
|
|
assert!(child1.has_changes());
|
|
assert_eq!(child1.start_byte(), 4);
|
|
assert_eq!(child1.end_byte(), 4);
|
|
assert!(child2.has_changes());
|
|
assert_eq!(child2.start_byte(), 4);
|
|
assert_eq!(child2.end_byte(), 4);
|
|
assert!(child3.has_changes());
|
|
assert_eq!(child3.start_byte(), 5);
|
|
assert_eq!(child3.end_byte(), 8);
|
|
}
|
|
|
|
// insertion at the end of the tree:
|
|
// extend the tree's content.
|
|
{
|
|
let mut tree = tree.clone();
|
|
tree.edit(&InputEdit {
|
|
start_byte: 15,
|
|
old_end_byte: 15,
|
|
new_end_byte: 16,
|
|
start_position: Point::new(0, 15),
|
|
old_end_position: Point::new(0, 15),
|
|
new_end_position: Point::new(0, 16),
|
|
});
|
|
|
|
let expr = tree.root_node().child(0).unwrap().child(0).unwrap();
|
|
let child1 = expr.child(0).unwrap();
|
|
let child2 = expr.child(1).unwrap();
|
|
let child3 = expr.child(2).unwrap();
|
|
|
|
assert!(expr.has_changes());
|
|
assert_eq!(expr.start_byte(), 2);
|
|
assert_eq!(expr.end_byte(), 16);
|
|
assert!(!child1.has_changes());
|
|
assert_eq!(child1.end_byte(), 5);
|
|
assert!(!child2.has_changes());
|
|
assert_eq!(child2.end_byte(), 10);
|
|
assert!(child3.has_changes());
|
|
assert_eq!(child3.end_byte(), 16);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_tree_cursor() {
|
|
let mut parser = Parser::new();
|
|
parser.set_language(get_language("rust")).unwrap();
|
|
|
|
let tree = parser
|
|
.parse(
|
|
"
|
|
struct Stuff {
|
|
a: A;
|
|
b: Option<B>,
|
|
}
|
|
",
|
|
None,
|
|
)
|
|
.unwrap();
|
|
|
|
let mut cursor = tree.walk();
|
|
assert_eq!(cursor.node().kind(), "source_file");
|
|
|
|
assert!(cursor.goto_first_child());
|
|
assert_eq!(cursor.node().kind(), "struct_item");
|
|
|
|
assert!(cursor.goto_first_child());
|
|
assert_eq!(cursor.node().kind(), "struct");
|
|
assert_eq!(cursor.node().is_named(), false);
|
|
|
|
assert!(cursor.goto_next_sibling());
|
|
assert_eq!(cursor.node().kind(), "type_identifier");
|
|
assert_eq!(cursor.node().is_named(), true);
|
|
|
|
assert!(cursor.goto_next_sibling());
|
|
assert_eq!(cursor.node().kind(), "field_declaration_list");
|
|
assert_eq!(cursor.node().is_named(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tree_cursor_fields() {
|
|
let mut parser = Parser::new();
|
|
parser.set_language(get_language("javascript")).unwrap();
|
|
|
|
let tree = parser
|
|
.parse("function /*1*/ bar /*2*/ () {}", None)
|
|
.unwrap();
|
|
|
|
let mut cursor = tree.walk();
|
|
assert_eq!(cursor.node().kind(), "program");
|
|
|
|
cursor.goto_first_child();
|
|
assert_eq!(cursor.node().kind(), "function_declaration");
|
|
assert_eq!(cursor.field_name(), None);
|
|
|
|
cursor.goto_first_child();
|
|
assert_eq!(cursor.node().kind(), "function");
|
|
assert_eq!(cursor.field_name(), None);
|
|
|
|
cursor.goto_next_sibling();
|
|
assert_eq!(cursor.node().kind(), "comment");
|
|
assert_eq!(cursor.field_name(), None);
|
|
|
|
cursor.goto_next_sibling();
|
|
assert_eq!(cursor.node().kind(), "identifier");
|
|
assert_eq!(cursor.field_name(), Some("name"));
|
|
|
|
cursor.goto_next_sibling();
|
|
assert_eq!(cursor.node().kind(), "comment");
|
|
assert_eq!(cursor.field_name(), None);
|
|
|
|
cursor.goto_next_sibling();
|
|
assert_eq!(cursor.node().kind(), "formal_parameters");
|
|
assert_eq!(cursor.field_name(), Some("parameters"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_tree_cursor_child_for_point() {
|
|
let mut parser = Parser::new();
|
|
parser.set_language(get_language("javascript")).unwrap();
|
|
let source = &"
|
|
[
|
|
one,
|
|
{
|
|
two: tree
|
|
},
|
|
four, five, six
|
|
];"[1..];
|
|
let tree = parser.parse(source, None).unwrap();
|
|
|
|
let mut c = tree.walk();
|
|
assert_eq!(c.node().kind(), "program");
|
|
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(7, 0)), None);
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(6, 6)), None);
|
|
assert_eq!(c.node().kind(), "program");
|
|
|
|
// descend to expression statement
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(6, 5)), Some(0));
|
|
assert_eq!(c.node().kind(), "expression_statement");
|
|
|
|
// step into ';' and back up
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(7, 0)), None);
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(6, 5)), Some(1));
|
|
assert_eq!(
|
|
(c.node().kind(), c.node().start_position()),
|
|
(";", Point::new(6, 5))
|
|
);
|
|
assert!(c.goto_parent());
|
|
|
|
// descend into array
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(6, 4)), Some(0));
|
|
assert_eq!(
|
|
(c.node().kind(), c.node().start_position()),
|
|
("array", Point::new(0, 4))
|
|
);
|
|
|
|
// step into '[' and back up
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(0, 4)), Some(0));
|
|
assert_eq!(
|
|
(c.node().kind(), c.node().start_position()),
|
|
("[", Point::new(0, 4))
|
|
);
|
|
assert!(c.goto_parent());
|
|
|
|
// step into identifier 'one' and back up
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(0, 5)), Some(1));
|
|
assert_eq!(
|
|
(c.node().kind(), c.node().start_position()),
|
|
("identifier", Point::new(1, 8))
|
|
);
|
|
assert!(c.goto_parent());
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(1, 10)), Some(1));
|
|
assert_eq!(
|
|
(c.node().kind(), c.node().start_position()),
|
|
("identifier", Point::new(1, 8))
|
|
);
|
|
assert!(c.goto_parent());
|
|
|
|
// step into first ',' and back up
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(1, 11)), Some(2));
|
|
assert_eq!(
|
|
(c.node().kind(), c.node().start_position()),
|
|
(",", Point::new(1, 11))
|
|
);
|
|
assert!(c.goto_parent());
|
|
|
|
// step into identifier 'four' and back up
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(4, 10)), Some(5));
|
|
assert_eq!(
|
|
(c.node().kind(), c.node().start_position()),
|
|
("identifier", Point::new(5, 8))
|
|
);
|
|
assert!(c.goto_parent());
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(5, 0)), Some(5));
|
|
assert_eq!(
|
|
(c.node().kind(), c.node().start_position()),
|
|
("identifier", Point::new(5, 8))
|
|
);
|
|
assert!(c.goto_parent());
|
|
|
|
// step into ']' and back up
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(6, 0)), Some(10));
|
|
assert_eq!(
|
|
(c.node().kind(), c.node().start_position()),
|
|
("]", Point::new(6, 4))
|
|
);
|
|
assert!(c.goto_parent());
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(5, 23)), Some(10));
|
|
assert_eq!(
|
|
(c.node().kind(), c.node().start_position()),
|
|
("]", Point::new(6, 4))
|
|
);
|
|
assert!(c.goto_parent());
|
|
|
|
// descend into object
|
|
assert_eq!(c.goto_first_child_for_point(Point::new(2, 0)), Some(3));
|
|
assert_eq!(
|
|
(c.node().kind(), c.node().start_position()),
|
|
("object", Point::new(2, 8))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tree_node_equality() {
|
|
let mut parser = Parser::new();
|
|
parser.set_language(get_language("rust")).unwrap();
|
|
let tree = parser.parse("struct A {}", None).unwrap();
|
|
let node1 = tree.root_node();
|
|
let node2 = tree.root_node();
|
|
assert_eq!(node1, node2);
|
|
assert_eq!(node1.child(0).unwrap(), node2.child(0).unwrap());
|
|
assert_ne!(node1.child(0).unwrap(), node2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_changed_ranges() {
|
|
let source_code = b"{a: null};\n".to_vec();
|
|
|
|
let mut parser = Parser::new();
|
|
parser.set_language(get_language("javascript")).unwrap();
|
|
let tree = parser.parse(&source_code, None).unwrap();
|
|
|
|
assert_eq!(
|
|
tree.root_node().to_sexp(),
|
|
"(program (expression_statement (object (pair key: (property_identifier) value: (null)))))"
|
|
);
|
|
|
|
// Updating one token
|
|
{
|
|
let mut tree = tree.clone();
|
|
let mut source_code = source_code.clone();
|
|
|
|
// Replace `null` with `nothing` - that token has changed syntax
|
|
let edit = Edit {
|
|
position: index_of(&source_code, "ull"),
|
|
deleted_length: 3,
|
|
inserted_text: b"othing".to_vec(),
|
|
};
|
|
let inverse_edit = invert_edit(&source_code, &edit);
|
|
let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, edit);
|
|
assert_eq!(ranges, vec![range_of(&source_code, "nothing")]);
|
|
|
|
// Replace `nothing` with `null` - that token has changed syntax
|
|
let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, inverse_edit);
|
|
assert_eq!(ranges, vec![range_of(&source_code, "null")]);
|
|
}
|
|
|
|
// Changing only leading whitespace
|
|
{
|
|
let mut tree = tree.clone();
|
|
let mut source_code = source_code.clone();
|
|
|
|
// Insert leading newline - no changed ranges
|
|
let edit = Edit {
|
|
position: 0,
|
|
deleted_length: 0,
|
|
inserted_text: b"\n".to_vec(),
|
|
};
|
|
let inverse_edit = invert_edit(&source_code, &edit);
|
|
let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, edit);
|
|
assert_eq!(ranges, vec![]);
|
|
|
|
// Remove leading newline - no changed ranges
|
|
let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, inverse_edit);
|
|
assert_eq!(ranges, vec![]);
|
|
}
|
|
|
|
// Inserting elements
|
|
{
|
|
let mut tree = tree.clone();
|
|
let mut source_code = source_code.clone();
|
|
|
|
// Insert a key-value pair before the `}` - those tokens are changed
|
|
let edit1 = Edit {
|
|
position: index_of(&source_code, "}"),
|
|
deleted_length: 0,
|
|
inserted_text: b", b: false".to_vec(),
|
|
};
|
|
let inverse_edit1 = invert_edit(&source_code, &edit1);
|
|
let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, edit1);
|
|
assert_eq!(ranges, vec![range_of(&source_code, ", b: false")]);
|
|
|
|
let edit2 = Edit {
|
|
position: index_of(&source_code, ", b"),
|
|
deleted_length: 0,
|
|
inserted_text: b", c: 1".to_vec(),
|
|
};
|
|
let inverse_edit2 = invert_edit(&source_code, &edit2);
|
|
let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, edit2);
|
|
assert_eq!(ranges, vec![range_of(&source_code, ", c: 1")]);
|
|
|
|
// Remove the middle pair
|
|
let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, inverse_edit2);
|
|
assert_eq!(ranges, vec![]);
|
|
|
|
// Remove the second pair
|
|
let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, inverse_edit1);
|
|
assert_eq!(ranges, vec![]);
|
|
}
|
|
|
|
// Wrapping elements in larger expressions
|
|
{
|
|
let mut tree = tree.clone();
|
|
let mut source_code = source_code.clone();
|
|
|
|
// Replace `null` with the binary expression `b === null`
|
|
let edit1 = Edit {
|
|
position: index_of(&source_code, "null"),
|
|
deleted_length: 0,
|
|
inserted_text: b"b === ".to_vec(),
|
|
};
|
|
let inverse_edit1 = invert_edit(&source_code, &edit1);
|
|
let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, edit1);
|
|
assert_eq!(ranges, vec![range_of(&source_code, "b === null")]);
|
|
|
|
// Undo
|
|
let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, inverse_edit1);
|
|
assert_eq!(ranges, vec![range_of(&source_code, "null")]);
|
|
}
|
|
}
|
|
|
|
fn index_of(text: &Vec<u8>, substring: &str) -> usize {
|
|
str::from_utf8(text.as_slice())
|
|
.unwrap()
|
|
.find(substring)
|
|
.unwrap()
|
|
}
|
|
|
|
fn range_of(text: &Vec<u8>, substring: &str) -> Range {
|
|
let start_byte = index_of(text, substring);
|
|
let end_byte = start_byte + substring.as_bytes().len();
|
|
Range {
|
|
start_byte,
|
|
end_byte,
|
|
start_point: Point::new(0, start_byte),
|
|
end_point: Point::new(0, end_byte),
|
|
}
|
|
}
|
|
|
|
fn get_changed_ranges(
|
|
parser: &mut Parser,
|
|
tree: &mut Tree,
|
|
source_code: &mut Vec<u8>,
|
|
edit: Edit,
|
|
) -> Vec<Range> {
|
|
perform_edit(tree, source_code, &edit);
|
|
let new_tree = parser.parse(&source_code, Some(tree)).unwrap();
|
|
let result = tree.changed_ranges(&new_tree).collect();
|
|
*tree = new_tree;
|
|
result
|
|
}
|