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