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::*;
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(¶ms, 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.update(|cx| cx.global::<VimState>().mode)
104 }
105
106 pub fn editor_text(&mut self) -> String {
107 self.editor
108 .update(self.cx, |editor, cx| editor.snapshot(cx).text())
109 }
110
111 pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
112 let keystroke = Keystroke::parse(keystroke_text).unwrap();
113 let input = if keystroke.modified() {
114 None
115 } else {
116 Some(keystroke.key.clone())
117 };
118 self.cx
119 .dispatch_keystroke(self.window_id, keystroke, input, false);
120 }
121
122 pub fn simulate_keystrokes(&mut self, keystroke_texts: &[&str]) {
123 for keystroke_text in keystroke_texts.into_iter() {
124 self.simulate_keystroke(keystroke_text);
125 }
126 }
127
128 pub fn assert_newest_selection_head_offset(&mut self, expected_offset: usize) {
129 let actual_head = self.newest_selection().head();
130 let (actual_offset, expected_head) = self.editor.update(self.cx, |editor, cx| {
131 let snapshot = editor.snapshot(cx);
132 (
133 actual_head.to_offset(&snapshot, Bias::Left),
134 expected_offset.to_display_point(&snapshot),
135 )
136 });
137 let mut actual_position_text = self.editor_text();
138 let mut expected_position_text = actual_position_text.clone();
139 actual_position_text.insert(actual_offset, '|');
140 expected_position_text.insert(expected_offset, '|');
141 assert_eq!(
142 actual_head, expected_head,
143 "\nActual Position: {}\nExpected Position: {}",
144 actual_position_text, expected_position_text
145 )
146 }
147
148 pub fn assert_editor_state(&mut self, text: &str) {
149 let (unmarked_text, markers) = marked_text(&text);
150 let editor_text = self.editor_text();
151 assert_eq!(
152 editor_text, unmarked_text,
153 "Unmarked text doesn't match editor text"
154 );
155 let expected_offset = markers[0];
156 let actual_head = self.newest_selection().head();
157 let (actual_offset, expected_head) = self.editor.update(self.cx, |editor, cx| {
158 let snapshot = editor.snapshot(cx);
159 (
160 actual_head.to_offset(&snapshot, Bias::Left),
161 expected_offset.to_display_point(&snapshot),
162 )
163 });
164 let mut actual_position_text = self.editor_text();
165 let mut expected_position_text = actual_position_text.clone();
166 actual_position_text.insert(actual_offset, '|');
167 expected_position_text.insert(expected_offset, '|');
168 assert_eq!(
169 actual_head, expected_head,
170 "\nActual Position: {}\nExpected Position: {}",
171 actual_position_text, expected_position_text
172 )
173 }
174}
175
176impl<'a> Deref for VimTestContext<'a> {
177 type Target = gpui::TestAppContext;
178
179 fn deref(&self) -> &Self::Target {
180 self.cx
181 }
182}