1use std::{
2 ops::{Deref, DerefMut, Range},
3 sync::Arc,
4};
5
6use futures::StreamExt;
7use indoc::indoc;
8
9use collections::BTreeMap;
10use gpui::{keymap::Keystroke, AppContext, ModelHandle, ViewContext, ViewHandle};
11use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, Selection};
12use project::{FakeFs, Project};
13use settings::Settings;
14use util::{
15 set_eq,
16 test::{marked_text, marked_text_ranges, marked_text_ranges_by, SetEqError},
17};
18
19use crate::{
20 display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
21 multi_buffer::ToPointUtf16,
22 Autoscroll, DisplayPoint, Editor, EditorMode, MultiBuffer, ToPoint,
23};
24
25#[cfg(test)]
26#[ctor::ctor]
27fn init_logger() {
28 if std::env::var("RUST_LOG").is_ok() {
29 env_logger::init();
30 }
31}
32
33// Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
34pub fn marked_display_snapshot(
35 text: &str,
36 cx: &mut gpui::MutableAppContext,
37) -> (DisplaySnapshot, Vec<DisplayPoint>) {
38 let (unmarked_text, markers) = marked_text(text);
39
40 let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
41 let font_id = cx
42 .font_cache()
43 .select_font(family_id, &Default::default())
44 .unwrap();
45 let font_size = 14.0;
46
47 let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
48 let display_map =
49 cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
50 let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
51 let markers = markers
52 .into_iter()
53 .map(|offset| offset.to_display_point(&snapshot))
54 .collect();
55
56 (snapshot, markers)
57}
58
59pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) {
60 let (umarked_text, text_ranges) = marked_text_ranges(marked_text);
61 assert_eq!(editor.text(cx), umarked_text);
62 editor.change_selections(None, cx, |s| s.select_ranges(text_ranges));
63}
64
65pub fn assert_text_with_selections(
66 editor: &mut Editor,
67 marked_text: &str,
68 cx: &mut ViewContext<Editor>,
69) {
70 let (unmarked_text, text_ranges) = marked_text_ranges(marked_text);
71
72 assert_eq!(editor.text(cx), unmarked_text);
73 assert_eq!(editor.selections.ranges(cx), text_ranges);
74}
75
76pub(crate) fn build_editor(
77 buffer: ModelHandle<MultiBuffer>,
78 cx: &mut ViewContext<Editor>,
79) -> Editor {
80 Editor::new(EditorMode::Full, buffer, None, None, cx)
81}
82
83pub struct EditorTestContext<'a> {
84 pub cx: &'a mut gpui::TestAppContext,
85 pub window_id: usize,
86 pub editor: ViewHandle<Editor>,
87}
88
89impl<'a> EditorTestContext<'a> {
90 pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
91 let (window_id, editor) = cx.update(|cx| {
92 cx.set_global(Settings::test(cx));
93 crate::init(cx);
94
95 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
96 build_editor(MultiBuffer::build_simple("", cx), cx)
97 });
98
99 editor.update(cx, |_, cx| cx.focus_self());
100
101 (window_id, editor)
102 });
103
104 Self {
105 cx,
106 window_id,
107 editor,
108 }
109 }
110
111 pub fn editor<F, T>(&mut self, read: F) -> T
112 where
113 F: FnOnce(&Editor, &AppContext) -> T,
114 {
115 self.editor.read_with(self.cx, read)
116 }
117
118 pub fn update_editor<F, T>(&mut self, update: F) -> T
119 where
120 F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
121 {
122 self.editor.update(self.cx, update)
123 }
124
125 pub fn buffer_text(&mut self) -> String {
126 self.editor.read_with(self.cx, |editor, cx| {
127 editor.buffer.read(cx).snapshot(cx).text()
128 })
129 }
130
131 pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
132 let keystroke = Keystroke::parse(keystroke_text).unwrap();
133 let input = if keystroke.modified() {
134 None
135 } else {
136 Some(keystroke.key.clone())
137 };
138 self.cx
139 .dispatch_keystroke(self.window_id, keystroke, input, false);
140 }
141
142 pub fn simulate_keystrokes<const COUNT: usize>(&mut self, keystroke_texts: [&str; COUNT]) {
143 for keystroke_text in keystroke_texts.into_iter() {
144 self.simulate_keystroke(keystroke_text);
145 }
146 }
147
148 pub fn display_point(&mut self, cursor_location: &str) -> DisplayPoint {
149 let (_, locations) = marked_text(cursor_location);
150 let snapshot = self
151 .editor
152 .update(self.cx, |editor, cx| editor.snapshot(cx));
153 locations[0].to_display_point(&snapshot.display_snapshot)
154 }
155
156 // Sets the editor state via a marked string.
157 // `|` characters represent empty selections
158 // `[` to `}` represents a non empty selection with the head at `}`
159 // `{` to `]` represents a non empty selection with the head at `{`
160 pub fn set_state(&mut self, text: &str) {
161 self.editor.update(self.cx, |editor, cx| {
162 let (unmarked_text, mut selection_ranges) = marked_text_ranges_by(
163 &text,
164 vec!['|'.into(), ('[', '}').into(), ('{', ']').into()],
165 );
166 editor.set_text(unmarked_text, cx);
167
168 let mut selections: Vec<Range<usize>> =
169 selection_ranges.remove(&'|'.into()).unwrap_or_default();
170 selections.extend(
171 selection_ranges
172 .remove(&('{', ']').into())
173 .unwrap_or_default()
174 .into_iter()
175 .map(|range| range.end..range.start),
176 );
177 selections.extend(
178 selection_ranges
179 .remove(&('[', '}').into())
180 .unwrap_or_default(),
181 );
182
183 editor.change_selections(Some(Autoscroll::Fit), cx, |s| s.select_ranges(selections));
184 })
185 }
186
187 // Asserts the editor state via a marked string.
188 // `|` characters represent empty selections
189 // `[` to `}` represents a non empty selection with the head at `}`
190 // `{` to `]` represents a non empty selection with the head at `{`
191 pub fn assert_editor_state(&mut self, text: &str) {
192 let (unmarked_text, mut selection_ranges) = marked_text_ranges_by(
193 &text,
194 vec!['|'.into(), ('[', '}').into(), ('{', ']').into()],
195 );
196 let buffer_text = self.buffer_text();
197 assert_eq!(
198 buffer_text, unmarked_text,
199 "Unmarked text doesn't match buffer text"
200 );
201
202 let expected_empty_selections = selection_ranges.remove(&'|'.into()).unwrap_or_default();
203 let expected_reverse_selections = selection_ranges
204 .remove(&('{', ']').into())
205 .unwrap_or_default();
206 let expected_forward_selections = selection_ranges
207 .remove(&('[', '}').into())
208 .unwrap_or_default();
209
210 self.assert_selections(
211 expected_empty_selections,
212 expected_reverse_selections,
213 expected_forward_selections,
214 Some(text.to_string()),
215 )
216 }
217
218 pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
219 let mut empty_selections = Vec::new();
220 let mut reverse_selections = Vec::new();
221 let mut forward_selections = Vec::new();
222
223 for selection in expected_selections {
224 let range = selection.range();
225 if selection.is_empty() {
226 empty_selections.push(range);
227 } else if selection.reversed {
228 reverse_selections.push(range);
229 } else {
230 forward_selections.push(range)
231 }
232 }
233
234 self.assert_selections(
235 empty_selections,
236 reverse_selections,
237 forward_selections,
238 None,
239 )
240 }
241
242 fn assert_selections(
243 &mut self,
244 expected_empty_selections: Vec<Range<usize>>,
245 expected_reverse_selections: Vec<Range<usize>>,
246 expected_forward_selections: Vec<Range<usize>>,
247 asserted_text: Option<String>,
248 ) {
249 let (empty_selections, reverse_selections, forward_selections) =
250 self.editor.read_with(self.cx, |editor, cx| {
251 let mut empty_selections = Vec::new();
252 let mut reverse_selections = Vec::new();
253 let mut forward_selections = Vec::new();
254
255 for selection in editor.selections.all::<usize>(cx) {
256 let range = selection.range();
257 if selection.is_empty() {
258 empty_selections.push(range);
259 } else if selection.reversed {
260 reverse_selections.push(range);
261 } else {
262 forward_selections.push(range)
263 }
264 }
265
266 (empty_selections, reverse_selections, forward_selections)
267 });
268
269 let asserted_selections = asserted_text.unwrap_or_else(|| {
270 self.insert_markers(
271 &expected_empty_selections,
272 &expected_reverse_selections,
273 &expected_forward_selections,
274 )
275 });
276 let actual_selections =
277 self.insert_markers(&empty_selections, &reverse_selections, &forward_selections);
278
279 let unmarked_text = self.buffer_text();
280 let all_eq: Result<(), SetEqError<String>> =
281 set_eq!(expected_empty_selections, empty_selections)
282 .map_err(|err| {
283 err.map(|missing| {
284 let mut error_text = unmarked_text.clone();
285 error_text.insert(missing.start, '|');
286 error_text
287 })
288 })
289 .and_then(|_| {
290 set_eq!(expected_reverse_selections, reverse_selections).map_err(|err| {
291 err.map(|missing| {
292 let mut error_text = unmarked_text.clone();
293 error_text.insert(missing.start, '{');
294 error_text.insert(missing.end, ']');
295 error_text
296 })
297 })
298 })
299 .and_then(|_| {
300 set_eq!(expected_forward_selections, forward_selections).map_err(|err| {
301 err.map(|missing| {
302 let mut error_text = unmarked_text.clone();
303 error_text.insert(missing.start, '[');
304 error_text.insert(missing.end, '}');
305 error_text
306 })
307 })
308 });
309
310 match all_eq {
311 Err(SetEqError::LeftMissing(location_text)) => {
312 panic!(
313 indoc! {"
314 Editor has extra selection
315 Extra Selection Location:
316 {}
317 Asserted selections:
318 {}
319 Actual selections:
320 {}"},
321 location_text, asserted_selections, actual_selections,
322 );
323 }
324 Err(SetEqError::RightMissing(location_text)) => {
325 panic!(
326 indoc! {"
327 Editor is missing empty selection
328 Missing Selection Location:
329 {}
330 Asserted selections:
331 {}
332 Actual selections:
333 {}"},
334 location_text, asserted_selections, actual_selections,
335 );
336 }
337 _ => {}
338 }
339 }
340
341 fn insert_markers(
342 &mut self,
343 empty_selections: &Vec<Range<usize>>,
344 reverse_selections: &Vec<Range<usize>>,
345 forward_selections: &Vec<Range<usize>>,
346 ) -> String {
347 let mut editor_text_with_selections = self.buffer_text();
348 let mut selection_marks = BTreeMap::new();
349 for range in empty_selections {
350 selection_marks.insert(&range.start, '|');
351 }
352 for range in reverse_selections {
353 selection_marks.insert(&range.start, '{');
354 selection_marks.insert(&range.end, ']');
355 }
356 for range in forward_selections {
357 selection_marks.insert(&range.start, '[');
358 selection_marks.insert(&range.end, '}');
359 }
360 for (offset, mark) in selection_marks.into_iter().rev() {
361 editor_text_with_selections.insert(*offset, mark);
362 }
363
364 editor_text_with_selections
365 }
366
367 pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
368 self.cx.update(|cx| {
369 let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
370 let expected_content = expected_content.map(|content| content.to_owned());
371 assert_eq!(actual_content, expected_content);
372 })
373 }
374}
375
376impl<'a> Deref for EditorTestContext<'a> {
377 type Target = gpui::TestAppContext;
378
379 fn deref(&self) -> &Self::Target {
380 self.cx
381 }
382}
383
384impl<'a> DerefMut for EditorTestContext<'a> {
385 fn deref_mut(&mut self) -> &mut Self::Target {
386 &mut self.cx
387 }
388}
389
390pub struct EditorLspTestContext<'a> {
391 pub cx: EditorTestContext<'a>,
392 pub lsp: lsp::FakeLanguageServer,
393}
394
395impl<'a> EditorLspTestContext<'a> {
396 pub async fn new(
397 mut language: Language,
398 capabilities: lsp::ServerCapabilities,
399 cx: &'a mut gpui::TestAppContext,
400 ) -> EditorLspTestContext<'a> {
401 let file_name = format!(
402 "/file.{}",
403 language
404 .path_suffixes()
405 .first()
406 .unwrap_or(&"txt".to_string())
407 );
408
409 let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
410 capabilities,
411 ..Default::default()
412 });
413
414 let fs = FakeFs::new(cx.background().clone());
415 fs.insert_file(file_name.clone(), "".to_string()).await;
416
417 let project = Project::test(fs, [file_name.as_ref()], cx).await;
418 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
419 let buffer = project
420 .update(cx, |project, cx| project.open_local_buffer(file_name, cx))
421 .await
422 .unwrap();
423
424 let (window_id, editor) = cx.update(|cx| {
425 cx.set_global(Settings::test(cx));
426 crate::init(cx);
427
428 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
429 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
430
431 Editor::new(EditorMode::Full, buffer, Some(project), None, cx)
432 });
433
434 editor.update(cx, |_, cx| cx.focus_self());
435
436 (window_id, editor)
437 });
438
439 let lsp = fake_servers.next().await.unwrap();
440
441 Self {
442 cx: EditorTestContext {
443 cx,
444 window_id,
445 editor,
446 },
447 lsp,
448 }
449 }
450
451 pub async fn new_rust(
452 capabilities: lsp::ServerCapabilities,
453 cx: &'a mut gpui::TestAppContext,
454 ) -> EditorLspTestContext<'a> {
455 let language = Language::new(
456 LanguageConfig {
457 name: "Rust".into(),
458 path_suffixes: vec!["rs".to_string()],
459 ..Default::default()
460 },
461 Some(tree_sitter_rust::language()),
462 );
463
464 Self::new(language, capabilities, cx).await
465 }
466
467 // Constructs lsp range using a marked string with '[', ']' range delimiters
468 pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
469 let (unmarked, mut ranges) = marked_text_ranges_by(marked_text, vec![('[', ']').into()]);
470 assert_eq!(unmarked, self.cx.buffer_text());
471 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
472
473 let offset_range = ranges.remove(&('[', ']').into()).unwrap()[0].clone();
474 let start_point = offset_range.start.to_point(&snapshot.buffer_snapshot);
475 let end_point = offset_range.end.to_point(&snapshot.buffer_snapshot);
476 self.editor(|editor, cx| {
477 let buffer = editor.buffer().read(cx);
478 let start = point_to_lsp(
479 buffer
480 .point_to_buffer_offset(start_point, cx)
481 .unwrap()
482 .1
483 .to_point_utf16(&buffer.read(cx)),
484 );
485 let end = point_to_lsp(
486 buffer
487 .point_to_buffer_offset(end_point, cx)
488 .unwrap()
489 .1
490 .to_point_utf16(&buffer.read(cx)),
491 );
492
493 lsp::Range { start, end }
494 })
495 }
496}
497
498impl<'a> Deref for EditorLspTestContext<'a> {
499 type Target = EditorTestContext<'a>;
500
501 fn deref(&self) -> &Self::Target {
502 &self.cx
503 }
504}
505
506impl<'a> DerefMut for EditorLspTestContext<'a> {
507 fn deref_mut(&mut self) -> &mut Self::Target {
508 &mut self.cx
509 }
510}