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