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