From 11b7913956829997a2ce6551e55d6280a173dd55 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Thu, 11 Sep 2025 15:42:09 -0600 Subject: [PATCH] Refactor/optimize tree-sitter utilities for finding nodes enclosing ranges (#37943) #35053 split out these utility functions. I found the names / doc comments a bit confusing so this improves that. Before that PR there was also a mild inefficiency - it would walk the cursor all the way down to a leaf and then back up to an ancestor. Release Notes: - N/A --- crates/language/src/buffer.rs | 89 ++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 51e6b6d1e032aa5e786e9117b96aff8adaba638f..f03df08a55b2759885d133b9a7dc3556b549a184 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -3463,46 +3463,53 @@ impl BufferSnapshot { (start..end, word_kind) } - /// Returns the closest syntax node enclosing the given range. - /// Positions a tree cursor at the leaf node that contains or touches the given range. - /// This is shared logic used by syntax navigation methods. - fn position_cursor_at_range(cursor: &mut tree_sitter::TreeCursor, range: &Range) { - // Descend to the first leaf that touches the start of the range. - // - // If the range is non-empty and the current node ends exactly at the start, - // move to the next sibling to find a node that extends beyond the start. - // - // If the range is empty and the current node starts after the range position, - // move to the previous sibling to find the node that contains the position. - while cursor.goto_first_child_for_byte(range.start).is_some() { - if !range.is_empty() && cursor.node().end_byte() == range.start { - cursor.goto_next_sibling(); - } - if range.is_empty() && cursor.node().start_byte() > range.start { - cursor.goto_previous_sibling(); - } - } - } - - /// Moves the cursor to find a node that contains the given range. - /// Returns true if such a node is found, false otherwise. - /// This is shared logic used by syntax navigation methods. - fn find_containing_node( + /// Moves the TreeCursor to the smallest descendant or ancestor syntax node enclosing the given + /// range. When `require_larger` is true, the node found must be larger than the query range. + /// + /// Returns true if a node was found, and false otherwise. In the `false` case the cursor will + /// be moved to the root of the tree. + fn goto_node_enclosing_range( cursor: &mut tree_sitter::TreeCursor, - range: &Range, - strict: bool, + query_range: &Range, + require_larger: bool, ) -> bool { + let mut ascending = false; loop { - let node_range = cursor.node().byte_range(); + let mut range = cursor.node().byte_range(); + if query_range.is_empty() { + // When the query range is empty and the current node starts after it, move to the + // previous sibling to find the node the containing node. + if range.start > query_range.start { + cursor.goto_previous_sibling(); + range = cursor.node().byte_range(); + } + } else { + // When the query range is non-empty and the current node ends exactly at the start, + // move to the next sibling to find a node that extends beyond the start. + if range.end == query_range.start { + cursor.goto_next_sibling(); + range = cursor.node().byte_range(); + } + } - if node_range.start <= range.start - && node_range.end >= range.end - && (!strict || node_range.len() > range.len()) - { + let encloses = range.contains_inclusive(query_range) + && (!require_larger || range.len() > query_range.len()); + if !encloses { + ascending = true; + if !cursor.goto_parent() { + return false; + } + continue; + } else if ascending { return true; } - if !cursor.goto_parent() { - return false; + + // Descend into the current node. + if cursor + .goto_first_child_for_byte(query_range.start) + .is_none() + { + return true; } } } @@ -3519,10 +3526,8 @@ impl BufferSnapshot { { let mut cursor = layer.node().walk(); - Self::position_cursor_at_range(&mut cursor, &range); - - // Ascend to the smallest ancestor that strictly contains the range. - if !Self::find_containing_node(&mut cursor, &range, true) { + // Find the node that both contains the range and is larger than it. + if !Self::goto_node_enclosing_range(&mut cursor, &range, true) { continue; } @@ -3588,10 +3593,8 @@ impl BufferSnapshot { { let mut cursor = layer.node().walk(); - Self::position_cursor_at_range(&mut cursor, &range); - // Find the node that contains the range - if !Self::find_containing_node(&mut cursor, &range, false) { + if !Self::goto_node_enclosing_range(&mut cursor, &range, false) { continue; } @@ -3641,10 +3644,8 @@ impl BufferSnapshot { { let mut cursor = layer.node().walk(); - Self::position_cursor_at_range(&mut cursor, &range); - // Find the node that contains the range - if !Self::find_containing_node(&mut cursor, &range, false) { + if !Self::goto_node_enclosing_range(&mut cursor, &range, false) { continue; }