diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 516422a04a..d6602469e0 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1100,6 +1100,11 @@ impl BufferSearchBar { } } } + + pub fn match_exists(&mut self, cx: &mut ViewContext) -> bool { + self.update_match_index(cx); + self.active_match_index.is_some() + } } #[cfg(test)] diff --git a/crates/vim/src/normal/repeat.rs b/crates/vim/src/normal/repeat.rs index a91327e497..646a2a202a 100644 --- a/crates/vim/src/normal/repeat.rs +++ b/crates/vim/src/normal/repeat.rs @@ -172,6 +172,13 @@ pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) { editor.show_local_selections = false; })?; for action in actions { + if !matches!( + cx.update(|cx| Vim::read(cx).workspace_state.replaying), + Ok(true) + ) { + break; + } + match action { ReplayableAction::Action(action) => { if should_replay(&action) { diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 84636af1d0..e9491b1a8e 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -341,6 +341,10 @@ impl Vim { } } + pub fn stop_replaying(&mut self) { + self.workspace_state.replaying = false; + } + /// When finishing an action that modifies the buffer, stop recording. /// as you usually call this within a keystroke handler we also ensure that /// the current action is recorded. @@ -499,6 +503,7 @@ impl Vim { self.sync_vim_settings(cx); popped_operator } + fn clear_operator(&mut self, cx: &mut WindowContext) { self.take_count(cx); self.update_state(|state| state.operator_stack.clear()); diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index e70da37a0e..f46ccc08a2 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -509,7 +509,6 @@ pub fn select_match( vim.update_active_editor(cx, |_, editor, _| { editor.set_collapse_matches(false); }); - if vim_is_normal { pane.update(cx, |pane, cx| { if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { @@ -521,21 +520,27 @@ pub fn select_match( } }); } - vim.update_active_editor(cx, |_, editor, cx| { let latest = editor.selections.newest::(cx); start_selection = latest.start; end_selection = latest.end; }); + let mut match_exists = false; pane.update(cx, |pane, cx| { if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { search_bar.update(cx, |search_bar, cx| { search_bar.update_match_index(cx); search_bar.select_match(direction, count, cx); + match_exists = search_bar.match_exists(cx); }); } }); + if !match_exists { + vim.clear_operator(cx); + vim.stop_replaying(); + return; + } vim.update_active_editor(cx, |_, editor, cx| { let latest = editor.selections.newest::(cx); if vim_is_normal { @@ -553,6 +558,7 @@ pub fn select_match( }); editor.set_collapse_matches(true); }); + match vim.maybe_pop_operator() { Some(Operator::Change) => substitute(vim, None, false, cx), Some(Operator::Delete) => { @@ -561,7 +567,7 @@ pub fn select_match( } Some(Operator::Yank) => yank(vim, cx), _ => {} // Ignoring other operators - }; + } } #[cfg(test)] @@ -1195,4 +1201,29 @@ mod test { cx.simulate_shared_keystrokes(["."]).await; cx.assert_shared_state("aa x ˇx aa aa").await; } + + #[gpui::test] + async fn test_cgn_nomatch(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state("aaˇ aa aa aa aa").await; + cx.simulate_shared_keystrokes(["/", "b", "b", "enter"]) + .await; + cx.assert_shared_state("aaˇ aa aa aa aa").await; + cx.simulate_shared_keystrokes(["c", "g", "n", "x", "escape"]) + .await; + cx.assert_shared_state("aaˇaa aa aa aa").await; + cx.simulate_shared_keystrokes(["."]).await; + cx.assert_shared_state("aaˇa aa aa aa").await; + + cx.set_shared_state("aaˇ bb aa aa aa").await; + cx.simulate_shared_keystrokes(["/", "b", "b", "enter"]) + .await; + cx.assert_shared_state("aa ˇbb aa aa aa").await; + cx.simulate_shared_keystrokes(["c", "g", "n", "x", "escape"]) + .await; + cx.assert_shared_state("aa ˇx aa aa aa").await; + cx.simulate_shared_keystrokes(["."]).await; + cx.assert_shared_state("aa ˇx aa aa aa").await; + } } diff --git a/crates/vim/test_data/test_cgn_nomatch.json b/crates/vim/test_data/test_cgn_nomatch.json new file mode 100644 index 0000000000..9c2f02bb85 --- /dev/null +++ b/crates/vim/test_data/test_cgn_nomatch.json @@ -0,0 +1,28 @@ +{"Put":{"state":"aaˇ aa aa aa aa"}} +{"Key":"/"} +{"Key":"b"} +{"Key":"b"} +{"Key":"enter"} +{"Get":{"state":"aaˇ aa aa aa aa","mode":"Normal"}} +{"Key":"c"} +{"Key":"g"} +{"Key":"n"} +{"Key":"x"} +{"Key":"escape"} +{"Get":{"state":"aaˇaa aa aa aa","mode":"Normal"}} +{"Key":"."} +{"Get":{"state":"aaˇa aa aa aa","mode":"Normal"}} +{"Put":{"state":"aaˇ bb aa aa aa"}} +{"Key":"/"} +{"Key":"b"} +{"Key":"b"} +{"Key":"enter"} +{"Get":{"state":"aa ˇbb aa aa aa","mode":"Normal"}} +{"Key":"c"} +{"Key":"g"} +{"Key":"n"} +{"Key":"x"} +{"Key":"escape"} +{"Get":{"state":"aa ˇx aa aa aa","mode":"Normal"}} +{"Key":"."} +{"Get":{"state":"aa ˇx aa aa aa","mode":"Normal"}}