search.rs

  1use gpui::{impl_actions, AppContext, ViewContext};
  2use search::{BufferSearchBar, SearchOptions};
  3use serde_derive::Deserialize;
  4use workspace::{searchable::Direction, Workspace};
  5
  6use crate::Vim;
  7
  8#[derive(Clone, Deserialize, PartialEq)]
  9#[serde(rename_all = "camelCase")]
 10pub(crate) struct MoveToNext {
 11    #[serde(default)]
 12    partial_word: bool,
 13}
 14
 15#[derive(Clone, Deserialize, PartialEq)]
 16#[serde(rename_all = "camelCase")]
 17pub(crate) struct MoveToPrev {
 18    #[serde(default)]
 19    partial_word: bool,
 20}
 21
 22#[derive(Clone, Deserialize, PartialEq)]
 23pub(crate) struct Search {
 24    #[serde(default)]
 25    backwards: bool,
 26}
 27
 28impl_actions!(vim, [MoveToNext, MoveToPrev, Search]);
 29
 30pub(crate) fn init(cx: &mut AppContext) {
 31    cx.add_action(move_to_next);
 32    cx.add_action(move_to_prev);
 33    cx.add_action(search);
 34}
 35
 36fn move_to_next(workspace: &mut Workspace, action: &MoveToNext, cx: &mut ViewContext<Workspace>) {
 37    move_to_internal(workspace, Direction::Next, !action.partial_word, cx)
 38}
 39
 40fn move_to_prev(workspace: &mut Workspace, action: &MoveToPrev, cx: &mut ViewContext<Workspace>) {
 41    move_to_internal(workspace, Direction::Prev, !action.partial_word, cx)
 42}
 43
 44fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Workspace>) {
 45    let pane = workspace.active_pane().clone();
 46    pane.update(cx, |pane, cx| {
 47        if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
 48            search_bar.update(cx, |search_bar, cx| {
 49                let options = SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX;
 50                let direction = if action.backwards {
 51                    Direction::Prev
 52                } else {
 53                    Direction::Next
 54                };
 55                search_bar.select_match(direction, cx);
 56            //    search_bar.show_with_options(true, false, options, cx);
 57            })
 58        }
 59    })
 60}
 61
 62pub fn move_to_internal(
 63    workspace: &mut Workspace,
 64    direction: Direction,
 65    whole_word: bool,
 66    cx: &mut ViewContext<Workspace>,
 67) {
 68    Vim::update(cx, |vim, cx| {
 69        let pane = workspace.active_pane().clone();
 70        pane.update(cx, |pane, cx| {
 71            if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
 72                search_bar.update(cx, |search_bar, cx| {
 73                    // let mut options = SearchOptions::CASE_SENSITIVE;
 74                    // options.set(SearchOptions::WHOLE_WORD, whole_word);
 75                    // search_bar.show(false, false, cx);
 76                    // let word = search_bar.query_suggestion();
 77                    // search_bar.show()
 78                    // search_bar.search(word, options)
 79
 80                    // search_bar.select_word_under_cursor(direction, options, cx);
 81                });
 82            }
 83        });
 84        vim.clear_operator(cx);
 85    });
 86}
 87
 88#[cfg(test)]
 89mod test {
 90    use std::sync::Arc;
 91
 92    use editor::DisplayPoint;
 93    use search::BufferSearchBar;
 94
 95    use crate::{state::Mode, test::VimTestContext};
 96
 97    #[gpui::test]
 98    async fn test_move_to_next(
 99        cx: &mut gpui::TestAppContext,
100        deterministic: Arc<gpui::executor::Deterministic>,
101    ) {
102        let mut cx = VimTestContext::new(cx, true).await;
103        let search_bar = cx.workspace(|workspace, cx| {
104            workspace
105                .active_pane()
106                .read(cx)
107                .toolbar()
108                .read(cx)
109                .item_of_type::<BufferSearchBar>()
110                .expect("Buffer search bar should be deployed")
111        });
112        cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal);
113
114        cx.simulate_keystrokes(["*"]);
115        deterministic.run_until_parked();
116        cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
117
118        cx.simulate_keystrokes(["*"]);
119        deterministic.run_until_parked();
120        cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
121
122        cx.simulate_keystrokes(["#"]);
123        deterministic.run_until_parked();
124        cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
125
126        cx.simulate_keystrokes(["#"]);
127        deterministic.run_until_parked();
128        cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
129
130        cx.simulate_keystrokes(["g", "*"]);
131        deterministic.run_until_parked();
132        cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
133
134        cx.simulate_keystrokes(["n"]);
135        cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
136
137        cx.simulate_keystrokes(["g", "#"]);
138        deterministic.run_until_parked();
139        cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
140    }
141
142    #[gpui::test]
143    async fn test_search(cx: &mut gpui::TestAppContext) {
144        let mut cx = VimTestContext::new(cx, true).await;
145
146        cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
147        cx.simulate_keystrokes(["/", "c", "c"]);
148
149        let search_bar = cx.workspace(|workspace, cx| {
150            workspace
151                .active_pane()
152                .read(cx)
153                .toolbar()
154                .read(cx)
155                .item_of_type::<BufferSearchBar>()
156                .expect("Buffer search bar should be deployed")
157        });
158
159        search_bar.read_with(cx.cx, |bar, cx| {
160            assert_eq!(bar.query_editor.read(cx).text(cx), "cc");
161        });
162
163        // wait for the query editor change event to fire.
164        search_bar.next_notification(&cx).await;
165
166        cx.update_editor(|editor, cx| {
167            let highlights = editor.all_background_highlights(cx);
168            assert_eq!(3, highlights.len());
169            assert_eq!(
170                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
171                highlights[0].0
172            )
173        });
174
175        cx.simulate_keystrokes(["enter"]);
176
177        // n to go to next/N to go to previous
178        cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
179        cx.simulate_keystrokes(["n"]);
180        cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
181        cx.simulate_keystrokes(["shift-n"]);
182
183        // ?<enter> to go to previous
184        cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
185        cx.simulate_keystrokes(["?", "enter"]);
186        cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
187        cx.simulate_keystrokes(["?", "enter"]);
188
189        // /<enter> to go to next
190        cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
191        cx.simulate_keystrokes(["/", "enter"]);
192        cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
193
194        // ?{search}<enter> to search backwards
195        cx.simulate_keystrokes(["?", "b", "enter"]);
196
197        // wait for the query editor change event to fire.
198        search_bar.next_notification(&cx).await;
199
200        cx.assert_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
201    }
202}