371 lines · 11.8 KB · Rust
RawDownload
use std::{    borrow::Cow,    ops::{Deref, DerefMut, Range},    path::Path,    sync::Arc,}; use anyhow::Result;use serde_json::json; use crate::{Editor, ToPoint};use collections::HashSet;use futures::Future;use gpui::{Context, Entity, Focusable as _, VisualTestContext, Window};use indoc::indoc;use language::{    point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageQueries,};use lsp::{notification, request};use multi_buffer::ToPointUtf16;use project::Project;use smol::stream::StreamExt;use workspace::{AppState, Workspace, WorkspaceHandle}; use super::editor_test_context::{AssertionContextManager, EditorTestContext}; pub struct EditorLspTestContext {    pub cx: EditorTestContext,    pub lsp: lsp::FakeLanguageServer,    pub workspace: Entity<Workspace>,    pub buffer_lsp_url: lsp::Url,} pub(crate) fn rust_lang() -> Arc<Language> {    let language = Language::new(        LanguageConfig {            name: "Rust".into(),            matcher: LanguageMatcher {                path_suffixes: vec!["rs".to_string()],                ..Default::default()            },            line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],            ..Default::default()        },        Some(tree_sitter_rust::LANGUAGE.into()),    )    .with_queries(LanguageQueries {        indents: Some(Cow::from(indoc! {r#"            [                ((where_clause) _ @end)                (field_expression)                (call_expression)                (assignment_expression)                (let_declaration)                (let_chain)                (await_expression)            ] @indent             (_ "[" "]" @end) @indent            (_ "<" ">" @end) @indent            (_ "{" "}" @end) @indent            (_ "(" ")" @end) @indent"#})),        brackets: Some(Cow::from(indoc! {r#"            ("(" @open ")" @close)            ("[" @open "]" @close)            ("{" @open "}" @close)            ("<" @open ">" @close)            ("\"" @open "\"" @close)            (closure_parameters "|" @open "|" @close)"#})),        text_objects: Some(Cow::from(indoc! {r#"            (function_item                body: (_                    "{"                    (_)* @function.inside                    "}" )) @function.around        "#})),        ..Default::default()    })    .expect("Could not parse queries");    Arc::new(language)}impl EditorLspTestContext {    pub async fn new(        language: Language,        capabilities: lsp::ServerCapabilities,        cx: &mut gpui::TestAppContext,    ) -> EditorLspTestContext {        let app_state = cx.update(AppState::test);         cx.update(|cx| {            assets::Assets.load_test_fonts(cx);            language::init(cx);            crate::init(cx);            workspace::init(app_state.clone(), cx);            Project::init_settings(cx);        });         let file_name = format!(            "file.{}",            language                .path_suffixes()                .first()                .expect("language must have a path suffix for EditorLspTestContext")        );         let project = Project::test(app_state.fs.clone(), [], cx).await;         let language_registry = project.read_with(cx, |project, _| project.languages().clone());        let mut fake_servers = language_registry.register_fake_lsp(            language.name(),            FakeLspAdapter {                capabilities,                ..Default::default()            },        );        language_registry.add(Arc::new(language));         let root = Self::root_path();         app_state            .fs            .as_fake()            .insert_tree(                root,                json!({                    ".git": {},                    "dir": {                        file_name.clone(): ""                    }                }),            )            .await;         let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));         let workspace = window.root(cx).unwrap();         let mut cx = VisualTestContext::from_window(*window.deref(), cx);        project            .update(&mut cx, |project, cx| {                project.find_or_create_worktree(root, true, cx)            })            .await            .unwrap();        cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))            .await;        let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());        let item = workspace            .update_in(&mut cx, |workspace, window, cx| {                workspace.open_path(file, None, true, window, cx)            })            .await            .expect("Could not open test file");        let editor = cx.update(|_, cx| {            item.act_as::<Editor>(cx)                .expect("Opened test file wasn't an editor")        });        editor.update_in(&mut cx, |editor, window, cx| {            window.focus(&editor.focus_handle(cx))        });         let lsp = fake_servers.next().await.unwrap();        Self {            cx: EditorTestContext {                cx,                window: window.into(),                editor,                assertion_cx: AssertionContextManager::new(),            },            lsp,            workspace,            buffer_lsp_url: lsp::Url::from_file_path(root.join("dir").join(file_name)).unwrap(),        }    }     pub async fn new_rust(        capabilities: lsp::ServerCapabilities,        cx: &mut gpui::TestAppContext,    ) -> EditorLspTestContext {        Self::new(Arc::into_inner(rust_lang()).unwrap(), capabilities, cx).await    }     pub async fn new_typescript(        capabilities: lsp::ServerCapabilities,        cx: &mut gpui::TestAppContext,    ) -> EditorLspTestContext {        let mut word_characters: HashSet<char> = Default::default();        word_characters.insert('$');        word_characters.insert('#');        let language = Language::new(            LanguageConfig {                name: "Typescript".into(),                matcher: LanguageMatcher {                    path_suffixes: vec!["ts".to_string()],                    ..Default::default()                },                brackets: language::BracketPairConfig {                    pairs: vec![language::BracketPair {                        start: "{".to_string(),                        end: "}".to_string(),                        close: true,                        surround: true,                        newline: true,                    }],                    disabled_scopes_by_bracket_ix: Default::default(),                },                word_characters,                ..Default::default()            },            Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),        )        .with_queries(LanguageQueries {            brackets: Some(Cow::from(indoc! {r#"                ("(" @open ")" @close)                ("[" @open "]" @close)                ("{" @open "}" @close)                ("<" @open ">" @close)                ("'" @open "'" @close)                ("`" @open "`" @close)                ("\"" @open "\"" @close)"#})),            indents: Some(Cow::from(indoc! {r#"                [                    (call_expression)                    (assignment_expression)                    (member_expression)                    (lexical_declaration)                    (variable_declaration)                    (assignment_expression)                    (if_statement)                    (for_statement)                ] @indent                 (_ "[" "]" @end) @indent                (_ "<" ">" @end) @indent                (_ "{" "}" @end) @indent                (_ "(" ")" @end) @indent                "#})),            ..Default::default()        })        .expect("Could not parse queries");         Self::new(language, capabilities, cx).await    }     pub async fn new_html(cx: &mut gpui::TestAppContext) -> Self {        let language = Language::new(            LanguageConfig {                name: "HTML".into(),                matcher: LanguageMatcher {                    path_suffixes: vec!["html".into()],                    ..Default::default()                },                block_comment: Some(("<!-- ".into(), " -->".into())),                word_characters: ['-'].into_iter().collect(),                ..Default::default()            },            Some(tree_sitter_html::LANGUAGE.into()),        )        .with_queries(LanguageQueries {            brackets: Some(Cow::from(indoc! {r#"                ("<" @open "/>" @close)                ("</" @open ">" @close)                ("<" @open ">" @close)                ("\"" @open "\"" @close)"#})),            ..Default::default()        })        .expect("Could not parse queries");        Self::new(language, Default::default(), cx).await    }     /// Constructs lsp range using a marked string with '[', ']' range delimiters    #[track_caller]    pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {        let ranges = self.ranges(marked_text);        self.to_lsp_range(ranges[0].clone())    }     pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {        let snapshot = self.update_editor(|editor, window, cx| editor.snapshot(window, cx));        let start_point = range.start.to_point(&snapshot.buffer_snapshot);        let end_point = range.end.to_point(&snapshot.buffer_snapshot);         self.editor(|editor, _, cx| {            let buffer = editor.buffer().read(cx);            let start = point_to_lsp(                buffer                    .point_to_buffer_offset(start_point, cx)                    .unwrap()                    .1                    .to_point_utf16(&buffer.read(cx)),            );            let end = point_to_lsp(                buffer                    .point_to_buffer_offset(end_point, cx)                    .unwrap()                    .1                    .to_point_utf16(&buffer.read(cx)),            );             lsp::Range { start, end }        })    }     pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {        let snapshot = self.update_editor(|editor, window, cx| editor.snapshot(window, cx));        let point = offset.to_point(&snapshot.buffer_snapshot);         self.editor(|editor, _, cx| {            let buffer = editor.buffer().read(cx);            point_to_lsp(                buffer                    .point_to_buffer_offset(point, cx)                    .unwrap()                    .1                    .to_point_utf16(&buffer.read(cx)),            )        })    }     pub fn update_workspace<F, T>(&mut self, update: F) -> T    where        F: FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) -> T,    {        self.workspace.update_in(&mut self.cx.cx, update)    }     pub fn handle_request<T, F, Fut>(        &self,        mut handler: F,    ) -> futures::channel::mpsc::UnboundedReceiver<()>    where        T: 'static + request::Request,        T::Params: 'static + Send,        F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncApp) -> Fut,        Fut: 'static + Send + Future<Output = Result<T::Result>>,    {        let url = self.buffer_lsp_url.clone();        self.lsp.handle_request::<T, _, _>(move |params, cx| {            let url = url.clone();            handler(url, params, cx)        })    }     pub fn notify<T: notification::Notification>(&self, params: T::Params) {        self.lsp.notify::<T>(&params);    }     #[cfg(target_os = "windows")]    fn root_path() -> &'static Path {        Path::new("C:\\root")    }     #[cfg(not(target_os = "windows"))]    fn root_path() -> &'static Path {        Path::new("/root")    }} impl Deref for EditorLspTestContext {    type Target = EditorTestContext;     fn deref(&self) -> &Self::Target {        &self.cx    }} impl DerefMut for EditorLspTestContext {    fn deref_mut(&mut self) -> &mut Self::Target {        &mut self.cx    }}