vim_test_context.rs

  1use std::ops::Deref;
  2
  3use editor::{display_map::ToDisplayPoint, Bias, DisplayPoint};
  4use gpui::{json::json, keymap::Keystroke, ViewHandle};
  5use language::{Point, Selection};
  6use util::test::marked_text;
  7use workspace::{WorkspaceHandle, WorkspaceParams};
  8
  9use crate::{state::Operator, *};
 10
 11pub struct VimTestContext<'a> {
 12    cx: &'a mut gpui::TestAppContext,
 13    window_id: usize,
 14    editor: ViewHandle<Editor>,
 15}
 16
 17impl<'a> VimTestContext<'a> {
 18    pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
 19        cx.update(|cx| {
 20            editor::init(cx);
 21            crate::init(cx);
 22
 23            settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
 24        });
 25
 26        let params = cx.update(WorkspaceParams::test);
 27
 28        cx.update(|cx| {
 29            cx.update_global(|settings: &mut Settings, _| {
 30                settings.vim_mode = enabled;
 31            });
 32        });
 33
 34        params
 35            .fs
 36            .as_fake()
 37            .insert_tree("/root", json!({ "dir": { "test.txt": "" } }))
 38            .await;
 39
 40        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&params, cx));
 41        params
 42            .project
 43            .update(cx, |project, cx| {
 44                project.find_or_create_local_worktree("/root", true, cx)
 45            })
 46            .await
 47            .unwrap();
 48        cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
 49            .await;
 50
 51        let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
 52        let item = workspace
 53            .update(cx, |workspace, cx| workspace.open_path(file, true, cx))
 54            .await
 55            .expect("Could not open test file");
 56
 57        let editor = cx.update(|cx| {
 58            item.act_as::<Editor>(cx)
 59                .expect("Opened test file wasn't an editor")
 60        });
 61        editor.update(cx, |_, cx| cx.focus_self());
 62
 63        Self {
 64            cx,
 65            window_id,
 66            editor,
 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 newest_selection(&mut self) -> Selection<DisplayPoint> {
 87        self.editor.update(self.cx, |editor, cx| {
 88            let snapshot = editor.snapshot(cx);
 89            editor
 90                .newest_selection::<Point>(cx)
 91                .map(|point| point.to_display_point(&snapshot.display_snapshot))
 92        })
 93    }
 94
 95    pub fn mode(&mut self) -> Mode {
 96        self.cx.read(|cx| cx.global::<Vim>().state.mode)
 97    }
 98
 99    pub fn active_operator(&mut self) -> Option<Operator> {
100        self.cx
101            .read(|cx| cx.global::<Vim>().state.operator_stack.last().copied())
102    }
103
104    pub fn editor_text(&mut self) -> String {
105        self.editor
106            .update(self.cx, |editor, cx| editor.snapshot(cx).text())
107    }
108
109    pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
110        let keystroke = Keystroke::parse(keystroke_text).unwrap();
111        let input = if keystroke.modified() {
112            None
113        } else {
114            Some(keystroke.key.clone())
115        };
116        self.cx
117            .dispatch_keystroke(self.window_id, keystroke, input, false);
118    }
119
120    pub fn simulate_keystrokes<const COUNT: usize>(&mut self, keystroke_texts: [&str; COUNT]) {
121        for keystroke_text in keystroke_texts.into_iter() {
122            self.simulate_keystroke(keystroke_text);
123        }
124    }
125
126    pub fn set_state(&mut self, text: &str, mode: Mode) {
127        self.cx
128            .update(|cx| Vim::update(cx, |vim, cx| vim.switch_mode(mode, cx)));
129        self.editor.update(self.cx, |editor, cx| {
130            let (unmarked_text, markers) = marked_text(&text);
131            editor.set_text(unmarked_text, cx);
132            let cursor_offset = markers[0];
133            editor.replace_selections_with(cx, |map| cursor_offset.to_display_point(map));
134        })
135    }
136
137    pub fn assert_newest_selection_head_offset(&mut self, expected_offset: usize) {
138        let actual_head = self.newest_selection().head();
139        let (actual_offset, expected_head) = self.editor.update(self.cx, |editor, cx| {
140            let snapshot = editor.snapshot(cx);
141            (
142                actual_head.to_offset(&snapshot, Bias::Left),
143                expected_offset.to_display_point(&snapshot),
144            )
145        });
146        let mut actual_position_text = self.editor_text();
147        let mut expected_position_text = actual_position_text.clone();
148        actual_position_text.insert(actual_offset, '|');
149        expected_position_text.insert(expected_offset, '|');
150        assert_eq!(
151            actual_head, expected_head,
152            "\nActual Position: {}\nExpected Position: {}",
153            actual_position_text, expected_position_text
154        )
155    }
156
157    pub fn assert_editor_state(&mut self, text: &str) {
158        let (unmarked_text, markers) = marked_text(&text);
159        let editor_text = self.editor_text();
160        assert_eq!(
161            editor_text, unmarked_text,
162            "Unmarked text doesn't match editor text"
163        );
164        let expected_offset = markers[0];
165        let actual_head = self.newest_selection().head();
166        let (actual_offset, expected_head) = self.editor.update(self.cx, |editor, cx| {
167            let snapshot = editor.snapshot(cx);
168            (
169                actual_head.to_offset(&snapshot, Bias::Left),
170                expected_offset.to_display_point(&snapshot),
171            )
172        });
173        let mut actual_position_text = self.editor_text();
174        let mut expected_position_text = actual_position_text.clone();
175        actual_position_text.insert(actual_offset, '|');
176        expected_position_text.insert(expected_offset, '|');
177        assert_eq!(
178            actual_head, expected_head,
179            "\nActual Position: {}\nExpected Position: {}",
180            actual_position_text, expected_position_text
181        )
182    }
183
184    pub fn assert_binding<const COUNT: usize>(
185        &mut self,
186        keystrokes: [&str; COUNT],
187        initial_state: &str,
188        initial_mode: Mode,
189        state_after: &str,
190        mode_after: Mode,
191    ) {
192        self.set_state(initial_state, initial_mode);
193        self.simulate_keystrokes(keystrokes);
194        self.assert_editor_state(state_after);
195        assert_eq!(self.mode(), mode_after);
196        assert_eq!(self.active_operator(), None);
197    }
198
199    pub fn binding<const COUNT: usize>(
200        mut self,
201        keystrokes: [&'static str; COUNT],
202    ) -> VimBindingTestContext<'a, COUNT> {
203        let mode = self.mode();
204        VimBindingTestContext::new(keystrokes, mode, mode, self)
205    }
206}
207
208impl<'a> Deref for VimTestContext<'a> {
209    type Target = gpui::TestAppContext;
210
211    fn deref(&self) -> &Self::Target {
212        self.cx
213    }
214}
215
216pub struct VimBindingTestContext<'a, const COUNT: usize> {
217    cx: VimTestContext<'a>,
218    keystrokes_under_test: [&'static str; COUNT],
219    initial_mode: Mode,
220    mode_after: Mode,
221}
222
223impl<'a, const COUNT: usize> VimBindingTestContext<'a, COUNT> {
224    pub fn new(
225        keystrokes_under_test: [&'static str; COUNT],
226        initial_mode: Mode,
227        mode_after: Mode,
228        cx: VimTestContext<'a>,
229    ) -> Self {
230        Self {
231            cx,
232            keystrokes_under_test,
233            initial_mode,
234            mode_after,
235        }
236    }
237
238    pub fn binding<const NEW_COUNT: usize>(
239        self,
240        keystrokes_under_test: [&'static str; NEW_COUNT],
241    ) -> VimBindingTestContext<'a, NEW_COUNT> {
242        VimBindingTestContext {
243            keystrokes_under_test,
244            cx: self.cx,
245            initial_mode: self.initial_mode,
246            mode_after: self.mode_after,
247        }
248    }
249
250    pub fn mode_after(mut self, mode_after: Mode) -> Self {
251        self.mode_after = mode_after;
252        self
253    }
254
255    pub fn assert(&mut self, initial_state: &str, state_after: &str) {
256        self.cx.assert_binding(
257            self.keystrokes_under_test,
258            initial_state,
259            self.initial_mode,
260            state_after,
261            self.mode_after,
262        )
263    }
264}
265
266impl<'a, const COUNT: usize> Deref for VimBindingTestContext<'a, COUNT> {
267    type Target = VimTestContext<'a>;
268
269    fn deref(&self) -> &Self::Target {
270        &self.cx
271    }
272}