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