Change behavior of search with vim mode enabled (#51073)

When vim mode is enabled, previously if Cmd-F (or platform equivalent)
was pressed, enter will go to the editor's first match, and then hitting
enter again goes to the next line rather than next match. This PR
changes it to make enter go to the next match, which matches the
convention in most other programs. The behavior when search is initiated
with / is left unchanged.

This is a reopen of #35157, rebased and fixed.
Closes #7692

Release Notes:

- In vim mode, when search is triggered by the non-vim mode shortcut
(cmd-f by default) enter will now behave as it does outside of vim mode.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Vivek Jain 2026-03-31 20:51:59 -07:00 committed by GitHub
parent 878aba817c
commit 0b275eaa44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 50 additions and 3 deletions

View file

@ -648,6 +648,7 @@ impl Vim {
self.search = SearchState {
direction: searchable::Direction::Next,
count: 1,
cmd_f_search: false,
prior_selections,
prior_operator: self.operator_stack.last().cloned(),
prior_mode: self.mode,

View file

@ -284,6 +284,7 @@ impl Vim {
self.search = SearchState {
direction,
count,
cmd_f_search: false,
prior_selections,
prior_operator: self.operator_stack.last().cloned(),
prior_mode,
@ -298,6 +299,7 @@ impl Vim {
let current_mode = self.mode;
self.search = Default::default();
self.search.prior_mode = current_mode;
self.search.cmd_f_search = true;
cx.propagate();
}
@ -957,6 +959,45 @@ mod test {
cx.assert_editor_state("«oneˇ» one one one");
}
#[gpui::test]
async fn test_non_vim_search_in_vim_mode(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.cx.set_state("ˇone one one one");
cx.run_until_parked();
cx.simulate_keystrokes("cmd-f");
cx.run_until_parked();
cx.assert_state("«oneˇ» one one one", Mode::Visual);
cx.simulate_keystrokes("enter");
cx.run_until_parked();
cx.assert_state("one «oneˇ» one one", Mode::Visual);
cx.simulate_keystrokes("shift-enter");
cx.run_until_parked();
cx.assert_state("«oneˇ» one one one", Mode::Visual);
cx.simulate_keystrokes("escape");
cx.run_until_parked();
cx.assert_state("«oneˇ» one one one", Mode::Visual);
}
#[gpui::test]
async fn test_non_vim_search_in_vim_insert_mode(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("ˇone one one one", Mode::Insert);
cx.run_until_parked();
cx.simulate_keystrokes("cmd-f");
cx.run_until_parked();
cx.assert_state("«oneˇ» one one one", Mode::Insert);
cx.simulate_keystrokes("enter");
cx.run_until_parked();
cx.assert_state("one «oneˇ» one one", Mode::Insert);
cx.simulate_keystrokes("escape");
cx.run_until_parked();
cx.assert_state("one «oneˇ» one one", Mode::Insert);
}
#[gpui::test]
async fn test_visual_star_hash(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;

View file

@ -1022,6 +1022,7 @@ impl Clone for ReplayableAction {
pub struct SearchState {
pub direction: Direction,
pub count: usize,
pub cmd_f_search: bool,
pub prior_selections: Vec<Range<Anchor>>,
pub prior_operator: Option<Operator>,

View file

@ -432,8 +432,12 @@ pub fn init(cx: &mut App) {
.and_then(|item| item.act_as::<Editor>(cx))
.and_then(|editor| editor.read(cx).addon::<VimAddon>().cloned());
let Some(vim) = vim else { return };
vim.entity.update(cx, |_, cx| {
cx.defer_in(window, |vim, window, cx| vim.search_submit(window, cx))
vim.entity.update(cx, |vim, cx| {
if !vim.search.cmd_f_search {
cx.defer_in(window, |vim, window, cx| vim.search_submit(window, cx))
} else {
cx.propagate()
}
})
});
workspace.register_action(|_, _: &GoToTab, window, cx| {
@ -2086,7 +2090,7 @@ impl Vim {
VimEditorSettingsState {
cursor_shape: self.cursor_shape(cx),
clip_at_line_ends: self.clip_at_line_ends(),
collapse_matches: !HelixModeSetting::get_global(cx).0,
collapse_matches: !HelixModeSetting::get_global(cx).0 && !self.search.cmd_f_search,
input_enabled: self.editor_input_enabled(),
expects_character_input: self.expects_character_input(),
autoindent: self.should_autoindent(),