From 068d64edd637be2e7e2a44b99ef4965550885b67 Mon Sep 17 00:00:00 2001 From: Liber Date: Wed, 20 May 2026 11:39:36 +0800 Subject: [PATCH] vim: Fix n/N and */# selecting text after cmd+f search (#54027) The cmd_f_search flag introduced in #51073 was never cleared when switching to vim-style search commands, causing collapse_matches to remain false and match ranges to appear as visual selections. Reset cmd_f_search when n/N or */# are used, restore Normal mode if Visual was entered due to the non-collapsed selection, and fix an early return in visual select_match that leaked collapse_matches. Fixes #53896 Self-Review Checklist: - [ ] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [ ] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [ ] Tests cover the new/changed behavior - [ ] Performance impact has been considered and is acceptable Closes #ISSUE Release Notes: - N/A or Added/Fixed/Improved ... Co-authored-by: Conrad Irwin --- crates/vim/src/normal/search.rs | 58 +++++++++++++++++++++++++++++++++ crates/vim/src/visual.rs | 3 ++ 2 files changed, 61 insertions(+) diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 1ebf51aa29..92baae1d37 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -389,6 +389,15 @@ impl Vim { }; let count = Vim::take_count(cx).unwrap_or(1); Vim::take_forced_motion(cx); + + if self.search.cmd_f_search { + self.search.cmd_f_search = false; + if self.mode.is_visual() { + self.switch_mode(Mode::Normal, false, window, cx); + } + self.sync_vim_settings(window, cx); + } + let prior_selections = self.editor_selections(window, cx); let success = pane.update(cx, |pane, cx| { @@ -433,6 +442,15 @@ impl Vim { }; let count = Vim::take_count(cx).unwrap_or(1); Vim::take_forced_motion(cx); + + if self.search.cmd_f_search { + self.search.cmd_f_search = false; + if self.mode.is_visual() { + self.switch_mode(Mode::Normal, false, window, cx); + } + self.sync_vim_settings(window, cx); + } + let prior_selections = self.editor_selections(window, cx); let vim = cx.entity(); @@ -1017,6 +1035,46 @@ mod test { cx.assert_state("one «oneˇ» one one", Mode::Insert); } + #[gpui::test] + async fn test_n_after_cmd_f_search(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.set_state("ˇone two one two one", Mode::Normal); + cx.run_until_parked(); + + // Use cmd+f to search (non-vim style) + cx.simulate_keystrokes("cmd-f"); + cx.run_until_parked(); + cx.simulate_keystrokes("escape"); + cx.run_until_parked(); + + // Now use n to go to next match — should move cursor, not create selection + cx.simulate_keystrokes("n"); + cx.run_until_parked(); + cx.assert_state("one two ˇone two one", Mode::Normal); + + cx.simulate_keystrokes("n"); + cx.run_until_parked(); + cx.assert_state("one two one two ˇone", Mode::Normal); + } + + #[gpui::test] + async fn test_star_after_cmd_f_search(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.set_state("ˇone two one two one", Mode::Normal); + cx.run_until_parked(); + + // Use cmd+f to search (non-vim style) + cx.simulate_keystrokes("cmd-f"); + cx.run_until_parked(); + cx.simulate_keystrokes("escape"); + cx.run_until_parked(); + + // Now use * to search under cursor — should move cursor, not create selection + cx.simulate_keystrokes("*"); + cx.run_until_parked(); + cx.assert_state("one two ˇone two one", Mode::Normal); + } + #[gpui::test] async fn test_visual_star_hash(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index bc53167b15..077e098bb3 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -897,6 +897,9 @@ impl Vim { } }); if !match_exists { + self.update_editor(cx, |_, editor, _| { + editor.set_collapse_matches(true); + }); self.clear_operator(window, cx); self.stop_replaying(cx); return;