4643 lines · 169.4 KB · Rust
RawDownload
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};use call::ActiveCall;use editor::{    DocumentColorsRenderMode, Editor, FETCH_COLORS_DEBOUNCE_TIMEOUT, MultiBufferOffset, RowInfo,    SelectionEffects,    actions::{        ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, CopyFileLocation,        CopyFileName, CopyFileNameWithoutExtension, ExpandMacroRecursively, MoveToEnd, Redo,        Rename, SelectAll, ToggleCodeActions, Undo,    },    test::{        editor_test_context::{AssertionContextManager, EditorTestContext},        expand_macro_recursively,    },};use fs::Fs;use futures::{SinkExt, StreamExt, channel::mpsc, lock::Mutex};use git::repository::repo_path;use gpui::{    App, Rgba, SharedString, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext,};use indoc::indoc;use language::{FakeLspAdapter, rust_lang};use lsp::LSP_REQUEST_TIMEOUT;use pretty_assertions::assert_eq;use project::{    ProgressToken, ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT,    lsp_store::lsp_ext_command::{ExpandedMacro, LspExtExpandMacro},};use recent_projects::disconnected_overlay::DisconnectedOverlay;use rpc::RECEIVE_TIMEOUT;use serde_json::json;use settings::{InlayHintSettingsContent, InlineBlameSettings, SettingsStore};use std::{    collections::BTreeSet,    ops::{Deref as _, Range},    path::{Path, PathBuf},    sync::{        Arc,        atomic::{self, AtomicBool, AtomicUsize},    },    time::Duration,};use text::Point;use util::{path, rel_path::rel_path, uri};use workspace::{CloseIntent, Workspace}; #[gpui::test(iterations = 10)]async fn test_host_disconnect(    cx_a: &mut TestAppContext,    cx_b: &mut TestAppContext,    cx_c: &mut TestAppContext,) {    let mut server = TestServer::start(cx_a.executor()).await;    let client_a = server.create_client(cx_a, "user_a").await;    let client_b = server.create_client(cx_b, "user_b").await;    let client_c = server.create_client(cx_c, "user_c").await;    server        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])        .await;     cx_b.update(editor::init);    cx_b.update(recent_projects::init);     client_a        .fs()        .insert_tree(            "/a",            json!({                "a.txt": "a-contents",                "b.txt": "b-contents",            }),        )        .await;     let active_call_a = cx_a.read(ActiveCall::global);    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;     let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());    let project_id = active_call_a        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))        .await        .unwrap();     let project_b = client_b.join_remote_project(project_id, cx_b).await;    cx_a.background_executor.run_until_parked();     assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));     let workspace_b = cx_b.add_window(|window, cx| {        Workspace::new(            None,            project_b.clone(),            client_b.app_state.clone(),            window,            cx,        )    });    let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);    let workspace_b_view = workspace_b.root(cx_b).unwrap();     let editor_b = workspace_b        .update(cx_b, |workspace, window, cx| {            workspace.open_path((worktree_id, rel_path("b.txt")), None, true, window, cx)        })        .unwrap()        .await        .unwrap()        .downcast::<Editor>()        .unwrap();     //TODO: focus    assert!(cx_b.update_window_entity(&editor_b, |editor, window, _| editor.is_focused(window)));    editor_b.update_in(cx_b, |editor, window, cx| editor.insert("X", window, cx));     cx_b.update(|_, cx| {        assert!(workspace_b_view.read(cx).is_edited());    });     // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.    server.forbid_connections();    server.disconnect_client(client_a.peer_id().unwrap());    cx_a.background_executor        .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);     project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());     project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));     project_b.read_with(cx_b, |project, cx| project.is_read_only(cx));     assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));     // Ensure client B's edited state is reset and that the whole window is blurred.    workspace_b        .update(cx_b, |workspace, _, cx| {            assert!(workspace.active_modal::<DisconnectedOverlay>(cx).is_some());            assert!(!workspace.is_edited());        })        .unwrap();     // Ensure client B is not prompted to save edits when closing window after disconnecting.    let can_close = workspace_b        .update(cx_b, |workspace, window, cx| {            workspace.prepare_to_close(CloseIntent::Quit, window, cx)        })        .unwrap()        .await        .unwrap();    assert!(can_close);     // Allow client A to reconnect to the server.    server.allow_connections();    cx_a.background_executor.advance_clock(RECEIVE_TIMEOUT);     // Client B calls client A again after they reconnected.    let active_call_b = cx_b.read(ActiveCall::global);    active_call_b        .update(cx_b, |call, cx| {            call.invite(client_a.user_id().unwrap(), None, cx)        })        .await        .unwrap();    cx_a.background_executor.run_until_parked();    active_call_a        .update(cx_a, |call, cx| call.accept_incoming(cx))        .await        .unwrap();     active_call_a        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))        .await        .unwrap();     // Drop client A's connection again. We should still unshare it successfully.    server.forbid_connections();    server.disconnect_client(client_a.peer_id().unwrap());    cx_a.background_executor        .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);     project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));} #[gpui::test]async fn test_newline_above_or_below_does_not_move_guest_cursor(    cx_a: &mut TestAppContext,    cx_b: &mut TestAppContext,) {    let mut server = TestServer::start(cx_a.executor()).await;    let client_a = server.create_client(cx_a, "user_a").await;    let client_b = server.create_client(cx_b, "user_b").await;    let executor = cx_a.executor();    server        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])        .await;    let active_call_a = cx_a.read(ActiveCall::global);     client_a        .fs()        .insert_tree(path!("/dir"), json!({ "a.txt": "Some text\n" }))        .await;    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;    let project_id = active_call_a        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))        .await        .unwrap();     let project_b = client_b.join_remote_project(project_id, cx_b).await;     // Open a buffer as client A    let buffer_a = project_a        .update(cx_a, |p, cx| {            p.open_buffer((worktree_id, rel_path("a.txt")), cx)        })        .await        .unwrap();    let cx_a = cx_a.add_empty_window();    let editor_a = cx_a        .new_window_entity(|window, cx| Editor::for_buffer(buffer_a, Some(project_a), window, cx));     let mut editor_cx_a = EditorTestContext {        cx: cx_a.clone(),        window: cx_a.window_handle(),        editor: editor_a,        assertion_cx: AssertionContextManager::new(),    };     let cx_b = cx_b.add_empty_window();    // Open a buffer as client B    let buffer_b = project_b        .update(cx_b, |p, cx| {            p.open_buffer((worktree_id, rel_path("a.txt")), cx)        })        .await        .unwrap();    let editor_b = cx_b        .new_window_entity(|window, cx| Editor::for_buffer(buffer_b, Some(project_b), window, cx));     let mut editor_cx_b = EditorTestContext {        cx: cx_b.clone(),        window: cx_b.window_handle(),        editor: editor_b,        assertion_cx: AssertionContextManager::new(),    };     // Test newline above    editor_cx_a.set_selections_state(indoc! {"        Some textˇ    "});    editor_cx_b.set_selections_state(indoc! {"        Some textˇ    "});    editor_cx_a.update_editor(|editor, window, cx| {        editor.newline_above(&editor::actions::NewlineAbove, window, cx)    });    executor.run_until_parked();    editor_cx_a.assert_editor_state(indoc! {"        ˇ        Some text    "});    editor_cx_b.assert_editor_state(indoc! {"         Some textˇ    "});     // Test newline below    editor_cx_a.set_selections_state(indoc! {"         Some textˇ    "});    editor_cx_b.set_selections_state(indoc! {"         Some textˇ    "});    editor_cx_a.update_editor(|editor, window, cx| {        editor.newline_below(&editor::actions::NewlineBelow, window, cx)    });    executor.run_until_parked();    editor_cx_a.assert_editor_state(indoc! {"         Some text        ˇ    "});    editor_cx_b.assert_editor_state(indoc! {"         Some textˇ     "});} #[gpui::test]async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {    let mut server = TestServer::start(cx_a.executor()).await;    let client_a = server.create_client(cx_a, "user_a").await;    let client_b = server.create_client(cx_b, "user_b").await;    server        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])        .await;    let active_call_a = cx_a.read(ActiveCall::global);     let capabilities = lsp::ServerCapabilities {        completion_provider: Some(lsp::CompletionOptions {            trigger_characters: Some(vec![".".to_string()]),            resolve_provider: Some(true),            ..lsp::CompletionOptions::default()        }),        ..lsp::ServerCapabilities::default()    };    client_a.language_registry().add(rust_lang());    let mut fake_language_servers = [        client_a.language_registry().register_fake_lsp(            "Rust",            FakeLspAdapter {                capabilities: capabilities.clone(),                initializer: Some(Box::new(|fake_server| {                    fake_server.set_request_handler::<lsp::request::Completion, _, _>(                        |params, _| async move {                            assert_eq!(                                params.text_document_position.text_document.uri,                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),                            );                            assert_eq!(                                params.text_document_position.position,                                lsp::Position::new(0, 14),                            );                             Ok(Some(lsp::CompletionResponse::Array(vec![                                lsp::CompletionItem {                                    label: "first_method(…)".into(),                                    detail: Some("fn(&mut self, B) -> C".into()),                                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {                                        new_text: "first_method($1)".to_string(),                                        range: lsp::Range::new(                                            lsp::Position::new(0, 14),                                            lsp::Position::new(0, 14),                                        ),                                    })),                                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),                                    ..Default::default()                                },                                lsp::CompletionItem {                                    label: "second_method(…)".into(),                                    detail: Some("fn(&mut self, C) -> D<E>".into()),                                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {                                        new_text: "second_method()".to_string(),                                        range: lsp::Range::new(                                            lsp::Position::new(0, 14),                                            lsp::Position::new(0, 14),                                        ),                                    })),                                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),                                    ..Default::default()                                },                            ])))                        },                    );                })),                ..FakeLspAdapter::default()            },        ),        client_a.language_registry().register_fake_lsp(            "Rust",            FakeLspAdapter {                name: "fake-analyzer",                capabilities: capabilities.clone(),                initializer: Some(Box::new(|fake_server| {                    fake_server.set_request_handler::<lsp::request::Completion, _, _>(                        |_, _| async move { Ok(None) },                    );                })),                ..FakeLspAdapter::default()            },        ),    ];    client_b.language_registry().add(rust_lang());    client_b.language_registry().register_fake_lsp_adapter(        "Rust",        FakeLspAdapter {            capabilities: capabilities.clone(),            ..FakeLspAdapter::default()        },    );    client_b.language_registry().register_fake_lsp_adapter(        "Rust",        FakeLspAdapter {            name: "fake-analyzer",            capabilities,            ..FakeLspAdapter::default()        },    );     client_a        .fs()        .insert_tree(            path!("/a"),            json!({                "main.rs": "fn main() { a }",                "other.rs": "",            }),        )        .await;    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;    let project_id = active_call_a        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))        .await        .unwrap();    let project_b = client_b.join_remote_project(project_id, cx_b).await;     // Open a file in an editor as the guest.    let buffer_b = project_b        .update(cx_b, |p, cx| {            p.open_buffer((worktree_id, rel_path("main.rs")), cx)        })        .await        .unwrap();    let cx_b = cx_b.add_empty_window();    let editor_b = cx_b.new_window_entity(|window, cx| {        Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), window, cx)    });     let fake_language_server = fake_language_servers[0].next().await.unwrap();    let second_fake_language_server = fake_language_servers[1].next().await.unwrap();    cx_a.background_executor.run_until_parked();    cx_b.background_executor.run_until_parked();     buffer_b.read_with(cx_b, |buffer, _| {        assert!(!buffer.completion_triggers().is_empty())    });     // Type a completion trigger character as the guest.    editor_b.update_in(cx_b, |editor, window, cx| {        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])        });        editor.handle_input(".", window, cx);    });    cx_b.focus(&editor_b);     // Allow the completion request to propagate from guest to host to LSP.    cx_b.background_executor.run_until_parked();    cx_a.background_executor.run_until_parked();     // Open the buffer on the host.    let buffer_a = project_a        .update(cx_a, |p, cx| {            p.open_buffer((worktree_id, rel_path("main.rs")), cx)        })        .await        .unwrap();    cx_a.executor().run_until_parked();     buffer_a.read_with(cx_a, |buffer, _| {        assert_eq!(buffer.text(), "fn main() { a. }")    });     // Confirm a completion on the guest.    editor_b.update_in(cx_b, |editor, window, cx| {        assert!(editor.context_menu_visible());        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);        assert_eq!(editor.text(cx), "fn main() { a.first_method() }");    });     // Return a resolved completion from the host's language server.    // The resolved completion has an additional text edit.    fake_language_server.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(        |params, _| async move {            assert_eq!(params.label, "first_method(…)");            Ok(lsp::CompletionItem {                label: "first_method(…)".into(),                detail: Some("fn(&mut self, B) -> C".into()),                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {                    new_text: "first_method($1)".to_string(),                    range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),                })),                additional_text_edits: Some(vec![lsp::TextEdit {                    new_text: "use d::SomeTrait;\n".to_string(),                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),                }]),                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),                ..Default::default()            })        },    );     // The additional edit is applied.    cx_a.executor().run_until_parked();    cx_b.executor().run_until_parked();     buffer_a.read_with(cx_a, |buffer, _| {        assert_eq!(            buffer.text(),            "use d::SomeTrait;\nfn main() { a.first_method() }"        );    });     buffer_b.read_with(cx_b, |buffer, _| {        assert_eq!(            buffer.text(),            "use d::SomeTrait;\nfn main() { a.first_method() }"        );

Lines 1–500 of 4643.