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::{json, keymap::Keystroke, AppContext, ModelHandle, ViewContext, ViewHandle};
13use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, Selection};
14use lsp::request;
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 condition(
116 &self,
117 predicate: impl FnMut(&Editor, &AppContext) -> bool,
118 ) -> impl Future<Output = ()> {
119 self.editor.condition(self.cx, predicate)
120 }
121
122 pub fn editor<F, T>(&mut self, read: F) -> T
123 where
124 F: FnOnce(&Editor, &AppContext) -> T,
125 {
126 self.editor.read_with(self.cx, read)
127 }
128
129 pub fn update_editor<F, T>(&mut self, update: F) -> T
130 where
131 F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
132 {
133 self.editor.update(self.cx, update)
134 }
135
136 pub fn buffer_text(&mut self) -> String {
137 self.editor.read_with(self.cx, |editor, cx| {
138 editor.buffer.read(cx).snapshot(cx).text()
139 })
140 }
141
142 pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
143 let keystroke = Keystroke::parse(keystroke_text).unwrap();
144 let input = if keystroke.modified() {
145 None
146 } else {
147 Some(keystroke.key.clone())
148 };
149 self.cx
150 .dispatch_keystroke(self.window_id, keystroke, input, false);
151 }
152
153 pub fn simulate_keystrokes<const COUNT: usize>(&mut self, keystroke_texts: [&str; COUNT]) {
154 for keystroke_text in keystroke_texts.into_iter() {
155 self.simulate_keystroke(keystroke_text);
156 }
157 }
158
159 pub fn display_point(&mut self, cursor_location: &str) -> DisplayPoint {
160 let (_, locations) = marked_text(cursor_location);
161 let snapshot = self
162 .editor
163 .update(self.cx, |editor, cx| editor.snapshot(cx));
164 locations[0].to_display_point(&snapshot.display_snapshot)
165 }
166
167 // Sets the editor state via a marked string.
168 // `|` characters represent empty selections
169 // `[` to `}` represents a non empty selection with the head at `}`
170 // `{` to `]` represents a non empty selection with the head at `{`
171 pub fn set_state(&mut self, text: &str) {
172 self.set_state_by(
173 vec![
174 '|'.into(),
175 ('[', '}').into(),
176 TextRangeMarker::ReverseRange('{', ']'),
177 ],
178 text,
179 );
180 }
181
182 pub fn set_state_by(&mut self, range_markers: Vec<TextRangeMarker>, text: &str) {
183 self.editor.update(self.cx, |editor, cx| {
184 let (unmarked_text, selection_ranges) = marked_text_ranges_by(&text, range_markers);
185 editor.set_text(unmarked_text, cx);
186
187 let selection_ranges: Vec<Range<usize>> = selection_ranges
188 .values()
189 .into_iter()
190 .flatten()
191 .cloned()
192 .collect();
193 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
194 s.select_ranges(selection_ranges)
195 })
196 })
197 }
198
199 // Asserts the editor state via a marked string.
200 // `|` characters represent empty selections
201 // `[` to `}` represents a non empty selection with the head at `}`
202 // `{` to `]` represents a non empty selection with the head at `{`
203 pub fn assert_editor_state(&mut self, text: &str) {
204 let (unmarked_text, mut selection_ranges) = marked_text_ranges_by(
205 &text,
206 vec!['|'.into(), ('[', '}').into(), ('{', ']').into()],
207 );
208 let buffer_text = self.buffer_text();
209 assert_eq!(
210 buffer_text, unmarked_text,
211 "Unmarked text doesn't match buffer text"
212 );
213
214 let expected_empty_selections = selection_ranges.remove(&'|'.into()).unwrap_or_default();
215 let expected_reverse_selections = selection_ranges
216 .remove(&('{', ']').into())
217 .unwrap_or_default();
218 let expected_forward_selections = selection_ranges
219 .remove(&('[', '}').into())
220 .unwrap_or_default();
221
222 self.assert_selections(
223 expected_empty_selections,
224 expected_reverse_selections,
225 expected_forward_selections,
226 Some(text.to_string()),
227 )
228 }
229
230 pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
231 let (unmarked, mut ranges) = marked_text_ranges_by(marked_text, vec![('[', ']').into()]);
232 assert_eq!(unmarked, self.buffer_text());
233
234 let asserted_ranges = ranges.remove(&('[', ']').into()).unwrap();
235 let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
236 let snapshot = editor.snapshot(cx);
237 editor
238 .background_highlights
239 .get(&TypeId::of::<Tag>())
240 .map(|h| h.1.clone())
241 .unwrap_or_default()
242 .into_iter()
243 .map(|range| range.to_offset(&snapshot.buffer_snapshot))
244 .collect()
245 });
246
247 assert_set_eq!(asserted_ranges, actual_ranges);
248 }
249
250 pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
251 let (unmarked, mut ranges) = marked_text_ranges_by(marked_text, vec![('[', ']').into()]);
252 assert_eq!(unmarked, self.buffer_text());
253
254 let asserted_ranges = ranges.remove(&('[', ']').into()).unwrap();
255 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
256 let actual_ranges: Vec<Range<usize>> = snapshot
257 .display_snapshot
258 .highlight_ranges::<Tag>()
259 .map(|ranges| ranges.as_ref().clone().1)
260 .unwrap_or_default()
261 .into_iter()
262 .map(|range| range.to_offset(&snapshot.buffer_snapshot))
263 .collect();
264
265 assert_set_eq!(asserted_ranges, actual_ranges);
266 }
267
268 pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
269 let mut empty_selections = Vec::new();
270 let mut reverse_selections = Vec::new();
271 let mut forward_selections = Vec::new();
272
273 for selection in expected_selections {
274 let range = selection.range();
275 if selection.is_empty() {
276 empty_selections.push(range);
277 } else if selection.reversed {
278 reverse_selections.push(range);
279 } else {
280 forward_selections.push(range)
281 }
282 }
283
284 self.assert_selections(
285 empty_selections,
286 reverse_selections,
287 forward_selections,
288 None,
289 )
290 }
291
292 fn assert_selections(
293 &mut self,
294 expected_empty_selections: Vec<Range<usize>>,
295 expected_reverse_selections: Vec<Range<usize>>,
296 expected_forward_selections: Vec<Range<usize>>,
297 asserted_text: Option<String>,
298 ) {
299 let (empty_selections, reverse_selections, forward_selections) =
300 self.editor.read_with(self.cx, |editor, cx| {
301 let mut empty_selections = Vec::new();
302 let mut reverse_selections = Vec::new();
303 let mut forward_selections = Vec::new();
304
305 for selection in editor.selections.all::<usize>(cx) {
306 let range = selection.range();
307 if selection.is_empty() {
308 empty_selections.push(range);
309 } else if selection.reversed {
310 reverse_selections.push(range);
311 } else {
312 forward_selections.push(range)
313 }
314 }
315
316 (empty_selections, reverse_selections, forward_selections)
317 });
318
319 let asserted_selections = asserted_text.unwrap_or_else(|| {
320 self.insert_markers(
321 &expected_empty_selections,
322 &expected_reverse_selections,
323 &expected_forward_selections,
324 )
325 });
326 let actual_selections =
327 self.insert_markers(&empty_selections, &reverse_selections, &forward_selections);
328
329 let unmarked_text = self.buffer_text();
330 let all_eq: Result<(), SetEqError<String>> =
331 set_eq!(expected_empty_selections, empty_selections)
332 .map_err(|err| {
333 err.map(|missing| {
334 let mut error_text = unmarked_text.clone();
335 error_text.insert(missing.start, '|');
336 error_text
337 })
338 })
339 .and_then(|_| {
340 set_eq!(expected_reverse_selections, reverse_selections).map_err(|err| {
341 err.map(|missing| {
342 let mut error_text = unmarked_text.clone();
343 error_text.insert(missing.start, '{');
344 error_text.insert(missing.end, ']');
345 error_text
346 })
347 })
348 })
349 .and_then(|_| {
350 set_eq!(expected_forward_selections, forward_selections).map_err(|err| {
351 err.map(|missing| {
352 let mut error_text = unmarked_text.clone();
353 error_text.insert(missing.start, '[');
354 error_text.insert(missing.end, '}');
355 error_text
356 })
357 })
358 });
359
360 match all_eq {
361 Err(SetEqError::LeftMissing(location_text)) => {
362 panic!(
363 indoc! {"
364 Editor has extra selection
365 Extra Selection Location:
366 {}
367 Asserted selections:
368 {}
369 Actual selections:
370 {}"},
371 location_text, asserted_selections, actual_selections,
372 );
373 }
374 Err(SetEqError::RightMissing(location_text)) => {
375 panic!(
376 indoc! {"
377 Editor is missing empty selection
378 Missing Selection Location:
379 {}
380 Asserted selections:
381 {}
382 Actual selections:
383 {}"},
384 location_text, asserted_selections, actual_selections,
385 );
386 }
387 _ => {}
388 }
389 }
390
391 fn insert_markers(
392 &mut self,
393 empty_selections: &Vec<Range<usize>>,
394 reverse_selections: &Vec<Range<usize>>,
395 forward_selections: &Vec<Range<usize>>,
396 ) -> String {
397 let mut editor_text_with_selections = self.buffer_text();
398 let mut selection_marks = BTreeMap::new();
399 for range in empty_selections {
400 selection_marks.insert(&range.start, '|');
401 }
402 for range in reverse_selections {
403 selection_marks.insert(&range.start, '{');
404 selection_marks.insert(&range.end, ']');
405 }
406 for range in forward_selections {
407 selection_marks.insert(&range.start, '[');
408 selection_marks.insert(&range.end, '}');
409 }
410 for (offset, mark) in selection_marks.into_iter().rev() {
411 editor_text_with_selections.insert(*offset, mark);
412 }
413
414 editor_text_with_selections
415 }
416}
417
418impl<'a> Deref for EditorTestContext<'a> {
419 type Target = gpui::TestAppContext;
420
421 fn deref(&self) -> &Self::Target {
422 self.cx
423 }
424}
425
426impl<'a> DerefMut for EditorTestContext<'a> {
427 fn deref_mut(&mut self) -> &mut Self::Target {
428 &mut self.cx
429 }
430}
431
432pub struct EditorLspTestContext<'a> {
433 pub cx: EditorTestContext<'a>,
434 pub lsp: lsp::FakeLanguageServer,
435 pub workspace: ViewHandle<Workspace>,
436 pub editor_lsp_url: lsp::Url,
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
463 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
464 capabilities,
465 ..Default::default()
466 }))
467 .await;
468
469 let project = Project::test(params.fs.clone(), [], cx).await;
470 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
471
472 params
473 .fs
474 .as_fake()
475 .insert_tree("/root", json!({ "dir": { file_name: "" }}))
476 .await;
477
478 let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
479 project
480 .update(cx, |project, cx| {
481 project.find_or_create_local_worktree("/root", true, cx)
482 })
483 .await
484 .unwrap();
485 cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
486 .await;
487
488 let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
489 let item = workspace
490 .update(cx, |workspace, cx| workspace.open_path(file, true, cx))
491 .await
492 .expect("Could not open test file");
493
494 let editor = cx.update(|cx| {
495 item.act_as::<Editor>(cx)
496 .expect("Opened test file wasn't an editor")
497 });
498 editor.update(cx, |_, cx| cx.focus_self());
499
500 let lsp = fake_servers.next().await.unwrap();
501
502 Self {
503 cx: EditorTestContext {
504 cx,
505 window_id,
506 editor,
507 },
508 lsp,
509 workspace,
510 editor_lsp_url: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
511 }
512 }
513
514 pub async fn new_rust(
515 capabilities: lsp::ServerCapabilities,
516 cx: &'a mut gpui::TestAppContext,
517 ) -> EditorLspTestContext<'a> {
518 let language = Language::new(
519 LanguageConfig {
520 name: "Rust".into(),
521 path_suffixes: vec!["rs".to_string()],
522 ..Default::default()
523 },
524 Some(tree_sitter_rust::language()),
525 );
526
527 Self::new(language, capabilities, cx).await
528 }
529
530 // Constructs lsp range using a marked string with '[', ']' range delimiters
531 pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
532 let (unmarked, mut ranges) = marked_text_ranges_by(marked_text, vec![('[', ']').into()]);
533 assert_eq!(unmarked, self.cx.buffer_text());
534 let offset_range = ranges.remove(&('[', ']').into()).unwrap()[0].clone();
535 self.to_lsp_range(offset_range)
536 }
537
538 pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
539 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
540 let start_point = range.start.to_point(&snapshot.buffer_snapshot);
541 let end_point = range.end.to_point(&snapshot.buffer_snapshot);
542
543 self.editor(|editor, cx| {
544 let buffer = editor.buffer().read(cx);
545 let start = point_to_lsp(
546 buffer
547 .point_to_buffer_offset(start_point, cx)
548 .unwrap()
549 .1
550 .to_point_utf16(&buffer.read(cx)),
551 );
552 let end = point_to_lsp(
553 buffer
554 .point_to_buffer_offset(end_point, cx)
555 .unwrap()
556 .1
557 .to_point_utf16(&buffer.read(cx)),
558 );
559
560 lsp::Range { start, end }
561 })
562 }
563
564 pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
565 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
566 let point = offset.to_point(&snapshot.buffer_snapshot);
567
568 self.editor(|editor, cx| {
569 let buffer = editor.buffer().read(cx);
570 point_to_lsp(
571 buffer
572 .point_to_buffer_offset(point, cx)
573 .unwrap()
574 .1
575 .to_point_utf16(&buffer.read(cx)),
576 )
577 })
578 }
579
580 pub fn update_workspace<F, T>(&mut self, update: F) -> T
581 where
582 F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
583 {
584 self.workspace.update(self.cx.cx, update)
585 }
586
587 pub fn handle_request<T, F, Fut>(
588 &self,
589 mut handler: F,
590 ) -> futures::channel::mpsc::UnboundedReceiver<()>
591 where
592 T: 'static + request::Request,
593 T::Params: 'static + Send,
594 F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
595 Fut: 'static + Send + Future<Output = Result<T::Result>>,
596 {
597 let url = self.editor_lsp_url.clone();
598 self.lsp.handle_request::<T, _, _>(move |params, cx| {
599 let url = url.clone();
600 handler(url, params, cx)
601 })
602 }
603}
604
605impl<'a> Deref for EditorLspTestContext<'a> {
606 type Target = EditorTestContext<'a>;
607
608 fn deref(&self) -> &Self::Target {
609 &self.cx
610 }
611}
612
613impl<'a> DerefMut for EditorLspTestContext<'a> {
614 fn deref_mut(&mut self) -> &mut Self::Target {
615 &mut self.cx
616 }
617}