test.rs

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