Refine behavior of select_larger_syntax_node

Max Brunsfeld and Antonio Scandurra created

Co-Authored-By: Antonio Scandurra <antonio@zed.dev>

Change summary

Cargo.lock                    |  8 ++--
crates/language/Cargo.toml    |  2 
crates/language/src/buffer.rs | 54 ++++++++++++++++++++++++++++++------
crates/zed/Cargo.toml         |  4 +-
4 files changed, 52 insertions(+), 16 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5187,9 +5187,9 @@ dependencies = [
 
 [[package]]
 name = "tree-sitter"
-version = "0.20.1"
+version = "0.20.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9394e9dbfe967b5f3d6ab79e302e78b5fb7b530c368d634ff3b8d67ede138bf1"
+checksum = "4e34327f8eac545e3f037382471b2b19367725a242bba7bc45edb9efb49fe39a"
 dependencies = [
  "cc",
  "regex",
@@ -5206,9 +5206,9 @@ dependencies = [
 
 [[package]]
 name = "tree-sitter-rust"
-version = "0.20.0"
+version = "0.20.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3df540a493d754015d22eaf57c38f58804be3713a22f6062db983ec15f85c3c9"
+checksum = "13470fafb7327a3acf96f5bc1013b5539a899a182f01c59b5af53f6b93195717"
 dependencies = [
  "cc",
  "tree-sitter",

crates/language/Cargo.toml 🔗

@@ -40,7 +40,7 @@ serde_json = { version = "1", features = ["preserve_order"] }
 similar = "1.3"
 smallvec = { version = "1.6", features = ["union"] }
 smol = "1.2"
-tree-sitter = "0.20.0"
+tree-sitter = "0.20"
 tree-sitter-rust = { version = "0.20.0", optional = true }
 
 [dev-dependencies]

crates/language/src/buffer.rs 🔗

@@ -2139,17 +2139,53 @@ impl BufferSnapshot {
     }
 
     pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
-        if let Some(tree) = self.tree.as_ref() {
-            let root = tree.root_node();
-            let range = range.start.to_offset(self)..range.end.to_offset(self);
-            let mut node = root.descendant_for_byte_range(range.start, range.end);
-            while node.map_or(false, |n| n.byte_range() == range) {
-                node = node.unwrap().parent();
+        let tree = self.tree.as_ref()?;
+        let range = range.start.to_offset(self)..range.end.to_offset(self);
+        let mut cursor = tree.root_node().walk();
+
+        // Descend to smallest leaf that touches or exceeds the start of the range.
+        while cursor.goto_first_child_for_byte(range.start).is_some() {}
+
+        // Ascend to the smallest ancestor that strictly contains the range.
+        loop {
+            let node_range = cursor.node().byte_range();
+            if node_range.start <= range.start
+                && node_range.end >= range.end
+                && node_range.len() > range.len()
+            {
+                break;
+            }
+            if !cursor.goto_parent() {
+                break;
             }
-            node.map(|n| n.byte_range())
-        } else {
-            None
         }
+
+        let left_node = cursor.node();
+
+        // For an empty range, try to find another node immediately to the right of the range.
+        if left_node.end_byte() == range.start {
+            let mut right_node = None;
+            while !cursor.goto_next_sibling() {
+                if !cursor.goto_parent() {
+                    break;
+                }
+            }
+
+            while cursor.node().start_byte() == range.start {
+                right_node = Some(cursor.node());
+                if !cursor.goto_first_child() {
+                    break;
+                }
+            }
+
+            if let Some(right_node) = right_node {
+                if right_node.is_named() || !left_node.is_named() {
+                    return Some(right_node.byte_range());
+                }
+            }
+        }
+
+        Some(left_node.byte_range())
     }
 
     pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {

crates/zed/Cargo.toml 🔗

@@ -89,8 +89,8 @@ thiserror = "1.0.29"
 time = "0.3"
 tiny_http = "0.8"
 toml = "0.5"
-tree-sitter = "0.20.0"
-tree-sitter-rust = "0.20.0"
+tree-sitter = "0.20.4"
+tree-sitter-rust = "0.20.1"
 tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
 url = "2.2"