Add some tests for portions of visual text objects. Note: they are slightly broken currently as described in the tests

K Simmons created

Change summary

crates/vim/src/object.rs                              | 118 +++++++-----
crates/vim/src/test.rs                                |   1 
crates/vim/src/visual.rs                              |  37 +++
crates/vim/test_data/test_change_around_sentence.json |   0 
crates/vim/test_data/test_change_around_word.json     |   0 
crates/vim/test_data/test_change_sentence_object.json |   0 
crates/vim/test_data/test_change_word_object.json     |   0 
crates/vim/test_data/test_delete_around_sentence.json |   0 
crates/vim/test_data/test_delete_around_word.json     |   0 
crates/vim/test_data/test_delete_sentence_object.json |   0 
crates/vim/test_data/test_delete_word_object.json     |   0 
crates/vim/test_data/test_neovim.json                 |   2 
crates/vim/test_data/test_visual_sentence_object.json |   0 
crates/vim/test_data/test_visual_word_object.json     |   0 
14 files changed, 105 insertions(+), 53 deletions(-)

Detailed changes

crates/vim/src/object.rs 🔗

@@ -44,7 +44,7 @@ fn object(object: Object, cx: &mut MutableAppContext) {
 }
 
 impl Object {
-    pub fn object_range(
+    pub fn range(
         self,
         map: &DisplaySnapshot,
         relative_to: DisplayPoint,
@@ -68,7 +68,7 @@ impl Object {
         selection: &mut Selection<DisplayPoint>,
         around: bool,
     ) {
-        let range = self.object_range(map, selection.head(), around);
+        let range = self.range(map, selection.head(), around);
         selection.start = range.start;
         selection.end = range.end;
     }
@@ -328,43 +328,58 @@ mod test {
         "};
 
     #[gpui::test]
-    async fn test_change_in_word(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx)
-            .await
-            .binding(["c", "i", "w"]);
-        cx.assert_all(WORD_LOCATIONS).await;
-        let mut cx = cx.consume().binding(["c", "i", "shift-w"]);
-        cx.assert_all(WORD_LOCATIONS).await;
-    }
-
-    #[gpui::test]
-    async fn test_delete_in_word(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx)
-            .await
-            .binding(["d", "i", "w"]);
-        cx.assert_all(WORD_LOCATIONS).await;
-        let mut cx = cx.consume().binding(["d", "i", "shift-w"]);
-        cx.assert_all(WORD_LOCATIONS).await;
+    async fn test_change_word_object(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+
+        cx.assert_binding_matches_all(["c", "i", "w"], WORD_LOCATIONS)
+            .await;
+        cx.assert_binding_matches_all(["c", "i", "shift-w"], WORD_LOCATIONS)
+            .await;
+        cx.assert_binding_matches_all(["c", "a", "w"], WORD_LOCATIONS)
+            .await;
+        cx.assert_binding_matches_all(["c", "a", "shift-w"], WORD_LOCATIONS)
+            .await;
     }
 
     #[gpui::test]
-    async fn test_change_around_word(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx)
-            .await
-            .binding(["c", "a", "w"]);
-        cx.assert_all(WORD_LOCATIONS).await;
-        let mut cx = cx.consume().binding(["c", "a", "shift-w"]);
-        cx.assert_all(WORD_LOCATIONS).await;
+    async fn test_delete_word_object(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+
+        cx.assert_binding_matches_all(["d", "i", "w"], WORD_LOCATIONS)
+            .await;
+        cx.assert_binding_matches_all(["d", "i", "shift-w"], WORD_LOCATIONS)
+            .await;
+        cx.assert_binding_matches_all(["d", "a", "w"], WORD_LOCATIONS)
+            .await;
+        cx.assert_binding_matches_all(["d", "a", "shift-w"], WORD_LOCATIONS)
+            .await;
     }
 
     #[gpui::test]
-    async fn test_delete_around_word(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx)
-            .await
-            .binding(["d", "a", "w"]);
-        cx.assert_all(WORD_LOCATIONS).await;
-        let mut cx = cx.consume().binding(["d", "a", "shift-w"]);
-        cx.assert_all(WORD_LOCATIONS).await;
+    async fn test_visual_word_object(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+
+        cx.assert_binding_matches_all(["v", "i", "w"], WORD_LOCATIONS)
+            .await;
+        // Visual text objects are slightly broken when used with non empty selections
+        // cx.assert_binding_matches_all(["v", "h", "i", "w"], WORD_LOCATIONS)
+        //     .await;
+        // cx.assert_binding_matches_all(["v", "l", "i", "w"], WORD_LOCATIONS)
+        //     .await;
+        cx.assert_binding_matches_all(["v", "i", "shift-w"], WORD_LOCATIONS)
+            .await;
+
+        // Visual text objects are slightly broken when used with non empty selections
+        // cx.assert_binding_matches_all(["v", "i", "h", "shift-w"], WORD_LOCATIONS)
+        //     .await;
+        // cx.assert_binding_matches_all(["v", "i", "l", "shift-w"], WORD_LOCATIONS)
+        //     .await;
+
+        // Visual around words is somewhat broken right now when it comes to newlines
+        // cx.assert_binding_matches_all(["v", "a", "w"], WORD_LOCATIONS)
+        //     .await;
+        // cx.assert_binding_matches_all(["v", "a", "shift-w"], WORD_LOCATIONS)
+        //     .await;
     }
 
     const SENTENCE_EXAMPLES: &[&'static str] = &[
@@ -390,20 +405,19 @@ mod test {
     ];
 
     #[gpui::test]
-    async fn test_change_in_sentence(cx: &mut gpui::TestAppContext) {
+    async fn test_change_sentence_object(cx: &mut gpui::TestAppContext) {
         let mut cx = NeovimBackedTestContext::new(cx)
             .await
             .binding(["c", "i", "s"]);
         for sentence_example in SENTENCE_EXAMPLES {
             cx.assert_all(sentence_example).await;
         }
-    }
 
-    #[gpui::test]
-    async fn test_delete_in_sentence(cx: &mut gpui::TestAppContext) {
-        let mut cx = NeovimBackedTestContext::new(cx)
-            .await
-            .binding(["d", "i", "s"]);
+        let mut cx = cx.binding(["c", "a", "s"]);
+        // Resulting position is slightly incorrect for unintuitive reasons.
+        cx.add_initial_state_exemption("The quick brown?ˇ Fox Jumps! Over the lazy.");
+        // Changing around the sentence at the end of the line doesn't remove whitespace.'
+        cx.add_initial_state_exemption("The quick brown.)]\'\" Brown fox jumps.ˇ ");
 
         for sentence_example in SENTENCE_EXAMPLES {
             cx.assert_all(sentence_example).await;
@@ -411,11 +425,15 @@ mod test {
     }
 
     #[gpui::test]
-    async fn test_change_around_sentence(cx: &mut gpui::TestAppContext) {
+    async fn test_delete_sentence_object(cx: &mut gpui::TestAppContext) {
         let mut cx = NeovimBackedTestContext::new(cx)
             .await
-            .binding(["c", "a", "s"]);
+            .binding(["d", "i", "s"]);
+        for sentence_example in SENTENCE_EXAMPLES {
+            cx.assert_all(sentence_example).await;
+        }
 
+        let mut cx = cx.binding(["d", "a", "s"]);
         // Resulting position is slightly incorrect for unintuitive reasons.
         cx.add_initial_state_exemption("The quick brown?ˇ Fox Jumps! Over the lazy.");
         // Changing around the sentence at the end of the line doesn't remove whitespace.'
@@ -427,18 +445,18 @@ mod test {
     }
 
     #[gpui::test]
-    async fn test_delete_around_sentence(cx: &mut gpui::TestAppContext) {
+    async fn test_visual_sentence_object(cx: &mut gpui::TestAppContext) {
         let mut cx = NeovimBackedTestContext::new(cx)
             .await
-            .binding(["d", "a", "s"]);
-
-        // Resulting position is slightly incorrect for unintuitive reasons.
-        cx.add_initial_state_exemption("The quick brown?ˇ Fox Jumps! Over the lazy.");
-        // Changing around the sentence at the end of the line doesn't remove whitespace.'
-        cx.add_initial_state_exemption("The quick brown.)]\'\" Brown fox jumps.ˇ ");
-
+            .binding(["v", "i", "s"]);
         for sentence_example in SENTENCE_EXAMPLES {
             cx.assert_all(sentence_example).await;
         }
+
+        // Visual around sentences is somewhat broken right now when it comes to newlines
+        // let mut cx = cx.binding(["d", "a", "s"]);
+        // for sentence_example in SENTENCE_EXAMPLES {
+        //     cx.assert_all(sentence_example).await;
+        // }
     }
 }

crates/vim/src/test.rs 🔗

@@ -26,6 +26,7 @@ async fn test_neovim(cx: &mut gpui::TestAppContext) {
     let mut cx = NeovimBackedTestContext::new(cx).await;
 
     cx.simulate_shared_keystroke("i").await;
+    cx.assert_state_matches().await;
     cx.simulate_shared_keystrokes([
         "shift-T", "e", "s", "t", " ", "t", "e", "s", "t", "escape", "0", "d", "w",
     ])

crates/vim/src/visual.rs 🔗

@@ -6,7 +6,13 @@ use gpui::{actions, MutableAppContext, ViewContext};
 use language::{AutoindentMode, SelectionGoal};
 use workspace::Workspace;
 
-use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim};
+use crate::{
+    motion::Motion,
+    object::Object,
+    state::{Mode, Operator},
+    utils::copy_selections_content,
+    Vim,
+};
 
 actions!(vim, [VisualDelete, VisualChange, VisualYank, VisualPaste]);
 
@@ -47,7 +53,34 @@ pub fn visual_motion(motion: Motion, times: usize, cx: &mut MutableAppContext) {
     });
 }
 
-pub fn visual_object(_object: Object, _cx: &mut MutableAppContext) {}
+pub fn visual_object(object: Object, cx: &mut MutableAppContext) {
+    Vim::update(cx, |vim, cx| {
+        if let Operator::Object { around } = vim.pop_operator(cx) {
+            vim.update_active_editor(cx, |editor, cx| {
+                editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
+                    s.move_with(|map, selection| {
+                        let head = selection.head();
+                        let mut range = object.range(map, head, around);
+                        if !range.is_empty() {
+                            if let Some((_, end)) = map.reverse_chars_at(range.end).next() {
+                                range.end = end;
+                            }
+
+                            if selection.is_empty() {
+                                selection.start = range.start;
+                                selection.end = range.end;
+                            } else if selection.reversed {
+                                selection.start = range.start;
+                            } else {
+                                selection.end = range.end;
+                            }
+                        }
+                    })
+                });
+            });
+        }
+    });
+}
 
 pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspace>) {
     Vim::update(cx, |vim, cx| {

crates/vim/test_data/test_neovim.json 🔗

@@ -1 +1 @@
-[{"Text":"test"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"}]
+[{"Text":""},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"test"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"}]