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