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