Implement `select_larger_syntax_node` for buffer

Antonio Scandurra created

Change summary

zed/src/editor/buffer/mod.rs           | 17 +++++++++++++
zed/src/editor/buffer_view.rs          | 35 +++++++++++++++++++++++++++
zed/src/editor/display_map/fold_map.rs | 12 +++++++++
zed/src/editor/display_map/mod.rs      |  4 +++
4 files changed, 67 insertions(+), 1 deletion(-)

Detailed changes

zed/src/editor/buffer/mod.rs 🔗

@@ -709,6 +709,23 @@ impl Buffer {
         })
     }
 
+    pub fn range_for_containing_syntax_node<T: ToOffset>(
+        &self,
+        range: Range<T>,
+    ) -> Option<Range<usize>> {
+        if let Some(tree) = self.syntax_tree() {
+            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();
+            }
+            node.map(|n| n.byte_range())
+        } else {
+            None
+        }
+    }
+
     fn diff(&self, new_text: Arc<str>, ctx: &AppContext) -> Task<Diff> {
         // TODO: it would be nice to not allocate here.
         let old_text = self.text();

zed/src/editor/buffer_view.rs 🔗

@@ -166,6 +166,11 @@ pub fn init(app: &mut MutableAppContext) {
             "buffer:add_selection_below",
             Some("BufferView"),
         ),
+        Binding::new(
+            "alt-up",
+            "buffer:select-larger-syntax-node",
+            Some("BufferView"),
+        ),
         Binding::new("pageup", "buffer:page_up", Some("BufferView")),
         Binding::new("pagedown", "buffer:page_down", Some("BufferView")),
         Binding::new("alt-cmd-[", "buffer:fold", Some("BufferView")),
@@ -270,6 +275,10 @@ pub fn init(app: &mut MutableAppContext) {
         "buffer:add_selection_below",
         BufferView::add_selection_below,
     );
+    app.add_action(
+        "buffer:select-larger-syntax-node",
+        BufferView::select_larger_syntax_node,
+    );
     app.add_action("buffer:page_up", BufferView::page_up);
     app.add_action("buffer:page_down", BufferView::page_down);
     app.add_action("buffer:fold", BufferView::fold);
@@ -1659,7 +1668,7 @@ impl BufferView {
         self.add_selection(false, ctx);
     }
 
-    pub fn add_selection(&mut self, above: bool, ctx: &mut ViewContext<Self>) {
+    fn add_selection(&mut self, above: bool, ctx: &mut ViewContext<Self>) {
         use super::RangeExt;
 
         let app = ctx.as_ref();
@@ -1751,6 +1760,30 @@ impl BufferView {
         }
     }
 
+    pub fn select_larger_syntax_node(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+        let app = ctx.as_ref();
+        let buffer = self.buffer.read(app);
+        let mut selections = self.selections(app).to_vec();
+        for selection in &mut selections {
+            let mut range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
+            while let Some(containing_range) =
+                buffer.range_for_containing_syntax_node(range.clone())
+            {
+                range = containing_range;
+                if !self.display_map.intersects_fold(range.start, app)
+                    && !self.display_map.intersects_fold(range.end, app)
+                {
+                    break;
+                }
+            }
+
+            selection.start = buffer.anchor_before(range.start);
+            selection.end = buffer.anchor_before(range.end);
+        }
+
+        self.update_selections(selections, true, ctx);
+    }
+
     fn build_columnar_selection(
         &mut self,
         row: u32,

zed/src/editor/display_map/fold_map.rs 🔗

@@ -192,6 +192,18 @@ impl FoldMap {
         })
     }
 
+    pub fn intersects_fold<T>(&self, offset: T, ctx: &AppContext) -> bool
+    where
+        T: ToOffset,
+    {
+        let buffer = self.buffer.read(ctx);
+        let offset = offset.to_offset(buffer);
+        let transforms = self.sync(ctx);
+        let mut cursor = transforms.cursor::<usize, usize>();
+        cursor.seek(&offset, SeekBias::Right, &());
+        cursor.item().map_or(false, |t| t.display_text.is_some())
+    }
+
     pub fn is_line_folded(&self, display_row: u32, ctx: &AppContext) -> bool {
         let transforms = self.sync(ctx);
         let mut cursor = transforms.cursor::<DisplayPoint, DisplayPoint>();

zed/src/editor/display_map/mod.rs 🔗

@@ -57,6 +57,10 @@ impl DisplayMap {
         self.fold_map.unfold(ranges, ctx)
     }
 
+    pub fn intersects_fold<T: ToOffset>(&self, offset: T, ctx: &AppContext) -> bool {
+        self.fold_map.intersects_fold(offset, ctx)
+    }
+
     pub fn is_line_folded(&self, display_row: u32, ctx: &AppContext) -> bool {
         self.fold_map.is_line_folded(display_row, ctx)
     }