From 90e0e28b952e546cc25cb960490ca501d03a6956 Mon Sep 17 00:00:00 2001 From: vanaigr Date: Tue, 23 Apr 2024 09:19:57 -0500 Subject: [PATCH] feat: reverse iteration through node parents (#3214) --- cli/src/tests/node_test.rs | 38 +++++++++++++++++++++++++++ lib/binding_rust/bindings.rs | 6 ++++- lib/binding_rust/lib.rs | 9 +++++++ lib/include/tree_sitter/api.h | 7 +++++ lib/src/node.c | 48 ++++++++++++++++++----------------- 5 files changed, 84 insertions(+), 24 deletions(-) diff --git a/cli/src/tests/node_test.rs b/cli/src/tests/node_test.rs index 59010e26..f226a890 100644 --- a/cli/src/tests/node_test.rs +++ b/cli/src/tests/node_test.rs @@ -168,6 +168,22 @@ fn test_node_child() { assert_eq!(object_node.parent().unwrap(), array_node); assert_eq!(array_node.parent().unwrap(), tree.root_node()); assert_eq!(tree.root_node().parent(), None); + + assert_eq!( + tree.root_node() + .child_containing_descendant(null_node) + .unwrap(), + array_node + ); + assert_eq!( + array_node.child_containing_descendant(null_node).unwrap(), + object_node + ); + assert_eq!( + object_node.child_containing_descendant(null_node).unwrap(), + pair_node + ); + assert_eq!(pair_node.child_containing_descendant(null_node), None); } #[test] @@ -267,6 +283,12 @@ fn test_parent_of_zero_width_node() { assert_eq!(block.to_string(), "(block)"); assert_eq!(block_parent.kind(), "function_definition"); assert_eq!(block_parent.to_string(), "(function_definition name: (identifier) parameters: (parameters (identifier)) body: (block))"); + + assert_eq!( + root.child_containing_descendant(block).unwrap(), + function_definition + ); + assert_eq!(function_definition.child_containing_descendant(block), None); } #[test] @@ -385,6 +407,22 @@ fn test_node_named_child() { assert_eq!(object_node.parent().unwrap(), array_node); assert_eq!(array_node.parent().unwrap(), tree.root_node()); assert_eq!(tree.root_node().parent(), None); + + assert_eq!( + tree.root_node() + .child_containing_descendant(null_node) + .unwrap(), + array_node + ); + assert_eq!( + array_node.child_containing_descendant(null_node).unwrap(), + object_node + ); + assert_eq!( + object_node.child_containing_descendant(null_node).unwrap(), + pair_node + ); + assert_eq!(pair_node.child_containing_descendant(null_node), None); } #[test] diff --git a/lib/binding_rust/bindings.rs b/lib/binding_rust/bindings.rs index cadbb93e..85201987 100644 --- a/lib/binding_rust/bindings.rs +++ b/lib/binding_rust/bindings.rs @@ -355,9 +355,13 @@ extern "C" { pub fn ts_node_next_parse_state(self_: TSNode) -> TSStateId; } extern "C" { - #[doc = " Get the node's immediate parent."] + #[doc = " Get the node's immediate parent.\n Prefer [`ts_node_child_containing_descendant`] for\n iterating over the node's ancestors."] pub fn ts_node_parent(self_: TSNode) -> TSNode; } +extern "C" { + #[doc = " Get the node's child that contains `descendant`."] + pub fn ts_node_child_containing_descendant(self_: TSNode, descendant: TSNode) -> TSNode; +} extern "C" { #[doc = " Get the node's child at the given index, where zero represents the first\n child."] pub fn ts_node_child(self_: TSNode, child_index: u32) -> TSNode; diff --git a/lib/binding_rust/lib.rs b/lib/binding_rust/lib.rs index 3ec3b6b0..14a0d219 100644 --- a/lib/binding_rust/lib.rs +++ b/lib/binding_rust/lib.rs @@ -1331,12 +1331,21 @@ impl<'tree> Node<'tree> { } /// Get this node's immediate parent. + /// Prefer [`child_containing_descendant`](Node::child_containing_descendant) + /// for iterating over this node's ancestors. #[doc(alias = "ts_node_parent")] #[must_use] pub fn parent(&self) -> Option { Self::new(unsafe { ffi::ts_node_parent(self.0) }) } + /// Get this node's child that contains `descendant`. + #[doc(alias = "ts_node_child_containing_descendant")] + #[must_use] + pub fn child_containing_descendant(&self, descendant: Self) -> Option { + Self::new(unsafe { ffi::ts_node_child_containing_descendant(self.0, descendant.0) }) + } + /// Get this node's next sibling. #[doc(alias = "ts_node_next_sibling")] #[must_use] diff --git a/lib/include/tree_sitter/api.h b/lib/include/tree_sitter/api.h index de122289..deb2364e 100644 --- a/lib/include/tree_sitter/api.h +++ b/lib/include/tree_sitter/api.h @@ -548,9 +548,16 @@ TSStateId ts_node_next_parse_state(TSNode self); /** * Get the node's immediate parent. + * Prefer [`ts_node_child_containing_descendant`] for + * iterating over the node's ancestors. */ TSNode ts_node_parent(TSNode self); +/** + * Get the node's child that contains `descendant`. + */ +TSNode ts_node_child_containing_descendant(TSNode self, TSNode descendant); + /** * Get the node's child at the given index, where zero represents the first * child. diff --git a/lib/src/node.c b/lib/src/node.c index f9960213..203d79b2 100644 --- a/lib/src/node.c +++ b/lib/src/node.c @@ -505,33 +505,35 @@ TSStateId ts_node_next_parse_state(TSNode self) { TSNode ts_node_parent(TSNode self) { TSNode node = ts_tree_root_node(self.tree); - uint32_t end_byte = ts_node_end_byte(self); if (node.id == self.id) return ts_node__null(); - TSNode last_visible_node = node; - bool did_descend = true; - while (did_descend) { - did_descend = false; - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&node); - while (ts_node_child_iterator_next(&iterator, &child)) { - if ( - ts_node_start_byte(child) > ts_node_start_byte(self) || - child.id == self.id - ) break; - if (iterator.position.bytes >= end_byte && ts_node_child_count(child) > 0) { - node = child; - if (ts_node__is_relevant(child, true)) { - last_visible_node = node; - } - did_descend = true; - break; - } - } + while (true) { + TSNode next_node = ts_node_child_containing_descendant(node, self); + if (ts_node_is_null(next_node)) break; + node = next_node; } - return last_visible_node; + return node; +} + +TSNode ts_node_child_containing_descendant(TSNode self, TSNode subnode) { + uint32_t start_byte = ts_node_start_byte(subnode); + uint32_t end_byte = ts_node_end_byte(subnode); + + do { + NodeChildIterator iter = ts_node_iterate_children(&self); + do { + if ( + !ts_node_child_iterator_next(&iter, &self) + || ts_node_start_byte(self) > start_byte + || self.id == subnode.id + ) { + return ts_node__null(); + } + } while (iter.position.bytes < end_byte || ts_node_child_count(self) == 0); + } while (!ts_node__is_relevant(self, true)); + + return self; } TSNode ts_node_child(TSNode self, uint32_t child_index) {