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(BufferSearchBar::new);
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, false, 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
157impl<'a> Deref for VimTestContext<'a> {
158 type Target = EditorTestContext<'a>;
159
160 fn deref(&self) -> &Self::Target {
161 &self.cx
162 }
163}
164
165impl<'a> DerefMut for VimTestContext<'a> {
166 fn deref_mut(&mut self) -> &mut Self::Target {
167 &mut self.cx
168 }
169}
170
171pub struct VimBindingTestContext<'a, const COUNT: usize> {
172 cx: VimTestContext<'a>,
173 keystrokes_under_test: [&'static str; COUNT],
174 mode_before: Mode,
175 mode_after: Mode,
176}
177
178impl<'a, const COUNT: usize> VimBindingTestContext<'a, COUNT> {
179 pub fn new(
180 keystrokes_under_test: [&'static str; COUNT],
181 mode_before: Mode,
182 mode_after: Mode,
183 cx: VimTestContext<'a>,
184 ) -> Self {
185 Self {
186 cx,
187 keystrokes_under_test,
188 mode_before,
189 mode_after,
190 }
191 }
192
193 pub fn binding<const NEW_COUNT: usize>(
194 self,
195 keystrokes_under_test: [&'static str; NEW_COUNT],
196 ) -> VimBindingTestContext<'a, NEW_COUNT> {
197 VimBindingTestContext {
198 keystrokes_under_test,
199 cx: self.cx,
200 mode_before: self.mode_before,
201 mode_after: self.mode_after,
202 }
203 }
204
205 pub fn mode_after(mut self, mode_after: Mode) -> Self {
206 self.mode_after = mode_after;
207 self
208 }
209
210 pub fn assert(&mut self, initial_state: &str, state_after: &str) {
211 self.cx.assert_binding(
212 self.keystrokes_under_test,
213 initial_state,
214 self.mode_before,
215 state_after,
216 self.mode_after,
217 )
218 }
219}
220
221impl<'a, const COUNT: usize> Deref for VimBindingTestContext<'a, COUNT> {
222 type Target = VimTestContext<'a>;
223
224 fn deref(&self) -> &Self::Target {
225 &self.cx
226 }
227}
228
229impl<'a, const COUNT: usize> DerefMut for VimBindingTestContext<'a, COUNT> {
230 fn deref_mut(&mut self) -> &mut Self::Target {
231 &mut self.cx
232 }
233}