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 gpui::{
 12    json, keymap::Keystroke, AppContext, ModelContext, ModelHandle, ViewContext, ViewHandle,
 13};
 14use language::{
 15    point_to_lsp, Buffer, BufferSnapshot, FakeLspAdapter, Language, LanguageConfig, Selection,
 16};
 17use lsp::{notification, request};
 18use project::Project;
 19use settings::Settings;
 20use util::{
 21    assert_set_eq, set_eq,
 22    test::{generate_marked_text, marked_text, parse_marked_text},
 23};
 24use workspace::{pane, AppState, Workspace, WorkspaceHandle};
 25
 26use crate::{
 27    display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
 28    multi_buffer::ToPointUtf16,
 29    AnchorRangeExt, Autoscroll, DisplayPoint, Editor, EditorMode, MultiBuffer, ToPoint,
 30};
 31
 32#[cfg(test)]
 33#[ctor::ctor]
 34fn init_logger() {
 35    if std::env::var("RUST_LOG").is_ok() {
 36        env_logger::init();
 37    }
 38}
 39
 40// Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
 41pub fn marked_display_snapshot(
 42    text: &str,
 43    cx: &mut gpui::MutableAppContext,
 44) -> (DisplaySnapshot, Vec<DisplayPoint>) {
 45    let (unmarked_text, markers) = marked_text(text);
 46
 47    let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
 48    let font_id = cx
 49        .font_cache()
 50        .select_font(family_id, &Default::default())
 51        .unwrap();
 52    let font_size = 14.0;
 53
 54    let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
 55    let display_map =
 56        cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
 57    let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
 58    let markers = markers
 59        .into_iter()
 60        .map(|offset| offset.to_display_point(&snapshot))
 61        .collect();
 62
 63    (snapshot, markers)
 64}
 65
 66pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) {
 67    let (umarked_text, text_ranges) = parse_marked_text(marked_text, true).unwrap();
 68    assert_eq!(editor.text(cx), umarked_text);
 69    editor.change_selections(None, cx, |s| s.select_ranges(text_ranges));
 70}
 71
 72pub fn assert_text_with_selections(
 73    editor: &mut Editor,
 74    marked_text: &str,
 75    cx: &mut ViewContext<Editor>,
 76) {
 77    let (unmarked_text, text_ranges) = parse_marked_text(marked_text, true).unwrap();
 78    assert_eq!(editor.text(cx), unmarked_text);
 79    assert_eq!(editor.selections.ranges(cx), text_ranges);
 80}
 81
 82pub(crate) fn build_editor(
 83    buffer: ModelHandle<MultiBuffer>,
 84    cx: &mut ViewContext<Editor>,
 85) -> Editor {
 86    Editor::new(EditorMode::Full, buffer, None, None, cx)
 87}
 88
 89pub struct EditorTestContext<'a> {
 90    pub cx: &'a mut gpui::TestAppContext,
 91    pub window_id: usize,
 92    pub editor: ViewHandle<Editor>,
 93}
 94
 95impl<'a> EditorTestContext<'a> {
 96    pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
 97        let (window_id, editor) = cx.update(|cx| {
 98            cx.set_global(Settings::test(cx));
 99            crate::init(cx);
100
101            let (window_id, editor) = cx.add_window(Default::default(), |cx| {
102                build_editor(MultiBuffer::build_simple("", cx), cx)
103            });
104
105            editor.update(cx, |_, cx| cx.focus_self());
106
107            (window_id, editor)
108        });
109
110        Self {
111            cx,
112            window_id,
113            editor,
114        }
115    }
116
117    pub fn condition(
118        &self,
119        predicate: impl FnMut(&Editor, &AppContext) -> bool,
120    ) -> impl Future<Output = ()> {
121        self.editor.condition(self.cx, predicate)
122    }
123
124    pub fn editor<F, T>(&self, read: F) -> T
125    where
126        F: FnOnce(&Editor, &AppContext) -> T,
127    {
128        self.editor.read_with(self.cx, read)
129    }
130
131    pub fn update_editor<F, T>(&mut self, update: F) -> T
132    where
133        F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
134    {
135        self.editor.update(self.cx, update)
136    }
137
138    pub fn multibuffer<F, T>(&self, read: F) -> T
139    where
140        F: FnOnce(&MultiBuffer, &AppContext) -> T,
141    {
142        self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
143    }
144
145    pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
146    where
147        F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T,
148    {
149        self.update_editor(|editor, cx| editor.buffer().update(cx, update))
150    }
151
152    pub fn buffer_text(&self) -> String {
153        self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
154    }
155
156    pub fn buffer<F, T>(&self, read: F) -> T
157    where
158        F: FnOnce(&Buffer, &AppContext) -> T,
159    {
160        self.multibuffer(|multibuffer, cx| {
161            let buffer = multibuffer.as_singleton().unwrap().read(cx);
162            read(buffer, cx)
163        })
164    }
165
166    pub fn update_buffer<F, T>(&mut self, update: F) -> T
167    where
168        F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T,
169    {
170        self.update_multibuffer(|multibuffer, cx| {
171            let buffer = multibuffer.as_singleton().unwrap();
172            buffer.update(cx, update)
173        })
174    }
175
176    pub fn buffer_snapshot(&self) -> BufferSnapshot {
177        self.buffer(|buffer, _| buffer.snapshot())
178    }
179
180    pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
181        let keystroke = Keystroke::parse(keystroke_text).unwrap();
182        self.cx.dispatch_keystroke(self.window_id, keystroke, false);
183    }
184
185    pub fn simulate_keystrokes<const COUNT: usize>(&mut self, keystroke_texts: [&str; COUNT]) {
186        for keystroke_text in keystroke_texts.into_iter() {
187            self.simulate_keystroke(keystroke_text);
188        }
189    }
190
191    pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> {
192        let (unmarked_text, ranges) = parse_marked_text(marked_text, false).unwrap();
193        assert_eq!(self.buffer_text(), unmarked_text);
194        ranges
195    }
196
197    pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
198        let ranges = self.ranges(marked_text);
199        let snapshot = self
200            .editor
201            .update(self.cx, |editor, cx| editor.snapshot(cx));
202        ranges[0].start.to_display_point(&snapshot)
203    }
204
205    // Returns anchors for the current buffer using `«` and `»`
206    pub fn text_anchor_range(&self, marked_text: &str) -> Range<language::Anchor> {
207        let ranges = self.ranges(marked_text);
208        let snapshot = self.buffer_snapshot();
209        snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
210    }
211
212    pub fn set_state(&mut self, marked_text: &str) {
213        let (unmarked_text, selection_ranges) = parse_marked_text(marked_text, true).unwrap();
214        self.editor.update(self.cx, |editor, cx| {
215            editor.set_text(unmarked_text, cx);
216            editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
217                s.select_ranges(selection_ranges)
218            })
219        })
220    }
221
222    pub fn assert_editor_state(&mut self, marked_text: &str) {
223        let (unmarked_text, expected_selections) = parse_marked_text(marked_text, true).unwrap();
224        let buffer_text = self.buffer_text();
225        assert_eq!(
226            buffer_text, unmarked_text,
227            "Unmarked text doesn't match buffer text"
228        );
229        self.assert_selections(expected_selections, marked_text.to_string())
230    }
231
232    pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
233        let expected_ranges = self.ranges(marked_text);
234        let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
235            let snapshot = editor.snapshot(cx);
236            editor
237                .background_highlights
238                .get(&TypeId::of::<Tag>())
239                .map(|h| h.1.clone())
240                .unwrap_or_default()
241                .into_iter()
242                .map(|range| range.to_offset(&snapshot.buffer_snapshot))
243                .collect()
244        });
245        assert_set_eq!(actual_ranges, expected_ranges);
246    }
247
248    pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
249        let expected_ranges = self.ranges(marked_text);
250        let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
251        let actual_ranges: Vec<Range<usize>> = snapshot
252            .highlight_ranges::<Tag>()
253            .map(|ranges| ranges.as_ref().clone().1)
254            .unwrap_or_default()
255            .into_iter()
256            .map(|range| range.to_offset(&snapshot.buffer_snapshot))
257            .collect();
258        assert_set_eq!(actual_ranges, expected_ranges);
259    }
260
261    pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
262        let expected_selections = expected_selections
263            .into_iter()
264            .map(|s| s.range())
265            .collect::<Vec<_>>();
266        let expected_marked_text =
267            generate_marked_text(&self.buffer_text(), &expected_selections, true);
268        self.assert_selections(expected_selections, expected_marked_text)
269    }
270
271    fn assert_selections(
272        &mut self,
273        expected_selections: Vec<Range<usize>>,
274        expected_marked_text: String,
275    ) {
276        let actual_selections = self
277            .editor
278            .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
279            .into_iter()
280            .map(|s| s.range())
281            .collect::<Vec<_>>();
282        let actual_marked_text =
283            generate_marked_text(&self.buffer_text(), &actual_selections, true);
284        if expected_selections != actual_selections {
285            panic!(
286                indoc! {"
287                    Editor has unexpected selections.
288                    Expected selections:
289                    {}
290                    Actual selections:
291                    {}",
292                },
293                expected_marked_text, actual_marked_text,
294            );
295        }
296    }
297}
298
299impl<'a> Deref for EditorTestContext<'a> {
300    type Target = gpui::TestAppContext;
301
302    fn deref(&self) -> &Self::Target {
303        self.cx
304    }
305}
306
307impl<'a> DerefMut for EditorTestContext<'a> {
308    fn deref_mut(&mut self) -> &mut Self::Target {
309        &mut self.cx
310    }
311}
312
313pub struct EditorLspTestContext<'a> {
314    pub cx: EditorTestContext<'a>,
315    pub lsp: lsp::FakeLanguageServer,
316    pub workspace: ViewHandle<Workspace>,
317    pub buffer_lsp_url: lsp::Url,
318}
319
320impl<'a> EditorLspTestContext<'a> {
321    pub async fn new(
322        mut language: Language,
323        capabilities: lsp::ServerCapabilities,
324        cx: &'a mut gpui::TestAppContext,
325    ) -> EditorLspTestContext<'a> {
326        use json::json;
327
328        cx.update(|cx| {
329            crate::init(cx);
330            pane::init(cx);
331        });
332
333        let params = cx.update(AppState::test);
334
335        let file_name = format!(
336            "file.{}",
337            language
338                .path_suffixes()
339                .first()
340                .unwrap_or(&"txt".to_string())
341        );
342
343        let mut fake_servers = language
344            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
345                capabilities,
346                ..Default::default()
347            }))
348            .await;
349
350        let project = Project::test(params.fs.clone(), [], cx).await;
351        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
352
353        params
354            .fs
355            .as_fake()
356            .insert_tree("/root", json!({ "dir": { file_name: "" }}))
357            .await;
358
359        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
360        project
361            .update(cx, |project, cx| {
362                project.find_or_create_local_worktree("/root", true, cx)
363            })
364            .await
365            .unwrap();
366        cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
367            .await;
368
369        let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
370        let item = workspace
371            .update(cx, |workspace, cx| workspace.open_path(file, true, cx))
372            .await
373            .expect("Could not open test file");
374
375        let editor = cx.update(|cx| {
376            item.act_as::<Editor>(cx)
377                .expect("Opened test file wasn't an editor")
378        });
379        editor.update(cx, |_, cx| cx.focus_self());
380
381        let lsp = fake_servers.next().await.unwrap();
382
383        Self {
384            cx: EditorTestContext {
385                cx,
386                window_id,
387                editor,
388            },
389            lsp,
390            workspace,
391            buffer_lsp_url: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
392        }
393    }
394
395    pub async fn new_rust(
396        capabilities: lsp::ServerCapabilities,
397        cx: &'a mut gpui::TestAppContext,
398    ) -> EditorLspTestContext<'a> {
399        let language = Language::new(
400            LanguageConfig {
401                name: "Rust".into(),
402                path_suffixes: vec!["rs".to_string()],
403                ..Default::default()
404            },
405            Some(tree_sitter_rust::language()),
406        );
407
408        Self::new(language, capabilities, cx).await
409    }
410
411    // Constructs lsp range using a marked string with '[', ']' range delimiters
412    pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
413        let ranges = self.ranges(marked_text);
414        self.to_lsp_range(ranges[0].clone())
415    }
416
417    pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
418        let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
419        let start_point = range.start.to_point(&snapshot.buffer_snapshot);
420        let end_point = range.end.to_point(&snapshot.buffer_snapshot);
421
422        self.editor(|editor, cx| {
423            let buffer = editor.buffer().read(cx);
424            let start = point_to_lsp(
425                buffer
426                    .point_to_buffer_offset(start_point, cx)
427                    .unwrap()
428                    .1
429                    .to_point_utf16(&buffer.read(cx)),
430            );
431            let end = point_to_lsp(
432                buffer
433                    .point_to_buffer_offset(end_point, cx)
434                    .unwrap()
435                    .1
436                    .to_point_utf16(&buffer.read(cx)),
437            );
438
439            lsp::Range { start, end }
440        })
441    }
442
443    pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
444        let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
445        let point = offset.to_point(&snapshot.buffer_snapshot);
446
447        self.editor(|editor, cx| {
448            let buffer = editor.buffer().read(cx);
449            point_to_lsp(
450                buffer
451                    .point_to_buffer_offset(point, cx)
452                    .unwrap()
453                    .1
454                    .to_point_utf16(&buffer.read(cx)),
455            )
456        })
457    }
458
459    pub fn update_workspace<F, T>(&mut self, update: F) -> T
460    where
461        F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
462    {
463        self.workspace.update(self.cx.cx, update)
464    }
465
466    pub fn handle_request<T, F, Fut>(
467        &self,
468        mut handler: F,
469    ) -> futures::channel::mpsc::UnboundedReceiver<()>
470    where
471        T: 'static + request::Request,
472        T::Params: 'static + Send,
473        F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
474        Fut: 'static + Send + Future<Output = Result<T::Result>>,
475    {
476        let url = self.buffer_lsp_url.clone();
477        self.lsp.handle_request::<T, _, _>(move |params, cx| {
478            let url = url.clone();
479            handler(url, params, cx)
480        })
481    }
482
483    pub fn notify<T: notification::Notification>(&self, params: T::Params) {
484        self.lsp.notify::<T>(params);
485    }
486}
487
488impl<'a> Deref for EditorLspTestContext<'a> {
489    type Target = EditorTestContext<'a>;
490
491    fn deref(&self) -> &Self::Target {
492        &self.cx
493    }
494}
495
496impl<'a> DerefMut for EditorLspTestContext<'a> {
497    fn deref_mut(&mut self) -> &mut Self::Target {
498        &mut self.cx
499    }
500}