994 lines · 33.2 KB · Rust
RawDownload
use editor::Editor;use gpui::{Context, Window, actions, impl_actions, impl_internal_actions};use language::Point;use schemars::JsonSchema;use search::{BufferSearchBar, SearchOptions, buffer_search};use serde_derive::Deserialize;use std::{iter::Peekable, str::Chars, time::Duration};use util::serde::default_true;use workspace::{notifications::NotifyResultExt, searchable::Direction}; use crate::{    Vim,    command::CommandRange,    motion::Motion,    state::{Mode, SearchState},}; #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]#[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,} #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]#[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,} #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]pub(crate) struct Search {    #[serde(default)]    backwards: bool,    #[serde(default = "default_true")]    regex: bool,} #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]pub struct FindCommand {    pub query: String,    pub backwards: bool,} #[derive(Clone, Debug, PartialEq)]pub struct ReplaceCommand {    pub(crate) range: CommandRange,    pub(crate) replacement: Replacement,} #[derive(Clone, Debug, PartialEq)]pub(crate) struct Replacement {    search: String,    replacement: String,    should_replace_all: bool,    is_case_sensitive: bool,} actions!(vim, [SearchSubmit, MoveToNextMatch, MoveToPreviousMatch]);impl_actions!(vim, [FindCommand, Search, MoveToPrevious, MoveToNext]);impl_internal_actions!(vim, [ReplaceCommand]); 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);        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;                    }                    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,                    }                });            }        })    }     // 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>) {        self.search = Default::default();        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>()?;            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().unwrap().start;                let is_different_head = self                    .search                    .prior_selections                    .last()                    .map_or(true, |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(window, cx, |_, editor, window, 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);        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);        let prior_selections = self.editor_selections(window, cx);        let cursor_word = self.editor_cursor_word(window, cx);        let vim = cx.entity().clone();         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, window, cx));                    return None;                };                 let query = regex::escape(&query);                Some(search_bar.search(&query, Some(options), 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), 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(window, cx, |vim, editor, window, cx| {            let range = action.range.buffer_range(vim, editor, window, cx)?;            let snapshot = &editor.snapshot(window, cx).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 vim = cx.entity().clone();        pane.update(cx, |pane, cx| {            let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else {                return;            };            let search = search_bar.update(cx, |search_bar, cx| {                if !search_bar.show(window, cx) {                    return None;                }                 let mut options = SearchOptions::REGEX;                if replacement.is_case_sensitive {                    options.set(SearchOptions::CASE_SENSITIVE, true)                }                let search = if replacement.search.is_empty() {                    search_bar.query(cx)                } else {                    replacement.search                };                if search_bar.should_use_smartcase_search(cx) {                    options.set(                        SearchOptions::CASE_SENSITIVE,                        search_bar.is_contains_uppercase(&search),                    );                }                search_bar.set_replacement(Some(&replacement.replacement), cx);                Some(search_bar.search(&search, Some(options), window, cx))            });            let Some(search) = search else { return };            let search_bar = search_bar.downgrade();            cx.spawn_in(window, async move |_, cx| {                search.await?;                search_bar.update_in(cx, |search_bar, window, cx| {                    if replacement.should_replace_all {                        search_bar.select_last_match(window, cx);                        search_bar.replace_all(&Default::default(), window, cx);                        cx.spawn(async move |_, cx| {                            cx.background_executor()                                .timer(Duration::from_millis(200))                                .await;                            editor                                .update(cx, |editor, cx| editor.clear_search_within_ranges(cx))                                .ok();                        })                        .detach();                        vim.update(cx, |vim, cx| {                            vim.move_cursor(                                Motion::StartOfLine {                                    display_lines: false,                                },                                None,                                window,                                cx,                            )                        });

Lines 1–500 of 994.