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