1use std::ops::{Deref, DerefMut};
2
3use editor::test::EditorTestContext;
4use gpui::{json::json, AppContext, ViewHandle};
5use project::Project;
6use search::{BufferSearchBar, ProjectSearchBar};
7use workspace::{pane, AppState, WorkspaceHandle};
8
9use crate::{state::Operator, *};
10
11pub struct VimTestContext<'a> {
12 cx: EditorTestContext<'a>,
13 workspace: ViewHandle<Workspace>,
14}
15
16impl<'a> VimTestContext<'a> {
17 pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
18 cx.update(|cx| {
19 editor::init(cx);
20 pane::init(cx);
21 search::init(cx);
22 crate::init(cx);
23
24 settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
25 });
26
27 let params = cx.update(AppState::test);
28 let project = Project::test(params.fs.clone(), [], cx).await;
29
30 cx.update(|cx| {
31 cx.update_global(|settings: &mut Settings, _| {
32 settings.vim_mode = enabled;
33 });
34 });
35
36 params
37 .fs
38 .as_fake()
39 .insert_tree("/root", json!({ "dir": { "test.txt": "" } }))
40 .await;
41
42 let (window_id, workspace) =
43 cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx));
44
45 // Setup search toolbars
46 workspace.update(cx, |workspace, cx| {
47 workspace.active_pane().update(cx, |pane, cx| {
48 pane.toolbar().update(cx, |toolbar, cx| {
49 let buffer_search_bar = cx.add_view(BufferSearchBar::new);
50 toolbar.add_item(buffer_search_bar, cx);
51 let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
52 toolbar.add_item(project_search_bar, cx);
53 })
54 });
55 });
56
57 project
58 .update(cx, |project, cx| {
59 project.find_or_create_local_worktree("/root", true, cx)
60 })
61 .await
62 .unwrap();
63 cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
64 .await;
65
66 let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
67 let item = workspace
68 .update(cx, |workspace, cx| workspace.open_path(file, true, cx))
69 .await
70 .expect("Could not open test file");
71
72 let editor = cx.update(|cx| {
73 item.act_as::<Editor>(cx)
74 .expect("Opened test file wasn't an editor")
75 });
76 editor.update(cx, |_, cx| cx.focus_self());
77
78 Self {
79 cx: EditorTestContext {
80 cx,
81 window_id,
82 editor,
83 },
84 workspace,
85 }
86 }
87
88 pub fn workspace<F, T>(&mut self, read: F) -> T
89 where
90 F: FnOnce(&Workspace, &AppContext) -> T,
91 {
92 self.workspace.read_with(self.cx.cx, read)
93 }
94
95 pub fn enable_vim(&mut self) {
96 self.cx.update(|cx| {
97 cx.update_global(|settings: &mut Settings, _| {
98 settings.vim_mode = true;
99 });
100 })
101 }
102
103 pub fn disable_vim(&mut self) {
104 self.cx.update(|cx| {
105 cx.update_global(|settings: &mut Settings, _| {
106 settings.vim_mode = false;
107 });
108 })
109 }
110
111 pub fn mode(&mut self) -> Mode {
112 self.cx.read(|cx| cx.global::<Vim>().state.mode)
113 }
114
115 pub fn active_operator(&mut self) -> Option<Operator> {
116 self.cx
117 .read(|cx| cx.global::<Vim>().state.operator_stack.last().copied())
118 }
119
120 pub fn set_state(&mut self, text: &str, mode: Mode) {
121 self.cx.update(|cx| {
122 Vim::update(cx, |vim, cx| {
123 vim.switch_mode(mode, false, cx);
124 })
125 });
126 self.cx.set_state(text);
127 }
128
129 pub fn assert_state(&mut self, text: &str, mode: Mode) {
130 self.assert_editor_state(text);
131 assert_eq!(self.mode(), mode);
132 }
133
134 pub fn assert_binding<const COUNT: usize>(
135 &mut self,
136 keystrokes: [&str; COUNT],
137 initial_state: &str,
138 initial_mode: Mode,
139 state_after: &str,
140 mode_after: Mode,
141 ) {
142 self.set_state(initial_state, initial_mode);
143 self.cx.simulate_keystrokes(keystrokes);
144 self.cx.assert_editor_state(state_after);
145 assert_eq!(self.mode(), mode_after);
146 assert_eq!(self.active_operator(), None);
147 }
148
149 pub fn binding<const COUNT: usize>(
150 mut self,
151 keystrokes: [&'static str; COUNT],
152 ) -> VimBindingTestContext<'a, COUNT> {
153 let mode = self.mode();
154 VimBindingTestContext::new(keystrokes, mode, mode, self)
155 }
156}
157
158impl<'a> Deref for VimTestContext<'a> {
159 type Target = EditorTestContext<'a>;
160
161 fn deref(&self) -> &Self::Target {
162 &self.cx
163 }
164}
165
166impl<'a> DerefMut for VimTestContext<'a> {
167 fn deref_mut(&mut self) -> &mut Self::Target {
168 &mut self.cx
169 }
170}
171
172pub struct VimBindingTestContext<'a, const COUNT: usize> {
173 cx: VimTestContext<'a>,
174 keystrokes_under_test: [&'static str; COUNT],
175 mode_before: Mode,
176 mode_after: Mode,
177}
178
179impl<'a, const COUNT: usize> VimBindingTestContext<'a, COUNT> {
180 pub fn new(
181 keystrokes_under_test: [&'static str; COUNT],
182 mode_before: Mode,
183 mode_after: Mode,
184 cx: VimTestContext<'a>,
185 ) -> Self {
186 Self {
187 cx,
188 keystrokes_under_test,
189 mode_before,
190 mode_after,
191 }
192 }
193
194 pub fn binding<const NEW_COUNT: usize>(
195 self,
196 keystrokes_under_test: [&'static str; NEW_COUNT],
197 ) -> VimBindingTestContext<'a, NEW_COUNT> {
198 VimBindingTestContext {
199 keystrokes_under_test,
200 cx: self.cx,
201 mode_before: self.mode_before,
202 mode_after: self.mode_after,
203 }
204 }
205
206 pub fn mode_after(mut self, mode_after: Mode) -> Self {
207 self.mode_after = mode_after;
208 self
209 }
210
211 pub fn assert(&mut self, initial_state: &str, state_after: &str) {
212 self.cx.assert_binding(
213 self.keystrokes_under_test,
214 initial_state,
215 self.mode_before,
216 state_after,
217 self.mode_after,
218 )
219 }
220}
221
222impl<'a, const COUNT: usize> Deref for VimBindingTestContext<'a, COUNT> {
223 type Target = VimTestContext<'a>;
224
225 fn deref(&self) -> &Self::Target {
226 &self.cx
227 }
228}
229
230impl<'a, const COUNT: usize> DerefMut for VimBindingTestContext<'a, COUNT> {
231 fn deref_mut(&mut self) -> &mut Self::Target {
232 &mut self.cx
233 }
234}