301 lines · 10.6 KB · Rust
RawDownload
use gpui::{actions, impl_actions, AppContext, ViewContext};use search::{buffer_search, BufferSearchBar, SearchMode, SearchOptions};use serde_derive::Deserialize;use workspace::{searchable::Direction, Pane, Workspace}; use crate::{state::SearchState, Vim}; #[derive(Clone, Deserialize, PartialEq)]#[serde(rename_all = "camelCase")]pub(crate) struct MoveToNext {    #[serde(default)]    partial_word: bool,} #[derive(Clone, Deserialize, PartialEq)]#[serde(rename_all = "camelCase")]pub(crate) struct MoveToPrev {    #[serde(default)]    partial_word: bool,} #[derive(Clone, Deserialize, PartialEq)]pub(crate) struct Search {    #[serde(default)]    backwards: bool,} impl_actions!(vim, [MoveToNext, MoveToPrev, Search]);actions!(vim, [SearchSubmit]); pub(crate) fn init(cx: &mut AppContext) {    cx.add_action(move_to_next);    cx.add_action(move_to_prev);    cx.add_action(search);    cx.add_action(search_submit);    cx.add_action(search_deploy);} fn move_to_next(workspace: &mut Workspace, action: &MoveToNext, cx: &mut ViewContext<Workspace>) {    move_to_internal(workspace, Direction::Next, !action.partial_word, cx)} fn move_to_prev(workspace: &mut Workspace, action: &MoveToPrev, cx: &mut ViewContext<Workspace>) {    move_to_internal(workspace, Direction::Prev, !action.partial_word, cx)} fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Workspace>) {    let pane = workspace.active_pane().clone();    let direction = if action.backwards {        Direction::Prev    } else {        Direction::Next    };    Vim::update(cx, |vim, cx| {        let count = vim.pop_number_operator(cx).unwrap_or(1);        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();                     if query.is_empty() {                        search_bar.set_search_options(SearchOptions::CASE_SENSITIVE, cx);                        search_bar.activate_search_mode(SearchMode::Regex, cx);                    }                    vim.workspace_state.search = SearchState {                        direction,                        count,                        initial_query: query.clone(),                    };                });            }        })    })} // hook into the existing to clear out any vim search state on cmd+f or edit -> find.fn search_deploy(_: &mut Pane, _: &buffer_search::Deploy, cx: &mut ViewContext<Pane>) {    Vim::update(cx, |vim, _| vim.workspace_state.search = Default::default());    cx.propagate_action();} fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext<Workspace>) {    Vim::update(cx, |vim, cx| {        let pane = workspace.active_pane().clone();        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| {                    let state = &mut vim.workspace_state.search;                    let mut count = state.count;                    let direction = state.direction;                     // in the case that the query has changed, the search bar                    // will have selected the next match already.                    if (search_bar.query(cx) != state.initial_query)                        && state.direction == Direction::Next                    {                        count = count.saturating_sub(1)                    }                    state.count = 1;                    search_bar.select_match(direction, count, cx);                    search_bar.focus_editor(&Default::default(), cx);                });            }        });    })} pub fn move_to_internal(    workspace: &mut Workspace,    direction: Direction,    whole_word: bool,    cx: &mut ViewContext<Workspace>,) {    Vim::update(cx, |vim, cx| {        let pane = workspace.active_pane().clone();        let count = vim.pop_number_operator(cx).unwrap_or(1);        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| {                    let mut options = SearchOptions::CASE_SENSITIVE;                    options.set(SearchOptions::WHOLE_WORD, whole_word);                    if search_bar.show(cx) {                        search_bar                            .query_suggestion(cx)                            .map(|query| search_bar.search(&query, Some(options), cx))                    } else {                        None                    }                });                 if let Some(search) = search {                    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)                        })?;                        anyhow::Ok(())                    })                    .detach_and_log_err(cx);                }            }        });        vim.clear_operator(cx);    });} #[cfg(test)]mod test {    use std::sync::Arc;     use editor::DisplayPoint;    use search::BufferSearchBar;     use crate::{state::Mode, test::VimTestContext};     #[gpui::test]    async fn test_move_to_next(        cx: &mut gpui::TestAppContext,        deterministic: Arc<gpui::executor::Deterministic>,    ) {        let mut cx = VimTestContext::new(cx, true).await;        cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal);         cx.simulate_keystrokes(["*"]);        deterministic.run_until_parked();        cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);         cx.simulate_keystrokes(["*"]);        deterministic.run_until_parked();        cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);         cx.simulate_keystrokes(["#"]);        deterministic.run_until_parked();        cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);         cx.simulate_keystrokes(["#"]);        deterministic.run_until_parked();        cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);         cx.simulate_keystrokes(["2", "*"]);        deterministic.run_until_parked();        cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);         cx.simulate_keystrokes(["g", "*"]);        deterministic.run_until_parked();        cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);         cx.simulate_keystrokes(["n"]);        cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);         cx.simulate_keystrokes(["g", "#"]);        deterministic.run_until_parked();        cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);    }     #[gpui::test]    async fn test_search(        cx: &mut gpui::TestAppContext,        deterministic: Arc<gpui::executor::Deterministic>,    ) {        let mut cx = VimTestContext::new(cx, true).await;         cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);        cx.simulate_keystrokes(["/", "c", "c"]);         let search_bar = cx.workspace(|workspace, cx| {            workspace                .active_pane()                .read(cx)                .toolbar()                .read(cx)                .item_of_type::<BufferSearchBar>()                .expect("Buffer search bar should be deployed")        });         search_bar.read_with(cx.cx, |bar, cx| {            assert_eq!(bar.query(cx), "cc");        });         deterministic.run_until_parked();         cx.update_editor(|editor, cx| {            let highlights = editor.all_background_highlights(cx);            assert_eq!(3, highlights.len());            assert_eq!(                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),                highlights[0].0            )        });         cx.simulate_keystrokes(["enter"]);        cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);         // n to go to next/N to go to previous        cx.simulate_keystrokes(["n"]);        cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);        cx.simulate_keystrokes(["shift-n"]);        cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);         // ?<enter> to go to previous        cx.simulate_keystrokes(["?", "enter"]);        deterministic.run_until_parked();        cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);        cx.simulate_keystrokes(["?", "enter"]);        deterministic.run_until_parked();        cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);         // /<enter> to go to next        cx.simulate_keystrokes(["/", "enter"]);        deterministic.run_until_parked();        cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);         // ?{search}<enter> to search backwards        cx.simulate_keystrokes(["?", "b", "enter"]);        deterministic.run_until_parked();        cx.assert_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);         // works with counts        cx.simulate_keystrokes(["4", "/", "c"]);        deterministic.run_until_parked();        cx.simulate_keystrokes(["enter"]);        cx.assert_state("aa\nbb\ncc\ncˇc\ncc\n", Mode::Normal);         // check that searching resumes from cursor, not previous match        cx.set_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);        cx.simulate_keystrokes(["/", "d"]);        deterministic.run_until_parked();        cx.simulate_keystrokes(["enter"]);        cx.assert_state("aa\nbb\nˇdd\ncc\nbb\n", Mode::Normal);        cx.update_editor(|editor, cx| editor.move_to_beginning(&Default::default(), cx));        cx.assert_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);        cx.simulate_keystrokes(["/", "b"]);        deterministic.run_until_parked();        cx.simulate_keystrokes(["enter"]);        cx.assert_state("aa\nˇbb\ndd\ncc\nbb\n", Mode::Normal);    }     #[gpui::test]    async fn test_non_vim_search(        cx: &mut gpui::TestAppContext,        deterministic: Arc<gpui::executor::Deterministic>,    ) {        let mut cx = VimTestContext::new(cx, false).await;        cx.set_state("ˇone one one one", Mode::Normal);        cx.simulate_keystrokes(["cmd-f"]);        deterministic.run_until_parked();         cx.assert_editor_state("«oneˇ» one one one");        cx.simulate_keystrokes(["enter"]);        cx.assert_editor_state("one «oneˇ» one one");        cx.simulate_keystrokes(["shift-enter"]);        cx.assert_editor_state("«oneˇ» one one one");    }}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t