test.rs

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