1use std::{
2 any::TypeId,
3 ops::{Deref, DerefMut, Range},
4};
5
6use futures::Future;
7use indoc::indoc;
8
9use crate::{
10 display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
11};
12use gpui::{keymap::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle};
13use language::{Buffer, BufferSnapshot};
14use settings::Settings;
15use util::{
16 assert_set_eq,
17 test::{generate_marked_text, marked_text_ranges},
18};
19
20use super::build_editor;
21
22pub struct EditorTestContext<'a> {
23 pub cx: &'a mut gpui::TestAppContext,
24 pub window_id: usize,
25 pub editor: ViewHandle<Editor>,
26}
27
28impl<'a> EditorTestContext<'a> {
29 pub fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
30 let (window_id, editor) = cx.update(|cx| {
31 cx.set_global(Settings::test(cx));
32 crate::init(cx);
33
34 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
35 build_editor(MultiBuffer::build_simple("", cx), cx)
36 });
37
38 editor.update(cx, |_, cx| cx.focus_self());
39
40 (window_id, editor)
41 });
42
43 Self {
44 cx,
45 window_id,
46 editor,
47 }
48 }
49
50 pub fn condition(
51 &self,
52 predicate: impl FnMut(&Editor, &AppContext) -> bool,
53 ) -> impl Future<Output = ()> {
54 self.editor.condition(self.cx, predicate)
55 }
56
57 pub fn editor<F, T>(&self, read: F) -> T
58 where
59 F: FnOnce(&Editor, &AppContext) -> T,
60 {
61 self.editor.read_with(self.cx, read)
62 }
63
64 pub fn update_editor<F, T>(&mut self, update: F) -> T
65 where
66 F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
67 {
68 self.editor.update(self.cx, update)
69 }
70
71 pub fn multibuffer<F, T>(&self, read: F) -> T
72 where
73 F: FnOnce(&MultiBuffer, &AppContext) -> T,
74 {
75 self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
76 }
77
78 pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
79 where
80 F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T,
81 {
82 self.update_editor(|editor, cx| editor.buffer().update(cx, update))
83 }
84
85 pub fn buffer_text(&self) -> String {
86 self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
87 }
88
89 pub fn buffer<F, T>(&self, read: F) -> T
90 where
91 F: FnOnce(&Buffer, &AppContext) -> T,
92 {
93 self.multibuffer(|multibuffer, cx| {
94 let buffer = multibuffer.as_singleton().unwrap().read(cx);
95 read(buffer, cx)
96 })
97 }
98
99 pub fn update_buffer<F, T>(&mut self, update: F) -> T
100 where
101 F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T,
102 {
103 self.update_multibuffer(|multibuffer, cx| {
104 let buffer = multibuffer.as_singleton().unwrap();
105 buffer.update(cx, update)
106 })
107 }
108
109 pub fn buffer_snapshot(&self) -> BufferSnapshot {
110 self.buffer(|buffer, _| buffer.snapshot())
111 }
112
113 pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
114 let keystroke_under_test_handle =
115 self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
116 let keystroke = Keystroke::parse(keystroke_text).unwrap();
117 self.cx.dispatch_keystroke(self.window_id, keystroke, false);
118 keystroke_under_test_handle
119 }
120
121 pub fn simulate_keystrokes<const COUNT: usize>(
122 &mut self,
123 keystroke_texts: [&str; COUNT],
124 ) -> ContextHandle {
125 let keystrokes_under_test_handle =
126 self.add_assertion_context(format!("Simulated Keystrokes: {:?}", keystroke_texts));
127 for keystroke_text in keystroke_texts.into_iter() {
128 self.simulate_keystroke(keystroke_text);
129 }
130 keystrokes_under_test_handle
131 }
132
133 pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> {
134 let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
135 assert_eq!(self.buffer_text(), unmarked_text);
136 ranges
137 }
138
139 pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
140 let ranges = self.ranges(marked_text);
141 let snapshot = self
142 .editor
143 .update(self.cx, |editor, cx| editor.snapshot(cx));
144 ranges[0].start.to_display_point(&snapshot)
145 }
146
147 // Returns anchors for the current buffer using `«` and `»`
148 pub fn text_anchor_range(&self, marked_text: &str) -> Range<language::Anchor> {
149 let ranges = self.ranges(marked_text);
150 let snapshot = self.buffer_snapshot();
151 snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
152 }
153
154 /// Change the editor's text and selections using a string containing
155 /// embedded range markers that represent the ranges and directions of
156 /// each selection.
157 ///
158 /// See the `util::test::marked_text_ranges` function for more information.
159 pub fn set_state(&mut self, marked_text: &str) -> ContextHandle {
160 let _state_context = self.add_assertion_context(format!(
161 "Editor State: \"{}\"",
162 marked_text.escape_debug().to_string()
163 ));
164 let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
165 self.editor.update(self.cx, |editor, cx| {
166 editor.set_text(unmarked_text, cx);
167 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
168 s.select_ranges(selection_ranges)
169 })
170 });
171 _state_context
172 }
173
174 /// Make an assertion about the editor's text and the ranges and directions
175 /// of its selections using a string containing embedded range markers.
176 ///
177 /// See the `util::test::marked_text_ranges` function for more information.
178 pub fn assert_editor_state(&mut self, marked_text: &str) {
179 let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
180 let buffer_text = self.buffer_text();
181 assert_eq!(
182 buffer_text, unmarked_text,
183 "Unmarked text doesn't match buffer text"
184 );
185 self.assert_selections(expected_selections, marked_text.to_string())
186 }
187
188 pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
189 let expected_ranges = self.ranges(marked_text);
190 let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
191 let snapshot = editor.snapshot(cx);
192 editor
193 .background_highlights
194 .get(&TypeId::of::<Tag>())
195 .map(|h| h.1.clone())
196 .unwrap_or_default()
197 .into_iter()
198 .map(|range| range.to_offset(&snapshot.buffer_snapshot))
199 .collect()
200 });
201 assert_set_eq!(actual_ranges, expected_ranges);
202 }
203
204 pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
205 let expected_ranges = self.ranges(marked_text);
206 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
207 let actual_ranges: Vec<Range<usize>> = snapshot
208 .highlight_ranges::<Tag>()
209 .map(|ranges| ranges.as_ref().clone().1)
210 .unwrap_or_default()
211 .into_iter()
212 .map(|range| range.to_offset(&snapshot.buffer_snapshot))
213 .collect();
214 assert_set_eq!(actual_ranges, expected_ranges);
215 }
216
217 pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
218 let expected_marked_text =
219 generate_marked_text(&self.buffer_text(), &expected_selections, true);
220 self.assert_selections(expected_selections, expected_marked_text)
221 }
222
223 fn assert_selections(
224 &mut self,
225 expected_selections: Vec<Range<usize>>,
226 expected_marked_text: String,
227 ) {
228 let actual_selections = self
229 .editor
230 .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
231 .into_iter()
232 .map(|s| {
233 if s.reversed {
234 s.end..s.start
235 } else {
236 s.start..s.end
237 }
238 })
239 .collect::<Vec<_>>();
240 let actual_marked_text =
241 generate_marked_text(&self.buffer_text(), &actual_selections, true);
242 if expected_selections != actual_selections {
243 panic!(
244 indoc! {"
245 {}Editor has unexpected selections.
246
247 Expected selections:
248 {}
249
250 Actual selections:
251 {}
252 "},
253 self.assertion_context(),
254 expected_marked_text,
255 actual_marked_text,
256 );
257 }
258 }
259}
260
261impl<'a> Deref for EditorTestContext<'a> {
262 type Target = gpui::TestAppContext;
263
264 fn deref(&self) -> &Self::Target {
265 self.cx
266 }
267}
268
269impl<'a> DerefMut for EditorTestContext<'a> {
270 fn deref_mut(&mut self) -> &mut Self::Target {
271 &mut self.cx
272 }
273}