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