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(
 19        cx: &'a mut gpui::TestAppContext,
 20        enabled: bool,
 21        initial_editor_text: &str,
 22    ) -> VimTestContext<'a> {
 23        cx.update(|cx| {
 24            editor::init(cx);
 25            crate::init(cx);
 26
 27            settings::KeymapFile::load("keymaps/vim.json", cx).unwrap();
 28        });
 29
 30        let params = cx.update(WorkspaceParams::test);
 31
 32        cx.update(|cx| {
 33            cx.update_global(|settings: &mut Settings, _| {
 34                settings.vim_mode = enabled;
 35            });
 36        });
 37
 38        params
 39            .fs
 40            .as_fake()
 41            .insert_tree(
 42                "/root",
 43                json!({ "dir": { "test.txt": initial_editor_text } }),
 44            )
 45            .await;
 46
 47        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&params, cx));
 48        params
 49            .project
 50            .update(cx, |project, cx| {
 51                project.find_or_create_local_worktree("/root", true, cx)
 52            })
 53            .await
 54            .unwrap();
 55        cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
 56            .await;
 57
 58        let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
 59        let item = workspace
 60            .update(cx, |workspace, cx| workspace.open_path(file, cx))
 61            .await
 62            .expect("Could not open test file");
 63
 64        let editor = cx.update(|cx| {
 65            item.act_as::<Editor>(cx)
 66                .expect("Opened test file wasn't an editor")
 67        });
 68        editor.update(cx, |_, cx| cx.focus_self());
 69
 70        Self {
 71            cx,
 72            window_id,
 73            editor,
 74        }
 75    }
 76
 77    pub fn enable_vim(&mut self) {
 78        self.cx.update(|cx| {
 79            cx.update_global(|settings: &mut Settings, _| {
 80                settings.vim_mode = true;
 81            });
 82        })
 83    }
 84
 85    pub fn disable_vim(&mut self) {
 86        self.cx.update(|cx| {
 87            cx.update_global(|settings: &mut Settings, _| {
 88                settings.vim_mode = false;
 89            });
 90        })
 91    }
 92
 93    pub fn newest_selection(&mut self) -> Selection<DisplayPoint> {
 94        self.editor.update(self.cx, |editor, cx| {
 95            let snapshot = editor.snapshot(cx);
 96            editor
 97                .newest_selection::<Point>(cx)
 98                .map(|point| point.to_display_point(&snapshot.display_snapshot))
 99        })
100    }
101
102    pub fn mode(&mut self) -> Mode {
103        self.cx.read(|cx| cx.global::<Vim>().state.mode)
104    }
105
106    pub fn active_operator(&mut self) -> Option<Operator> {
107        self.cx
108            .read(|cx| cx.global::<Vim>().state.operator_stack.last().copied())
109    }
110
111    pub fn editor_text(&mut self) -> String {
112        self.editor
113            .update(self.cx, |editor, cx| editor.snapshot(cx).text())
114    }
115
116    pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
117        let keystroke = Keystroke::parse(keystroke_text).unwrap();
118        let input = if keystroke.modified() {
119            None
120        } else {
121            Some(keystroke.key.clone())
122        };
123        self.cx
124            .dispatch_keystroke(self.window_id, keystroke, input, false);
125    }
126
127    pub fn simulate_keystrokes<const COUNT: usize>(&mut self, keystroke_texts: [&str; COUNT]) {
128        for keystroke_text in keystroke_texts.into_iter() {
129            self.simulate_keystroke(keystroke_text);
130        }
131    }
132
133    pub fn set_state(&mut self, text: &str, mode: Mode) {
134        self.cx
135            .update(|cx| Vim::update(cx, |vim, cx| vim.switch_mode(mode, cx)));
136        self.editor.update(self.cx, |editor, cx| {
137            let (unmarked_text, markers) = marked_text(&text);
138            editor.set_text(unmarked_text, cx);
139            let cursor_offset = markers[0];
140            editor.replace_selections_with(cx, |map| cursor_offset.to_display_point(map));
141        })
142    }
143
144    pub fn assert_newest_selection_head_offset(&mut self, expected_offset: usize) {
145        let actual_head = self.newest_selection().head();
146        let (actual_offset, expected_head) = self.editor.update(self.cx, |editor, cx| {
147            let snapshot = editor.snapshot(cx);
148            (
149                actual_head.to_offset(&snapshot, Bias::Left),
150                expected_offset.to_display_point(&snapshot),
151            )
152        });
153        let mut actual_position_text = self.editor_text();
154        let mut expected_position_text = actual_position_text.clone();
155        actual_position_text.insert(actual_offset, '|');
156        expected_position_text.insert(expected_offset, '|');
157        assert_eq!(
158            actual_head, expected_head,
159            "\nActual Position: {}\nExpected Position: {}",
160            actual_position_text, expected_position_text
161        )
162    }
163
164    pub fn assert_editor_state(&mut self, text: &str) {
165        let (unmarked_text, markers) = marked_text(&text);
166        let editor_text = self.editor_text();
167        assert_eq!(
168            editor_text, unmarked_text,
169            "Unmarked text doesn't match editor text"
170        );
171        let expected_offset = markers[0];
172        let actual_head = self.newest_selection().head();
173        let (actual_offset, expected_head) = self.editor.update(self.cx, |editor, cx| {
174            let snapshot = editor.snapshot(cx);
175            (
176                actual_head.to_offset(&snapshot, Bias::Left),
177                expected_offset.to_display_point(&snapshot),
178            )
179        });
180        let mut actual_position_text = self.editor_text();
181        let mut expected_position_text = actual_position_text.clone();
182        actual_position_text.insert(actual_offset, '|');
183        expected_position_text.insert(expected_offset, '|');
184        assert_eq!(
185            actual_head, expected_head,
186            "\nActual Position: {}\nExpected Position: {}",
187            actual_position_text, expected_position_text
188        )
189    }
190
191    pub fn assert_binding<const COUNT: usize>(
192        &mut self,
193        keystrokes: [&str; COUNT],
194        initial_state: &str,
195        initial_mode: Mode,
196        state_after: &str,
197        mode_after: Mode,
198    ) {
199        self.set_state(initial_state, initial_mode);
200        self.simulate_keystrokes(keystrokes);
201        self.assert_editor_state(state_after);
202        assert_eq!(self.mode(), mode_after);
203        assert_eq!(self.active_operator(), None);
204    }
205}
206
207impl<'a> Deref for VimTestContext<'a> {
208    type Target = gpui::TestAppContext;
209
210    fn deref(&self) -> &Self::Target {
211        self.cx
212    }
213}