892 lines · 30.3 KB · Rust
RawDownload
use std::{iter::Peekable, str::Chars, time::Duration}; use editor::Editor;use gpui::{actions, impl_actions, ViewContext};use language::Point;use search::{buffer_search, BufferSearchBar, SearchOptions};use serde_derive::Deserialize;use util::serde::default_true;use workspace::{notifications::NotifyResultExt, searchable::Direction}; use crate::{    command::CommandRange,    motion::Motion,    state::{Mode, SearchState},    Vim,}; #[derive(Clone, Deserialize, PartialEq)]#[serde(rename_all = "camelCase")]pub(crate) struct MoveToNext {    #[serde(default = "default_true")]    case_sensitive: bool,    #[serde(default)]    partial_word: bool,    #[serde(default = "default_true")]    regex: bool,} #[derive(Clone, Deserialize, PartialEq)]#[serde(rename_all = "camelCase")]pub(crate) struct MoveToPrev {    #[serde(default = "default_true")]    case_sensitive: bool,    #[serde(default)]    partial_word: bool,    #[serde(default = "default_true")]    regex: bool,} #[derive(Clone, Deserialize, PartialEq)]pub(crate) struct Search {    #[serde(default)]    backwards: bool,    #[serde(default = "default_true")]    regex: bool,} #[derive(Debug, Clone, PartialEq, Deserialize)]pub struct FindCommand {    pub query: String,    pub backwards: bool,} #[derive(Debug, Clone, PartialEq, Deserialize)]pub struct ReplaceCommand {    pub(crate) range: CommandRange,    pub(crate) replacement: Replacement,} #[derive(Debug, Default, PartialEq, Deserialize, Clone)]pub(crate) struct Replacement {    search: String,    replacement: String,    should_replace_all: bool,    is_case_sensitive: bool,} actions!(vim, [SearchSubmit, MoveToNextMatch, MoveToPrevMatch]);impl_actions!(    vim,    [FindCommand, ReplaceCommand, Search, MoveToPrev, MoveToNext]); pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {    Vim::action(editor, cx, Vim::move_to_next);    Vim::action(editor, cx, Vim::move_to_prev);    Vim::action(editor, cx, Vim::move_to_next_match);    Vim::action(editor, cx, Vim::move_to_prev_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, cx: &mut ViewContext<Self>) {        self.move_to_internal(            Direction::Next,            action.case_sensitive,            !action.partial_word,            action.regex,            cx,        )    }     fn move_to_prev(&mut self, action: &MoveToPrev, cx: &mut ViewContext<Self>) {        self.move_to_internal(            Direction::Prev,            action.case_sensitive,            !action.partial_word,            action.regex,            cx,        )    }     fn move_to_next_match(&mut self, _: &MoveToNextMatch, cx: &mut ViewContext<Self>) {        self.move_to_match_internal(self.search.direction, cx)    }     fn move_to_prev_match(&mut self, _: &MoveToPrevMatch, cx: &mut ViewContext<Self>) {        self.move_to_match_internal(self.search.direction.opposite(), cx)    }     fn search(&mut self, action: &Search, cx: &mut ViewContext<Self>) {        let Some(pane) = self.pane(cx) else { return };        let direction = if action.backwards {            Direction::Prev        } else {            Direction::Next        };        let count = self.take_count(cx).unwrap_or(1);        let prior_selections = self.editor_selections(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(cx) {                        return;                    }                    let query = search_bar.query(cx);                     search_bar.select_query(cx);                    cx.focus_self();                     search_bar.set_replacement(None, cx);                    let mut options = SearchOptions::NONE;                    if action.regex {                        options |= SearchOptions::REGEX;                    }                    search_bar.set_search_options(options, cx);                     self.search = SearchState {                        direction,                        count,                        initial_query: query,                        prior_selections,                        prior_operator: self.operator_stack.last().cloned(),                        prior_mode: self.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, cx: &mut ViewContext<Self>) {        self.search = Default::default();        cx.propagate();    }     pub fn search_submit(&mut self, cx: &mut ViewContext<Self>) {        self.store_visual_marks(cx);        let Some(pane) = self.pane(cx) else { return };        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;                // in the case that the query has changed, the search bar                // will have selected the next match already.                if (search_bar.query(cx) != self.search.initial_query)                    && self.search.direction == Direction::Next                {                    count = count.saturating_sub(1)                }                self.search.count = 1;                search_bar.select_match(direction, count, cx);                search_bar.focus_editor(&Default::default(), 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(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(cx).buffer_snapshot)            })            .unwrap_or(true)        }) {            prior_selections.clear();        }         if prior_mode != self.mode {            self.switch_mode(prior_mode, true, cx);        }        if let Some(operator) = prior_operator {            self.push_operator(operator, cx);        };        self.search_motion(            Motion::ZedSearchResult {                prior_selections,                new_selections,            },            cx,        );    }     pub fn move_to_match_internal(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {        let Some(pane) = self.pane(cx) else { return };        let count = self.take_count(cx).unwrap_or(1);        let prior_selections = self.editor_selections(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(cx) {                    return false;                }                search_bar.select_match(direction, count, cx);                true            })        });        if !success {            return;        }         let new_selections = self.editor_selections(cx);        self.search_motion(            Motion::ZedSearchResult {                prior_selections,                new_selections,            },            cx,        );    }     pub fn move_to_internal(        &mut self,        direction: Direction,        case_sensitive: bool,        whole_word: bool,        regex: bool,        cx: &mut ViewContext<Self>,    ) {        let Some(pane) = self.pane(cx) else { return };        let count = self.take_count(cx).unwrap_or(1);        let prior_selections = self.editor_selections(cx);        let vim = cx.view().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(cx) {                    return None;                }                let Some(query) = search_bar.query_suggestion(cx) else {                    drop(search_bar.search("", None, cx));                    return None;                };                let query = regex::escape(&query);                Some(search_bar.search(&query, Some(options), cx))            });             let Some(search) = search else { return false };             let search_bar = search_bar.downgrade();            cx.spawn(|_, mut cx| async move {                search.await?;                search_bar.update(&mut cx, |search_bar, cx| {                    search_bar.select_match(direction, count, cx);                     vim.update(cx, |vim, cx| {                        let new_selections = vim.editor_selections(cx);                        vim.search_motion(                            Motion::ZedSearchResult {                                prior_selections,                                new_selections,                            },                            cx,                        )                    });                })?;                anyhow::Ok(())            })            .detach_and_log_err(cx);            true        });        if !searched {            self.clear_operator(cx)        }         if self.mode.is_visual() {            self.switch_mode(Mode::Normal, false, cx)        }    }     fn find_command(&mut self, action: &FindCommand, cx: &mut ViewContext<Self>) {        let Some(pane) = self.pane(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(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), 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(|_, mut cx| async move {                    search.await?;                    search_bar.update(&mut cx, |search_bar, cx| {                        search_bar.select_match(direction, 1, cx)                    })?;                    anyhow::Ok(())                })                .detach_and_log_err(cx);            }        })    }     fn replace_command(&mut self, action: &ReplaceCommand, cx: &mut ViewContext<Self>) {        let replacement = action.replacement.clone();        let Some(((pane, workspace), editor)) =            self.pane(cx).zip(self.workspace(cx)).zip(self.editor())        else {            return;        };        if let Some(result) = self.update_editor(cx, |vim, editor, cx| {            let range = action.range.buffer_range(vim, editor, cx)?;            let snapshot = &editor.snapshot(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.view().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(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), cx))            });            let Some(search) = search else { return };            let search_bar = search_bar.downgrade();            cx.spawn(|_, mut cx| async move {                search.await?;                search_bar.update(&mut cx, |search_bar, cx| {                    if replacement.should_replace_all {                        search_bar.select_last_match(cx);                        search_bar.replace_all(&Default::default(), cx);                        cx.spawn(|_, mut cx| async move {                            cx.background_executor()                                .timer(Duration::from_millis(200))                                .await;                            editor                                .update(&mut 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,                                cx,                            )                        });                    }                })?;                anyhow::Ok(())            })            .detach_and_log_err(cx);        })    }} impl Replacement {    // convert a vim query into something more usable by zed.    // we don't attempt to fully convert between the two regex syntaxes,    // but we do flip \( and \) to ( and ) (and vice-versa) in the pattern,    // and convert \0..\9 to $0..$9 in the replacement so that common idioms work.    pub(crate) fn parse(mut chars: Peekable<Chars>) -> Option<Replacement> {        let delimiter = chars            .next()            .filter(|c| !c.is_alphanumeric() && *c != '"' && *c != '|' && *c != '\'')?;         let mut search = String::new();        let mut replacement = String::new();        let mut flags = String::new();         let mut buffer = &mut search;         let mut escaped = false;        // 0 - parsing search        // 1 - parsing replacement        // 2 - parsing flags        let mut phase = 0;         for c in chars {            if escaped {                escaped = false;                if phase == 1 && c.is_ascii_digit() {                    buffer.push('$')                // unescape escaped parens                } else if phase == 0 && c == '(' || c == ')' {                } else if c != delimiter {                    buffer.push('\\')                }                buffer.push(c)            } else if c == '\\' {                escaped = true;            } else if c == delimiter {                if phase == 0 {                    buffer = &mut replacement;                    phase = 1;                } else if phase == 1 {                    buffer = &mut flags;                    phase = 2;                } else {                    break;                }            } else {                // escape unescaped parens                if phase == 0 && c == '(' || c == ')' {                    buffer.push('\\')                }                buffer.push(c)            }

Lines 1–500 of 892.