editor_tests.rs

   1use crate::{
   2    rpc::RECONNECT_TIMEOUT,
   3    tests::{TestServer, rust_lang},
   4};
   5use call::ActiveCall;
   6use editor::{
   7    DocumentColorsRenderMode, Editor, EditorSettings, RowInfo,
   8    actions::{
   9        ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst,
  10        ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo,
  11    },
  12    test::{
  13        editor_test_context::{AssertionContextManager, EditorTestContext},
  14        expand_macro_recursively,
  15    },
  16};
  17use fs::Fs;
  18use futures::{StreamExt, lock::Mutex};
  19use gpui::{App, Rgba, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
  20use indoc::indoc;
  21use language::{
  22    FakeLspAdapter,
  23    language_settings::{AllLanguageSettings, InlayHintSettings},
  24};
  25use project::{
  26    ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT,
  27    lsp_store::{
  28        lsp_ext_command::{ExpandedMacro, LspExtExpandMacro},
  29        rust_analyzer_ext::RUST_ANALYZER_NAME,
  30    },
  31    project_settings::{InlineBlameSettings, ProjectSettings},
  32};
  33use recent_projects::disconnected_overlay::DisconnectedOverlay;
  34use rpc::RECEIVE_TIMEOUT;
  35use serde_json::json;
  36use settings::SettingsStore;
  37use std::{
  38    collections::BTreeSet,
  39    ops::{Deref as _, Range},
  40    path::{Path, PathBuf},
  41    sync::{
  42        Arc,
  43        atomic::{self, AtomicBool, AtomicUsize},
  44    },
  45};
  46use text::Point;
  47use util::{path, uri};
  48use workspace::{CloseIntent, Workspace};
  49
  50#[gpui::test(iterations = 10)]
  51async fn test_host_disconnect(
  52    cx_a: &mut TestAppContext,
  53    cx_b: &mut TestAppContext,
  54    cx_c: &mut TestAppContext,
  55) {
  56    let mut server = TestServer::start(cx_a.executor()).await;
  57    let client_a = server.create_client(cx_a, "user_a").await;
  58    let client_b = server.create_client(cx_b, "user_b").await;
  59    let client_c = server.create_client(cx_c, "user_c").await;
  60    server
  61        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
  62        .await;
  63
  64    cx_b.update(editor::init);
  65    cx_b.update(recent_projects::init);
  66
  67    client_a
  68        .fs()
  69        .insert_tree(
  70            "/a",
  71            json!({
  72                "a.txt": "a-contents",
  73                "b.txt": "b-contents",
  74            }),
  75        )
  76        .await;
  77
  78    let active_call_a = cx_a.read(ActiveCall::global);
  79    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
  80
  81    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
  82    let project_id = active_call_a
  83        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
  84        .await
  85        .unwrap();
  86
  87    let project_b = client_b.join_remote_project(project_id, cx_b).await;
  88    cx_a.background_executor.run_until_parked();
  89
  90    assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
  91
  92    let workspace_b = cx_b.add_window(|window, cx| {
  93        Workspace::new(
  94            None,
  95            project_b.clone(),
  96            client_b.app_state.clone(),
  97            window,
  98            cx,
  99        )
 100    });
 101    let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
 102    let workspace_b_view = workspace_b.root(cx_b).unwrap();
 103
 104    let editor_b = workspace_b
 105        .update(cx_b, |workspace, window, cx| {
 106            workspace.open_path((worktree_id, "b.txt"), None, true, window, cx)
 107        })
 108        .unwrap()
 109        .await
 110        .unwrap()
 111        .downcast::<Editor>()
 112        .unwrap();
 113
 114    //TODO: focus
 115    assert!(cx_b.update_window_entity(&editor_b, |editor, window, _| editor.is_focused(window)));
 116    editor_b.update_in(cx_b, |editor, window, cx| editor.insert("X", window, cx));
 117
 118    cx_b.update(|_, cx| {
 119        assert!(workspace_b_view.read(cx).is_edited());
 120    });
 121
 122    // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
 123    server.forbid_connections();
 124    server.disconnect_client(client_a.peer_id().unwrap());
 125    cx_a.background_executor
 126        .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 127
 128    project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());
 129
 130    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 131
 132    project_b.read_with(cx_b, |project, cx| project.is_read_only(cx));
 133
 134    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
 135
 136    // Ensure client B's edited state is reset and that the whole window is blurred.
 137    workspace_b
 138        .update(cx_b, |workspace, _, cx| {
 139            assert!(workspace.active_modal::<DisconnectedOverlay>(cx).is_some());
 140            assert!(!workspace.is_edited());
 141        })
 142        .unwrap();
 143
 144    // Ensure client B is not prompted to save edits when closing window after disconnecting.
 145    let can_close = workspace_b
 146        .update(cx_b, |workspace, window, cx| {
 147            workspace.prepare_to_close(CloseIntent::Quit, window, cx)
 148        })
 149        .unwrap()
 150        .await
 151        .unwrap();
 152    assert!(can_close);
 153
 154    // Allow client A to reconnect to the server.
 155    server.allow_connections();
 156    cx_a.background_executor.advance_clock(RECEIVE_TIMEOUT);
 157
 158    // Client B calls client A again after they reconnected.
 159    let active_call_b = cx_b.read(ActiveCall::global);
 160    active_call_b
 161        .update(cx_b, |call, cx| {
 162            call.invite(client_a.user_id().unwrap(), None, cx)
 163        })
 164        .await
 165        .unwrap();
 166    cx_a.background_executor.run_until_parked();
 167    active_call_a
 168        .update(cx_a, |call, cx| call.accept_incoming(cx))
 169        .await
 170        .unwrap();
 171
 172    active_call_a
 173        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 174        .await
 175        .unwrap();
 176
 177    // Drop client A's connection again. We should still unshare it successfully.
 178    server.forbid_connections();
 179    server.disconnect_client(client_a.peer_id().unwrap());
 180    cx_a.background_executor
 181        .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 182
 183    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 184}
 185
 186#[gpui::test]
 187async fn test_newline_above_or_below_does_not_move_guest_cursor(
 188    cx_a: &mut TestAppContext,
 189    cx_b: &mut TestAppContext,
 190) {
 191    let mut server = TestServer::start(cx_a.executor()).await;
 192    let client_a = server.create_client(cx_a, "user_a").await;
 193    let client_b = server.create_client(cx_b, "user_b").await;
 194    let executor = cx_a.executor();
 195    server
 196        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 197        .await;
 198    let active_call_a = cx_a.read(ActiveCall::global);
 199
 200    client_a
 201        .fs()
 202        .insert_tree(path!("/dir"), json!({ "a.txt": "Some text\n" }))
 203        .await;
 204    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
 205    let project_id = active_call_a
 206        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 207        .await
 208        .unwrap();
 209
 210    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 211
 212    // Open a buffer as client A
 213    let buffer_a = project_a
 214        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 215        .await
 216        .unwrap();
 217    let cx_a = cx_a.add_empty_window();
 218    let editor_a = cx_a
 219        .new_window_entity(|window, cx| Editor::for_buffer(buffer_a, Some(project_a), window, cx));
 220
 221    let mut editor_cx_a = EditorTestContext {
 222        cx: cx_a.clone(),
 223        window: cx_a.window_handle(),
 224        editor: editor_a,
 225        assertion_cx: AssertionContextManager::new(),
 226    };
 227
 228    let cx_b = cx_b.add_empty_window();
 229    // Open a buffer as client B
 230    let buffer_b = project_b
 231        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 232        .await
 233        .unwrap();
 234    let editor_b = cx_b
 235        .new_window_entity(|window, cx| Editor::for_buffer(buffer_b, Some(project_b), window, cx));
 236
 237    let mut editor_cx_b = EditorTestContext {
 238        cx: cx_b.clone(),
 239        window: cx_b.window_handle(),
 240        editor: editor_b,
 241        assertion_cx: AssertionContextManager::new(),
 242    };
 243
 244    // Test newline above
 245    editor_cx_a.set_selections_state(indoc! {"
 246        Some textˇ
 247    "});
 248    editor_cx_b.set_selections_state(indoc! {"
 249        Some textˇ
 250    "});
 251    editor_cx_a.update_editor(|editor, window, cx| {
 252        editor.newline_above(&editor::actions::NewlineAbove, window, cx)
 253    });
 254    executor.run_until_parked();
 255    editor_cx_a.assert_editor_state(indoc! {"
 256        ˇ
 257        Some text
 258    "});
 259    editor_cx_b.assert_editor_state(indoc! {"
 260
 261        Some textˇ
 262    "});
 263
 264    // Test newline below
 265    editor_cx_a.set_selections_state(indoc! {"
 266
 267        Some textˇ
 268    "});
 269    editor_cx_b.set_selections_state(indoc! {"
 270
 271        Some textˇ
 272    "});
 273    editor_cx_a.update_editor(|editor, window, cx| {
 274        editor.newline_below(&editor::actions::NewlineBelow, window, cx)
 275    });
 276    executor.run_until_parked();
 277    editor_cx_a.assert_editor_state(indoc! {"
 278
 279        Some text
 280        ˇ
 281    "});
 282    editor_cx_b.assert_editor_state(indoc! {"
 283
 284        Some textˇ
 285
 286    "});
 287}
 288
 289#[gpui::test(iterations = 10)]
 290async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 291    let mut server = TestServer::start(cx_a.executor()).await;
 292    let client_a = server.create_client(cx_a, "user_a").await;
 293    let client_b = server.create_client(cx_b, "user_b").await;
 294    server
 295        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 296        .await;
 297    let active_call_a = cx_a.read(ActiveCall::global);
 298
 299    client_a.language_registry().add(rust_lang());
 300    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
 301        "Rust",
 302        FakeLspAdapter {
 303            capabilities: lsp::ServerCapabilities {
 304                completion_provider: Some(lsp::CompletionOptions {
 305                    trigger_characters: Some(vec![".".to_string()]),
 306                    resolve_provider: Some(true),
 307                    ..Default::default()
 308                }),
 309                ..Default::default()
 310            },
 311            ..Default::default()
 312        },
 313    );
 314
 315    client_a
 316        .fs()
 317        .insert_tree(
 318            path!("/a"),
 319            json!({
 320                "main.rs": "fn main() { a }",
 321                "other.rs": "",
 322            }),
 323        )
 324        .await;
 325    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
 326    let project_id = active_call_a
 327        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 328        .await
 329        .unwrap();
 330    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 331
 332    // Open a file in an editor as the guest.
 333    let buffer_b = project_b
 334        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
 335        .await
 336        .unwrap();
 337    let cx_b = cx_b.add_empty_window();
 338    let editor_b = cx_b.new_window_entity(|window, cx| {
 339        Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), window, cx)
 340    });
 341
 342    let fake_language_server = fake_language_servers.next().await.unwrap();
 343    cx_a.background_executor.run_until_parked();
 344
 345    buffer_b.read_with(cx_b, |buffer, _| {
 346        assert!(!buffer.completion_triggers().is_empty())
 347    });
 348
 349    // Type a completion trigger character as the guest.
 350    editor_b.update_in(cx_b, |editor, window, cx| {
 351        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
 352        editor.handle_input(".", window, cx);
 353    });
 354    cx_b.focus(&editor_b);
 355
 356    // Receive a completion request as the host's language server.
 357    // Return some completions from the host's language server.
 358    cx_a.executor().start_waiting();
 359    fake_language_server
 360        .set_request_handler::<lsp::request::Completion, _, _>(|params, _| async move {
 361            assert_eq!(
 362                params.text_document_position.text_document.uri,
 363                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 364            );
 365            assert_eq!(
 366                params.text_document_position.position,
 367                lsp::Position::new(0, 14),
 368            );
 369
 370            Ok(Some(lsp::CompletionResponse::Array(vec![
 371                lsp::CompletionItem {
 372                    label: "first_method(…)".into(),
 373                    detail: Some("fn(&mut self, B) -> C".into()),
 374                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 375                        new_text: "first_method($1)".to_string(),
 376                        range: lsp::Range::new(
 377                            lsp::Position::new(0, 14),
 378                            lsp::Position::new(0, 14),
 379                        ),
 380                    })),
 381                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 382                    ..Default::default()
 383                },
 384                lsp::CompletionItem {
 385                    label: "second_method(…)".into(),
 386                    detail: Some("fn(&mut self, C) -> D<E>".into()),
 387                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 388                        new_text: "second_method()".to_string(),
 389                        range: lsp::Range::new(
 390                            lsp::Position::new(0, 14),
 391                            lsp::Position::new(0, 14),
 392                        ),
 393                    })),
 394                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 395                    ..Default::default()
 396                },
 397            ])))
 398        })
 399        .next()
 400        .await
 401        .unwrap();
 402    cx_a.executor().finish_waiting();
 403
 404    // Open the buffer on the host.
 405    let buffer_a = project_a
 406        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
 407        .await
 408        .unwrap();
 409    cx_a.executor().run_until_parked();
 410
 411    buffer_a.read_with(cx_a, |buffer, _| {
 412        assert_eq!(buffer.text(), "fn main() { a. }")
 413    });
 414
 415    // Confirm a completion on the guest.
 416    editor_b.update_in(cx_b, |editor, window, cx| {
 417        assert!(editor.context_menu_visible());
 418        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
 419        assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
 420    });
 421
 422    // Return a resolved completion from the host's language server.
 423    // The resolved completion has an additional text edit.
 424    fake_language_server.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
 425        |params, _| async move {
 426            assert_eq!(params.label, "first_method(…)");
 427            Ok(lsp::CompletionItem {
 428                label: "first_method(…)".into(),
 429                detail: Some("fn(&mut self, B) -> C".into()),
 430                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 431                    new_text: "first_method($1)".to_string(),
 432                    range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
 433                })),
 434                additional_text_edits: Some(vec![lsp::TextEdit {
 435                    new_text: "use d::SomeTrait;\n".to_string(),
 436                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 437                }]),
 438                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 439                ..Default::default()
 440            })
 441        },
 442    );
 443
 444    // The additional edit is applied.
 445    cx_a.executor().run_until_parked();
 446
 447    buffer_a.read_with(cx_a, |buffer, _| {
 448        assert_eq!(
 449            buffer.text(),
 450            "use d::SomeTrait;\nfn main() { a.first_method() }"
 451        );
 452    });
 453
 454    buffer_b.read_with(cx_b, |buffer, _| {
 455        assert_eq!(
 456            buffer.text(),
 457            "use d::SomeTrait;\nfn main() { a.first_method() }"
 458        );
 459    });
 460
 461    // Now we do a second completion, this time to ensure that documentation/snippets are
 462    // resolved
 463    editor_b.update_in(cx_b, |editor, window, cx| {
 464        editor.change_selections(None, window, cx, |s| s.select_ranges([46..46]));
 465        editor.handle_input("; a", window, cx);
 466        editor.handle_input(".", window, cx);
 467    });
 468
 469    buffer_b.read_with(cx_b, |buffer, _| {
 470        assert_eq!(
 471            buffer.text(),
 472            "use d::SomeTrait;\nfn main() { a.first_method(); a. }"
 473        );
 474    });
 475
 476    let mut completion_response = fake_language_server
 477        .set_request_handler::<lsp::request::Completion, _, _>(|params, _| async move {
 478            assert_eq!(
 479                params.text_document_position.text_document.uri,
 480                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 481            );
 482            assert_eq!(
 483                params.text_document_position.position,
 484                lsp::Position::new(1, 32),
 485            );
 486
 487            Ok(Some(lsp::CompletionResponse::Array(vec![
 488                lsp::CompletionItem {
 489                    label: "third_method(…)".into(),
 490                    detail: Some("fn(&mut self, B, C, D) -> E".into()),
 491                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 492                        // no snippet placehodlers
 493                        new_text: "third_method".to_string(),
 494                        range: lsp::Range::new(
 495                            lsp::Position::new(1, 32),
 496                            lsp::Position::new(1, 32),
 497                        ),
 498                    })),
 499                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 500                    documentation: None,
 501                    ..Default::default()
 502                },
 503            ])))
 504        });
 505
 506    // The completion now gets a new `text_edit.new_text` when resolving the completion item
 507    let mut resolve_completion_response = fake_language_server
 508        .set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(|params, _| async move {
 509            assert_eq!(params.label, "third_method(…)");
 510            Ok(lsp::CompletionItem {
 511                label: "third_method(…)".into(),
 512                detail: Some("fn(&mut self, B, C, D) -> E".into()),
 513                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 514                    // Now it's a snippet
 515                    new_text: "third_method($1, $2, $3)".to_string(),
 516                    range: lsp::Range::new(lsp::Position::new(1, 32), lsp::Position::new(1, 32)),
 517                })),
 518                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 519                documentation: Some(lsp::Documentation::String(
 520                    "this is the documentation".into(),
 521                )),
 522                ..Default::default()
 523            })
 524        });
 525
 526    cx_b.executor().run_until_parked();
 527
 528    completion_response.next().await.unwrap();
 529
 530    editor_b.update_in(cx_b, |editor, window, cx| {
 531        assert!(editor.context_menu_visible());
 532        editor.context_menu_first(&ContextMenuFirst {}, window, cx);
 533    });
 534
 535    resolve_completion_response.next().await.unwrap();
 536    cx_b.executor().run_until_parked();
 537
 538    // When accepting the completion, the snippet is insert.
 539    editor_b.update_in(cx_b, |editor, window, cx| {
 540        assert!(editor.context_menu_visible());
 541        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
 542        assert_eq!(
 543            editor.text(cx),
 544            "use d::SomeTrait;\nfn main() { a.first_method(); a.third_method(, , ) }"
 545        );
 546    });
 547}
 548
 549#[gpui::test(iterations = 10)]
 550async fn test_collaborating_with_code_actions(
 551    cx_a: &mut TestAppContext,
 552    cx_b: &mut TestAppContext,
 553) {
 554    let mut server = TestServer::start(cx_a.executor()).await;
 555    let client_a = server.create_client(cx_a, "user_a").await;
 556    //
 557    let client_b = server.create_client(cx_b, "user_b").await;
 558    server
 559        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 560        .await;
 561    let active_call_a = cx_a.read(ActiveCall::global);
 562
 563    cx_b.update(editor::init);
 564
 565    // Set up a fake language server.
 566    client_a.language_registry().add(rust_lang());
 567    let mut fake_language_servers = client_a
 568        .language_registry()
 569        .register_fake_lsp("Rust", FakeLspAdapter::default());
 570
 571    client_a
 572        .fs()
 573        .insert_tree(
 574            path!("/a"),
 575            json!({
 576                "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
 577                "other.rs": "pub fn foo() -> usize { 4 }",
 578            }),
 579        )
 580        .await;
 581    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
 582    let project_id = active_call_a
 583        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 584        .await
 585        .unwrap();
 586
 587    // Join the project as client B.
 588    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 589    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 590    let editor_b = workspace_b
 591        .update_in(cx_b, |workspace, window, cx| {
 592            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
 593        })
 594        .await
 595        .unwrap()
 596        .downcast::<Editor>()
 597        .unwrap();
 598
 599    let mut fake_language_server = fake_language_servers.next().await.unwrap();
 600    let mut requests = fake_language_server
 601        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
 602            assert_eq!(
 603                params.text_document.uri,
 604                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 605            );
 606            assert_eq!(params.range.start, lsp::Position::new(0, 0));
 607            assert_eq!(params.range.end, lsp::Position::new(0, 0));
 608            Ok(None)
 609        });
 610    cx_a.background_executor
 611        .advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
 612    requests.next().await;
 613
 614    // Move cursor to a location that contains code actions.
 615    editor_b.update_in(cx_b, |editor, window, cx| {
 616        editor.change_selections(None, window, cx, |s| {
 617            s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
 618        });
 619    });
 620    cx_b.focus(&editor_b);
 621
 622    let mut requests = fake_language_server
 623        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
 624            assert_eq!(
 625                params.text_document.uri,
 626                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 627            );
 628            assert_eq!(params.range.start, lsp::Position::new(1, 31));
 629            assert_eq!(params.range.end, lsp::Position::new(1, 31));
 630
 631            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 632                lsp::CodeAction {
 633                    title: "Inline into all callers".to_string(),
 634                    edit: Some(lsp::WorkspaceEdit {
 635                        changes: Some(
 636                            [
 637                                (
 638                                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 639                                    vec![lsp::TextEdit::new(
 640                                        lsp::Range::new(
 641                                            lsp::Position::new(1, 22),
 642                                            lsp::Position::new(1, 34),
 643                                        ),
 644                                        "4".to_string(),
 645                                    )],
 646                                ),
 647                                (
 648                                    lsp::Url::from_file_path(path!("/a/other.rs")).unwrap(),
 649                                    vec![lsp::TextEdit::new(
 650                                        lsp::Range::new(
 651                                            lsp::Position::new(0, 0),
 652                                            lsp::Position::new(0, 27),
 653                                        ),
 654                                        "".to_string(),
 655                                    )],
 656                                ),
 657                            ]
 658                            .into_iter()
 659                            .collect(),
 660                        ),
 661                        ..Default::default()
 662                    }),
 663                    data: Some(json!({
 664                        "codeActionParams": {
 665                            "range": {
 666                                "start": {"line": 1, "column": 31},
 667                                "end": {"line": 1, "column": 31},
 668                            }
 669                        }
 670                    })),
 671                    ..Default::default()
 672                },
 673            )]))
 674        });
 675    cx_a.background_executor
 676        .advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
 677    requests.next().await;
 678
 679    // Toggle code actions and wait for them to display.
 680    editor_b.update_in(cx_b, |editor, window, cx| {
 681        editor.toggle_code_actions(
 682            &ToggleCodeActions {
 683                deployed_from: None,
 684                quick_launch: false,
 685            },
 686            window,
 687            cx,
 688        );
 689    });
 690    cx_a.background_executor.run_until_parked();
 691
 692    editor_b.update(cx_b, |editor, _| assert!(editor.context_menu_visible()));
 693
 694    fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
 695
 696    // Confirming the code action will trigger a resolve request.
 697    let confirm_action = editor_b
 698        .update_in(cx_b, |editor, window, cx| {
 699            Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, window, cx)
 700        })
 701        .unwrap();
 702    fake_language_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>(
 703        |_, _| async move {
 704            Ok(lsp::CodeAction {
 705                title: "Inline into all callers".to_string(),
 706                edit: Some(lsp::WorkspaceEdit {
 707                    changes: Some(
 708                        [
 709                            (
 710                                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 711                                vec![lsp::TextEdit::new(
 712                                    lsp::Range::new(
 713                                        lsp::Position::new(1, 22),
 714                                        lsp::Position::new(1, 34),
 715                                    ),
 716                                    "4".to_string(),
 717                                )],
 718                            ),
 719                            (
 720                                lsp::Url::from_file_path(path!("/a/other.rs")).unwrap(),
 721                                vec![lsp::TextEdit::new(
 722                                    lsp::Range::new(
 723                                        lsp::Position::new(0, 0),
 724                                        lsp::Position::new(0, 27),
 725                                    ),
 726                                    "".to_string(),
 727                                )],
 728                            ),
 729                        ]
 730                        .into_iter()
 731                        .collect(),
 732                    ),
 733                    ..Default::default()
 734                }),
 735                ..Default::default()
 736            })
 737        },
 738    );
 739
 740    // After the action is confirmed, an editor containing both modified files is opened.
 741    confirm_action.await.unwrap();
 742
 743    let code_action_editor = workspace_b.update(cx_b, |workspace, cx| {
 744        workspace
 745            .active_item(cx)
 746            .unwrap()
 747            .downcast::<Editor>()
 748            .unwrap()
 749    });
 750    code_action_editor.update_in(cx_b, |editor, window, cx| {
 751        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 752        editor.undo(&Undo, window, cx);
 753        assert_eq!(
 754            editor.text(cx),
 755            "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
 756        );
 757        editor.redo(&Redo, window, cx);
 758        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 759    });
 760}
 761
 762#[gpui::test(iterations = 10)]
 763async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 764    let mut server = TestServer::start(cx_a.executor()).await;
 765    let client_a = server.create_client(cx_a, "user_a").await;
 766    let client_b = server.create_client(cx_b, "user_b").await;
 767    server
 768        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 769        .await;
 770    let active_call_a = cx_a.read(ActiveCall::global);
 771
 772    cx_b.update(editor::init);
 773
 774    // Set up a fake language server.
 775    client_a.language_registry().add(rust_lang());
 776    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
 777        "Rust",
 778        FakeLspAdapter {
 779            capabilities: lsp::ServerCapabilities {
 780                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
 781                    prepare_provider: Some(true),
 782                    work_done_progress_options: Default::default(),
 783                })),
 784                ..Default::default()
 785            },
 786            ..Default::default()
 787        },
 788    );
 789
 790    client_a
 791        .fs()
 792        .insert_tree(
 793            path!("/dir"),
 794            json!({
 795                "one.rs": "const ONE: usize = 1;",
 796                "two.rs": "const TWO: usize = one::ONE + one::ONE;"
 797            }),
 798        )
 799        .await;
 800    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
 801    let project_id = active_call_a
 802        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 803        .await
 804        .unwrap();
 805    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 806
 807    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 808    let editor_b = workspace_b
 809        .update_in(cx_b, |workspace, window, cx| {
 810            workspace.open_path((worktree_id, "one.rs"), None, true, window, cx)
 811        })
 812        .await
 813        .unwrap()
 814        .downcast::<Editor>()
 815        .unwrap();
 816    let fake_language_server = fake_language_servers.next().await.unwrap();
 817
 818    // Move cursor to a location that can be renamed.
 819    let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
 820        editor.change_selections(None, window, cx, |s| s.select_ranges([7..7]));
 821        editor.rename(&Rename, window, cx).unwrap()
 822    });
 823
 824    fake_language_server
 825        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
 826            assert_eq!(
 827                params.text_document.uri.as_str(),
 828                uri!("file:///dir/one.rs")
 829            );
 830            assert_eq!(params.position, lsp::Position::new(0, 7));
 831            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
 832                lsp::Position::new(0, 6),
 833                lsp::Position::new(0, 9),
 834            ))))
 835        })
 836        .next()
 837        .await
 838        .unwrap();
 839    prepare_rename.await.unwrap();
 840    editor_b.update(cx_b, |editor, cx| {
 841        use editor::ToOffset;
 842        let rename = editor.pending_rename().unwrap();
 843        let buffer = editor.buffer().read(cx).snapshot(cx);
 844        assert_eq!(
 845            rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
 846            6..9
 847        );
 848        rename.editor.update(cx, |rename_editor, cx| {
 849            let rename_selection = rename_editor.selections.newest::<usize>(cx);
 850            assert_eq!(
 851                rename_selection.range(),
 852                0..3,
 853                "Rename that was triggered from zero selection caret, should propose the whole word."
 854            );
 855            rename_editor.buffer().update(cx, |rename_buffer, cx| {
 856                rename_buffer.edit([(0..3, "THREE")], None, cx);
 857            });
 858        });
 859    });
 860
 861    // Cancel the rename, and repeat the same, but use selections instead of cursor movement
 862    editor_b.update_in(cx_b, |editor, window, cx| {
 863        editor.cancel(&editor::actions::Cancel, window, cx);
 864    });
 865    let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
 866        editor.change_selections(None, window, cx, |s| s.select_ranges([7..8]));
 867        editor.rename(&Rename, window, cx).unwrap()
 868    });
 869
 870    fake_language_server
 871        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
 872            assert_eq!(
 873                params.text_document.uri.as_str(),
 874                uri!("file:///dir/one.rs")
 875            );
 876            assert_eq!(params.position, lsp::Position::new(0, 8));
 877            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
 878                lsp::Position::new(0, 6),
 879                lsp::Position::new(0, 9),
 880            ))))
 881        })
 882        .next()
 883        .await
 884        .unwrap();
 885    prepare_rename.await.unwrap();
 886    editor_b.update(cx_b, |editor, cx| {
 887        use editor::ToOffset;
 888        let rename = editor.pending_rename().unwrap();
 889        let buffer = editor.buffer().read(cx).snapshot(cx);
 890        let lsp_rename_start = rename.range.start.to_offset(&buffer);
 891        let lsp_rename_end = rename.range.end.to_offset(&buffer);
 892        assert_eq!(lsp_rename_start..lsp_rename_end, 6..9);
 893        rename.editor.update(cx, |rename_editor, cx| {
 894            let rename_selection = rename_editor.selections.newest::<usize>(cx);
 895            assert_eq!(
 896                rename_selection.range(),
 897                1..2,
 898                "Rename that was triggered from a selection, should have the same selection range in the rename proposal"
 899            );
 900            rename_editor.buffer().update(cx, |rename_buffer, cx| {
 901                rename_buffer.edit([(0..lsp_rename_end - lsp_rename_start, "THREE")], None, cx);
 902            });
 903        });
 904    });
 905
 906    let confirm_rename = editor_b.update_in(cx_b, |editor, window, cx| {
 907        Editor::confirm_rename(editor, &ConfirmRename, window, cx).unwrap()
 908    });
 909    fake_language_server
 910        .set_request_handler::<lsp::request::Rename, _, _>(|params, _| async move {
 911            assert_eq!(
 912                params.text_document_position.text_document.uri.as_str(),
 913                uri!("file:///dir/one.rs")
 914            );
 915            assert_eq!(
 916                params.text_document_position.position,
 917                lsp::Position::new(0, 6)
 918            );
 919            assert_eq!(params.new_name, "THREE");
 920            Ok(Some(lsp::WorkspaceEdit {
 921                changes: Some(
 922                    [
 923                        (
 924                            lsp::Url::from_file_path(path!("/dir/one.rs")).unwrap(),
 925                            vec![lsp::TextEdit::new(
 926                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
 927                                "THREE".to_string(),
 928                            )],
 929                        ),
 930                        (
 931                            lsp::Url::from_file_path(path!("/dir/two.rs")).unwrap(),
 932                            vec![
 933                                lsp::TextEdit::new(
 934                                    lsp::Range::new(
 935                                        lsp::Position::new(0, 24),
 936                                        lsp::Position::new(0, 27),
 937                                    ),
 938                                    "THREE".to_string(),
 939                                ),
 940                                lsp::TextEdit::new(
 941                                    lsp::Range::new(
 942                                        lsp::Position::new(0, 35),
 943                                        lsp::Position::new(0, 38),
 944                                    ),
 945                                    "THREE".to_string(),
 946                                ),
 947                            ],
 948                        ),
 949                    ]
 950                    .into_iter()
 951                    .collect(),
 952                ),
 953                ..Default::default()
 954            }))
 955        })
 956        .next()
 957        .await
 958        .unwrap();
 959    confirm_rename.await.unwrap();
 960
 961    let rename_editor = workspace_b.update(cx_b, |workspace, cx| {
 962        workspace.active_item_as::<Editor>(cx).unwrap()
 963    });
 964
 965    rename_editor.update_in(cx_b, |editor, window, cx| {
 966        assert_eq!(
 967            editor.text(cx),
 968            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
 969        );
 970        editor.undo(&Undo, window, cx);
 971        assert_eq!(
 972            editor.text(cx),
 973            "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
 974        );
 975        editor.redo(&Redo, window, cx);
 976        assert_eq!(
 977            editor.text(cx),
 978            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
 979        );
 980    });
 981
 982    // Ensure temporary rename edits cannot be undone/redone.
 983    editor_b.update_in(cx_b, |editor, window, cx| {
 984        editor.undo(&Undo, window, cx);
 985        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
 986        editor.undo(&Undo, window, cx);
 987        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
 988        editor.redo(&Redo, window, cx);
 989        assert_eq!(editor.text(cx), "const THREE: usize = 1;");
 990    })
 991}
 992
 993#[gpui::test(iterations = 10)]
 994async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 995    let mut server = TestServer::start(cx_a.executor()).await;
 996    let executor = cx_a.executor();
 997    let client_a = server.create_client(cx_a, "user_a").await;
 998    let client_b = server.create_client(cx_b, "user_b").await;
 999    server
1000        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1001        .await;
1002    let active_call_a = cx_a.read(ActiveCall::global);
1003
1004    cx_b.update(editor::init);
1005
1006    client_a.language_registry().add(rust_lang());
1007    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1008        "Rust",
1009        FakeLspAdapter {
1010            name: "the-language-server",
1011            ..Default::default()
1012        },
1013    );
1014
1015    client_a
1016        .fs()
1017        .insert_tree(
1018            path!("/dir"),
1019            json!({
1020                "main.rs": "const ONE: usize = 1;",
1021            }),
1022        )
1023        .await;
1024    let (project_a, _) = client_a.build_local_project(path!("/dir"), cx_a).await;
1025
1026    let _buffer_a = project_a
1027        .update(cx_a, |p, cx| {
1028            p.open_local_buffer_with_lsp(path!("/dir/main.rs"), cx)
1029        })
1030        .await
1031        .unwrap();
1032
1033    let fake_language_server = fake_language_servers.next().await.unwrap();
1034    fake_language_server.start_progress("the-token").await;
1035
1036    executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
1037    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
1038        token: lsp::NumberOrString::String("the-token".to_string()),
1039        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
1040            lsp::WorkDoneProgressReport {
1041                message: Some("the-message".to_string()),
1042                ..Default::default()
1043            },
1044        )),
1045    });
1046    executor.run_until_parked();
1047
1048    project_a.read_with(cx_a, |project, cx| {
1049        let status = project.language_server_statuses(cx).next().unwrap().1;
1050        assert_eq!(status.name, "the-language-server");
1051        assert_eq!(status.pending_work.len(), 1);
1052        assert_eq!(
1053            status.pending_work["the-token"].message.as_ref().unwrap(),
1054            "the-message"
1055        );
1056    });
1057
1058    let project_id = active_call_a
1059        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1060        .await
1061        .unwrap();
1062    executor.run_until_parked();
1063    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1064
1065    project_b.read_with(cx_b, |project, cx| {
1066        let status = project.language_server_statuses(cx).next().unwrap().1;
1067        assert_eq!(status.name, "the-language-server");
1068    });
1069
1070    executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
1071    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
1072        token: lsp::NumberOrString::String("the-token".to_string()),
1073        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
1074            lsp::WorkDoneProgressReport {
1075                message: Some("the-message-2".to_string()),
1076                ..Default::default()
1077            },
1078        )),
1079    });
1080    executor.run_until_parked();
1081
1082    project_a.read_with(cx_a, |project, cx| {
1083        let status = project.language_server_statuses(cx).next().unwrap().1;
1084        assert_eq!(status.name, "the-language-server");
1085        assert_eq!(status.pending_work.len(), 1);
1086        assert_eq!(
1087            status.pending_work["the-token"].message.as_ref().unwrap(),
1088            "the-message-2"
1089        );
1090    });
1091
1092    project_b.read_with(cx_b, |project, cx| {
1093        let status = project.language_server_statuses(cx).next().unwrap().1;
1094        assert_eq!(status.name, "the-language-server");
1095        assert_eq!(status.pending_work.len(), 1);
1096        assert_eq!(
1097            status.pending_work["the-token"].message.as_ref().unwrap(),
1098            "the-message-2"
1099        );
1100    });
1101}
1102
1103#[gpui::test(iterations = 10)]
1104async fn test_share_project(
1105    cx_a: &mut TestAppContext,
1106    cx_b: &mut TestAppContext,
1107    cx_c: &mut TestAppContext,
1108) {
1109    let executor = cx_a.executor();
1110    let cx_b = cx_b.add_empty_window();
1111    let mut server = TestServer::start(executor.clone()).await;
1112    let client_a = server.create_client(cx_a, "user_a").await;
1113    let client_b = server.create_client(cx_b, "user_b").await;
1114    let client_c = server.create_client(cx_c, "user_c").await;
1115    server
1116        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1117        .await;
1118    let active_call_a = cx_a.read(ActiveCall::global);
1119    let active_call_b = cx_b.read(ActiveCall::global);
1120    let active_call_c = cx_c.read(ActiveCall::global);
1121
1122    client_a
1123        .fs()
1124        .insert_tree(
1125            path!("/a"),
1126            json!({
1127                ".gitignore": "ignored-dir",
1128                "a.txt": "a-contents",
1129                "b.txt": "b-contents",
1130                "ignored-dir": {
1131                    "c.txt": "",
1132                    "d.txt": "",
1133                }
1134            }),
1135        )
1136        .await;
1137
1138    // Invite client B to collaborate on a project
1139    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1140    active_call_a
1141        .update(cx_a, |call, cx| {
1142            call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
1143        })
1144        .await
1145        .unwrap();
1146
1147    // Join that project as client B
1148
1149    let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
1150    executor.run_until_parked();
1151    let call = incoming_call_b.borrow().clone().unwrap();
1152    assert_eq!(call.calling_user.github_login, "user_a");
1153    let initial_project = call.initial_project.unwrap();
1154    active_call_b
1155        .update(cx_b, |call, cx| call.accept_incoming(cx))
1156        .await
1157        .unwrap();
1158    let client_b_peer_id = client_b.peer_id().unwrap();
1159    let project_b = client_b.join_remote_project(initial_project.id, cx_b).await;
1160
1161    let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
1162
1163    executor.run_until_parked();
1164
1165    project_a.read_with(cx_a, |project, _| {
1166        let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
1167        assert_eq!(client_b_collaborator.replica_id, replica_id_b);
1168    });
1169
1170    project_b.read_with(cx_b, |project, cx| {
1171        let worktree = project.worktrees(cx).next().unwrap().read(cx);
1172        assert_eq!(
1173            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1174            [
1175                Path::new(".gitignore"),
1176                Path::new("a.txt"),
1177                Path::new("b.txt"),
1178                Path::new("ignored-dir"),
1179            ]
1180        );
1181    });
1182
1183    project_b
1184        .update(cx_b, |project, cx| {
1185            let worktree = project.worktrees(cx).next().unwrap();
1186            let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
1187            project.expand_entry(worktree_id, entry.id, cx).unwrap()
1188        })
1189        .await
1190        .unwrap();
1191
1192    project_b.read_with(cx_b, |project, cx| {
1193        let worktree = project.worktrees(cx).next().unwrap().read(cx);
1194        assert_eq!(
1195            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1196            [
1197                Path::new(".gitignore"),
1198                Path::new("a.txt"),
1199                Path::new("b.txt"),
1200                Path::new("ignored-dir"),
1201                Path::new("ignored-dir/c.txt"),
1202                Path::new("ignored-dir/d.txt"),
1203            ]
1204        );
1205    });
1206
1207    // Open the same file as client B and client A.
1208    let buffer_b = project_b
1209        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1210        .await
1211        .unwrap();
1212
1213    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
1214
1215    project_a.read_with(cx_a, |project, cx| {
1216        assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
1217    });
1218    let buffer_a = project_a
1219        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1220        .await
1221        .unwrap();
1222
1223    let editor_b =
1224        cx_b.new_window_entity(|window, cx| Editor::for_buffer(buffer_b, None, window, cx));
1225
1226    // Client A sees client B's selection
1227    executor.run_until_parked();
1228
1229    buffer_a.read_with(cx_a, |buffer, _| {
1230        buffer
1231            .snapshot()
1232            .selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
1233            .count()
1234            == 1
1235    });
1236
1237    // Edit the buffer as client B and see that edit as client A.
1238    editor_b.update_in(cx_b, |editor, window, cx| {
1239        editor.handle_input("ok, ", window, cx)
1240    });
1241    executor.run_until_parked();
1242
1243    buffer_a.read_with(cx_a, |buffer, _| {
1244        assert_eq!(buffer.text(), "ok, b-contents")
1245    });
1246
1247    // Client B can invite client C on a project shared by client A.
1248    active_call_b
1249        .update(cx_b, |call, cx| {
1250            call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
1251        })
1252        .await
1253        .unwrap();
1254
1255    let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
1256    executor.run_until_parked();
1257    let call = incoming_call_c.borrow().clone().unwrap();
1258    assert_eq!(call.calling_user.github_login, "user_b");
1259    let initial_project = call.initial_project.unwrap();
1260    active_call_c
1261        .update(cx_c, |call, cx| call.accept_incoming(cx))
1262        .await
1263        .unwrap();
1264    let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
1265
1266    // Client B closes the editor, and client A sees client B's selections removed.
1267    cx_b.update(move |_, _| drop(editor_b));
1268    executor.run_until_parked();
1269
1270    buffer_a.read_with(cx_a, |buffer, _| {
1271        buffer
1272            .snapshot()
1273            .selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
1274            .count()
1275            == 0
1276    });
1277}
1278
1279#[gpui::test(iterations = 10)]
1280async fn test_on_input_format_from_host_to_guest(
1281    cx_a: &mut TestAppContext,
1282    cx_b: &mut TestAppContext,
1283) {
1284    let mut server = TestServer::start(cx_a.executor()).await;
1285    let executor = cx_a.executor();
1286    let client_a = server.create_client(cx_a, "user_a").await;
1287    let client_b = server.create_client(cx_b, "user_b").await;
1288    server
1289        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1290        .await;
1291    let active_call_a = cx_a.read(ActiveCall::global);
1292
1293    client_a.language_registry().add(rust_lang());
1294    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1295        "Rust",
1296        FakeLspAdapter {
1297            capabilities: lsp::ServerCapabilities {
1298                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1299                    first_trigger_character: ":".to_string(),
1300                    more_trigger_character: Some(vec![">".to_string()]),
1301                }),
1302                ..Default::default()
1303            },
1304            ..Default::default()
1305        },
1306    );
1307
1308    client_a
1309        .fs()
1310        .insert_tree(
1311            path!("/a"),
1312            json!({
1313                "main.rs": "fn main() { a }",
1314                "other.rs": "// Test file",
1315            }),
1316        )
1317        .await;
1318    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1319    let project_id = active_call_a
1320        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1321        .await
1322        .unwrap();
1323    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1324
1325    // Open a file in an editor as the host.
1326    let buffer_a = project_a
1327        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1328        .await
1329        .unwrap();
1330    let cx_a = cx_a.add_empty_window();
1331    let editor_a = cx_a.new_window_entity(|window, cx| {
1332        Editor::for_buffer(buffer_a, Some(project_a.clone()), window, cx)
1333    });
1334
1335    let fake_language_server = fake_language_servers.next().await.unwrap();
1336    executor.run_until_parked();
1337
1338    // Receive an OnTypeFormatting request as the host's language server.
1339    // Return some formatting from the host's language server.
1340    fake_language_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
1341        |params, _| async move {
1342            assert_eq!(
1343                params.text_document_position.text_document.uri,
1344                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1345            );
1346            assert_eq!(
1347                params.text_document_position.position,
1348                lsp::Position::new(0, 14),
1349            );
1350
1351            Ok(Some(vec![lsp::TextEdit {
1352                new_text: "~<".to_string(),
1353                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1354            }]))
1355        },
1356    );
1357
1358    // Open the buffer on the guest and see that the formatting worked
1359    let buffer_b = project_b
1360        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1361        .await
1362        .unwrap();
1363
1364    // Type a on type formatting trigger character as the guest.
1365    cx_a.focus(&editor_a);
1366    editor_a.update_in(cx_a, |editor, window, cx| {
1367        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1368        editor.handle_input(">", window, cx);
1369    });
1370
1371    executor.run_until_parked();
1372
1373    buffer_b.read_with(cx_b, |buffer, _| {
1374        assert_eq!(buffer.text(), "fn main() { a>~< }")
1375    });
1376
1377    // Undo should remove LSP edits first
1378    editor_a.update_in(cx_a, |editor, window, cx| {
1379        assert_eq!(editor.text(cx), "fn main() { a>~< }");
1380        editor.undo(&Undo, window, cx);
1381        assert_eq!(editor.text(cx), "fn main() { a> }");
1382    });
1383    executor.run_until_parked();
1384
1385    buffer_b.read_with(cx_b, |buffer, _| {
1386        assert_eq!(buffer.text(), "fn main() { a> }")
1387    });
1388
1389    editor_a.update_in(cx_a, |editor, window, cx| {
1390        assert_eq!(editor.text(cx), "fn main() { a> }");
1391        editor.undo(&Undo, window, cx);
1392        assert_eq!(editor.text(cx), "fn main() { a }");
1393    });
1394    executor.run_until_parked();
1395
1396    buffer_b.read_with(cx_b, |buffer, _| {
1397        assert_eq!(buffer.text(), "fn main() { a }")
1398    });
1399}
1400
1401#[gpui::test(iterations = 10)]
1402async fn test_on_input_format_from_guest_to_host(
1403    cx_a: &mut TestAppContext,
1404    cx_b: &mut TestAppContext,
1405) {
1406    let mut server = TestServer::start(cx_a.executor()).await;
1407    let executor = cx_a.executor();
1408    let client_a = server.create_client(cx_a, "user_a").await;
1409    let client_b = server.create_client(cx_b, "user_b").await;
1410    server
1411        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1412        .await;
1413    let active_call_a = cx_a.read(ActiveCall::global);
1414
1415    client_a.language_registry().add(rust_lang());
1416    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1417        "Rust",
1418        FakeLspAdapter {
1419            capabilities: lsp::ServerCapabilities {
1420                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1421                    first_trigger_character: ":".to_string(),
1422                    more_trigger_character: Some(vec![">".to_string()]),
1423                }),
1424                ..Default::default()
1425            },
1426            ..Default::default()
1427        },
1428    );
1429
1430    client_a
1431        .fs()
1432        .insert_tree(
1433            path!("/a"),
1434            json!({
1435                "main.rs": "fn main() { a }",
1436                "other.rs": "// Test file",
1437            }),
1438        )
1439        .await;
1440    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1441    let project_id = active_call_a
1442        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1443        .await
1444        .unwrap();
1445    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1446
1447    // Open a file in an editor as the guest.
1448    let buffer_b = project_b
1449        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1450        .await
1451        .unwrap();
1452    let cx_b = cx_b.add_empty_window();
1453    let editor_b = cx_b.new_window_entity(|window, cx| {
1454        Editor::for_buffer(buffer_b, Some(project_b.clone()), window, cx)
1455    });
1456
1457    let fake_language_server = fake_language_servers.next().await.unwrap();
1458    executor.run_until_parked();
1459
1460    // Type a on type formatting trigger character as the guest.
1461    cx_b.focus(&editor_b);
1462    editor_b.update_in(cx_b, |editor, window, cx| {
1463        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1464        editor.handle_input(":", window, cx);
1465    });
1466
1467    // Receive an OnTypeFormatting request as the host's language server.
1468    // Return some formatting from the host's language server.
1469    executor.start_waiting();
1470    fake_language_server
1471        .set_request_handler::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
1472            assert_eq!(
1473                params.text_document_position.text_document.uri,
1474                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1475            );
1476            assert_eq!(
1477                params.text_document_position.position,
1478                lsp::Position::new(0, 14),
1479            );
1480
1481            Ok(Some(vec![lsp::TextEdit {
1482                new_text: "~:".to_string(),
1483                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1484            }]))
1485        })
1486        .next()
1487        .await
1488        .unwrap();
1489    executor.finish_waiting();
1490
1491    // Open the buffer on the host and see that the formatting worked
1492    let buffer_a = project_a
1493        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1494        .await
1495        .unwrap();
1496    executor.run_until_parked();
1497
1498    buffer_a.read_with(cx_a, |buffer, _| {
1499        assert_eq!(buffer.text(), "fn main() { a:~: }")
1500    });
1501
1502    // Undo should remove LSP edits first
1503    editor_b.update_in(cx_b, |editor, window, cx| {
1504        assert_eq!(editor.text(cx), "fn main() { a:~: }");
1505        editor.undo(&Undo, window, cx);
1506        assert_eq!(editor.text(cx), "fn main() { a: }");
1507    });
1508    executor.run_until_parked();
1509
1510    buffer_a.read_with(cx_a, |buffer, _| {
1511        assert_eq!(buffer.text(), "fn main() { a: }")
1512    });
1513
1514    editor_b.update_in(cx_b, |editor, window, cx| {
1515        assert_eq!(editor.text(cx), "fn main() { a: }");
1516        editor.undo(&Undo, window, cx);
1517        assert_eq!(editor.text(cx), "fn main() { a }");
1518    });
1519    executor.run_until_parked();
1520
1521    buffer_a.read_with(cx_a, |buffer, _| {
1522        assert_eq!(buffer.text(), "fn main() { a }")
1523    });
1524}
1525
1526#[gpui::test(iterations = 10)]
1527async fn test_mutual_editor_inlay_hint_cache_update(
1528    cx_a: &mut TestAppContext,
1529    cx_b: &mut TestAppContext,
1530) {
1531    let mut server = TestServer::start(cx_a.executor()).await;
1532    let executor = cx_a.executor();
1533    let client_a = server.create_client(cx_a, "user_a").await;
1534    let client_b = server.create_client(cx_b, "user_b").await;
1535    server
1536        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1537        .await;
1538    let active_call_a = cx_a.read(ActiveCall::global);
1539    let active_call_b = cx_b.read(ActiveCall::global);
1540
1541    cx_a.update(editor::init);
1542    cx_b.update(editor::init);
1543
1544    cx_a.update(|cx| {
1545        SettingsStore::update_global(cx, |store, cx| {
1546            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1547                settings.defaults.inlay_hints = Some(InlayHintSettings {
1548                    enabled: true,
1549                    show_value_hints: true,
1550                    edit_debounce_ms: 0,
1551                    scroll_debounce_ms: 0,
1552                    show_type_hints: true,
1553                    show_parameter_hints: false,
1554                    show_other_hints: true,
1555                    show_background: false,
1556                    toggle_on_modifiers_press: None,
1557                })
1558            });
1559        });
1560    });
1561    cx_b.update(|cx| {
1562        SettingsStore::update_global(cx, |store, cx| {
1563            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1564                settings.defaults.inlay_hints = Some(InlayHintSettings {
1565                    show_value_hints: true,
1566                    enabled: true,
1567                    edit_debounce_ms: 0,
1568                    scroll_debounce_ms: 0,
1569                    show_type_hints: true,
1570                    show_parameter_hints: false,
1571                    show_other_hints: true,
1572                    show_background: false,
1573                    toggle_on_modifiers_press: None,
1574                })
1575            });
1576        });
1577    });
1578
1579    client_a.language_registry().add(rust_lang());
1580    client_b.language_registry().add(rust_lang());
1581    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1582        "Rust",
1583        FakeLspAdapter {
1584            capabilities: lsp::ServerCapabilities {
1585                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1586                ..Default::default()
1587            },
1588            ..Default::default()
1589        },
1590    );
1591
1592    // Client A opens a project.
1593    client_a
1594        .fs()
1595        .insert_tree(
1596            path!("/a"),
1597            json!({
1598                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1599                "other.rs": "// Test file",
1600            }),
1601        )
1602        .await;
1603    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1604    active_call_a
1605        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1606        .await
1607        .unwrap();
1608    let project_id = active_call_a
1609        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1610        .await
1611        .unwrap();
1612
1613    // Client B joins the project
1614    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1615    active_call_b
1616        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1617        .await
1618        .unwrap();
1619
1620    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
1621    executor.start_waiting();
1622
1623    // The host opens a rust file.
1624    let _buffer_a = project_a
1625        .update(cx_a, |project, cx| {
1626            project.open_local_buffer(path!("/a/main.rs"), cx)
1627        })
1628        .await
1629        .unwrap();
1630    let editor_a = workspace_a
1631        .update_in(cx_a, |workspace, window, cx| {
1632            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1633        })
1634        .await
1635        .unwrap()
1636        .downcast::<Editor>()
1637        .unwrap();
1638
1639    let fake_language_server = fake_language_servers.next().await.unwrap();
1640
1641    // Set up the language server to return an additional inlay hint on each request.
1642    let edits_made = Arc::new(AtomicUsize::new(0));
1643    let closure_edits_made = Arc::clone(&edits_made);
1644    fake_language_server
1645        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1646            let task_edits_made = Arc::clone(&closure_edits_made);
1647            async move {
1648                assert_eq!(
1649                    params.text_document.uri,
1650                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1651                );
1652                let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
1653                Ok(Some(vec![lsp::InlayHint {
1654                    position: lsp::Position::new(0, edits_made as u32),
1655                    label: lsp::InlayHintLabel::String(edits_made.to_string()),
1656                    kind: None,
1657                    text_edits: None,
1658                    tooltip: None,
1659                    padding_left: None,
1660                    padding_right: None,
1661                    data: None,
1662                }]))
1663            }
1664        })
1665        .next()
1666        .await
1667        .unwrap();
1668
1669    executor.run_until_parked();
1670
1671    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
1672    editor_a.update(cx_a, |editor, _| {
1673        assert_eq!(
1674            vec![initial_edit.to_string()],
1675            extract_hint_labels(editor),
1676            "Host should get its first hints when opens an editor"
1677        );
1678    });
1679    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1680    let editor_b = workspace_b
1681        .update_in(cx_b, |workspace, window, cx| {
1682            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1683        })
1684        .await
1685        .unwrap()
1686        .downcast::<Editor>()
1687        .unwrap();
1688
1689    executor.run_until_parked();
1690    editor_b.update(cx_b, |editor, _| {
1691        assert_eq!(
1692            vec![initial_edit.to_string()],
1693            extract_hint_labels(editor),
1694            "Client should get its first hints when opens an editor"
1695        );
1696    });
1697
1698    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1699    editor_b.update_in(cx_b, |editor, window, cx| {
1700        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
1701        editor.handle_input(":", window, cx);
1702    });
1703    cx_b.focus(&editor_b);
1704
1705    executor.run_until_parked();
1706    editor_a.update(cx_a, |editor, _| {
1707        assert_eq!(
1708            vec![after_client_edit.to_string()],
1709            extract_hint_labels(editor),
1710        );
1711    });
1712    editor_b.update(cx_b, |editor, _| {
1713        assert_eq!(
1714            vec![after_client_edit.to_string()],
1715            extract_hint_labels(editor),
1716        );
1717    });
1718
1719    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1720    editor_a.update_in(cx_a, |editor, window, cx| {
1721        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1722        editor.handle_input("a change to increment both buffers' versions", window, cx);
1723    });
1724    cx_a.focus(&editor_a);
1725
1726    executor.run_until_parked();
1727    editor_a.update(cx_a, |editor, _| {
1728        assert_eq!(
1729            vec![after_host_edit.to_string()],
1730            extract_hint_labels(editor),
1731        );
1732    });
1733    editor_b.update(cx_b, |editor, _| {
1734        assert_eq!(
1735            vec![after_host_edit.to_string()],
1736            extract_hint_labels(editor),
1737        );
1738    });
1739
1740    let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1741    fake_language_server
1742        .request::<lsp::request::InlayHintRefreshRequest>(())
1743        .await
1744        .into_response()
1745        .expect("inlay refresh request failed");
1746
1747    executor.run_until_parked();
1748    editor_a.update(cx_a, |editor, _| {
1749        assert_eq!(
1750            vec![after_special_edit_for_refresh.to_string()],
1751            extract_hint_labels(editor),
1752            "Host should react to /refresh LSP request"
1753        );
1754    });
1755    editor_b.update(cx_b, |editor, _| {
1756        assert_eq!(
1757            vec![after_special_edit_for_refresh.to_string()],
1758            extract_hint_labels(editor),
1759            "Guest should get a /refresh LSP request propagated by host"
1760        );
1761    });
1762}
1763
1764#[gpui::test(iterations = 10)]
1765async fn test_inlay_hint_refresh_is_forwarded(
1766    cx_a: &mut TestAppContext,
1767    cx_b: &mut TestAppContext,
1768) {
1769    let mut server = TestServer::start(cx_a.executor()).await;
1770    let executor = cx_a.executor();
1771    let client_a = server.create_client(cx_a, "user_a").await;
1772    let client_b = server.create_client(cx_b, "user_b").await;
1773    server
1774        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1775        .await;
1776    let active_call_a = cx_a.read(ActiveCall::global);
1777    let active_call_b = cx_b.read(ActiveCall::global);
1778
1779    cx_a.update(editor::init);
1780    cx_b.update(editor::init);
1781
1782    cx_a.update(|cx| {
1783        SettingsStore::update_global(cx, |store, cx| {
1784            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1785                settings.defaults.inlay_hints = Some(InlayHintSettings {
1786                    show_value_hints: true,
1787                    enabled: false,
1788                    edit_debounce_ms: 0,
1789                    scroll_debounce_ms: 0,
1790                    show_type_hints: false,
1791                    show_parameter_hints: false,
1792                    show_other_hints: false,
1793                    show_background: false,
1794                    toggle_on_modifiers_press: None,
1795                })
1796            });
1797        });
1798    });
1799    cx_b.update(|cx| {
1800        SettingsStore::update_global(cx, |store, cx| {
1801            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1802                settings.defaults.inlay_hints = Some(InlayHintSettings {
1803                    show_value_hints: true,
1804                    enabled: true,
1805                    edit_debounce_ms: 0,
1806                    scroll_debounce_ms: 0,
1807                    show_type_hints: true,
1808                    show_parameter_hints: true,
1809                    show_other_hints: true,
1810                    show_background: false,
1811                    toggle_on_modifiers_press: None,
1812                })
1813            });
1814        });
1815    });
1816
1817    client_a.language_registry().add(rust_lang());
1818    client_b.language_registry().add(rust_lang());
1819    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1820        "Rust",
1821        FakeLspAdapter {
1822            capabilities: lsp::ServerCapabilities {
1823                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1824                ..Default::default()
1825            },
1826            ..Default::default()
1827        },
1828    );
1829
1830    client_a
1831        .fs()
1832        .insert_tree(
1833            path!("/a"),
1834            json!({
1835                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1836                "other.rs": "// Test file",
1837            }),
1838        )
1839        .await;
1840    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1841    active_call_a
1842        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1843        .await
1844        .unwrap();
1845    let project_id = active_call_a
1846        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1847        .await
1848        .unwrap();
1849
1850    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1851    active_call_b
1852        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1853        .await
1854        .unwrap();
1855
1856    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
1857    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1858
1859    cx_a.background_executor.start_waiting();
1860
1861    let editor_a = workspace_a
1862        .update_in(cx_a, |workspace, window, cx| {
1863            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1864        })
1865        .await
1866        .unwrap()
1867        .downcast::<Editor>()
1868        .unwrap();
1869
1870    let editor_b = workspace_b
1871        .update_in(cx_b, |workspace, window, cx| {
1872            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1873        })
1874        .await
1875        .unwrap()
1876        .downcast::<Editor>()
1877        .unwrap();
1878
1879    let other_hints = Arc::new(AtomicBool::new(false));
1880    let fake_language_server = fake_language_servers.next().await.unwrap();
1881    let closure_other_hints = Arc::clone(&other_hints);
1882    fake_language_server
1883        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1884            let task_other_hints = Arc::clone(&closure_other_hints);
1885            async move {
1886                assert_eq!(
1887                    params.text_document.uri,
1888                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1889                );
1890                let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
1891                let character = if other_hints { 0 } else { 2 };
1892                let label = if other_hints {
1893                    "other hint"
1894                } else {
1895                    "initial hint"
1896                };
1897                Ok(Some(vec![lsp::InlayHint {
1898                    position: lsp::Position::new(0, character),
1899                    label: lsp::InlayHintLabel::String(label.to_string()),
1900                    kind: None,
1901                    text_edits: None,
1902                    tooltip: None,
1903                    padding_left: None,
1904                    padding_right: None,
1905                    data: None,
1906                }]))
1907            }
1908        })
1909        .next()
1910        .await
1911        .unwrap();
1912    executor.finish_waiting();
1913
1914    executor.run_until_parked();
1915    editor_a.update(cx_a, |editor, _| {
1916        assert!(
1917            extract_hint_labels(editor).is_empty(),
1918            "Host should get no hints due to them turned off"
1919        );
1920    });
1921
1922    executor.run_until_parked();
1923    editor_b.update(cx_b, |editor, _| {
1924        assert_eq!(
1925            vec!["initial hint".to_string()],
1926            extract_hint_labels(editor),
1927            "Client should get its first hints when opens an editor"
1928        );
1929    });
1930
1931    other_hints.fetch_or(true, atomic::Ordering::Release);
1932    fake_language_server
1933        .request::<lsp::request::InlayHintRefreshRequest>(())
1934        .await
1935        .into_response()
1936        .expect("inlay refresh request failed");
1937    executor.run_until_parked();
1938    editor_a.update(cx_a, |editor, _| {
1939        assert!(
1940            extract_hint_labels(editor).is_empty(),
1941            "Host should get no hints due to them turned off, even after the /refresh"
1942        );
1943    });
1944
1945    executor.run_until_parked();
1946    editor_b.update(cx_b, |editor, _| {
1947        assert_eq!(
1948            vec!["other hint".to_string()],
1949            extract_hint_labels(editor),
1950            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
1951        );
1952    });
1953}
1954
1955#[gpui::test(iterations = 10)]
1956async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1957    let expected_color = Rgba {
1958        r: 0.33,
1959        g: 0.33,
1960        b: 0.33,
1961        a: 0.33,
1962    };
1963    let mut server = TestServer::start(cx_a.executor()).await;
1964    let executor = cx_a.executor();
1965    let client_a = server.create_client(cx_a, "user_a").await;
1966    let client_b = server.create_client(cx_b, "user_b").await;
1967    server
1968        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1969        .await;
1970    let active_call_a = cx_a.read(ActiveCall::global);
1971    let active_call_b = cx_b.read(ActiveCall::global);
1972
1973    cx_a.update(editor::init);
1974    cx_b.update(editor::init);
1975
1976    cx_a.update(|cx| {
1977        SettingsStore::update_global(cx, |store, cx| {
1978            store.update_user_settings::<EditorSettings>(cx, |settings| {
1979                settings.lsp_document_colors = Some(DocumentColorsRenderMode::None);
1980            });
1981        });
1982    });
1983    cx_b.update(|cx| {
1984        SettingsStore::update_global(cx, |store, cx| {
1985            store.update_user_settings::<EditorSettings>(cx, |settings| {
1986                settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
1987            });
1988        });
1989    });
1990
1991    client_a.language_registry().add(rust_lang());
1992    client_b.language_registry().add(rust_lang());
1993    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1994        "Rust",
1995        FakeLspAdapter {
1996            capabilities: lsp::ServerCapabilities {
1997                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
1998                ..lsp::ServerCapabilities::default()
1999            },
2000            ..FakeLspAdapter::default()
2001        },
2002    );
2003
2004    // Client A opens a project.
2005    client_a
2006        .fs()
2007        .insert_tree(
2008            path!("/a"),
2009            json!({
2010                "main.rs": "fn main() { a }",
2011            }),
2012        )
2013        .await;
2014    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2015    active_call_a
2016        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2017        .await
2018        .unwrap();
2019    let project_id = active_call_a
2020        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2021        .await
2022        .unwrap();
2023
2024    // Client B joins the project
2025    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2026    active_call_b
2027        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2028        .await
2029        .unwrap();
2030
2031    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2032
2033    // The host opens a rust file.
2034    let _buffer_a = project_a
2035        .update(cx_a, |project, cx| {
2036            project.open_local_buffer(path!("/a/main.rs"), cx)
2037        })
2038        .await
2039        .unwrap();
2040    let editor_a = workspace_a
2041        .update_in(cx_a, |workspace, window, cx| {
2042            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2043        })
2044        .await
2045        .unwrap()
2046        .downcast::<Editor>()
2047        .unwrap();
2048
2049    let fake_language_server = fake_language_servers.next().await.unwrap();
2050
2051    let requests_made = Arc::new(AtomicUsize::new(0));
2052    let closure_requests_made = Arc::clone(&requests_made);
2053    let mut color_request_handle = fake_language_server
2054        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
2055            let requests_made = Arc::clone(&closure_requests_made);
2056            async move {
2057                assert_eq!(
2058                    params.text_document.uri,
2059                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2060                );
2061                requests_made.fetch_add(1, atomic::Ordering::Release);
2062                Ok(vec![lsp::ColorInformation {
2063                    range: lsp::Range {
2064                        start: lsp::Position {
2065                            line: 0,
2066                            character: 0,
2067                        },
2068                        end: lsp::Position {
2069                            line: 0,
2070                            character: 1,
2071                        },
2072                    },
2073                    color: lsp::Color {
2074                        red: 0.33,
2075                        green: 0.33,
2076                        blue: 0.33,
2077                        alpha: 0.33,
2078                    },
2079                }])
2080            }
2081        });
2082    executor.run_until_parked();
2083
2084    assert_eq!(
2085        0,
2086        requests_made.load(atomic::Ordering::Acquire),
2087        "Host did not enable document colors, hence should query for none"
2088    );
2089    editor_a.update(cx_a, |editor, cx| {
2090        assert_eq!(
2091            Vec::<Rgba>::new(),
2092            extract_color_inlays(editor, cx),
2093            "No query colors should result in no hints"
2094        );
2095    });
2096
2097    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2098    let editor_b = workspace_b
2099        .update_in(cx_b, |workspace, window, cx| {
2100            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2101        })
2102        .await
2103        .unwrap()
2104        .downcast::<Editor>()
2105        .unwrap();
2106
2107    color_request_handle.next().await.unwrap();
2108    executor.run_until_parked();
2109
2110    assert_eq!(
2111        1,
2112        requests_made.load(atomic::Ordering::Acquire),
2113        "The client opened the file and got its first colors back"
2114    );
2115    editor_b.update(cx_b, |editor, cx| {
2116        assert_eq!(
2117            vec![expected_color],
2118            extract_color_inlays(editor, cx),
2119            "With document colors as inlays, color inlays should be pushed"
2120        );
2121    });
2122
2123    editor_a.update_in(cx_a, |editor, window, cx| {
2124        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
2125        editor.handle_input(":", window, cx);
2126    });
2127    color_request_handle.next().await.unwrap();
2128    executor.run_until_parked();
2129    assert_eq!(
2130        2,
2131        requests_made.load(atomic::Ordering::Acquire),
2132        "After the host edits his file, the client should request the colors again"
2133    );
2134    editor_a.update(cx_a, |editor, cx| {
2135        assert_eq!(
2136            Vec::<Rgba>::new(),
2137            extract_color_inlays(editor, cx),
2138            "Host has no colors still"
2139        );
2140    });
2141    editor_b.update(cx_b, |editor, cx| {
2142        assert_eq!(vec![expected_color], extract_color_inlays(editor, cx),);
2143    });
2144
2145    cx_b.update(|_, cx| {
2146        SettingsStore::update_global(cx, |store, cx| {
2147            store.update_user_settings::<EditorSettings>(cx, |settings| {
2148                settings.lsp_document_colors = Some(DocumentColorsRenderMode::Background);
2149            });
2150        });
2151    });
2152    executor.run_until_parked();
2153    assert_eq!(
2154        2,
2155        requests_made.load(atomic::Ordering::Acquire),
2156        "After the client have changed the colors settings, no extra queries should happen"
2157    );
2158    editor_a.update(cx_a, |editor, cx| {
2159        assert_eq!(
2160            Vec::<Rgba>::new(),
2161            extract_color_inlays(editor, cx),
2162            "Host is unaffected by the client's settings changes"
2163        );
2164    });
2165    editor_b.update(cx_b, |editor, cx| {
2166        assert_eq!(
2167            Vec::<Rgba>::new(),
2168            extract_color_inlays(editor, cx),
2169            "Client should have no colors hints, as in the settings"
2170        );
2171    });
2172
2173    cx_b.update(|_, cx| {
2174        SettingsStore::update_global(cx, |store, cx| {
2175            store.update_user_settings::<EditorSettings>(cx, |settings| {
2176                settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2177            });
2178        });
2179    });
2180    executor.run_until_parked();
2181    assert_eq!(
2182        2,
2183        requests_made.load(atomic::Ordering::Acquire),
2184        "After falling back to colors as inlays, no extra LSP queries are made"
2185    );
2186    editor_a.update(cx_a, |editor, cx| {
2187        assert_eq!(
2188            Vec::<Rgba>::new(),
2189            extract_color_inlays(editor, cx),
2190            "Host is unaffected by the client's settings changes, again"
2191        );
2192    });
2193    editor_b.update(cx_b, |editor, cx| {
2194        assert_eq!(
2195            vec![expected_color],
2196            extract_color_inlays(editor, cx),
2197            "Client should have its color hints back"
2198        );
2199    });
2200
2201    cx_a.update(|_, cx| {
2202        SettingsStore::update_global(cx, |store, cx| {
2203            store.update_user_settings::<EditorSettings>(cx, |settings| {
2204                settings.lsp_document_colors = Some(DocumentColorsRenderMode::Border);
2205            });
2206        });
2207    });
2208    color_request_handle.next().await.unwrap();
2209    executor.run_until_parked();
2210    assert_eq!(
2211        3,
2212        requests_made.load(atomic::Ordering::Acquire),
2213        "After the host enables document colors, another LSP query should be made"
2214    );
2215    editor_a.update(cx_a, |editor, cx| {
2216        assert_eq!(
2217            Vec::<Rgba>::new(),
2218            extract_color_inlays(editor, cx),
2219            "Host did not configure document colors as hints hence gets nothing"
2220        );
2221    });
2222    editor_b.update(cx_b, |editor, cx| {
2223        assert_eq!(
2224            vec![expected_color],
2225            extract_color_inlays(editor, cx),
2226            "Client should be unaffected by the host's settings changes"
2227        );
2228    });
2229}
2230
2231#[gpui::test(iterations = 10)]
2232async fn test_lsp_pull_diagnostics(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2233    let mut server = TestServer::start(cx_a.executor()).await;
2234    let executor = cx_a.executor();
2235    let client_a = server.create_client(cx_a, "user_a").await;
2236    let client_b = server.create_client(cx_b, "user_b").await;
2237    server
2238        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2239        .await;
2240    let active_call_a = cx_a.read(ActiveCall::global);
2241    let active_call_b = cx_b.read(ActiveCall::global);
2242
2243    cx_a.update(editor::init);
2244    cx_b.update(editor::init);
2245
2246    client_a.language_registry().add(rust_lang());
2247    client_b.language_registry().add(rust_lang());
2248    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2249        "Rust",
2250        FakeLspAdapter {
2251            capabilities: lsp::ServerCapabilities {
2252                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
2253                    lsp::DiagnosticOptions {
2254                        identifier: Some("test-pulls".to_string()),
2255                        inter_file_dependencies: true,
2256                        workspace_diagnostics: true,
2257                        work_done_progress_options: lsp::WorkDoneProgressOptions {
2258                            work_done_progress: None,
2259                        },
2260                    },
2261                )),
2262                ..lsp::ServerCapabilities::default()
2263            },
2264            ..FakeLspAdapter::default()
2265        },
2266    );
2267
2268    // Client A opens a project.
2269    client_a
2270        .fs()
2271        .insert_tree(
2272            path!("/a"),
2273            json!({
2274                "main.rs": "fn main() { a }",
2275                "lib.rs": "fn other() {}",
2276            }),
2277        )
2278        .await;
2279    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2280    active_call_a
2281        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2282        .await
2283        .unwrap();
2284    let project_id = active_call_a
2285        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2286        .await
2287        .unwrap();
2288
2289    // Client B joins the project
2290    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2291    active_call_b
2292        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2293        .await
2294        .unwrap();
2295
2296    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2297    executor.start_waiting();
2298
2299    // The host opens a rust file.
2300    let _buffer_a = project_a
2301        .update(cx_a, |project, cx| {
2302            project.open_local_buffer(path!("/a/main.rs"), cx)
2303        })
2304        .await
2305        .unwrap();
2306    let editor_a_main = workspace_a
2307        .update_in(cx_a, |workspace, window, cx| {
2308            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2309        })
2310        .await
2311        .unwrap()
2312        .downcast::<Editor>()
2313        .unwrap();
2314
2315    let fake_language_server = fake_language_servers.next().await.unwrap();
2316    let expected_push_diagnostic_main_message = "pushed main diagnostic";
2317    let expected_push_diagnostic_lib_message = "pushed lib diagnostic";
2318    let expected_pull_diagnostic_main_message = "pulled main diagnostic";
2319    let expected_pull_diagnostic_lib_message = "pulled lib diagnostic";
2320    let expected_workspace_pull_diagnostics_main_message = "pulled workspace main diagnostic";
2321    let expected_workspace_pull_diagnostics_lib_message = "pulled workspace lib diagnostic";
2322
2323    let diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<Option<String>>::new()));
2324    let workspace_diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<String>::new()));
2325    let diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2326    let closure_diagnostics_pulls_made = diagnostics_pulls_made.clone();
2327    let closure_diagnostics_pulls_result_ids = diagnostics_pulls_result_ids.clone();
2328    let mut pull_diagnostics_handle = fake_language_server
2329        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
2330            let requests_made = closure_diagnostics_pulls_made.clone();
2331            let diagnostics_pulls_result_ids = closure_diagnostics_pulls_result_ids.clone();
2332            async move {
2333                let message = if lsp::Url::from_file_path(path!("/a/main.rs")).unwrap()
2334                    == params.text_document.uri
2335                {
2336                    expected_pull_diagnostic_main_message.to_string()
2337                } else if lsp::Url::from_file_path(path!("/a/lib.rs")).unwrap()
2338                    == params.text_document.uri
2339                {
2340                    expected_pull_diagnostic_lib_message.to_string()
2341                } else {
2342                    panic!("Unexpected document: {}", params.text_document.uri)
2343                };
2344                {
2345                    diagnostics_pulls_result_ids
2346                        .lock()
2347                        .await
2348                        .insert(params.previous_result_id);
2349                }
2350                let new_requests_count = requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2351                Ok(lsp::DocumentDiagnosticReportResult::Report(
2352                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
2353                        related_documents: None,
2354                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
2355                            result_id: Some(format!("pull-{new_requests_count}")),
2356                            items: vec![lsp::Diagnostic {
2357                                range: lsp::Range {
2358                                    start: lsp::Position {
2359                                        line: 0,
2360                                        character: 0,
2361                                    },
2362                                    end: lsp::Position {
2363                                        line: 0,
2364                                        character: 2,
2365                                    },
2366                                },
2367                                severity: Some(lsp::DiagnosticSeverity::ERROR),
2368                                message,
2369                                ..lsp::Diagnostic::default()
2370                            }],
2371                        },
2372                    }),
2373                ))
2374            }
2375        });
2376
2377    let workspace_diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2378    let closure_workspace_diagnostics_pulls_made = workspace_diagnostics_pulls_made.clone();
2379    let closure_workspace_diagnostics_pulls_result_ids =
2380        workspace_diagnostics_pulls_result_ids.clone();
2381    let mut workspace_diagnostics_pulls_handle = fake_language_server
2382        .set_request_handler::<lsp::request::WorkspaceDiagnosticRequest, _, _>(
2383        move |params, _| {
2384            let workspace_requests_made = closure_workspace_diagnostics_pulls_made.clone();
2385            let workspace_diagnostics_pulls_result_ids =
2386                closure_workspace_diagnostics_pulls_result_ids.clone();
2387            async move {
2388                let workspace_request_count =
2389                    workspace_requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2390                {
2391                    workspace_diagnostics_pulls_result_ids
2392                        .lock()
2393                        .await
2394                        .extend(params.previous_result_ids.into_iter().map(|id| id.value));
2395                }
2396                Ok(lsp::WorkspaceDiagnosticReportResult::Report(
2397                    lsp::WorkspaceDiagnosticReport {
2398                        items: vec![
2399                            lsp::WorkspaceDocumentDiagnosticReport::Full(
2400                                lsp::WorkspaceFullDocumentDiagnosticReport {
2401                                    uri: lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2402                                    version: None,
2403                                    full_document_diagnostic_report:
2404                                        lsp::FullDocumentDiagnosticReport {
2405                                            result_id: Some(format!(
2406                                                "workspace_{workspace_request_count}"
2407                                            )),
2408                                            items: vec![lsp::Diagnostic {
2409                                                range: lsp::Range {
2410                                                    start: lsp::Position {
2411                                                        line: 0,
2412                                                        character: 1,
2413                                                    },
2414                                                    end: lsp::Position {
2415                                                        line: 0,
2416                                                        character: 3,
2417                                                    },
2418                                                },
2419                                                severity: Some(lsp::DiagnosticSeverity::WARNING),
2420                                                message:
2421                                                    expected_workspace_pull_diagnostics_main_message
2422                                                        .to_string(),
2423                                                ..lsp::Diagnostic::default()
2424                                            }],
2425                                        },
2426                                },
2427                            ),
2428                            lsp::WorkspaceDocumentDiagnosticReport::Full(
2429                                lsp::WorkspaceFullDocumentDiagnosticReport {
2430                                    uri: lsp::Url::from_file_path(path!("/a/lib.rs")).unwrap(),
2431                                    version: None,
2432                                    full_document_diagnostic_report:
2433                                        lsp::FullDocumentDiagnosticReport {
2434                                            result_id: Some(format!(
2435                                                "workspace_{workspace_request_count}"
2436                                            )),
2437                                            items: vec![lsp::Diagnostic {
2438                                                range: lsp::Range {
2439                                                    start: lsp::Position {
2440                                                        line: 0,
2441                                                        character: 1,
2442                                                    },
2443                                                    end: lsp::Position {
2444                                                        line: 0,
2445                                                        character: 3,
2446                                                    },
2447                                                },
2448                                                severity: Some(lsp::DiagnosticSeverity::WARNING),
2449                                                message:
2450                                                    expected_workspace_pull_diagnostics_lib_message
2451                                                        .to_string(),
2452                                                ..lsp::Diagnostic::default()
2453                                            }],
2454                                        },
2455                                },
2456                            ),
2457                        ],
2458                    },
2459                ))
2460            }
2461        },
2462    );
2463
2464    workspace_diagnostics_pulls_handle.next().await.unwrap();
2465    assert_eq!(
2466        1,
2467        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2468        "Workspace diagnostics should be pulled initially on a server startup"
2469    );
2470    pull_diagnostics_handle.next().await.unwrap();
2471    assert_eq!(
2472        1,
2473        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2474        "Host should query pull diagnostics when the editor is opened"
2475    );
2476    executor.run_until_parked();
2477    editor_a_main.update(cx_a, |editor, cx| {
2478        let snapshot = editor.buffer().read(cx).snapshot(cx);
2479        let all_diagnostics = snapshot
2480            .diagnostics_in_range(0..snapshot.len())
2481            .collect::<Vec<_>>();
2482        assert_eq!(
2483            all_diagnostics.len(),
2484            1,
2485            "Expected single diagnostic, but got: {all_diagnostics:?}"
2486        );
2487        let diagnostic = &all_diagnostics[0];
2488        let expected_messages = [
2489            expected_workspace_pull_diagnostics_main_message,
2490            expected_pull_diagnostic_main_message,
2491        ];
2492        assert!(
2493            expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2494            "Expected {expected_messages:?} on the host, but got: {}",
2495            diagnostic.diagnostic.message
2496        );
2497    });
2498
2499    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2500        &lsp::PublishDiagnosticsParams {
2501            uri: lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2502            diagnostics: vec![lsp::Diagnostic {
2503                range: lsp::Range {
2504                    start: lsp::Position {
2505                        line: 0,
2506                        character: 3,
2507                    },
2508                    end: lsp::Position {
2509                        line: 0,
2510                        character: 4,
2511                    },
2512                },
2513                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
2514                message: expected_push_diagnostic_main_message.to_string(),
2515                ..lsp::Diagnostic::default()
2516            }],
2517            version: None,
2518        },
2519    );
2520    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2521        &lsp::PublishDiagnosticsParams {
2522            uri: lsp::Url::from_file_path(path!("/a/lib.rs")).unwrap(),
2523            diagnostics: vec![lsp::Diagnostic {
2524                range: lsp::Range {
2525                    start: lsp::Position {
2526                        line: 0,
2527                        character: 3,
2528                    },
2529                    end: lsp::Position {
2530                        line: 0,
2531                        character: 4,
2532                    },
2533                },
2534                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
2535                message: expected_push_diagnostic_lib_message.to_string(),
2536                ..lsp::Diagnostic::default()
2537            }],
2538            version: None,
2539        },
2540    );
2541    executor.run_until_parked();
2542    editor_a_main.update(cx_a, |editor, cx| {
2543        let snapshot = editor.buffer().read(cx).snapshot(cx);
2544        let all_diagnostics = snapshot
2545            .diagnostics_in_range(0..snapshot.len())
2546            .collect::<Vec<_>>();
2547        assert_eq!(
2548            all_diagnostics.len(),
2549            2,
2550            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
2551        );
2552        let expected_messages = [
2553            expected_workspace_pull_diagnostics_main_message,
2554            expected_pull_diagnostic_main_message,
2555            expected_push_diagnostic_main_message,
2556        ];
2557        for diagnostic in all_diagnostics {
2558            assert!(
2559                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2560                "Expected push and pull messages on the host: {expected_messages:?}, but got: {}",
2561                diagnostic.diagnostic.message
2562            );
2563        }
2564    });
2565
2566    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2567    let editor_b_main = workspace_b
2568        .update_in(cx_b, |workspace, window, cx| {
2569            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2570        })
2571        .await
2572        .unwrap()
2573        .downcast::<Editor>()
2574        .unwrap();
2575
2576    pull_diagnostics_handle.next().await.unwrap();
2577    assert_eq!(
2578        2,
2579        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2580        "Client should query pull diagnostics when its editor is opened"
2581    );
2582    executor.run_until_parked();
2583    assert_eq!(
2584        1,
2585        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2586        "Workspace diagnostics should not be changed as the remote client does not initialize the workspace diagnostics pull"
2587    );
2588    editor_b_main.update(cx_b, |editor, cx| {
2589        let snapshot = editor.buffer().read(cx).snapshot(cx);
2590        let all_diagnostics = snapshot
2591            .diagnostics_in_range(0..snapshot.len())
2592            .collect::<Vec<_>>();
2593        assert_eq!(
2594            all_diagnostics.len(),
2595            2,
2596            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
2597        );
2598
2599        // Despite the workspace diagnostics not re-initialized for the remote client, we can still expect its message synced from the host.
2600        let expected_messages = [
2601            expected_workspace_pull_diagnostics_main_message,
2602            expected_pull_diagnostic_main_message,
2603            expected_push_diagnostic_main_message,
2604        ];
2605        for diagnostic in all_diagnostics {
2606            assert!(
2607                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2608                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
2609                diagnostic.diagnostic.message
2610            );
2611        }
2612    });
2613
2614    let editor_b_lib = workspace_b
2615        .update_in(cx_b, |workspace, window, cx| {
2616            workspace.open_path((worktree_id, "lib.rs"), None, true, window, cx)
2617        })
2618        .await
2619        .unwrap()
2620        .downcast::<Editor>()
2621        .unwrap();
2622
2623    pull_diagnostics_handle.next().await.unwrap();
2624    assert_eq!(
2625        3,
2626        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2627        "Client should query pull diagnostics when its another editor is opened"
2628    );
2629    executor.run_until_parked();
2630    assert_eq!(
2631        1,
2632        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2633        "The remote client still did not anything to trigger the workspace diagnostics pull"
2634    );
2635    editor_b_lib.update(cx_b, |editor, cx| {
2636        let snapshot = editor.buffer().read(cx).snapshot(cx);
2637        let all_diagnostics = snapshot
2638            .diagnostics_in_range(0..snapshot.len())
2639            .collect::<Vec<_>>();
2640        let expected_messages = [
2641            expected_pull_diagnostic_lib_message,
2642            // TODO bug: the pushed diagnostics are not being sent to the client when they open the corresponding buffer.
2643            // expected_push_diagnostic_lib_message,
2644        ];
2645        assert_eq!(
2646            all_diagnostics.len(),
2647            1,
2648            "Expected pull diagnostics, but got: {all_diagnostics:?}"
2649        );
2650        for diagnostic in all_diagnostics {
2651            assert!(
2652                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2653                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
2654                diagnostic.diagnostic.message
2655            );
2656        }
2657    });
2658    {
2659        assert_eq!(
2660            &BTreeSet::from_iter([None]),
2661            diagnostics_pulls_result_ids.lock().await.deref(),
2662            "Initial diagnostics pulls should not reuse any result ids"
2663        );
2664        assert_eq!(
2665            0,
2666            workspace_diagnostics_pulls_result_ids
2667                .lock()
2668                .await
2669                .deref()
2670                .len(),
2671            "After the initial workspace request, opening files should not reuse any result ids"
2672        );
2673    }
2674
2675    editor_b_lib.update_in(cx_b, |editor, window, cx| {
2676        editor.move_to_end(&MoveToEnd, window, cx);
2677        editor.handle_input(":", window, cx);
2678    });
2679    pull_diagnostics_handle.next().await.unwrap();
2680    assert_eq!(
2681        4,
2682        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2683        "Client lib.rs edits should trigger another diagnostics pull for a buffer"
2684    );
2685    workspace_diagnostics_pulls_handle.next().await.unwrap();
2686    assert_eq!(
2687        2,
2688        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2689        "After client lib.rs edits, the workspace diagnostics request should follow"
2690    );
2691    executor.run_until_parked();
2692    {
2693        assert_eq!(
2694            2,
2695            diagnostics_pulls_result_ids.lock().await.len(),
2696            "One new id for a client's lib.rs pull"
2697        );
2698        assert_eq!(
2699            2,
2700            workspace_diagnostics_pulls_result_ids.lock().await.len(),
2701            "Two more entries for 2 currently opened files that previously pulled the diagnostics"
2702        );
2703    }
2704
2705    editor_b_main.update_in(cx_b, |editor, window, cx| {
2706        editor.move_to_end(&MoveToEnd, window, cx);
2707        editor.handle_input(":", window, cx);
2708    });
2709    pull_diagnostics_handle.next().await.unwrap();
2710    pull_diagnostics_handle.next().await.unwrap();
2711    assert_eq!(
2712        6,
2713        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2714        "Client main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
2715    );
2716    workspace_diagnostics_pulls_handle.next().await.unwrap();
2717    assert_eq!(
2718        3,
2719        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2720        "After client main.rs edits, the workspace diagnostics pull should follow"
2721    );
2722    executor.run_until_parked();
2723    {
2724        assert_eq!(
2725            3,
2726            diagnostics_pulls_result_ids.lock().await.len(),
2727            "One new id for a client's main.rs pull"
2728        );
2729        assert_eq!(4, workspace_diagnostics_pulls_result_ids.lock().await.len());
2730    }
2731
2732    editor_a_main.update_in(cx_a, |editor, window, cx| {
2733        editor.move_to_end(&MoveToEnd, window, cx);
2734        editor.handle_input(":", window, cx);
2735    });
2736    pull_diagnostics_handle.next().await.unwrap();
2737    pull_diagnostics_handle.next().await.unwrap();
2738    assert_eq!(
2739        8,
2740        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2741        "Host main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
2742    );
2743    workspace_diagnostics_pulls_handle.next().await.unwrap();
2744    assert_eq!(
2745        4,
2746        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2747        "After host main.rs edits, the workspace diagnostics pull should follow"
2748    );
2749    executor.run_until_parked();
2750    {
2751        assert_eq!(5, diagnostics_pulls_result_ids.lock().await.len());
2752        assert_eq!(6, workspace_diagnostics_pulls_result_ids.lock().await.len());
2753    }
2754
2755    fake_language_server
2756        .request::<lsp::request::WorkspaceDiagnosticRefresh>(())
2757        .await
2758        .into_response()
2759        .expect("workspace diagnostics refresh request failed");
2760    assert_eq!(
2761        8,
2762        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2763        "No single file pulls should happen after the diagnostics refresh server request"
2764    );
2765    workspace_diagnostics_pulls_handle.next().await.unwrap();
2766    assert_eq!(
2767        5,
2768        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2769        "Another workspace diagnostics pull should happen after the diagnostics refresh server request"
2770    );
2771    {
2772        assert_eq!(
2773            5,
2774            diagnostics_pulls_result_ids.lock().await.len(),
2775            "Pulls should not happen hence no extra ids should appear"
2776        );
2777        assert_eq!(8, workspace_diagnostics_pulls_result_ids.lock().await.len(),);
2778    }
2779    editor_b_lib.update(cx_b, |editor, cx| {
2780        let snapshot = editor.buffer().read(cx).snapshot(cx);
2781        let all_diagnostics = snapshot
2782            .diagnostics_in_range(0..snapshot.len())
2783            .collect::<Vec<_>>();
2784        let expected_messages = [
2785            expected_workspace_pull_diagnostics_lib_message,
2786            expected_pull_diagnostic_lib_message,
2787            expected_push_diagnostic_lib_message,
2788        ];
2789        assert_eq!(all_diagnostics.len(), 1);
2790        for diagnostic in &all_diagnostics {
2791            assert!(
2792                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2793                "Unexpected diagnostics: {all_diagnostics:?}"
2794            );
2795        }
2796    });
2797    editor_b_main.update(cx_b, |editor, cx| {
2798        let snapshot = editor.buffer().read(cx).snapshot(cx);
2799        let all_diagnostics = snapshot
2800            .diagnostics_in_range(0..snapshot.len())
2801            .collect::<Vec<_>>();
2802        assert_eq!(all_diagnostics.len(), 2);
2803
2804        let expected_messages = [
2805            expected_workspace_pull_diagnostics_main_message,
2806            expected_pull_diagnostic_main_message,
2807            expected_push_diagnostic_main_message,
2808        ];
2809        for diagnostic in &all_diagnostics {
2810            assert!(
2811                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2812                "Unexpected diagnostics: {all_diagnostics:?}"
2813            );
2814        }
2815    });
2816    editor_a_main.update(cx_a, |editor, cx| {
2817        let snapshot = editor.buffer().read(cx).snapshot(cx);
2818        let all_diagnostics = snapshot
2819            .diagnostics_in_range(0..snapshot.len())
2820            .collect::<Vec<_>>();
2821        assert_eq!(all_diagnostics.len(), 2);
2822        let expected_messages = [
2823            expected_workspace_pull_diagnostics_main_message,
2824            expected_pull_diagnostic_main_message,
2825            expected_push_diagnostic_main_message,
2826        ];
2827        for diagnostic in &all_diagnostics {
2828            assert!(
2829                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2830                "Unexpected diagnostics: {all_diagnostics:?}"
2831            );
2832        }
2833    });
2834}
2835
2836#[gpui::test(iterations = 10)]
2837async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2838    let mut server = TestServer::start(cx_a.executor()).await;
2839    let client_a = server.create_client(cx_a, "user_a").await;
2840    let client_b = server.create_client(cx_b, "user_b").await;
2841    server
2842        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2843        .await;
2844    let active_call_a = cx_a.read(ActiveCall::global);
2845
2846    cx_a.update(editor::init);
2847    cx_b.update(editor::init);
2848    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
2849    let inline_blame_off_settings = Some(InlineBlameSettings {
2850        enabled: false,
2851        delay_ms: None,
2852        min_column: None,
2853        show_commit_summary: false,
2854    });
2855    cx_a.update(|cx| {
2856        SettingsStore::update_global(cx, |store, cx| {
2857            store.update_user_settings::<ProjectSettings>(cx, |settings| {
2858                settings.git.inline_blame = inline_blame_off_settings;
2859            });
2860        });
2861    });
2862    cx_b.update(|cx| {
2863        SettingsStore::update_global(cx, |store, cx| {
2864            store.update_user_settings::<ProjectSettings>(cx, |settings| {
2865                settings.git.inline_blame = inline_blame_off_settings;
2866            });
2867        });
2868    });
2869
2870    client_a
2871        .fs()
2872        .insert_tree(
2873            path!("/my-repo"),
2874            json!({
2875                ".git": {},
2876                "file.txt": "line1\nline2\nline3\nline\n",
2877            }),
2878        )
2879        .await;
2880
2881    let blame = git::blame::Blame {
2882        entries: vec![
2883            blame_entry("1b1b1b", 0..1),
2884            blame_entry("0d0d0d", 1..2),
2885            blame_entry("3a3a3a", 2..3),
2886            blame_entry("4c4c4c", 3..4),
2887        ],
2888        messages: [
2889            ("1b1b1b", "message for idx-0"),
2890            ("0d0d0d", "message for idx-1"),
2891            ("3a3a3a", "message for idx-2"),
2892            ("4c4c4c", "message for idx-3"),
2893        ]
2894        .into_iter()
2895        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
2896        .collect(),
2897        remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
2898    };
2899    client_a.fs().set_blame_for_repo(
2900        Path::new(path!("/my-repo/.git")),
2901        vec![("file.txt".into(), blame)],
2902    );
2903
2904    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
2905    let project_id = active_call_a
2906        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2907        .await
2908        .unwrap();
2909
2910    // Create editor_a
2911    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2912    let editor_a = workspace_a
2913        .update_in(cx_a, |workspace, window, cx| {
2914            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2915        })
2916        .await
2917        .unwrap()
2918        .downcast::<Editor>()
2919        .unwrap();
2920
2921    // Join the project as client B.
2922    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2923    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2924    let editor_b = workspace_b
2925        .update_in(cx_b, |workspace, window, cx| {
2926            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2927        })
2928        .await
2929        .unwrap()
2930        .downcast::<Editor>()
2931        .unwrap();
2932    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
2933        editor_b
2934            .buffer()
2935            .read(cx)
2936            .as_singleton()
2937            .unwrap()
2938            .read(cx)
2939            .remote_id()
2940    });
2941
2942    // client_b now requests git blame for the open buffer
2943    editor_b.update_in(cx_b, |editor_b, window, cx| {
2944        assert!(editor_b.blame().is_none());
2945        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
2946    });
2947
2948    cx_a.executor().run_until_parked();
2949    cx_b.executor().run_until_parked();
2950
2951    editor_b.update(cx_b, |editor_b, cx| {
2952        let blame = editor_b.blame().expect("editor_b should have blame now");
2953        let entries = blame.update(cx, |blame, cx| {
2954            blame
2955                .blame_for_rows(
2956                    &(0..4)
2957                        .map(|row| RowInfo {
2958                            buffer_row: Some(row),
2959                            buffer_id: Some(buffer_id_b),
2960                            ..Default::default()
2961                        })
2962                        .collect::<Vec<_>>(),
2963                    cx,
2964                )
2965                .collect::<Vec<_>>()
2966        });
2967
2968        assert_eq!(
2969            entries,
2970            vec![
2971                Some(blame_entry("1b1b1b", 0..1)),
2972                Some(blame_entry("0d0d0d", 1..2)),
2973                Some(blame_entry("3a3a3a", 2..3)),
2974                Some(blame_entry("4c4c4c", 3..4)),
2975            ]
2976        );
2977
2978        blame.update(cx, |blame, _| {
2979            for (idx, entry) in entries.iter().flatten().enumerate() {
2980                let details = blame.details_for_entry(entry).unwrap();
2981                assert_eq!(details.message, format!("message for idx-{}", idx));
2982                assert_eq!(
2983                    details.permalink.unwrap().to_string(),
2984                    format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
2985                );
2986            }
2987        });
2988    });
2989
2990    // editor_b updates the file, which gets sent to client_a, which updates git blame,
2991    // which gets back to client_b.
2992    editor_b.update_in(cx_b, |editor_b, _, cx| {
2993        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
2994    });
2995
2996    cx_a.executor().run_until_parked();
2997    cx_b.executor().run_until_parked();
2998
2999    editor_b.update(cx_b, |editor_b, cx| {
3000        let blame = editor_b.blame().expect("editor_b should have blame now");
3001        let entries = blame.update(cx, |blame, cx| {
3002            blame
3003                .blame_for_rows(
3004                    &(0..4)
3005                        .map(|row| RowInfo {
3006                            buffer_row: Some(row),
3007                            buffer_id: Some(buffer_id_b),
3008                            ..Default::default()
3009                        })
3010                        .collect::<Vec<_>>(),
3011                    cx,
3012                )
3013                .collect::<Vec<_>>()
3014        });
3015
3016        assert_eq!(
3017            entries,
3018            vec![
3019                None,
3020                Some(blame_entry("0d0d0d", 1..2)),
3021                Some(blame_entry("3a3a3a", 2..3)),
3022                Some(blame_entry("4c4c4c", 3..4)),
3023            ]
3024        );
3025    });
3026
3027    // Now editor_a also updates the file
3028    editor_a.update_in(cx_a, |editor_a, _, cx| {
3029        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
3030    });
3031
3032    cx_a.executor().run_until_parked();
3033    cx_b.executor().run_until_parked();
3034
3035    editor_b.update(cx_b, |editor_b, cx| {
3036        let blame = editor_b.blame().expect("editor_b should have blame now");
3037        let entries = blame.update(cx, |blame, cx| {
3038            blame
3039                .blame_for_rows(
3040                    &(0..4)
3041                        .map(|row| RowInfo {
3042                            buffer_row: Some(row),
3043                            buffer_id: Some(buffer_id_b),
3044                            ..Default::default()
3045                        })
3046                        .collect::<Vec<_>>(),
3047                    cx,
3048                )
3049                .collect::<Vec<_>>()
3050        });
3051
3052        assert_eq!(
3053            entries,
3054            vec![
3055                None,
3056                None,
3057                Some(blame_entry("3a3a3a", 2..3)),
3058                Some(blame_entry("4c4c4c", 3..4)),
3059            ]
3060        );
3061    });
3062}
3063
3064#[gpui::test(iterations = 30)]
3065async fn test_collaborating_with_editorconfig(
3066    cx_a: &mut TestAppContext,
3067    cx_b: &mut TestAppContext,
3068) {
3069    let mut server = TestServer::start(cx_a.executor()).await;
3070    let client_a = server.create_client(cx_a, "user_a").await;
3071    let client_b = server.create_client(cx_b, "user_b").await;
3072    server
3073        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3074        .await;
3075    let active_call_a = cx_a.read(ActiveCall::global);
3076
3077    cx_b.update(editor::init);
3078
3079    // Set up a fake language server.
3080    client_a.language_registry().add(rust_lang());
3081    client_a
3082        .fs()
3083        .insert_tree(
3084            path!("/a"),
3085            json!({
3086                "src": {
3087                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
3088                    "other_mod": {
3089                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
3090                        ".editorconfig": "",
3091                    },
3092                },
3093                ".editorconfig": "[*]\ntab_width = 2\n",
3094            }),
3095        )
3096        .await;
3097    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
3098    let project_id = active_call_a
3099        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3100        .await
3101        .unwrap();
3102    let main_buffer_a = project_a
3103        .update(cx_a, |p, cx| {
3104            p.open_buffer((worktree_id, "src/main.rs"), cx)
3105        })
3106        .await
3107        .unwrap();
3108    let other_buffer_a = project_a
3109        .update(cx_a, |p, cx| {
3110            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
3111        })
3112        .await
3113        .unwrap();
3114    let cx_a = cx_a.add_empty_window();
3115    let main_editor_a = cx_a.new_window_entity(|window, cx| {
3116        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
3117    });
3118    let other_editor_a = cx_a.new_window_entity(|window, cx| {
3119        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
3120    });
3121    let mut main_editor_cx_a = EditorTestContext {
3122        cx: cx_a.clone(),
3123        window: cx_a.window_handle(),
3124        editor: main_editor_a,
3125        assertion_cx: AssertionContextManager::new(),
3126    };
3127    let mut other_editor_cx_a = EditorTestContext {
3128        cx: cx_a.clone(),
3129        window: cx_a.window_handle(),
3130        editor: other_editor_a,
3131        assertion_cx: AssertionContextManager::new(),
3132    };
3133
3134    // Join the project as client B.
3135    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3136    let main_buffer_b = project_b
3137        .update(cx_b, |p, cx| {
3138            p.open_buffer((worktree_id, "src/main.rs"), cx)
3139        })
3140        .await
3141        .unwrap();
3142    let other_buffer_b = project_b
3143        .update(cx_b, |p, cx| {
3144            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
3145        })
3146        .await
3147        .unwrap();
3148    let cx_b = cx_b.add_empty_window();
3149    let main_editor_b = cx_b.new_window_entity(|window, cx| {
3150        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
3151    });
3152    let other_editor_b = cx_b.new_window_entity(|window, cx| {
3153        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
3154    });
3155    let mut main_editor_cx_b = EditorTestContext {
3156        cx: cx_b.clone(),
3157        window: cx_b.window_handle(),
3158        editor: main_editor_b,
3159        assertion_cx: AssertionContextManager::new(),
3160    };
3161    let mut other_editor_cx_b = EditorTestContext {
3162        cx: cx_b.clone(),
3163        window: cx_b.window_handle(),
3164        editor: other_editor_b,
3165        assertion_cx: AssertionContextManager::new(),
3166    };
3167
3168    let initial_main = indoc! {"
3169ˇmod other;
3170fn main() { let foo = other::foo(); }"};
3171    let initial_other = indoc! {"
3172ˇpub fn foo() -> usize {
3173    4
3174}"};
3175
3176    let first_tabbed_main = indoc! {"
3177  ˇmod other;
3178fn main() { let foo = other::foo(); }"};
3179    tab_undo_assert(
3180        &mut main_editor_cx_a,
3181        &mut main_editor_cx_b,
3182        initial_main,
3183        first_tabbed_main,
3184        true,
3185    );
3186    tab_undo_assert(
3187        &mut main_editor_cx_a,
3188        &mut main_editor_cx_b,
3189        initial_main,
3190        first_tabbed_main,
3191        false,
3192    );
3193
3194    let first_tabbed_other = indoc! {"
3195  ˇpub fn foo() -> usize {
3196    4
3197}"};
3198    tab_undo_assert(
3199        &mut other_editor_cx_a,
3200        &mut other_editor_cx_b,
3201        initial_other,
3202        first_tabbed_other,
3203        true,
3204    );
3205    tab_undo_assert(
3206        &mut other_editor_cx_a,
3207        &mut other_editor_cx_b,
3208        initial_other,
3209        first_tabbed_other,
3210        false,
3211    );
3212
3213    client_a
3214        .fs()
3215        .atomic_write(
3216            PathBuf::from(path!("/a/src/.editorconfig")),
3217            "[*]\ntab_width = 3\n".to_owned(),
3218        )
3219        .await
3220        .unwrap();
3221    cx_a.run_until_parked();
3222    cx_b.run_until_parked();
3223
3224    let second_tabbed_main = indoc! {"
3225   ˇmod other;
3226fn main() { let foo = other::foo(); }"};
3227    tab_undo_assert(
3228        &mut main_editor_cx_a,
3229        &mut main_editor_cx_b,
3230        initial_main,
3231        second_tabbed_main,
3232        true,
3233    );
3234    tab_undo_assert(
3235        &mut main_editor_cx_a,
3236        &mut main_editor_cx_b,
3237        initial_main,
3238        second_tabbed_main,
3239        false,
3240    );
3241
3242    let second_tabbed_other = indoc! {"
3243   ˇpub fn foo() -> usize {
3244    4
3245}"};
3246    tab_undo_assert(
3247        &mut other_editor_cx_a,
3248        &mut other_editor_cx_b,
3249        initial_other,
3250        second_tabbed_other,
3251        true,
3252    );
3253    tab_undo_assert(
3254        &mut other_editor_cx_a,
3255        &mut other_editor_cx_b,
3256        initial_other,
3257        second_tabbed_other,
3258        false,
3259    );
3260
3261    let editorconfig_buffer_b = project_b
3262        .update(cx_b, |p, cx| {
3263            p.open_buffer((worktree_id, "src/other_mod/.editorconfig"), cx)
3264        })
3265        .await
3266        .unwrap();
3267    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
3268        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
3269    });
3270    project_b
3271        .update(cx_b, |project, cx| {
3272            project.save_buffer(editorconfig_buffer_b.clone(), cx)
3273        })
3274        .await
3275        .unwrap();
3276    cx_a.run_until_parked();
3277    cx_b.run_until_parked();
3278
3279    tab_undo_assert(
3280        &mut main_editor_cx_a,
3281        &mut main_editor_cx_b,
3282        initial_main,
3283        second_tabbed_main,
3284        true,
3285    );
3286    tab_undo_assert(
3287        &mut main_editor_cx_a,
3288        &mut main_editor_cx_b,
3289        initial_main,
3290        second_tabbed_main,
3291        false,
3292    );
3293
3294    let third_tabbed_other = indoc! {"
3295      ˇpub fn foo() -> usize {
3296    4
3297}"};
3298    tab_undo_assert(
3299        &mut other_editor_cx_a,
3300        &mut other_editor_cx_b,
3301        initial_other,
3302        third_tabbed_other,
3303        true,
3304    );
3305
3306    tab_undo_assert(
3307        &mut other_editor_cx_a,
3308        &mut other_editor_cx_b,
3309        initial_other,
3310        third_tabbed_other,
3311        false,
3312    );
3313}
3314
3315#[gpui::test]
3316async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3317    let executor = cx_a.executor();
3318    let mut server = TestServer::start(executor.clone()).await;
3319    let client_a = server.create_client(cx_a, "user_a").await;
3320    let client_b = server.create_client(cx_b, "user_b").await;
3321    server
3322        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3323        .await;
3324    let active_call_a = cx_a.read(ActiveCall::global);
3325    let active_call_b = cx_b.read(ActiveCall::global);
3326    cx_a.update(editor::init);
3327    cx_b.update(editor::init);
3328    client_a
3329        .fs()
3330        .insert_tree(
3331            "/a",
3332            json!({
3333                "test.txt": "one\ntwo\nthree\nfour\nfive",
3334            }),
3335        )
3336        .await;
3337    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3338    let project_path = ProjectPath {
3339        worktree_id,
3340        path: Arc::from(Path::new(&"test.txt")),
3341    };
3342    let abs_path = project_a.read_with(cx_a, |project, cx| {
3343        project
3344            .absolute_path(&project_path, cx)
3345            .map(|path_buf| Arc::from(path_buf.to_owned()))
3346            .unwrap()
3347    });
3348
3349    active_call_a
3350        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
3351        .await
3352        .unwrap();
3353    let project_id = active_call_a
3354        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3355        .await
3356        .unwrap();
3357    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3358    active_call_b
3359        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
3360        .await
3361        .unwrap();
3362    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3363    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3364
3365    // Client A opens an editor.
3366    let editor_a = workspace_a
3367        .update_in(cx_a, |workspace, window, cx| {
3368            workspace.open_path(project_path.clone(), None, true, window, cx)
3369        })
3370        .await
3371        .unwrap()
3372        .downcast::<Editor>()
3373        .unwrap();
3374
3375    // Client B opens same editor as A.
3376    let editor_b = workspace_b
3377        .update_in(cx_b, |workspace, window, cx| {
3378            workspace.open_path(project_path.clone(), None, true, window, cx)
3379        })
3380        .await
3381        .unwrap()
3382        .downcast::<Editor>()
3383        .unwrap();
3384
3385    cx_a.run_until_parked();
3386    cx_b.run_until_parked();
3387
3388    // Client A adds breakpoint on line (1)
3389    editor_a.update_in(cx_a, |editor, window, cx| {
3390        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3391    });
3392
3393    cx_a.run_until_parked();
3394    cx_b.run_until_parked();
3395
3396    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3397        editor
3398            .breakpoint_store()
3399            .clone()
3400            .unwrap()
3401            .read(cx)
3402            .all_source_breakpoints(cx)
3403            .clone()
3404    });
3405    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3406        editor
3407            .breakpoint_store()
3408            .clone()
3409            .unwrap()
3410            .read(cx)
3411            .all_source_breakpoints(cx)
3412            .clone()
3413    });
3414
3415    assert_eq!(1, breakpoints_a.len());
3416    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
3417    assert_eq!(breakpoints_a, breakpoints_b);
3418
3419    // Client B adds breakpoint on line(2)
3420    editor_b.update_in(cx_b, |editor, window, cx| {
3421        editor.move_down(&editor::actions::MoveDown, window, cx);
3422        editor.move_down(&editor::actions::MoveDown, window, cx);
3423        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3424    });
3425
3426    cx_a.run_until_parked();
3427    cx_b.run_until_parked();
3428
3429    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3430        editor
3431            .breakpoint_store()
3432            .clone()
3433            .unwrap()
3434            .read(cx)
3435            .all_source_breakpoints(cx)
3436            .clone()
3437    });
3438    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3439        editor
3440            .breakpoint_store()
3441            .clone()
3442            .unwrap()
3443            .read(cx)
3444            .all_source_breakpoints(cx)
3445            .clone()
3446    });
3447
3448    assert_eq!(1, breakpoints_a.len());
3449    assert_eq!(breakpoints_a, breakpoints_b);
3450    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
3451
3452    // Client A removes last added breakpoint from client B
3453    editor_a.update_in(cx_a, |editor, window, cx| {
3454        editor.move_down(&editor::actions::MoveDown, window, cx);
3455        editor.move_down(&editor::actions::MoveDown, window, cx);
3456        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3457    });
3458
3459    cx_a.run_until_parked();
3460    cx_b.run_until_parked();
3461
3462    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3463        editor
3464            .breakpoint_store()
3465            .clone()
3466            .unwrap()
3467            .read(cx)
3468            .all_source_breakpoints(cx)
3469            .clone()
3470    });
3471    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3472        editor
3473            .breakpoint_store()
3474            .clone()
3475            .unwrap()
3476            .read(cx)
3477            .all_source_breakpoints(cx)
3478            .clone()
3479    });
3480
3481    assert_eq!(1, breakpoints_a.len());
3482    assert_eq!(breakpoints_a, breakpoints_b);
3483    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
3484
3485    // Client B removes first added breakpoint by client A
3486    editor_b.update_in(cx_b, |editor, window, cx| {
3487        editor.move_up(&editor::actions::MoveUp, window, cx);
3488        editor.move_up(&editor::actions::MoveUp, window, cx);
3489        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3490    });
3491
3492    cx_a.run_until_parked();
3493    cx_b.run_until_parked();
3494
3495    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3496        editor
3497            .breakpoint_store()
3498            .clone()
3499            .unwrap()
3500            .read(cx)
3501            .all_source_breakpoints(cx)
3502            .clone()
3503    });
3504    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3505        editor
3506            .breakpoint_store()
3507            .clone()
3508            .unwrap()
3509            .read(cx)
3510            .all_source_breakpoints(cx)
3511            .clone()
3512    });
3513
3514    assert_eq!(0, breakpoints_a.len());
3515    assert_eq!(breakpoints_a, breakpoints_b);
3516}
3517
3518#[gpui::test]
3519async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3520    let mut server = TestServer::start(cx_a.executor()).await;
3521    let client_a = server.create_client(cx_a, "user_a").await;
3522    let client_b = server.create_client(cx_b, "user_b").await;
3523    server
3524        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3525        .await;
3526    let active_call_a = cx_a.read(ActiveCall::global);
3527    let active_call_b = cx_b.read(ActiveCall::global);
3528
3529    cx_a.update(editor::init);
3530    cx_b.update(editor::init);
3531
3532    client_a.language_registry().add(rust_lang());
3533    client_b.language_registry().add(rust_lang());
3534    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
3535        "Rust",
3536        FakeLspAdapter {
3537            name: RUST_ANALYZER_NAME,
3538            ..FakeLspAdapter::default()
3539        },
3540    );
3541
3542    client_a
3543        .fs()
3544        .insert_tree(
3545            path!("/a"),
3546            json!({
3547                "main.rs": "fn main() {}",
3548            }),
3549        )
3550        .await;
3551    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
3552    active_call_a
3553        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
3554        .await
3555        .unwrap();
3556    let project_id = active_call_a
3557        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3558        .await
3559        .unwrap();
3560
3561    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3562    active_call_b
3563        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
3564        .await
3565        .unwrap();
3566
3567    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3568    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3569
3570    let editor_a = workspace_a
3571        .update_in(cx_a, |workspace, window, cx| {
3572            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
3573        })
3574        .await
3575        .unwrap()
3576        .downcast::<Editor>()
3577        .unwrap();
3578
3579    let editor_b = workspace_b
3580        .update_in(cx_b, |workspace, window, cx| {
3581            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
3582        })
3583        .await
3584        .unwrap()
3585        .downcast::<Editor>()
3586        .unwrap();
3587
3588    let fake_language_server = fake_language_servers.next().await.unwrap();
3589
3590    // host
3591    let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
3592        |params, _| async move {
3593            assert_eq!(
3594                params.text_document.uri,
3595                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
3596            );
3597            assert_eq!(params.position, lsp::Position::new(0, 0));
3598            Ok(Some(ExpandedMacro {
3599                name: "test_macro_name".to_string(),
3600                expansion: "test_macro_expansion on the host".to_string(),
3601            }))
3602        },
3603    );
3604
3605    editor_a.update_in(cx_a, |editor, window, cx| {
3606        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
3607    });
3608    expand_request_a.next().await.unwrap();
3609    cx_a.run_until_parked();
3610
3611    workspace_a.update(cx_a, |workspace, cx| {
3612        workspace.active_pane().update(cx, |pane, cx| {
3613            assert_eq!(
3614                pane.items_len(),
3615                2,
3616                "Should have added a macro expansion to the host's pane"
3617            );
3618            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
3619            new_editor.update(cx, |editor, cx| {
3620                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
3621            });
3622        })
3623    });
3624
3625    // client
3626    let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
3627        |params, _| async move {
3628            assert_eq!(
3629                params.text_document.uri,
3630                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
3631            );
3632            assert_eq!(
3633                params.position,
3634                lsp::Position::new(0, 12),
3635                "editor_b has selected the entire text and should query for a different position"
3636            );
3637            Ok(Some(ExpandedMacro {
3638                name: "test_macro_name".to_string(),
3639                expansion: "test_macro_expansion on the client".to_string(),
3640            }))
3641        },
3642    );
3643
3644    editor_b.update_in(cx_b, |editor, window, cx| {
3645        editor.select_all(&SelectAll, window, cx);
3646        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
3647    });
3648    expand_request_b.next().await.unwrap();
3649    cx_b.run_until_parked();
3650
3651    workspace_b.update(cx_b, |workspace, cx| {
3652        workspace.active_pane().update(cx, |pane, cx| {
3653            assert_eq!(
3654                pane.items_len(),
3655                2,
3656                "Should have added a macro expansion to the client's pane"
3657            );
3658            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
3659            new_editor.update(cx, |editor, cx| {
3660                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
3661            });
3662        })
3663    });
3664}
3665
3666#[track_caller]
3667fn tab_undo_assert(
3668    cx_a: &mut EditorTestContext,
3669    cx_b: &mut EditorTestContext,
3670    expected_initial: &str,
3671    expected_tabbed: &str,
3672    a_tabs: bool,
3673) {
3674    cx_a.assert_editor_state(expected_initial);
3675    cx_b.assert_editor_state(expected_initial);
3676
3677    if a_tabs {
3678        cx_a.update_editor(|editor, window, cx| {
3679            editor.tab(&editor::actions::Tab, window, cx);
3680        });
3681    } else {
3682        cx_b.update_editor(|editor, window, cx| {
3683            editor.tab(&editor::actions::Tab, window, cx);
3684        });
3685    }
3686
3687    cx_a.run_until_parked();
3688    cx_b.run_until_parked();
3689
3690    cx_a.assert_editor_state(expected_tabbed);
3691    cx_b.assert_editor_state(expected_tabbed);
3692
3693    if a_tabs {
3694        cx_a.update_editor(|editor, window, cx| {
3695            editor.undo(&editor::actions::Undo, window, cx);
3696        });
3697    } else {
3698        cx_b.update_editor(|editor, window, cx| {
3699            editor.undo(&editor::actions::Undo, window, cx);
3700        });
3701    }
3702    cx_a.run_until_parked();
3703    cx_b.run_until_parked();
3704    cx_a.assert_editor_state(expected_initial);
3705    cx_b.assert_editor_state(expected_initial);
3706}
3707
3708fn extract_hint_labels(editor: &Editor) -> Vec<String> {
3709    let mut labels = Vec::new();
3710    for hint in editor.inlay_hint_cache().hints() {
3711        match hint.label {
3712            project::InlayHintLabel::String(s) => labels.push(s),
3713            _ => unreachable!(),
3714        }
3715    }
3716    labels
3717}
3718
3719#[track_caller]
3720fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
3721    editor
3722        .all_inlays(cx)
3723        .into_iter()
3724        .filter_map(|inlay| inlay.get_color())
3725        .map(Rgba::from)
3726        .collect()
3727}
3728
3729fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
3730    git::blame::BlameEntry {
3731        sha: sha.parse().unwrap(),
3732        range,
3733        ..Default::default()
3734    }
3735}