Add unit test for `BufferView::select_{larger,smaller}_syntax_node`

Antonio Scandurra created

Change summary

zed/src/editor/buffer/mod.rs  |   5 
zed/src/editor/buffer_view.rs | 151 ++++++++++++++++++++++++++++++++++++
2 files changed, 148 insertions(+), 8 deletions(-)

Detailed changes

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

@@ -709,10 +709,7 @@ impl Buffer {
         })
     }
 
-    pub fn range_for_containing_syntax_node<T: ToOffset>(
-        &self,
-        range: Range<T>,
-    ) -> Option<Range<usize>> {
+    pub fn range_for_syntax_ancestor<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);

zed/src/editor/buffer_view.rs 🔗

@@ -1782,9 +1782,7 @@ impl BufferView {
         for selection in &old_selections {
             let old_range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
             let mut new_range = old_range.clone();
-            while let Some(containing_range) =
-                buffer.range_for_containing_syntax_node(new_range.clone())
-            {
+            while let Some(containing_range) = buffer.range_for_syntax_ancestor(new_range.clone()) {
                 new_range = containing_range;
                 if !self.display_map.intersects_fold(new_range.start, app)
                     && !self.display_map.intersects_fold(new_range.end, app)
@@ -2445,7 +2443,12 @@ impl workspace::ItemView for BufferView {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{editor::Point, settings, test::sample_text};
+    use crate::{
+        editor::Point,
+        settings,
+        test::{build_app_state, sample_text},
+    };
+    use buffer::History;
     use unindent::Unindent;
 
     #[gpui::test]
@@ -3926,6 +3929,146 @@ mod tests {
         );
     }
 
+    #[gpui::test]
+    async fn test_select_larger_smaller_syntax_node(mut app: gpui::TestAppContext) {
+        let app_state = app.read(build_app_state);
+        let lang = app_state.language_registry.select_language("z.rs");
+        let text = r#"
+            use mod1::mod2::{mod3, mod4};
+
+            fn fn_1(param1: bool, param2: &str) {
+                let var1 = "text";
+            }
+        "#
+        .unindent();
+        let buffer = app.add_model(|ctx| {
+            let history = History::new(text.into());
+            Buffer::from_history(0, history, None, lang.cloned(), ctx)
+        });
+        let (_, view) =
+            app.add_window(|ctx| BufferView::for_buffer(buffer, app_state.settings, ctx));
+        view.condition(&app, |view, ctx| !view.buffer.read(ctx).is_parsing())
+            .await;
+
+        view.update(&mut app, |view, ctx| {
+            view.select_display_ranges(
+                &[
+                    DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
+                    DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
+                    DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
+                ],
+                ctx,
+            )
+            .unwrap();
+            view.select_larger_syntax_node(&(), ctx);
+        });
+        assert_eq!(
+            view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
+            &[
+                DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
+                DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
+                DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
+            ]
+        );
+
+        view.update(&mut app, |view, ctx| {
+            view.select_larger_syntax_node(&(), ctx);
+        });
+        assert_eq!(
+            view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
+            &[
+                DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
+                DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
+            ]
+        );
+
+        view.update(&mut app, |view, ctx| {
+            view.select_larger_syntax_node(&(), ctx);
+        });
+        assert_eq!(
+            view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
+            &[DisplayPoint::new(0, 0)..DisplayPoint::new(5, 0)]
+        );
+
+        // Trying to expand the selected syntax node one more time has no effect.
+        view.update(&mut app, |view, ctx| {
+            view.select_larger_syntax_node(&(), ctx);
+        });
+        assert_eq!(
+            view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
+            &[DisplayPoint::new(0, 0)..DisplayPoint::new(5, 0)]
+        );
+
+        view.update(&mut app, |view, ctx| {
+            view.select_smaller_syntax_node(&(), ctx);
+        });
+        assert_eq!(
+            view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
+            &[
+                DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
+                DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
+            ]
+        );
+
+        view.update(&mut app, |view, ctx| {
+            view.select_smaller_syntax_node(&(), ctx);
+        });
+        assert_eq!(
+            view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
+            &[
+                DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
+                DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
+                DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
+            ]
+        );
+
+        view.update(&mut app, |view, ctx| {
+            view.select_smaller_syntax_node(&(), ctx);
+        });
+        assert_eq!(
+            view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
+            &[
+                DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
+                DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
+                DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
+            ]
+        );
+
+        // Trying to shrink the selected syntax node one more time has no effect.
+        view.update(&mut app, |view, ctx| {
+            view.select_smaller_syntax_node(&(), ctx);
+        });
+        assert_eq!(
+            view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
+            &[
+                DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
+                DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
+                DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
+            ]
+        );
+
+        // Ensure that we keep expanding the selection if the larger selection starts or ends within
+        // a fold.
+        view.update(&mut app, |view, ctx| {
+            view.fold_ranges(
+                vec![
+                    Point::new(0, 21)..Point::new(0, 24),
+                    Point::new(3, 20)..Point::new(3, 22),
+                ],
+                ctx,
+            );
+            view.select_larger_syntax_node(&(), ctx);
+        });
+        assert_eq!(
+            view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
+            &[
+                DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
+                DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
+                DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
+            ]
+        );
+    }
+
     impl BufferView {
         fn selection_ranges(&self, app: &AppContext) -> Vec<Range<DisplayPoint>> {
             self.selections_in_range(DisplayPoint::zero()..self.max_point(app), app)