test.rs

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