Allow repeat in visual mode (#33569)

Conrad Irwin created

Release Notes:

- vim: Allow `.` in visual mode.

Change summary

assets/keymaps/vim.json         |   4 
crates/vim/src/normal/repeat.rs | 102 +++++++++++++++++-----------------
crates/vim/src/test.rs          |  39 +++++++++++++
3 files changed, 93 insertions(+), 52 deletions(-)

Detailed changes

assets/keymaps/vim.json 🔗

@@ -210,7 +210,8 @@
       "ctrl-w space": "editor::OpenExcerptsSplit",
       "ctrl-w g space": "editor::OpenExcerptsSplit",
       "ctrl-6": "pane::AlternateFile",
-      "ctrl-^": "pane::AlternateFile"
+      "ctrl-^": "pane::AlternateFile",
+      ".": "vim::Repeat"
     }
   },
   {
@@ -219,7 +220,6 @@
       "ctrl-[": "editor::Cancel",
       "escape": "editor::Cancel",
       ":": "command_palette::Toggle",
-      ".": "vim::Repeat",
       "c": "vim::PushChange",
       "shift-c": "vim::ChangeToEndOfLine",
       "d": "vim::PushDelete",

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

@@ -245,61 +245,63 @@ impl Vim {
         }) else {
             return;
         };
-        if let Some(mode) = mode {
-            self.switch_mode(mode, false, window, cx)
-        }
+        if mode != Some(self.mode) {
+            if let Some(mode) = mode {
+                self.switch_mode(mode, false, window, cx)
+            }
 
-        match selection {
-            RecordedSelection::SingleLine { cols } => {
-                if cols > 1 {
-                    self.visual_motion(Motion::Right, Some(cols as usize - 1), window, cx)
+            match selection {
+                RecordedSelection::SingleLine { cols } => {
+                    if cols > 1 {
+                        self.visual_motion(Motion::Right, Some(cols as usize - 1), window, cx)
+                    }
                 }
-            }
-            RecordedSelection::Visual { rows, cols } => {
-                self.visual_motion(
-                    Motion::Down {
-                        display_lines: false,
-                    },
-                    Some(rows as usize),
-                    window,
-                    cx,
-                );
-                self.visual_motion(
-                    Motion::StartOfLine {
-                        display_lines: false,
-                    },
-                    None,
-                    window,
-                    cx,
-                );
-                if cols > 1 {
-                    self.visual_motion(Motion::Right, Some(cols as usize - 1), window, cx)
+                RecordedSelection::Visual { rows, cols } => {
+                    self.visual_motion(
+                        Motion::Down {
+                            display_lines: false,
+                        },
+                        Some(rows as usize),
+                        window,
+                        cx,
+                    );
+                    self.visual_motion(
+                        Motion::StartOfLine {
+                            display_lines: false,
+                        },
+                        None,
+                        window,
+                        cx,
+                    );
+                    if cols > 1 {
+                        self.visual_motion(Motion::Right, Some(cols as usize - 1), window, cx)
+                    }
                 }
-            }
-            RecordedSelection::VisualBlock { rows, cols } => {
-                self.visual_motion(
-                    Motion::Down {
-                        display_lines: false,
-                    },
-                    Some(rows as usize),
-                    window,
-                    cx,
-                );
-                if cols > 1 {
-                    self.visual_motion(Motion::Right, Some(cols as usize - 1), window, cx);
+                RecordedSelection::VisualBlock { rows, cols } => {
+                    self.visual_motion(
+                        Motion::Down {
+                            display_lines: false,
+                        },
+                        Some(rows as usize),
+                        window,
+                        cx,
+                    );
+                    if cols > 1 {
+                        self.visual_motion(Motion::Right, Some(cols as usize - 1), window, cx);
+                    }
                 }
+                RecordedSelection::VisualLine { rows } => {
+                    self.visual_motion(
+                        Motion::Down {
+                            display_lines: false,
+                        },
+                        Some(rows as usize),
+                        window,
+                        cx,
+                    );
+                }
+                RecordedSelection::None => {}
             }
-            RecordedSelection::VisualLine { rows } => {
-                self.visual_motion(
-                    Motion::Down {
-                        display_lines: false,
-                    },
-                    Some(rows as usize),
-                    window,
-                    cx,
-                );
-            }
-            RecordedSelection::None => {}
         }
 
         // insert internally uses repeat to handle counts

crates/vim/src/test.rs 🔗

@@ -2071,3 +2071,42 @@ async fn test_paragraph_multi_delete(cx: &mut gpui::TestAppContext) {
     cx.simulate_shared_keystrokes("4 d a p").await;
     cx.shared_state().await.assert_eq(indoc! {"ˇ"});
 }
+
+#[gpui::test]
+async fn test_multi_cursor_replay(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+    cx.set_state(
+        indoc! {
+            "
+        oˇne one one
+
+        two two two
+        "
+        },
+        Mode::Normal,
+    );
+
+    cx.simulate_keystrokes("3 g l s wow escape escape");
+    cx.assert_state(
+        indoc! {
+            "
+        woˇw wow wow
+
+        two two two
+        "
+        },
+        Mode::Normal,
+    );
+
+    cx.simulate_keystrokes("2 j 3 g l .");
+    cx.assert_state(
+        indoc! {
+            "
+        wow wow wow
+
+        woˇw woˇw woˇw
+        "
+        },
+        Mode::Normal,
+    );
+}