5494 lines · 177.0 KB · Rust
RawDownload
use crate::{Event, *};use fs::FakeFs;use futures::{future, StreamExt};use gpui::{AppContext, SemanticVersion, UpdateGlobal};use http_client::Url;use language::{    language_settings::{        language_settings, AllLanguageSettings, LanguageSettingsContent, SoftWrap,    },    tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticSet, FakeLspAdapter,    LanguageConfig, LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint,};use lsp::{DiagnosticSeverity, NumberOrString};use parking_lot::Mutex;use pretty_assertions::assert_eq;use serde_json::json;#[cfg(not(windows))]use std::os; use std::{mem, num::NonZeroU32, ops::Range, task::Poll};use task::{ResolvedTask, TaskContext};use unindent::Unindent as _;use util::{assert_set_eq, paths::PathMatcher, test::temp_tree, TryFutureExt as _}; #[gpui::test]async fn test_block_via_channel(cx: &mut gpui::TestAppContext) {    cx.executor().allow_parking();     let (tx, mut rx) = futures::channel::mpsc::unbounded();    let _thread = std::thread::spawn(move || {        std::fs::metadata("/tmp").unwrap();        std::thread::sleep(Duration::from_millis(1000));        tx.unbounded_send(1).unwrap();    });    rx.next().await.unwrap();} #[gpui::test]async fn test_block_via_smol(cx: &mut gpui::TestAppContext) {    cx.executor().allow_parking();     let io_task = smol::unblock(move || {        println!("sleeping on thread {:?}", std::thread::current().id());        std::thread::sleep(Duration::from_millis(10));        1    });     let task = cx.foreground_executor().spawn(async move {        io_task.await;    });     task.await;} #[cfg(not(windows))]#[gpui::test]async fn test_symlinks(cx: &mut gpui::TestAppContext) {    init_test(cx);    cx.executor().allow_parking();     let dir = temp_tree(json!({        "root": {            "apple": "",            "banana": {                "carrot": {                    "date": "",                    "endive": "",                }            },            "fennel": {                "grape": "",            }        }    }));     let root_link_path = dir.path().join("root_link");    os::unix::fs::symlink(dir.path().join("root"), &root_link_path).unwrap();    os::unix::fs::symlink(        dir.path().join("root/fennel"),        dir.path().join("root/finnochio"),    )    .unwrap();     let project = Project::test(Arc::new(RealFs::default()), [root_link_path.as_ref()], cx).await;     project.update(cx, |project, cx| {        let tree = project.worktrees(cx).next().unwrap().read(cx);        assert_eq!(tree.file_count(), 5);        assert_eq!(            tree.inode_for_path("fennel/grape"),            tree.inode_for_path("finnochio/grape")        );    });} #[gpui::test]async fn test_editorconfig_support(cx: &mut gpui::TestAppContext) {    init_test(cx);     let dir = temp_tree(json!({        ".editorconfig": r#"        root = true        [*.rs]            indent_style = tab            indent_size = 3            end_of_line = lf            insert_final_newline = true            trim_trailing_whitespace = true            max_line_length = 80        [*.js]            tab_width = 10        "#,        ".zed": {            "settings.json": r#"{                "tab_size": 8,                "hard_tabs": false,                "ensure_final_newline_on_save": false,                "remove_trailing_whitespace_on_save": false,                "preferred_line_length": 64,                "soft_wrap": "editor_width"            }"#,        },        "a.rs": "fn a() {\n    A\n}",        "b": {            ".editorconfig": r#"            [*.rs]                indent_size = 2                max_line_length = off            "#,            "b.rs": "fn b() {\n    B\n}",        },        "c.js": "def c\n  C\nend",        "README.json": "tabs are better\n",    }));     let path = dir.path();    let fs = FakeFs::new(cx.executor());    fs.insert_tree_from_real_fs(path, path).await;    let project = Project::test(fs, [path], cx).await;     let language_registry = project.read_with(cx, |project, _| project.languages().clone());    language_registry.add(js_lang());    language_registry.add(json_lang());    language_registry.add(rust_lang());     let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());     cx.executor().run_until_parked();     cx.update(|cx| {        let tree = worktree.read(cx);        let settings_for = |path: &str| {            let file_entry = tree.entry_for_path(path).unwrap().clone();            let file = File::for_entry(file_entry, worktree.clone());            let file_language = project                .read(cx)                .languages()                .language_for_file_path(file.path.as_ref());            let file_language = cx                .background_executor()                .block(file_language)                .expect("Failed to get file language");            let file = file as _;            language_settings(Some(file_language.name()), Some(&file), cx).into_owned()        };         let settings_a = settings_for("a.rs");        let settings_b = settings_for("b/b.rs");        let settings_c = settings_for("c.js");        let settings_readme = settings_for("README.json");         // .editorconfig overrides .zed/settings        assert_eq!(Some(settings_a.tab_size), NonZeroU32::new(3));        assert_eq!(settings_a.hard_tabs, true);        assert_eq!(settings_a.ensure_final_newline_on_save, true);        assert_eq!(settings_a.remove_trailing_whitespace_on_save, true);        assert_eq!(settings_a.preferred_line_length, 80);         // "max_line_length" also sets "soft_wrap"        assert_eq!(settings_a.soft_wrap, SoftWrap::PreferredLineLength);         // .editorconfig in b/ overrides .editorconfig in root        assert_eq!(Some(settings_b.tab_size), NonZeroU32::new(2));         // "indent_size" is not set, so "tab_width" is used        assert_eq!(Some(settings_c.tab_size), NonZeroU32::new(10));         // When max_line_length is "off", default to .zed/settings.json        assert_eq!(settings_b.preferred_line_length, 64);        assert_eq!(settings_b.soft_wrap, SoftWrap::EditorWidth);         // README.md should not be affected by .editorconfig's globe "*.rs"        assert_eq!(Some(settings_readme.tab_size), NonZeroU32::new(8));    });} #[gpui::test]async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) {    init_test(cx);    TaskStore::init(None);     let fs = FakeFs::new(cx.executor());    fs.insert_tree(        "/the-root",        json!({            ".zed": {                "settings.json": r#"{ "tab_size": 8 }"#,                "tasks.json": r#"[{                    "label": "cargo check all",                    "command": "cargo",                    "args": ["check", "--all"]                },]"#,            },            "a": {                "a.rs": "fn a() {\n    A\n}"            },            "b": {                ".zed": {                    "settings.json": r#"{ "tab_size": 2 }"#,                    "tasks.json": r#"[{                        "label": "cargo check",                        "command": "cargo",                        "args": ["check"]                    },]"#,                },                "b.rs": "fn b() {\n  B\n}"            }        }),    )    .await;     let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());    let task_context = TaskContext::default();     cx.executor().run_until_parked();    let worktree_id = cx.update(|cx| {        project.update(cx, |project, cx| {            project.worktrees(cx).next().unwrap().read(cx).id()        })    });    let topmost_local_task_source_kind = TaskSourceKind::Worktree {        id: worktree_id,        directory_in_worktree: PathBuf::from(".zed"),        id_base: "local worktree tasks from directory \".zed\"".into(),    };     let all_tasks = cx        .update(|cx| {            let tree = worktree.read(cx);             let file_a = File::for_entry(                tree.entry_for_path("a/a.rs").unwrap().clone(),                worktree.clone(),            ) as _;            let settings_a = language_settings(None, Some(&file_a), cx);            let file_b = File::for_entry(                tree.entry_for_path("b/b.rs").unwrap().clone(),                worktree.clone(),            ) as _;            let settings_b = language_settings(None, Some(&file_b), cx);             assert_eq!(settings_a.tab_size.get(), 8);            assert_eq!(settings_b.tab_size.get(), 2);             get_all_tasks(&project, Some(worktree_id), &task_context, cx)        })        .into_iter()        .map(|(source_kind, task)| {            let resolved = task.resolved.unwrap();            (                source_kind,                task.resolved_label,                resolved.args,                resolved.env,            )        })        .collect::<Vec<_>>();    assert_eq!(        all_tasks,        vec![            (                TaskSourceKind::Worktree {                    id: worktree_id,                    directory_in_worktree: PathBuf::from("b/.zed"),                    id_base: "local worktree tasks from directory \"b/.zed\"".into(),                },                "cargo check".to_string(),                vec!["check".to_string()],                HashMap::default(),            ),            (                topmost_local_task_source_kind.clone(),                "cargo check all".to_string(),                vec!["check".to_string(), "--all".to_string()],                HashMap::default(),            ),        ]    );     let (_, resolved_task) = cx        .update(|cx| get_all_tasks(&project, Some(worktree_id), &task_context, cx))        .into_iter()        .find(|(source_kind, _)| source_kind == &topmost_local_task_source_kind)        .expect("should have one global task");    project.update(cx, |project, cx| {        let task_inventory = project            .task_store            .read(cx)            .task_inventory()            .cloned()            .unwrap();        task_inventory.update(cx, |inventory, _| {            inventory.task_scheduled(topmost_local_task_source_kind.clone(), resolved_task);            inventory                .update_file_based_tasks(                    None,                    Some(                        &json!([{                            "label": "cargo check unstable",                            "command": "cargo",                            "args": [                                "check",                                "--all",                                "--all-targets"                            ],                            "env": {                                "RUSTFLAGS": "-Zunstable-options"                            }                        }])                        .to_string(),                    ),                )                .unwrap();        });    });    cx.run_until_parked();     let all_tasks = cx        .update(|cx| get_all_tasks(&project, Some(worktree_id), &task_context, cx))        .into_iter()        .map(|(source_kind, task)| {            let resolved = task.resolved.unwrap();            (                source_kind,                task.resolved_label,                resolved.args,                resolved.env,            )        })        .collect::<Vec<_>>();    assert_eq!(        all_tasks,        vec![            (                topmost_local_task_source_kind.clone(),                "cargo check all".to_string(),                vec!["check".to_string(), "--all".to_string()],                HashMap::default(),            ),            (                TaskSourceKind::Worktree {                    id: worktree_id,                    directory_in_worktree: PathBuf::from("b/.zed"),                    id_base: "local worktree tasks from directory \"b/.zed\"".into(),                },                "cargo check".to_string(),                vec!["check".to_string()],                HashMap::default(),            ),            (                TaskSourceKind::AbsPath {                    abs_path: paths::tasks_file().clone(),                    id_base: "global tasks.json".into(),                },                "cargo check unstable".to_string(),                vec![                    "check".to_string(),                    "--all".to_string(),                    "--all-targets".to_string(),                ],                HashMap::from_iter(Some((                    "RUSTFLAGS".to_string(),                    "-Zunstable-options".to_string()                ))),            ),        ]    );} #[gpui::test]async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {    init_test(cx);     let fs = FakeFs::new(cx.executor());    fs.insert_tree(        "/the-root",        json!({            "test.rs": "const A: i32 = 1;",            "test2.rs": "",            "Cargo.toml": "a = 1",            "package.json": "{\"a\": 1}",        }),    )    .await;     let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;    let language_registry = project.read_with(cx, |project, _| project.languages().clone());     let mut fake_rust_servers = language_registry.register_fake_lsp(        "Rust",        FakeLspAdapter {            name: "the-rust-language-server",            capabilities: lsp::ServerCapabilities {                completion_provider: Some(lsp::CompletionOptions {                    trigger_characters: Some(vec![".".to_string(), "::".to_string()]),                    ..Default::default()                }),                text_document_sync: Some(lsp::TextDocumentSyncCapability::Options(                    lsp::TextDocumentSyncOptions {                        save: Some(lsp::TextDocumentSyncSaveOptions::Supported(true)),                        ..Default::default()                    },                )),                ..Default::default()            },            ..Default::default()        },    );    let mut fake_json_servers = language_registry.register_fake_lsp(        "JSON",        FakeLspAdapter {            name: "the-json-language-server",            capabilities: lsp::ServerCapabilities {                completion_provider: Some(lsp::CompletionOptions {                    trigger_characters: Some(vec![":".to_string()]),                    ..Default::default()                }),                text_document_sync: Some(lsp::TextDocumentSyncCapability::Options(                    lsp::TextDocumentSyncOptions {                        save: Some(lsp::TextDocumentSyncSaveOptions::Supported(true)),                        ..Default::default()                    },                )),                ..Default::default()            },            ..Default::default()        },    );     // Open a buffer without an associated language server.    let toml_buffer = project        .update(cx, |project, cx| {            project.open_local_buffer("/the-root/Cargo.toml", cx)        })        .await        .unwrap();     // Open a buffer with an associated language server before the language for it has been loaded.    let rust_buffer = project        .update(cx, |project, cx| {            project.open_local_buffer("/the-root/test.rs", cx)        })        .await        .unwrap();    rust_buffer.update(cx, |buffer, _| {        assert_eq!(buffer.language().map(|l| l.name()), None);    });     // Now we add the languages to the project, and ensure they get assigned to all    // the relevant open buffers.    language_registry.add(json_lang());    language_registry.add(rust_lang());    cx.executor().run_until_parked();    rust_buffer.update(cx, |buffer, _| {        assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into()));    });     // A server is started up, and it is notified about Rust files.    let mut fake_rust_server = fake_rust_servers.next().await.unwrap();    assert_eq!(        fake_rust_server            .receive_notification::<lsp::notification::DidOpenTextDocument>()            .await            .text_document,        lsp::TextDocumentItem {            uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(),            version: 0,            text: "const A: i32 = 1;".to_string(),            language_id: "rust".to_string(),        }    );     // The buffer is configured based on the language server's capabilities.    rust_buffer.update(cx, |buffer, _| {        assert_eq!(            buffer.completion_triggers(),            &[".".to_string(), "::".to_string()]        );    });

Lines 1–500 of 5494.