6219 lines · 199.9 KB · Rust
RawDownload
use crate::{task_inventory::TaskContexts, Event, *};use buffer_diff::{assert_hunks, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};use fs::FakeFs;use futures::{future, StreamExt};use gpui::{App, SemanticVersion, UpdateGlobal};use http_client::Url;use language::{    language_settings::{language_settings, AllLanguageSettings, LanguageSettingsContent},    tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticEntry, DiagnosticSet,    DiskState, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageName, LineEnding,    OffsetRangeExt, Point, ToPoint,};use lsp::{    notification::DidRenameFiles, DiagnosticSeverity, DocumentChanges, FileOperationFilter,    NumberOrString, TextDocumentEdit, WillRenameFiles,};use parking_lot::Mutex;use pretty_assertions::{assert_eq, assert_matches};use serde_json::json;#[cfg(not(windows))]use std::os;use std::{str::FromStr, sync::OnceLock}; use std::{mem, num::NonZeroU32, ops::Range, task::Poll};use task::{ResolvedTask, TaskContext};use unindent::Unindent as _;use util::{    assert_set_eq, path,    paths::PathMatcher,    separator,    test::{marked_text_offsets, TempTree},    uri, 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 || {        #[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::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 = 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_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.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(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(                    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, &task_contexts, 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(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#"[{                    "label": "test worktree root",                    "command": "echo $ZED_WORKTREE_ROOT"                }]"#,            },            "a": {                "a.rs": "fn a() {\n    A\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 active_non_worktree_item_tasks = cx.update(|cx| {        get_all_tasks(            &project,            &TaskContexts {                active_item_context: Some((Some(worktree_id), None, TaskContext::default())),                active_worktree_context: None,                other_worktree_contexts: Vec::new(),            },            cx,        )    });    assert!(        active_non_worktree_item_tasks.is_empty(),        "A task can not be resolved with context with no ZED_WORKTREE_ROOT data"    );     let active_worktree_tasks = cx.update(|cx| {        get_all_tasks(            &project,            &TaskContexts {                active_item_context: Some((Some(worktree_id), None, TaskContext::default())),                active_worktree_context: Some((worktree_id, {                    let mut worktree_context = TaskContext::default();                    worktree_context                        .task_variables                        .insert(task::VariableName::WorktreeRoot, "/dir".to_string());                    worktree_context                })),                other_worktree_contexts: Vec::new(),            },            cx,        )    });    assert_eq!(        active_worktree_tasks            .into_iter()            .map(|(source_kind, task)| {                let resolved = task.resolved.unwrap();                (source_kind, resolved.command)            })            .collect::<Vec<_>>(),        vec![(            TaskSourceKind::Worktree {                id: worktree_id,                directory_in_worktree: PathBuf::from(separator!(".zed")),                id_base: if cfg!(windows) {                    "local worktree tasks from directory \".zed\"".into()                } else {                    "local worktree tasks from directory \".zed\"".into()                },            },            "echo /dir".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(        path!("/dir"),        json!({            "test.rs": "const A: i32 = 1;",

Lines 1–500 of 6219.