From 1fb2922be3ace02b0c295f72d000b871a130a4d9 Mon Sep 17 00:00:00 2001 From: abdellah hariti Date: Fri, 9 Jan 2026 05:13:51 +0100 Subject: [PATCH] Vim search */# without moving cursor initially (#46244) The default behavior for Vim search with `*` and `#` in normal mode is to initiate a search and immediately jump to the next or previous match respectively. This behavior can be annoying, so Vim has many plugins to address this specifically: - [vim-asterisk](https://github.com/haya14busa/vim-asterisk) - [vim-SearchHighlighting](https://github.com/inkarkat/vim-SearchHighlighting) - [vim-tranquille](https://github.com/RRethy/vim-tranquille) This PR tries to emulate this behavior natively keeping up with Zed's sane defaults and deviating from vanilla Vim when it makes sense. Release Notes: - Vim: `*` and `#` search doesn't jump immediately to next / previous search. --- crates/vim/src/normal/search.rs | 109 ++++++++++++++++++++++++++------ 1 file changed, 88 insertions(+), 21 deletions(-) diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 36a529da5da4be4ea3437a766daa1bc18bcfdd68..d945aea263305ba2f92ccd2ee88afe9393990b34 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -42,6 +42,32 @@ pub(crate) struct MoveToPrevious { regex: bool, } +/// Searches for the word under the cursor without moving. +#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] +#[serde(deny_unknown_fields)] +pub(crate) struct SearchUnderCursor { + #[serde(default = "default_true")] + case_sensitive: bool, + #[serde(default)] + partial_word: bool, + #[serde(default = "default_true")] + regex: bool, +} + +/// Searches for the word under the cursor without moving (backwards). +#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] +#[serde(deny_unknown_fields)] +pub(crate) struct SearchUnderCursorPrevious { + #[serde(default = "default_true")] + case_sensitive: bool, + #[serde(default)] + partial_word: bool, + #[serde(default = "default_true")] + regex: bool, +} + /// Initiates a search operation with the specified parameters. #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Action)] #[action(namespace = vim)] @@ -95,6 +121,8 @@ actions!( pub(crate) fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, Vim::move_to_next); Vim::action(editor, cx, Vim::move_to_previous); + Vim::action(editor, cx, Vim::search_under_cursor); + Vim::action(editor, cx, Vim::search_under_cursor_previous); Vim::action(editor, cx, Vim::move_to_next_match); Vim::action(editor, cx, Vim::move_to_previous_match); Vim::action(editor, cx, Vim::search); @@ -104,12 +132,47 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context) { } impl Vim { + fn search_under_cursor( + &mut self, + action: &SearchUnderCursor, + window: &mut Window, + cx: &mut Context, + ) { + self.move_to_internal( + Direction::Next, + action.case_sensitive, + !action.partial_word, + action.regex, + false, + window, + cx, + ) + } + + fn search_under_cursor_previous( + &mut self, + action: &SearchUnderCursorPrevious, + window: &mut Window, + cx: &mut Context, + ) { + self.move_to_internal( + Direction::Prev, + action.case_sensitive, + !action.partial_word, + action.regex, + false, + window, + cx, + ) + } + fn move_to_next(&mut self, action: &MoveToNext, window: &mut Window, cx: &mut Context) { self.move_to_internal( Direction::Next, action.case_sensitive, !action.partial_word, action.regex, + true, window, cx, ) @@ -126,6 +189,7 @@ impl Vim { action.case_sensitive, !action.partial_word, action.regex, + true, window, cx, ) @@ -332,6 +396,7 @@ impl Vim { case_sensitive: bool, whole_word: bool, regex: bool, + move_cursor: bool, window: &mut Window, cx: &mut Context, ) { @@ -377,27 +442,29 @@ impl Vim { let Some(search) = search else { return false }; - let search_bar = search_bar.downgrade(); - cx.spawn_in(window, async move |_, cx| { - search.await?; - search_bar.update_in(cx, |search_bar, window, cx| { - search_bar.select_match(direction, count, window, cx); - - vim.update(cx, |vim, cx| { - let new_selections = vim.editor_selections(window, cx); - vim.search_motion( - Motion::ZedSearchResult { - prior_selections, - new_selections, - }, - window, - cx, - ) - }); - })?; - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + if move_cursor { + let search_bar = search_bar.downgrade(); + cx.spawn_in(window, async move |_, cx| { + search.await?; + search_bar.update_in(cx, |search_bar, window, cx| { + search_bar.select_match(direction, count, window, cx); + + vim.update(cx, |vim, cx| { + let new_selections = vim.editor_selections(window, cx); + vim.search_motion( + Motion::ZedSearchResult { + prior_selections, + new_selections, + }, + window, + cx, + ) + }); + })?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } true }); if !searched {