test.rs

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