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