use editor::{Editor, EditorSettings};use gpui::{Action, Context, Window, actions};use language::Point;use schemars::JsonSchema;use search::{BufferSearchBar, SearchOptions, buffer_search};use serde::Deserialize;use settings::Settings;use std::{iter::Peekable, str::Chars};use util::serde::default_true;use workspace::{notifications::NotifyResultExt, searchable::Direction}; use crate::{ Vim, command::CommandRange, motion::Motion, state::{Mode, SearchState},}; /// Moves to the next search match.#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]pub(crate) struct MoveToNext { #[serde(default = "default_true")] case_sensitive: bool, #[serde(default)] partial_word: bool, #[serde(default = "default_true")] regex: bool,} /// Moves to the previous search match.#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]pub(crate) struct MoveToPrevious { #[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)]#[serde(deny_unknown_fields)]pub(crate) struct Search { #[serde(default)] backwards: bool, #[serde(default = "default_true")] regex: bool,} /// Executes a find command to search for patterns in the buffer.#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]pub struct FindCommand { pub query: String, pub backwards: bool,} /// Executes a search and replace command within the specified range.#[derive(Clone, Debug, PartialEq, Action)]#[action(namespace = vim, no_json, no_register)]pub struct ReplaceCommand { pub(crate) range: CommandRange, pub(crate) replacement: Replacement,} #[derive(Clone, Debug, PartialEq)]pub struct Replacement { search: String, replacement: String, case_sensitive: Option<bool>, flag_n: bool, flag_g: bool, flag_c: bool,} actions!( vim, [ /// Submits the current search query. SearchSubmit, /// Moves to the next search match. MoveToNextMatch, /// Moves to the previous search match. MoveToPreviousMatch ]); pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) { Vim::action(editor, cx, Vim::move_to_next); Vim::action(editor, cx, Vim::move_to_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); Vim::action(editor, cx, Vim::search_deploy); Vim::action(editor, cx, Vim::find_command); Vim::action(editor, cx, Vim::replace_command);} impl Vim { fn move_to_next(&mut self, action: &MoveToNext, window: &mut Window, cx: &mut Context<Self>) { self.move_to_internal( Direction::Next, action.case_sensitive, !action.partial_word, action.regex, window, cx, ) } fn move_to_previous( &mut self, action: &MoveToPrevious, window: &mut Window, cx: &mut Context<Self>, ) { self.move_to_internal( Direction::Prev, action.case_sensitive, !action.partial_word, action.regex, window, cx, ) } fn move_to_next_match( &mut self, _: &MoveToNextMatch, window: &mut Window, cx: &mut Context<Self>, ) { self.move_to_match_internal(self.search.direction, window, cx) } fn move_to_previous_match( &mut self, _: &MoveToPreviousMatch, window: &mut Window, cx: &mut Context<Self>, ) { self.move_to_match_internal(self.search.direction.opposite(), window, cx) } fn search(&mut self, action: &Search, window: &mut Window, cx: &mut Context<Self>) { let Some(pane) = self.pane(window, cx) else { return; }; let direction = if action.backwards { Direction::Prev } else { Direction::Next }; let count = Vim::take_count(cx).unwrap_or(1); Vim::take_forced_motion(cx); let prior_selections = self.editor_selections(window, cx); 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| { if !search_bar.show(window, cx) { return; } search_bar.select_query(window, cx); cx.focus_self(window); search_bar.set_replacement(None, cx); let mut options = SearchOptions::NONE; if action.regex { options |= SearchOptions::REGEX; } if action.backwards { options |= SearchOptions::BACKWARDS; } if EditorSettings::get_global(cx).search.case_sensitive { options |= SearchOptions::CASE_SENSITIVE; } search_bar.set_search_options(options, cx); let prior_mode = if self.temp_mode { Mode::Insert } else { self.mode }; self.search = SearchState { direction, count, prior_selections, prior_operator: self.operator_stack.last().cloned(), prior_mode, helix_select: false, } }); } }) } // hook into the existing to clear out any vim search state on cmd+f or edit -> find. fn search_deploy(&mut self, _: &buffer_search::Deploy, _: &mut Window, cx: &mut Context<Self>) { // Preserve the current mode when resetting search state let current_mode = self.mode; self.search = Default::default(); self.search.prior_mode = current_mode; cx.propagate(); } pub fn search_submit(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.store_visual_marks(window, cx); let Some(pane) = self.pane(window, cx) else { return; }; let new_selections = self.editor_selections(window, cx); let result = pane.update(cx, |pane, cx| { let search_bar = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>()?; if self.search.helix_select { search_bar.update(cx, |search_bar, cx| { search_bar.select_all_matches(&Default::default(), window, cx) }); return None; } search_bar.update(cx, |search_bar, cx| { let mut count = self.search.count; let direction = self.search.direction; search_bar.has_active_match(); let new_head = new_selections.last()?.start; let is_different_head = self .search .prior_selections .last() .is_none_or(|range| range.start != new_head); if is_different_head { count = count.saturating_sub(1) } self.search.count = 1; search_bar.select_match(direction, count, window, cx); search_bar.focus_editor(&Default::default(), window, cx); let prior_selections: Vec<_> = self.search.prior_selections.drain(..).collect(); let prior_mode = self.search.prior_mode; let prior_operator = self.search.prior_operator.take(); let query = search_bar.query(cx).into(); Vim::globals(cx).registers.insert('/', query); Some((prior_selections, prior_mode, prior_operator)) }) }); let Some((mut prior_selections, prior_mode, prior_operator)) = result else { return; }; let new_selections = self.editor_selections(window, cx); // If the active editor has changed during a search, don't panic. if prior_selections.iter().any(|s| { self.update_editor(cx, |_, editor, cx| { !s.start .is_valid(&editor.snapshot(window, cx).buffer_snapshot()) }) .unwrap_or(true) }) { prior_selections.clear(); } if prior_mode != self.mode { self.switch_mode(prior_mode, true, window, cx); } if let Some(operator) = prior_operator { self.push_operator(operator, window, cx); }; self.search_motion( Motion::ZedSearchResult { prior_selections, new_selections, }, window, cx, ); } pub fn move_to_match_internal( &mut self, direction: Direction, window: &mut Window, cx: &mut Context<Self>, ) { let Some(pane) = self.pane(window, cx) else { return; }; let count = Vim::take_count(cx).unwrap_or(1); Vim::take_forced_motion(cx); let prior_selections = self.editor_selections(window, cx); let success = pane.update(cx, |pane, cx| { let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else { return false; }; search_bar.update(cx, |search_bar, cx| { if !search_bar.has_active_match() || !search_bar.show(window, cx) { return false; } search_bar.select_match(direction, count, window, cx); true }) }); if !success { return; } let new_selections = self.editor_selections(window, cx); self.search_motion( Motion::ZedSearchResult { prior_selections, new_selections, }, window, cx, ); } pub fn move_to_internal( &mut self, direction: Direction, case_sensitive: bool, whole_word: bool, regex: bool, window: &mut Window, cx: &mut Context<Self>, ) { let Some(pane) = self.pane(window, cx) else { return; }; let count = Vim::take_count(cx).unwrap_or(1); Vim::take_forced_motion(cx); let prior_selections = self.editor_selections(window, cx); let cursor_word = self.editor_cursor_word(window, cx); let vim = cx.entity(); let searched = pane.update(cx, |pane, cx| { self.search.direction = direction; let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else { return false; }; let search = search_bar.update(cx, |search_bar, cx| { let mut options = SearchOptions::NONE; if case_sensitive { options |= SearchOptions::CASE_SENSITIVE; } if regex { options |= SearchOptions::REGEX; } if whole_word { options |= SearchOptions::WHOLE_WORD; } if !search_bar.show(window, cx) { return None; } let Some(query) = search_bar .query_suggestion(window, cx) .or_else(|| cursor_word) else { drop(search_bar.search("", None, false, window, cx)); return None; }; let query = regex::escape(&query); Some(search_bar.search(&query, Some(options), true, window, cx)) }); 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); true }); if !searched { self.clear_operator(window, cx) } if self.mode.is_visual() { self.switch_mode(Mode::Normal, false, window, cx) } } fn find_command(&mut self, action: &FindCommand, window: &mut Window, cx: &mut Context<Self>) { let Some(pane) = self.pane(window, cx) else { return; }; 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| { if !search_bar.show(window, cx) { return None; } let mut query = action.query.clone(); if query.is_empty() { query = search_bar.query(cx); }; let mut options = SearchOptions::REGEX | SearchOptions::CASE_SENSITIVE; if search_bar.should_use_smartcase_search(cx) { options.set( SearchOptions::CASE_SENSITIVE, search_bar.is_contains_uppercase(&query), ); } Some(search_bar.search(&query, Some(options), true, window, cx)) }); let Some(search) = search else { return }; let search_bar = search_bar.downgrade(); let direction = if action.backwards { Direction::Prev } else { Direction::Next }; cx.spawn_in(window, async move |_, cx| { search.await?; search_bar.update_in(cx, |search_bar, window, cx| { search_bar.select_match(direction, 1, window, cx) })?; anyhow::Ok(()) }) .detach_and_log_err(cx); } }) } fn replace_command( &mut self, action: &ReplaceCommand, window: &mut Window, cx: &mut Context<Self>, ) { let replacement = action.replacement.clone(); let Some(((pane, workspace), editor)) = self .pane(window, cx) .zip(self.workspace(window)) .zip(self.editor()) else { return; }; if let Some(result) = self.update_editor(cx, |vim, editor, cx| { let range = action.range.buffer_range(vim, editor, window, cx)?; let snapshot = editor.snapshot(window, cx); let snapshot = snapshot.buffer_snapshot(); let end_point = Point::new(range.end.0, snapshot.line_len(range.end)); let range = snapshot.anchor_before(Point::new(range.start.0, 0)) ..snapshot.anchor_after(end_point); editor.set_search_within_ranges(&[range], cx); anyhow::Ok(()) }) { workspace.update(cx, |workspace, cx| { result.notify_err(workspace, cx); }) } let Some(search_bar) = pane.update(cx, |pane, cx| { pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() }) else { return; }; let mut options = SearchOptions::REGEX; let search = search_bar.update(cx, |search_bar, cx| { if !search_bar.show(window, cx) { return None; } let search = if replacement.search.is_empty() { search_bar.query(cx) } else { replacement.search }; Lines 1–500 of 1171. Load more
use editor::{Editor, EditorSettings}; use gpui::{Action, Context, Window, actions}; use language::Point; use schemars::JsonSchema; use search::{BufferSearchBar, SearchOptions, buffer_search}; use serde::Deserialize; use settings::Settings; use std::{iter::Peekable, str::Chars}; use util::serde::default_true; use workspace::{notifications::NotifyResultExt, searchable::Direction}; use crate::{ Vim, command::CommandRange, motion::Motion, state::{Mode, SearchState}, }; /// Moves to the next search match. #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Action)] #[action(namespace = vim)] #[serde(deny_unknown_fields)] pub(crate) struct MoveToNext { #[serde(default = "default_true")] case_sensitive: bool, #[serde(default)] partial_word: bool, #[serde(default = "default_true")] regex: bool, } /// Moves to the previous search match. #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Action)] #[action(namespace = vim)] #[serde(deny_unknown_fields)] pub(crate) struct MoveToPrevious { #[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)] #[serde(deny_unknown_fields)] pub(crate) struct Search { #[serde(default)] backwards: bool, #[serde(default = "default_true")] regex: bool, } /// Executes a find command to search for patterns in the buffer. #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Action)] #[action(namespace = vim)] #[serde(deny_unknown_fields)] pub struct FindCommand { pub query: String, pub backwards: bool, } /// Executes a search and replace command within the specified range. #[derive(Clone, Debug, PartialEq, Action)] #[action(namespace = vim, no_json, no_register)] pub struct ReplaceCommand { pub(crate) range: CommandRange, pub(crate) replacement: Replacement, } #[derive(Clone, Debug, PartialEq)] pub struct Replacement { search: String, replacement: String, case_sensitive: Option<bool>, flag_n: bool, flag_g: bool, flag_c: bool, } actions!( vim, [ /// Submits the current search query. SearchSubmit, /// Moves to the next search match. MoveToNextMatch, /// Moves to the previous search match. MoveToPreviousMatch ] ); pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) { Vim::action(editor, cx, Vim::move_to_next); Vim::action(editor, cx, Vim::move_to_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); Vim::action(editor, cx, Vim::search_deploy); Vim::action(editor, cx, Vim::find_command); Vim::action(editor, cx, Vim::replace_command); } impl Vim { fn move_to_next(&mut self, action: &MoveToNext, window: &mut Window, cx: &mut Context<Self>) { self.move_to_internal( Direction::Next, action.case_sensitive, !action.partial_word, action.regex, window, cx, ) } fn move_to_previous( &mut self, action: &MoveToPrevious, window: &mut Window, cx: &mut Context<Self>, ) { self.move_to_internal( Direction::Prev, action.case_sensitive, !action.partial_word, action.regex, window, cx, ) } fn move_to_next_match( &mut self, _: &MoveToNextMatch, window: &mut Window, cx: &mut Context<Self>, ) { self.move_to_match_internal(self.search.direction, window, cx) } fn move_to_previous_match( &mut self, _: &MoveToPreviousMatch, window: &mut Window, cx: &mut Context<Self>, ) { self.move_to_match_internal(self.search.direction.opposite(), window, cx) } fn search(&mut self, action: &Search, window: &mut Window, cx: &mut Context<Self>) { let Some(pane) = self.pane(window, cx) else { return; }; let direction = if action.backwards { Direction::Prev } else { Direction::Next }; let count = Vim::take_count(cx).unwrap_or(1); Vim::take_forced_motion(cx); let prior_selections = self.editor_selections(window, cx); 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| { if !search_bar.show(window, cx) { return; } search_bar.select_query(window, cx); cx.focus_self(window); search_bar.set_replacement(None, cx); let mut options = SearchOptions::NONE; if action.regex { options |= SearchOptions::REGEX; } if action.backwards { options |= SearchOptions::BACKWARDS; } if EditorSettings::get_global(cx).search.case_sensitive { options |= SearchOptions::CASE_SENSITIVE; } search_bar.set_search_options(options, cx); let prior_mode = if self.temp_mode { Mode::Insert } else { self.mode }; self.search = SearchState { direction, count, prior_selections, prior_operator: self.operator_stack.last().cloned(), prior_mode, helix_select: false, } }); } }) } // hook into the existing to clear out any vim search state on cmd+f or edit -> find. fn search_deploy(&mut self, _: &buffer_search::Deploy, _: &mut Window, cx: &mut Context<Self>) { // Preserve the current mode when resetting search state let current_mode = self.mode; self.search = Default::default(); self.search.prior_mode = current_mode; cx.propagate(); } pub fn search_submit(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.store_visual_marks(window, cx); let Some(pane) = self.pane(window, cx) else { return; }; let new_selections = self.editor_selections(window, cx); let result = pane.update(cx, |pane, cx| { let search_bar = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>()?; if self.search.helix_select { search_bar.update(cx, |search_bar, cx| { search_bar.select_all_matches(&Default::default(), window, cx) }); return None; } search_bar.update(cx, |search_bar, cx| { let mut count = self.search.count; let direction = self.search.direction; search_bar.has_active_match(); let new_head = new_selections.last()?.start; let is_different_head = self .search .prior_selections .last() .is_none_or(|range| range.start != new_head); if is_different_head { count = count.saturating_sub(1) } self.search.count = 1; search_bar.select_match(direction, count, window, cx); search_bar.focus_editor(&Default::default(), window, cx); let prior_selections: Vec<_> = self.search.prior_selections.drain(..).collect(); let prior_mode = self.search.prior_mode; let prior_operator = self.search.prior_operator.take(); let query = search_bar.query(cx).into(); Vim::globals(cx).registers.insert('/', query); Some((prior_selections, prior_mode, prior_operator)) }) }); let Some((mut prior_selections, prior_mode, prior_operator)) = result else { return; }; let new_selections = self.editor_selections(window, cx); // If the active editor has changed during a search, don't panic. if prior_selections.iter().any(|s| { self.update_editor(cx, |_, editor, cx| { !s.start .is_valid(&editor.snapshot(window, cx).buffer_snapshot()) }) .unwrap_or(true) }) { prior_selections.clear(); } if prior_mode != self.mode { self.switch_mode(prior_mode, true, window, cx); } if let Some(operator) = prior_operator { self.push_operator(operator, window, cx); }; self.search_motion( Motion::ZedSearchResult { prior_selections, new_selections, }, window, cx, ); } pub fn move_to_match_internal( &mut self, direction: Direction, window: &mut Window, cx: &mut Context<Self>, ) { let Some(pane) = self.pane(window, cx) else { return; }; let count = Vim::take_count(cx).unwrap_or(1); Vim::take_forced_motion(cx); let prior_selections = self.editor_selections(window, cx); let success = pane.update(cx, |pane, cx| { let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else { return false; }; search_bar.update(cx, |search_bar, cx| { if !search_bar.has_active_match() || !search_bar.show(window, cx) { return false; } search_bar.select_match(direction, count, window, cx); true }) }); if !success { return; } let new_selections = self.editor_selections(window, cx); self.search_motion( Motion::ZedSearchResult { prior_selections, new_selections, }, window, cx, ); } pub fn move_to_internal( &mut self, direction: Direction, case_sensitive: bool, whole_word: bool, regex: bool, window: &mut Window, cx: &mut Context<Self>, ) { let Some(pane) = self.pane(window, cx) else { return; }; let count = Vim::take_count(cx).unwrap_or(1); Vim::take_forced_motion(cx); let prior_selections = self.editor_selections(window, cx); let cursor_word = self.editor_cursor_word(window, cx); let vim = cx.entity(); let searched = pane.update(cx, |pane, cx| { self.search.direction = direction; let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else { return false; }; let search = search_bar.update(cx, |search_bar, cx| { let mut options = SearchOptions::NONE; if case_sensitive { options |= SearchOptions::CASE_SENSITIVE; } if regex { options |= SearchOptions::REGEX; } if whole_word { options |= SearchOptions::WHOLE_WORD; } if !search_bar.show(window, cx) { return None; } let Some(query) = search_bar .query_suggestion(window, cx) .or_else(|| cursor_word) else { drop(search_bar.search("", None, false, window, cx)); return None; }; let query = regex::escape(&query); Some(search_bar.search(&query, Some(options), true, window, cx)) }); 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); true }); if !searched { self.clear_operator(window, cx) } if self.mode.is_visual() { self.switch_mode(Mode::Normal, false, window, cx) } } fn find_command(&mut self, action: &FindCommand, window: &mut Window, cx: &mut Context<Self>) { let Some(pane) = self.pane(window, cx) else { return; }; 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| { if !search_bar.show(window, cx) { return None; } let mut query = action.query.clone(); if query.is_empty() { query = search_bar.query(cx); }; let mut options = SearchOptions::REGEX | SearchOptions::CASE_SENSITIVE; if search_bar.should_use_smartcase_search(cx) { options.set( SearchOptions::CASE_SENSITIVE, search_bar.is_contains_uppercase(&query), ); } Some(search_bar.search(&query, Some(options), true, window, cx)) }); let Some(search) = search else { return }; let search_bar = search_bar.downgrade(); let direction = if action.backwards { Direction::Prev } else { Direction::Next }; cx.spawn_in(window, async move |_, cx| { search.await?; search_bar.update_in(cx, |search_bar, window, cx| { search_bar.select_match(direction, 1, window, cx) })?; anyhow::Ok(()) }) .detach_and_log_err(cx); } }) } fn replace_command( &mut self, action: &ReplaceCommand, window: &mut Window, cx: &mut Context<Self>, ) { let replacement = action.replacement.clone(); let Some(((pane, workspace), editor)) = self .pane(window, cx) .zip(self.workspace(window)) .zip(self.editor()) else { return; }; if let Some(result) = self.update_editor(cx, |vim, editor, cx| { let range = action.range.buffer_range(vim, editor, window, cx)?; let snapshot = editor.snapshot(window, cx); let snapshot = snapshot.buffer_snapshot(); let end_point = Point::new(range.end.0, snapshot.line_len(range.end)); let range = snapshot.anchor_before(Point::new(range.start.0, 0)) ..snapshot.anchor_after(end_point); editor.set_search_within_ranges(&[range], cx); anyhow::Ok(()) }) { workspace.update(cx, |workspace, cx| { result.notify_err(workspace, cx); }) } let Some(search_bar) = pane.update(cx, |pane, cx| { pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() }) else { return; }; let mut options = SearchOptions::REGEX; let search = search_bar.update(cx, |search_bar, cx| { if !search_bar.show(window, cx) { return None; } let search = if replacement.search.is_empty() { search_bar.query(cx) } else { replacement.search };