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_state(&mut self, text: &str, mode: Mode) {
129 self.assert_editor_state(text);
130 assert_eq!(self.mode(), mode);
131 }
132
133 pub fn assert_binding<const COUNT: usize>(
134 &mut self,
135 keystrokes: [&str; COUNT],
136 initial_state: &str,
137 initial_mode: Mode,
138 state_after: &str,
139 mode_after: Mode,
140 ) {
141 self.set_state(initial_state, initial_mode);
142 self.cx.simulate_keystrokes(keystrokes);
143 self.cx.assert_editor_state(state_after);
144 assert_eq!(self.mode(), mode_after);
145 assert_eq!(self.active_operator(), None);
146 }
147
148 pub fn binding<const COUNT: usize>(
149 mut self,
150 keystrokes: [&'static str; COUNT],
151 ) -> VimBindingTestContext<'a, COUNT> {
152 let mode = self.mode();
153 VimBindingTestContext::new(keystrokes, mode, mode, self)
154 }
155
156 pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
157 self.cx.update(|cx| {
158 let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
159 let expected_content = expected_content.map(|content| content.to_owned());
160 assert_eq!(actual_content, expected_content);
161 })
162 }
163}
164
165impl<'a> Deref for VimTestContext<'a> {
166 type Target = EditorTestContext<'a>;
167
168 fn deref(&self) -> &Self::Target {
169 &self.cx
170 }
171}
172
173impl<'a> DerefMut for VimTestContext<'a> {
174 fn deref_mut(&mut self) -> &mut Self::Target {
175 &mut self.cx
176 }
177}
178
179pub struct VimBindingTestContext<'a, const COUNT: usize> {
180 cx: VimTestContext<'a>,
181 keystrokes_under_test: [&'static str; COUNT],
182 mode_before: Mode,
183 mode_after: Mode,
184}
185
186impl<'a, const COUNT: usize> VimBindingTestContext<'a, COUNT> {
187 pub fn new(
188 keystrokes_under_test: [&'static str; COUNT],
189 mode_before: Mode,
190 mode_after: Mode,
191 cx: VimTestContext<'a>,
192 ) -> Self {
193 Self {
194 cx,
195 keystrokes_under_test,
196 mode_before,
197 mode_after,
198 }
199 }
200
201 pub fn binding<const NEW_COUNT: usize>(
202 self,
203 keystrokes_under_test: [&'static str; NEW_COUNT],
204 ) -> VimBindingTestContext<'a, NEW_COUNT> {
205 VimBindingTestContext {
206 keystrokes_under_test,
207 cx: self.cx,
208 mode_before: self.mode_before,
209 mode_after: self.mode_after,
210 }
211 }
212
213 pub fn mode_after(mut self, mode_after: Mode) -> Self {
214 self.mode_after = mode_after;
215 self
216 }
217
218 pub fn assert(&mut self, initial_state: &str, state_after: &str) {
219 self.cx.assert_binding(
220 self.keystrokes_under_test,
221 initial_state,
222 self.mode_before,
223 state_after,
224 self.mode_after,
225 )
226 }
227}
228
229impl<'a, const COUNT: usize> Deref for VimBindingTestContext<'a, COUNT> {
230 type Target = VimTestContext<'a>;
231
232 fn deref(&self) -> &Self::Target {
233 &self.cx
234 }
235}
236
237impl<'a, const COUNT: usize> DerefMut for VimBindingTestContext<'a, COUNT> {
238 fn deref_mut(&mut self) -> &mut Self::Target {
239 &mut self.cx
240 }
241}