9035 lines · 285.8 KB · Rust
RawDownload
#![allow(clippy::format_collect)] use crate::{    Event, git_store::StatusEntry, task_inventory::TaskContexts, task_store::TaskSettingsLocation,    *,};use buffer_diff::{    BufferDiffEvent, CALCULATE_DIFF_TASK, DiffHunkSecondaryStatus, DiffHunkStatus,    DiffHunkStatusKind, assert_hunks,};use fs::FakeFs;use futures::{StreamExt, future};use git::{    GitHostingProviderRegistry,    repository::RepoPath,    status::{StatusCode, TrackedStatus},};use git2::RepositoryInitOptions;use gpui::{App, BackgroundExecutor, SemanticVersion, UpdateGlobal};use http_client::Url;use language::{    Diagnostic, DiagnosticEntry, DiagnosticSet, DiskState, FakeLspAdapter, LanguageConfig,    LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint,    language_settings::{AllLanguageSettings, LanguageSettingsContent, language_settings},    tree_sitter_rust, tree_sitter_typescript,};use lsp::{    DiagnosticSeverity, DocumentChanges, FileOperationFilter, NumberOrString, TextDocumentEdit,    WillRenameFiles, notification::DidRenameFiles,};use parking_lot::Mutex;use paths::{config_dir, tasks_file};use postage::stream::Stream as _;use pretty_assertions::{assert_eq, assert_matches};use rand::{Rng as _, rngs::StdRng};use serde_json::json;#[cfg(not(windows))]use std::os;use std::{env, mem, num::NonZeroU32, ops::Range, str::FromStr, sync::OnceLock, task::Poll};use task::{ResolvedTask, TaskContext};use unindent::Unindent as _;use util::{    TryFutureExt as _, assert_set_eq, maybe, path,    paths::PathMatcher,    separator,    test::{TempTree, marked_text_offsets},    uri,};use worktree::WorktreeModelHandle 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 || {        #[cfg(not(target_os = "windows"))]        std::fs::metadata("/tmp").unwrap();        #[cfg(target_os = "windows")]        std::fs::metadata("C:/Windows").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 = TempTree::new(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::new(None, cx.executor())),        [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 = TempTree::new(json!({        ".editorconfig": r#"        root = true        [*.rs]            indent_style = tab            indent_size = 3            end_of_line = lf            insert_final_newline = true            trim_trailing_whitespace = true        [*.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,                "soft_wrap": "editor_width"            }"#,        },        "a.rs": "fn a() {\n    A\n}",        "b": {            ".editorconfig": r#"            [*.rs]                indent_size = 2            "#,            "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);         // .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));         // 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_git_provider_project_setting(cx: &mut gpui::TestAppContext) {    init_test(cx);    cx.update(|cx| {        GitHostingProviderRegistry::default_global(cx);        git_hosting_providers::init(cx);    });     let fs = FakeFs::new(cx.executor());    let str_path = path!("/dir");    let path = Path::new(str_path);     fs.insert_tree(        path!("/dir"),        json!({            ".zed": {                "settings.json": r#"{                    "git_hosting_providers": [                        {                            "provider": "gitlab",                            "base_url": "https://google.com",                            "name": "foo"                        }                    ]                }"#            },        }),    )    .await;     let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;    let (_worktree, _) =        project.read_with(cx, |project, cx| project.find_worktree(path, cx).unwrap());    cx.executor().run_until_parked();     cx.update(|cx| {        let provider = GitHostingProviderRegistry::global(cx);        assert!(            provider                .list_hosting_providers()                .into_iter()                .any(|provider| provider.name() == "foo")        );    });     fs.atomic_write(        Path::new(path!("/dir/.zed/settings.json")).to_owned(),        "{}".into(),    )    .await    .unwrap();     cx.run_until_parked();     cx.update(|cx| {        let provider = GitHostingProviderRegistry::global(cx);        assert!(            !provider                .list_hosting_providers()                .into_iter()                .any(|provider| provider.name() == "foo")        );    });} #[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(        path!("/dir"),        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(), [path!("/dir").as_ref()], cx).await;    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());     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 mut task_contexts = TaskContexts::default();    task_contexts.active_worktree_context = Some((worktree_id, TaskContext::default()));     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, &task_contexts, cx)        })        .into_iter()        .map(|(source_kind, task)| {            let resolved = task.resolved;            (                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(separator!("b/.zed")),                    id_base: if cfg!(windows) {                        "local worktree tasks from directory \"b\\\\.zed\"".into()                    } else {                        "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, &task_contexts, 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(                    TaskSettingsLocation::Global(tasks_file()),                    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, &task_contexts, cx))        .into_iter()        .map(|(source_kind, task)| {            let resolved = task.resolved;            (                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(separator!("b/.zed")),                    id_base: if cfg!(windows) {                        "local worktree tasks from directory \"b\\\\.zed\"".into()                    } else {                        "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_fallback_to_single_worktree_tasks(cx: &mut gpui::TestAppContext) {    init_test(cx);    TaskStore::init(None);     let fs = FakeFs::new(cx.executor());    fs.insert_tree(        path!("/dir"),        json!({            ".zed": {                "tasks.json": r#"[{

Lines 1–500 of 9035.