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}