Fix 0 used in a count

Conrad Irwin created

Change summary

crates/vim/src/insert.rs                          |  2 
crates/vim/src/motion.rs                          |  2 
crates/vim/src/normal.rs                          | 10 +++---
crates/vim/src/normal/case.rs                     |  2 
crates/vim/src/normal/repeat.rs                   |  4 +-
crates/vim/src/normal/scroll.rs                   |  2 
crates/vim/src/normal/search.rs                   |  4 +-
crates/vim/src/normal/substitute.rs               |  4 +-
crates/vim/src/state.rs                           |  1 
crates/vim/src/test.rs                            | 27 ++++++++++++++++
crates/vim/src/test/neovim_backed_test_context.rs | 14 ++++++++
crates/vim/src/vim.rs                             | 19 +++++------
crates/vim/test_data/test_clear_counts.json       |  2 
crates/vim/test_data/test_dot_repeat.json         |  2 
crates/vim/test_data/test_zero.json               |  7 ++++
15 files changed, 73 insertions(+), 29 deletions(-)

Detailed changes

crates/vim/src/insert.rs 🔗

@@ -12,7 +12,7 @@ pub fn init(cx: &mut AppContext) {
 
 fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext<Workspace>) {
     let should_repeat = Vim::update(cx, |vim, cx| {
-        let count = vim.take_count().unwrap_or(1);
+        let count = vim.take_count(cx).unwrap_or(1);
         vim.stop_recording_immediately(action.boxed_clone());
         if count <= 1 || vim.workspace_state.replaying {
             vim.update_active_editor(cx, |editor, cx| {

crates/vim/src/motion.rs 🔗

@@ -229,7 +229,7 @@ pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
         Vim::update(cx, |vim, cx| vim.pop_operator(cx));
     }
 
-    let count = Vim::update(cx, |vim, _| vim.take_count());
+    let count = Vim::update(cx, |vim, cx| vim.take_count(cx));
     let operator = Vim::read(cx).active_operator();
     match Vim::read(cx).state().mode {
         Mode::Normal => normal_motion(motion, operator, count, cx),

crates/vim/src/normal.rs 🔗

@@ -68,21 +68,21 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
         Vim::update(cx, |vim, cx| {
             vim.record_current_action(cx);
-            let times = vim.take_count();
+            let times = vim.take_count(cx);
             delete_motion(vim, Motion::Left, times, cx);
         })
     });
     cx.add_action(|_: &mut Workspace, _: &DeleteRight, cx| {
         Vim::update(cx, |vim, cx| {
             vim.record_current_action(cx);
-            let times = vim.take_count();
+            let times = vim.take_count(cx);
             delete_motion(vim, Motion::Right, times, cx);
         })
     });
     cx.add_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
         Vim::update(cx, |vim, cx| {
             vim.start_recording(cx);
-            let times = vim.take_count();
+            let times = vim.take_count(cx);
             change_motion(
                 vim,
                 Motion::EndOfLine {
@@ -96,7 +96,7 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
         Vim::update(cx, |vim, cx| {
             vim.record_current_action(cx);
-            let times = vim.take_count();
+            let times = vim.take_count(cx);
             delete_motion(
                 vim,
                 Motion::EndOfLine {
@@ -110,7 +110,7 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(|_: &mut Workspace, _: &JoinLines, cx| {
         Vim::update(cx, |vim, cx| {
             vim.record_current_action(cx);
-            let mut times = vim.take_count().unwrap_or(1);
+            let mut times = vim.take_count(cx).unwrap_or(1);
             if vim.state().mode.is_visual() {
                 times = 1;
             } else if times > 1 {

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

@@ -8,7 +8,7 @@ use crate::{normal::ChangeCase, state::Mode, Vim};
 pub fn change_case(_: &mut Workspace, _: &ChangeCase, cx: &mut ViewContext<Workspace>) {
     Vim::update(cx, |vim, cx| {
         vim.record_current_action(cx);
-        let count = vim.take_count().unwrap_or(1) as u32;
+        let count = vim.take_count(cx).unwrap_or(1) as u32;
         vim.update_active_editor(cx, |editor, cx| {
             let mut ranges = Vec::new();
             let mut cursor_positions = Vec::new();

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

@@ -60,7 +60,7 @@ pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
         let Some(editor) = vim.active_editor.clone() else {
             return None;
         };
-        let count = vim.take_count();
+        let count = vim.take_count(cx);
 
         let selection = vim.workspace_state.recorded_selection.clone();
         match selection {
@@ -253,7 +253,7 @@ mod test {
         deterministic.run_until_parked();
         cx.simulate_shared_keystrokes(["."]).await;
         deterministic.run_until_parked();
-        cx.set_shared_state("THE QUICK ˇbrown fox").await;
+        cx.assert_shared_state("THE QUICK ˇbrown fox").await;
     }
 
     #[gpui::test]

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

@@ -48,7 +48,7 @@ pub fn init(cx: &mut AppContext) {
 
 fn scroll(cx: &mut ViewContext<Workspace>, by: fn(c: Option<f32>) -> ScrollAmount) {
     Vim::update(cx, |vim, cx| {
-        let amount = by(vim.take_count().map(|c| c as f32));
+        let amount = by(vim.take_count(cx).map(|c| c as f32));
         vim.update_active_editor(cx, |editor, cx| scroll_editor(editor, &amount, cx));
     })
 }

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

@@ -52,7 +52,7 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
         Direction::Next
     };
     Vim::update(cx, |vim, cx| {
-        let count = vim.take_count().unwrap_or(1);
+        let count = vim.take_count(cx).unwrap_or(1);
         pane.update(cx, |pane, cx| {
             if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
                 search_bar.update(cx, |search_bar, cx| {
@@ -119,7 +119,7 @@ pub fn move_to_internal(
 ) {
     Vim::update(cx, |vim, cx| {
         let pane = workspace.active_pane().clone();
-        let count = vim.take_count().unwrap_or(1);
+        let count = vim.take_count(cx).unwrap_or(1);
         pane.update(cx, |pane, cx| {
             if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
                 let search = search_bar.update(cx, |search_bar, cx| {

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

@@ -11,7 +11,7 @@ pub(crate) fn init(cx: &mut AppContext) {
     cx.add_action(|_: &mut Workspace, _: &Substitute, cx| {
         Vim::update(cx, |vim, cx| {
             vim.start_recording(cx);
-            let count = vim.take_count();
+            let count = vim.take_count(cx);
             substitute(vim, count, vim.state().mode == Mode::VisualLine, cx);
         })
     });
@@ -22,7 +22,7 @@ pub(crate) fn init(cx: &mut AppContext) {
             if matches!(vim.state().mode, Mode::VisualBlock | Mode::Visual) {
                 vim.switch_mode(Mode::VisualLine, false, cx)
             }
-            let count = vim.take_count();
+            let count = vim.take_count(cx);
             substitute(vim, count, true, cx)
         })
     });

crates/vim/src/state.rs 🔗

@@ -186,6 +186,7 @@ impl EditorState {
         if self.active_operator().is_none() && self.pre_count.is_some()
             || self.active_operator().is_some() && self.post_count.is_some()
         {
+            dbg!("VimCount");
             context.add_identifier("VimCount");
         }
 

crates/vim/src/test.rs 🔗

@@ -587,9 +587,34 @@ async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
 
     cx.simulate_shared_keystrokes(["4", "escape", "3", "d", "l"])
         .await;
-    cx.set_shared_state(indoc! {"
+    cx.assert_shared_state(indoc! {"
         The quick brown
         fox juˇ over
         the lazy dog"})
         .await;
 }
+
+#[gpui::test]
+async fn test_zero(cx: &mut gpui::TestAppContext) {
+    let mut cx = NeovimBackedTestContext::new(cx).await;
+
+    cx.set_shared_state(indoc! {"
+        The quˇick brown
+        fox jumps over
+        the lazy dog"})
+        .await;
+
+    cx.simulate_shared_keystrokes(["0"]).await;
+    cx.assert_shared_state(indoc! {"
+        ˇThe quick brown
+        fox jumps over
+        the lazy dog"})
+        .await;
+
+    cx.simulate_shared_keystrokes(["1", "0", "l"]).await;
+    cx.assert_shared_state(indoc! {"
+        The quick ˇbrown
+        fox jumps over
+        the lazy dog"})
+        .await;
+}

crates/vim/src/test/neovim_backed_test_context.rs 🔗

@@ -68,6 +68,8 @@ pub struct NeovimBackedTestContext<'a> {
 
     last_set_state: Option<String>,
     recent_keystrokes: Vec<String>,
+
+    is_dirty: bool,
 }
 
 impl<'a> NeovimBackedTestContext<'a> {
@@ -81,6 +83,7 @@ impl<'a> NeovimBackedTestContext<'a> {
 
             last_set_state: None,
             recent_keystrokes: Default::default(),
+            is_dirty: false,
         }
     }
 
@@ -128,6 +131,7 @@ impl<'a> NeovimBackedTestContext<'a> {
         self.last_set_state = Some(marked_text.to_string());
         self.recent_keystrokes = Vec::new();
         self.neovim.set_state(marked_text).await;
+        self.is_dirty = true;
         context_handle
     }
 
@@ -153,6 +157,7 @@ impl<'a> NeovimBackedTestContext<'a> {
     }
 
     pub async fn assert_shared_state(&mut self, marked_text: &str) {
+        self.is_dirty = false;
         let marked_text = marked_text.replace("•", " ");
         let neovim = self.neovim_state().await;
         let editor = self.editor_state();
@@ -258,6 +263,7 @@ impl<'a> NeovimBackedTestContext<'a> {
     }
 
     pub async fn assert_state_matches(&mut self) {
+        self.is_dirty = false;
         let neovim = self.neovim_state().await;
         let editor = self.editor_state();
         let initial_state = self
@@ -383,6 +389,14 @@ impl<'a> DerefMut for NeovimBackedTestContext<'a> {
     }
 }
 
+impl<'a> Drop for NeovimBackedTestContext<'a> {
+    fn drop(&mut self) {
+        if self.is_dirty {
+            panic!("Test context was dropped after set_shared_state before assert_shared_state")
+        }
+    }
+}
+
 #[cfg(test)]
 mod test {
     use gpui::TestAppContext;

crates/vim/src/vim.rs 🔗

@@ -73,7 +73,7 @@ pub fn init(cx: &mut AppContext) {
         },
     );
     cx.add_action(|_: &mut Workspace, n: &Number, cx: _| {
-        Vim::update(cx, |vim, _| vim.push_count_digit(n.0));
+        Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
     });
 
     cx.add_action(|_: &mut Workspace, _: &Tab, cx| {
@@ -228,13 +228,7 @@ impl Vim {
         let editor = self.active_editor.clone()?.upgrade(cx)?;
         Some(editor.update(cx, update))
     }
-    // ~, shift-j, x, shift-x, p
-    // shift-c, shift-d, shift-i, i, a, o, shift-o, s
-    // c, d
-    // r
 
-    // TODO: shift-j?
-    //
     pub fn start_recording(&mut self, cx: &mut WindowContext) {
         if !self.workspace_state.replaying {
             self.workspace_state.recording = true;
@@ -309,7 +303,7 @@ impl Vim {
             state.operator_stack.clear();
         });
         if mode != Mode::Insert {
-            self.take_count();
+            self.take_count(cx);
         }
 
         cx.emit_global(VimEvent::ModeChanged { mode });
@@ -363,7 +357,7 @@ impl Vim {
         });
     }
 
-    fn push_count_digit(&mut self, number: usize) {
+    fn push_count_digit(&mut self, number: usize, cx: &mut WindowContext) {
         if self.active_operator().is_some() {
             self.update_state(|state| {
                 state.post_count = Some(state.post_count.unwrap_or(0) * 10 + number)
@@ -373,9 +367,11 @@ impl Vim {
                 state.pre_count = Some(state.pre_count.unwrap_or(0) * 10 + number)
             })
         }
+        // update the keymap so that 0 works
+        self.sync_vim_settings(cx)
     }
 
-    fn take_count(&mut self) -> Option<usize> {
+    fn take_count(&mut self, cx: &mut WindowContext) -> Option<usize> {
         if self.workspace_state.replaying {
             return self.workspace_state.recorded_count;
         }
@@ -390,6 +386,7 @@ impl Vim {
         if self.workspace_state.recording {
             self.workspace_state.recorded_count = count;
         }
+        self.sync_vim_settings(cx);
         count
     }
 
@@ -415,7 +412,7 @@ impl Vim {
         popped_operator
     }
     fn clear_operator(&mut self, cx: &mut WindowContext) {
-        self.take_count();
+        self.take_count(cx);
         self.update_state(|state| state.operator_stack.clear());
         self.sync_vim_settings(cx);
     }

crates/vim/test_data/test_clear_counts.json 🔗

@@ -4,4 +4,4 @@
 {"Key":"3"}
 {"Key":"d"}
 {"Key":"l"}
-{"Put":{"state":"The quick brown\nfox juˇ over\nthe lazy dog"}}
+{"Get":{"state":"The quick brown\nfox juˇ over\nthe lazy dog","mode":"Normal"}}

crates/vim/test_data/test_dot_repeat.json 🔗

@@ -35,4 +35,4 @@
 {"Key":"."}
 {"Put":{"state":"THE QUIˇck brown fox"}}
 {"Key":"."}
-{"Put":{"state":"THE QUICK ˇbrown fox"}}
+{"Get":{"state":"THE QUICK ˇbrown fox","mode":"Normal"}}

crates/vim/test_data/test_zero.json 🔗

@@ -0,0 +1,7 @@
+{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"0"}
+{"Get":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"0"}
+{"Key":"l"}
+{"Get":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog","mode":"Normal"}}