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