From 919e9745a64c2dba8d57f5f3e3654d77891b04c9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 27 May 2021 12:30:17 -0700 Subject: [PATCH] Add `ts_tree_cursor_goto_first_child_for_point` function 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. --- cli/src/tests/tree_test.rs | 107 ++++++++++++++++++++++++++++++++++ lib/binding_rust/bindings.rs | 6 +- lib/binding_rust/lib.rs | 15 +++++ lib/include/tree_sitter/api.h | 3 +- lib/src/tree_cursor.c | 41 +++++++++++-- 5 files changed, 166 insertions(+), 6 deletions(-) diff --git a/cli/src/tests/tree_test.rs b/cli/src/tests/tree_test.rs index fb461d1b..db13ca3a 100644 --- a/cli/src/tests/tree_test.rs +++ b/cli/src/tests/tree_test.rs @@ -262,6 +262,113 @@ fn test_tree_cursor_fields() { 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(); diff --git a/lib/binding_rust/bindings.rs b/lib/binding_rust/bindings.rs index 22a6cea0..38021fe7 100644 --- a/lib/binding_rust/bindings.rs +++ b/lib/binding_rust/bindings.rs @@ -586,12 +586,16 @@ extern "C" { } extern "C" { #[doc = " Move the cursor to the first child of its current node that extends beyond"] - #[doc = " the given byte offset."] + #[doc = " the given byte offset or point."] #[doc = ""] #[doc = " This returns the index of the child node if one was found, and returns -1"] #[doc = " if no such child was found."] pub fn ts_tree_cursor_goto_first_child_for_byte(arg1: *mut TSTreeCursor, arg2: u32) -> i64; } +extern "C" { + pub fn ts_tree_cursor_goto_first_child_for_point(arg1: *mut TSTreeCursor, arg2: TSPoint) + -> i64; +} extern "C" { pub fn ts_tree_cursor_copy(arg1: *const TSTreeCursor) -> TSTreeCursor; } diff --git a/lib/binding_rust/lib.rs b/lib/binding_rust/lib.rs index 88124f08..2cf5898e 100644 --- a/lib/binding_rust/lib.rs +++ b/lib/binding_rust/lib.rs @@ -1172,6 +1172,21 @@ impl<'a> TreeCursor<'a> { } } + /// Move this cursor to the first child of its current node that extends beyond + /// the given byte offset. + /// + /// This returns the index of the child node if one was found, and returns `None` + /// if no such child was found. + pub fn goto_first_child_for_point(&mut self, point: Point) -> Option { + let result = + unsafe { ffi::ts_tree_cursor_goto_first_child_for_point(&mut self.0, point.into()) }; + if result < 0 { + None + } else { + Some(result as usize) + } + } + /// Re-initialize this tree cursor to start at a different node. pub fn reset(&mut self, node: Node<'a>) { unsafe { ffi::ts_tree_cursor_reset(&mut self.0, node.0) }; diff --git a/lib/include/tree_sitter/api.h b/lib/include/tree_sitter/api.h index 43315415..44e07396 100644 --- a/lib/include/tree_sitter/api.h +++ b/lib/include/tree_sitter/api.h @@ -651,12 +651,13 @@ bool ts_tree_cursor_goto_first_child(TSTreeCursor *); /** * Move the cursor to the first child of its current node that extends beyond - * the given byte offset. + * the given byte offset or point. * * This returns the index of the child node if one was found, and returns -1 * if no such child was found. */ int64_t ts_tree_cursor_goto_first_child_for_byte(TSTreeCursor *, uint32_t); +int64_t ts_tree_cursor_goto_first_child_for_point(TSTreeCursor *, TSPoint); TSTreeCursor ts_tree_cursor_copy(const TSTreeCursor *); diff --git a/lib/src/tree_cursor.c b/lib/src/tree_cursor.c index c4ee7a90..6b4829f5 100644 --- a/lib/src/tree_cursor.c +++ b/lib/src/tree_cursor.c @@ -159,10 +159,43 @@ int64_t ts_tree_cursor_goto_first_child_for_byte(TSTreeCursor *_self, uint32_t g } } while (did_descend); - if (self->stack.size > initial_size && - ts_tree_cursor_goto_next_sibling((TSTreeCursor *)self)) { - return visible_child_index; - } + self->stack.size = initial_size; + return -1; +} + +int64_t ts_tree_cursor_goto_first_child_for_point(TSTreeCursor *_self, TSPoint goal_point) { + TreeCursor *self = (TreeCursor *)_self; + uint32_t initial_size = self->stack.size; + uint32_t visible_child_index = 0; + + bool did_descend; + do { + did_descend = false; + + bool visible; + TreeCursorEntry entry; + CursorChildIterator iterator = ts_tree_cursor_iterate_children(self); + while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) { + TSPoint end_point = point_add(entry.position.extent, ts_subtree_size(*entry.subtree).extent); + bool at_goal = point_gt(end_point, goal_point); + uint32_t visible_child_count = ts_subtree_visible_child_count(*entry.subtree); + if (at_goal) { + if (visible) { + array_push(&self->stack, entry); + return visible_child_index; + } + if (visible_child_count > 0) { + array_push(&self->stack, entry); + did_descend = true; + break; + } + } else if (visible) { + visible_child_index++; + } else { + visible_child_index += visible_child_count; + } + } + } while (did_descend); self->stack.size = initial_size; return -1;