Fix edge-cases in visual block insert

Conrad Irwin created

Change summary

crates/vim/src/normal/substitute.rs                |  3 
crates/vim/src/vim.rs                              |  2 
crates/vim/src/visual.rs                           | 58 ++++++++++++++++
crates/vim/test_data/test_visual_block_insert.json | 18 ++++
4 files changed, 79 insertions(+), 2 deletions(-)

Detailed changes

crates/vim/src/normal/substitute.rs 🔗

@@ -5,8 +5,8 @@ use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
 
 pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
     let line_mode = vim.state().mode == Mode::VisualLine;
-    vim.switch_mode(Mode::Insert, true, cx);
     vim.update_active_editor(cx, |editor, cx| {
+        editor.set_clip_at_line_ends(false, cx);
         editor.transact(cx, |editor, cx| {
             editor.change_selections(None, cx, |s| {
                 s.move_with(|map, selection| {
@@ -32,6 +32,7 @@ pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
             editor.edit(edits, cx);
         });
     });
+    vim.switch_mode(Mode::Insert, true, cx);
 }
 
 #[cfg(test)]

crates/vim/src/vim.rs 🔗

@@ -232,7 +232,7 @@ impl Vim {
                 s.move_with(|map, selection| {
                     if last_mode.is_visual() && !mode.is_visual() {
                         let mut point = selection.head();
-                        if !selection.reversed {
+                        if !selection.reversed && !selection.is_empty() {
                             point = movement::left(map, selection.head());
                         }
                         selection.collapse_to(point, selection.goal)

crates/vim/src/visual.rs 🔗

@@ -157,9 +157,12 @@ pub fn visual_block_motion(
 
         let columns = if is_reversed {
             head.column()..tail.column()
+        } else if head.column() == tail.column() {
+            head.column()..(head.column() + 1)
         } else {
             tail.column()..head.column()
         };
+
         let mut selections = Vec::new();
         let mut row = tail.row();
 
@@ -972,6 +975,61 @@ mod test {
         .await;
     }
 
+    #[gpui::test]
+    async fn test_visual_block_insert(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+
+        cx.set_shared_state(indoc! {
+            "ˇThe quick brown
+            fox jumps over
+            the lazy dog
+            "
+        })
+        .await;
+        cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await;
+        cx.assert_shared_state(indoc! {
+            "«Tˇ»he quick brown
+            «fˇ»ox jumps over
+            «tˇ»he lazy dog
+            ˇ"
+        })
+        .await;
+
+        cx.simulate_shared_keystrokes(["shift-i", "k", "escape"])
+            .await;
+        cx.assert_shared_state(indoc! {
+            "ˇkThe quick brown
+            kfox jumps over
+            kthe lazy dog
+            k"
+        })
+        .await;
+
+        cx.set_shared_state(indoc! {
+            "ˇThe quick brown
+            fox jumps over
+            the lazy dog
+            "
+        })
+        .await;
+        cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await;
+        cx.assert_shared_state(indoc! {
+            "«Tˇ»he quick brown
+            «fˇ»ox jumps over
+            «tˇ»he lazy dog
+            ˇ"
+        })
+        .await;
+        cx.simulate_shared_keystrokes(["c", "k", "escape"]).await;
+        cx.assert_shared_state(indoc! {
+            "ˇkhe quick brown
+            kox jumps over
+            khe lazy dog
+            k"
+        })
+        .await;
+    }
+
     #[gpui::test]
     async fn test_mode_across_command(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new(cx, true).await;

crates/vim/test_data/test_visual_block_insert.json 🔗

@@ -0,0 +1,18 @@
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog\n"}}
+{"Key":"ctrl-v"}
+{"Key":"9"}
+{"Key":"down"}
+{"Get":{"state":"«Tˇ»he quick brown\n«fˇ»ox jumps over\n«tˇ»he lazy dog\nˇ","mode":"VisualBlock"}}
+{"Key":"shift-i"}
+{"Key":"k"}
+{"Key":"escape"}
+{"Get":{"state":"ˇkThe quick brown\nkfox jumps over\nkthe lazy dog\nk","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog\n"}}
+{"Key":"ctrl-v"}
+{"Key":"9"}
+{"Key":"down"}
+{"Get":{"state":"«Tˇ»he quick brown\n«fˇ»ox jumps over\n«tˇ»he lazy dog\nˇ","mode":"VisualBlock"}}
+{"Key":"c"}
+{"Key":"k"}
+{"Key":"escape"}
+{"Get":{"state":"ˇkhe quick brown\nkox jumps over\nkhe lazy dog\nk","mode":"Normal"}}