vim: Fix gv after actions (#14829)

Conrad Irwin created

Fixes: #13720

Co-Authored-By: <tobbe@tlundberg.com>



Release Notes:

- vim: Fixed `gv` after `y`, `d`, etc.
([#13760](https://github.com/zed-industries/zed/issues/13760)).

Change summary

crates/vim/src/normal/case.rs       |  2 ++
crates/vim/src/normal/increment.rs  |  1 +
crates/vim/src/normal/paste.rs      |  2 ++
crates/vim/src/normal/search.rs     |  1 +
crates/vim/src/normal/substitute.rs |  1 +
crates/vim/src/vim.rs               | 17 +++++++++++++----
crates/vim/src/visual.rs            | 12 ++++++++++++
7 files changed, 32 insertions(+), 4 deletions(-)

Detailed changes

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

@@ -125,7 +125,9 @@ where
 {
     Vim::update(cx, |vim, cx| {
         vim.record_current_action(cx);
+        vim.store_visual_marks(cx);
         let count = vim.take_count(cx).unwrap_or(1) as u32;
+
         vim.update_active_editor(cx, |vim, editor, cx| {
             let mut ranges = Vec::new();
             let mut cursor_positions = Vec::new();

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

@@ -44,6 +44,7 @@ pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
 }
 
 fn increment(vim: &mut Vim, mut delta: i32, step: i32, cx: &mut WindowContext) {
+    vim.store_visual_marks(cx);
     vim.update_active_editor(cx, |vim, editor, cx| {
         let mut edits = Vec::new();
         let mut new_anchors = Vec::new();

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

@@ -30,7 +30,9 @@ pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>
 fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
     Vim::update(cx, |vim, cx| {
         vim.record_current_action(cx);
+        vim.store_visual_marks(cx);
         let count = vim.take_count(cx).unwrap_or(1);
+
         vim.update_active_editor(cx, |vim, editor, cx| {
             let text_layout_details = editor.text_layout_details(cx);
             editor.transact(cx, |editor, cx| {

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

@@ -155,6 +155,7 @@ fn search_deploy(_: &mut Workspace, _: &buffer_search::Deploy, cx: &mut ViewCont
 fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext<Workspace>) {
     let mut motion = None;
     Vim::update(cx, |vim, cx| {
+        vim.store_visual_marks(cx);
         let pane = workspace.active_pane().clone();
         pane.update(cx, |pane, cx| {
             if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {

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

@@ -29,6 +29,7 @@ pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>
 }
 
 pub fn substitute(vim: &mut Vim, count: Option<usize>, line_mode: bool, cx: &mut WindowContext) {
+    vim.store_visual_marks(cx);
     vim.update_active_editor(cx, |vim, editor, cx| {
         editor.set_clip_at_line_ends(false, cx);
         editor.transact(cx, |editor, cx| {

crates/vim/src/vim.rs 🔗

@@ -391,6 +391,15 @@ impl Vim {
         self.stop_recording();
     }
 
+    // When handling an action, you must create visual marks if you will switch to normal
+    // mode without the default selection behaviour.
+    fn store_visual_marks(&mut self, cx: &mut WindowContext) {
+        let mode = self.state().mode;
+        if mode.is_visual() {
+            create_visual_marks(self, mode, cx);
+        }
+    }
+
     fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
         let state = self.state();
         let last_mode = state.mode;
@@ -412,14 +421,14 @@ impl Vim {
         // Sync editor settings like clip mode
         self.sync_vim_settings(cx);
 
-        if !mode.is_visual() && last_mode.is_visual() {
-            create_visual_marks(self, last_mode, cx);
-        }
-
         if leave_selections {
             return;
         }
 
+        if !mode.is_visual() && last_mode.is_visual() {
+            create_visual_marks(self, last_mode, cx);
+        }
+
         // Adjust selections
         self.update_active_editor(cx, |_, editor, cx| {
             if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock

crates/vim/src/visual.rs 🔗

@@ -410,6 +410,7 @@ pub fn other_end(_: &mut Workspace, _: &OtherEnd, cx: &mut ViewContext<Workspace
 }
 
 pub fn delete(vim: &mut Vim, line_mode: bool, cx: &mut WindowContext) {
+    vim.store_visual_marks(cx);
     vim.update_active_editor(cx, |vim, editor, cx| {
         let mut original_columns: HashMap<_, _> = Default::default();
         let line_mode = line_mode || editor.selections.line_mode;
@@ -463,6 +464,7 @@ pub fn delete(vim: &mut Vim, line_mode: bool, cx: &mut WindowContext) {
 }
 
 pub fn yank(vim: &mut Vim, cx: &mut WindowContext) {
+    vim.store_visual_marks(cx);
     vim.update_active_editor(cx, |vim, editor, cx| {
         let line_mode = editor.selections.line_mode;
         yank_selections_content(vim, editor, line_mode, cx);
@@ -1357,5 +1359,15 @@ mod test {
             "},
             Mode::Visual,
         );
+        cx.simulate_keystrokes("y g v");
+        cx.assert_state(
+            indoc! {"
+                «fishˇ» one
+                «fishˇ» two
+                «fishˇ» red
+                «fishˇ» blue
+            "},
+            Mode::Visual,
+        );
     }
 }