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!(
2660            diagnostics_pulls_result_ids.lock().await.len() > 0,
2661            "Initial diagnostics pulls should report None at least"
2662        );
2663        assert_eq!(
2664            0,
2665            workspace_diagnostics_pulls_result_ids
2666                .lock()
2667                .await
2668                .deref()
2669                .len(),
2670            "After the initial workspace request, opening files should not reuse any result ids"
2671        );
2672    }
2673
2674    editor_b_lib.update_in(cx_b, |editor, window, cx| {
2675        editor.move_to_end(&MoveToEnd, window, cx);
2676        editor.handle_input(":", window, cx);
2677    });
2678    pull_diagnostics_handle.next().await.unwrap();
2679    assert_eq!(
2680        4,
2681        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2682        "Client lib.rs edits should trigger another diagnostics pull for a buffer"
2683    );
2684    workspace_diagnostics_pulls_handle.next().await.unwrap();
2685    assert_eq!(
2686        2,
2687        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2688        "After client lib.rs edits, the workspace diagnostics request should follow"
2689    );
2690    executor.run_until_parked();
2691
2692    editor_b_main.update_in(cx_b, |editor, window, cx| {
2693        editor.move_to_end(&MoveToEnd, window, cx);
2694        editor.handle_input(":", window, cx);
2695    });
2696    pull_diagnostics_handle.next().await.unwrap();
2697    pull_diagnostics_handle.next().await.unwrap();
2698    assert_eq!(
2699        6,
2700        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2701        "Client main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
2702    );
2703    workspace_diagnostics_pulls_handle.next().await.unwrap();
2704    assert_eq!(
2705        3,
2706        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2707        "After client main.rs edits, the workspace diagnostics pull should follow"
2708    );
2709    executor.run_until_parked();
2710
2711    editor_a_main.update_in(cx_a, |editor, window, cx| {
2712        editor.move_to_end(&MoveToEnd, window, cx);
2713        editor.handle_input(":", window, cx);
2714    });
2715    pull_diagnostics_handle.next().await.unwrap();
2716    pull_diagnostics_handle.next().await.unwrap();
2717    assert_eq!(
2718        8,
2719        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2720        "Host main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
2721    );
2722    workspace_diagnostics_pulls_handle.next().await.unwrap();
2723    assert_eq!(
2724        4,
2725        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2726        "After host main.rs edits, the workspace diagnostics pull should follow"
2727    );
2728    executor.run_until_parked();
2729    let diagnostic_pulls_result_ids = diagnostics_pulls_result_ids.lock().await.len();
2730    let workspace_pulls_result_ids = workspace_diagnostics_pulls_result_ids.lock().await.len();
2731    {
2732        assert!(
2733            diagnostic_pulls_result_ids > 1,
2734            "Should have sent result ids when pulling diagnostics"
2735        );
2736        assert!(
2737            workspace_pulls_result_ids > 1,
2738            "Should have sent result ids when pulling workspace diagnostics"
2739        );
2740    }
2741
2742    fake_language_server
2743        .request::<lsp::request::WorkspaceDiagnosticRefresh>(())
2744        .await
2745        .into_response()
2746        .expect("workspace diagnostics refresh request failed");
2747    assert_eq!(
2748        8,
2749        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2750        "No single file pulls should happen after the diagnostics refresh server request"
2751    );
2752    workspace_diagnostics_pulls_handle.next().await.unwrap();
2753    assert_eq!(
2754        5,
2755        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2756        "Another workspace diagnostics pull should happen after the diagnostics refresh server request"
2757    );
2758    {
2759        assert!(
2760            diagnostics_pulls_result_ids.lock().await.len() == diagnostic_pulls_result_ids,
2761            "Pulls should not happen hence no extra ids should appear"
2762        );
2763        assert!(
2764            workspace_diagnostics_pulls_result_ids.lock().await.len() > workspace_pulls_result_ids,
2765            "More workspace diagnostics should be pulled"
2766        );
2767    }
2768    editor_b_lib.update(cx_b, |editor, cx| {
2769        let snapshot = editor.buffer().read(cx).snapshot(cx);
2770        let all_diagnostics = snapshot
2771            .diagnostics_in_range(0..snapshot.len())
2772            .collect::<Vec<_>>();
2773        let expected_messages = [
2774            expected_workspace_pull_diagnostics_lib_message,
2775            expected_pull_diagnostic_lib_message,
2776            expected_push_diagnostic_lib_message,
2777        ];
2778        assert_eq!(all_diagnostics.len(), 1);
2779        for diagnostic in &all_diagnostics {
2780            assert!(
2781                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2782                "Unexpected diagnostics: {all_diagnostics:?}"
2783            );
2784        }
2785    });
2786    editor_b_main.update(cx_b, |editor, cx| {
2787        let snapshot = editor.buffer().read(cx).snapshot(cx);
2788        let all_diagnostics = snapshot
2789            .diagnostics_in_range(0..snapshot.len())
2790            .collect::<Vec<_>>();
2791        assert_eq!(all_diagnostics.len(), 2);
2792
2793        let expected_messages = [
2794            expected_workspace_pull_diagnostics_main_message,
2795            expected_pull_diagnostic_main_message,
2796            expected_push_diagnostic_main_message,
2797        ];
2798        for diagnostic in &all_diagnostics {
2799            assert!(
2800                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2801                "Unexpected diagnostics: {all_diagnostics:?}"
2802            );
2803        }
2804    });
2805    editor_a_main.update(cx_a, |editor, cx| {
2806        let snapshot = editor.buffer().read(cx).snapshot(cx);
2807        let all_diagnostics = snapshot
2808            .diagnostics_in_range(0..snapshot.len())
2809            .collect::<Vec<_>>();
2810        assert_eq!(all_diagnostics.len(), 2);
2811        let expected_messages = [
2812            expected_workspace_pull_diagnostics_main_message,
2813            expected_pull_diagnostic_main_message,
2814            expected_push_diagnostic_main_message,
2815        ];
2816        for diagnostic in &all_diagnostics {
2817            assert!(
2818                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2819                "Unexpected diagnostics: {all_diagnostics:?}"
2820            );
2821        }
2822    });
2823}
2824
2825#[gpui::test(iterations = 10)]
2826async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2827    let mut server = TestServer::start(cx_a.executor()).await;
2828    let client_a = server.create_client(cx_a, "user_a").await;
2829    let client_b = server.create_client(cx_b, "user_b").await;
2830    server
2831        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2832        .await;
2833    let active_call_a = cx_a.read(ActiveCall::global);
2834
2835    cx_a.update(editor::init);
2836    cx_b.update(editor::init);
2837    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
2838    let inline_blame_off_settings = Some(InlineBlameSettings {
2839        enabled: false,
2840        delay_ms: None,
2841        min_column: None,
2842        show_commit_summary: false,
2843    });
2844    cx_a.update(|cx| {
2845        SettingsStore::update_global(cx, |store, cx| {
2846            store.update_user_settings::<ProjectSettings>(cx, |settings| {
2847                settings.git.inline_blame = inline_blame_off_settings;
2848            });
2849        });
2850    });
2851    cx_b.update(|cx| {
2852        SettingsStore::update_global(cx, |store, cx| {
2853            store.update_user_settings::<ProjectSettings>(cx, |settings| {
2854                settings.git.inline_blame = inline_blame_off_settings;
2855            });
2856        });
2857    });
2858
2859    client_a
2860        .fs()
2861        .insert_tree(
2862            path!("/my-repo"),
2863            json!({
2864                ".git": {},
2865                "file.txt": "line1\nline2\nline3\nline\n",
2866            }),
2867        )
2868        .await;
2869
2870    let blame = git::blame::Blame {
2871        entries: vec![
2872            blame_entry("1b1b1b", 0..1),
2873            blame_entry("0d0d0d", 1..2),
2874            blame_entry("3a3a3a", 2..3),
2875            blame_entry("4c4c4c", 3..4),
2876        ],
2877        messages: [
2878            ("1b1b1b", "message for idx-0"),
2879            ("0d0d0d", "message for idx-1"),
2880            ("3a3a3a", "message for idx-2"),
2881            ("4c4c4c", "message for idx-3"),
2882        ]
2883        .into_iter()
2884        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
2885        .collect(),
2886        remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
2887    };
2888    client_a.fs().set_blame_for_repo(
2889        Path::new(path!("/my-repo/.git")),
2890        vec![("file.txt".into(), blame)],
2891    );
2892
2893    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
2894    let project_id = active_call_a
2895        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2896        .await
2897        .unwrap();
2898
2899    // Create editor_a
2900    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2901    let editor_a = workspace_a
2902        .update_in(cx_a, |workspace, window, cx| {
2903            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2904        })
2905        .await
2906        .unwrap()
2907        .downcast::<Editor>()
2908        .unwrap();
2909
2910    // Join the project as client B.
2911    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2912    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2913    let editor_b = workspace_b
2914        .update_in(cx_b, |workspace, window, cx| {
2915            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2916        })
2917        .await
2918        .unwrap()
2919        .downcast::<Editor>()
2920        .unwrap();
2921    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
2922        editor_b
2923            .buffer()
2924            .read(cx)
2925            .as_singleton()
2926            .unwrap()
2927            .read(cx)
2928            .remote_id()
2929    });
2930
2931    // client_b now requests git blame for the open buffer
2932    editor_b.update_in(cx_b, |editor_b, window, cx| {
2933        assert!(editor_b.blame().is_none());
2934        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
2935    });
2936
2937    cx_a.executor().run_until_parked();
2938    cx_b.executor().run_until_parked();
2939
2940    editor_b.update(cx_b, |editor_b, cx| {
2941        let blame = editor_b.blame().expect("editor_b should have blame now");
2942        let entries = blame.update(cx, |blame, cx| {
2943            blame
2944                .blame_for_rows(
2945                    &(0..4)
2946                        .map(|row| RowInfo {
2947                            buffer_row: Some(row),
2948                            buffer_id: Some(buffer_id_b),
2949                            ..Default::default()
2950                        })
2951                        .collect::<Vec<_>>(),
2952                    cx,
2953                )
2954                .collect::<Vec<_>>()
2955        });
2956
2957        assert_eq!(
2958            entries,
2959            vec![
2960                Some(blame_entry("1b1b1b", 0..1)),
2961                Some(blame_entry("0d0d0d", 1..2)),
2962                Some(blame_entry("3a3a3a", 2..3)),
2963                Some(blame_entry("4c4c4c", 3..4)),
2964            ]
2965        );
2966
2967        blame.update(cx, |blame, _| {
2968            for (idx, entry) in entries.iter().flatten().enumerate() {
2969                let details = blame.details_for_entry(entry).unwrap();
2970                assert_eq!(details.message, format!("message for idx-{}", idx));
2971                assert_eq!(
2972                    details.permalink.unwrap().to_string(),
2973                    format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
2974                );
2975            }
2976        });
2977    });
2978
2979    // editor_b updates the file, which gets sent to client_a, which updates git blame,
2980    // which gets back to client_b.
2981    editor_b.update_in(cx_b, |editor_b, _, cx| {
2982        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
2983    });
2984
2985    cx_a.executor().run_until_parked();
2986    cx_b.executor().run_until_parked();
2987
2988    editor_b.update(cx_b, |editor_b, cx| {
2989        let blame = editor_b.blame().expect("editor_b should have blame now");
2990        let entries = blame.update(cx, |blame, cx| {
2991            blame
2992                .blame_for_rows(
2993                    &(0..4)
2994                        .map(|row| RowInfo {
2995                            buffer_row: Some(row),
2996                            buffer_id: Some(buffer_id_b),
2997                            ..Default::default()
2998                        })
2999                        .collect::<Vec<_>>(),
3000                    cx,
3001                )
3002                .collect::<Vec<_>>()
3003        });
3004
3005        assert_eq!(
3006            entries,
3007            vec![
3008                None,
3009                Some(blame_entry("0d0d0d", 1..2)),
3010                Some(blame_entry("3a3a3a", 2..3)),
3011                Some(blame_entry("4c4c4c", 3..4)),
3012            ]
3013        );
3014    });
3015
3016    // Now editor_a also updates the file
3017    editor_a.update_in(cx_a, |editor_a, _, cx| {
3018        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
3019    });
3020
3021    cx_a.executor().run_until_parked();
3022    cx_b.executor().run_until_parked();
3023
3024    editor_b.update(cx_b, |editor_b, cx| {
3025        let blame = editor_b.blame().expect("editor_b should have blame now");
3026        let entries = blame.update(cx, |blame, cx| {
3027            blame
3028                .blame_for_rows(
3029                    &(0..4)
3030                        .map(|row| RowInfo {
3031                            buffer_row: Some(row),
3032                            buffer_id: Some(buffer_id_b),
3033                            ..Default::default()
3034                        })
3035                        .collect::<Vec<_>>(),
3036                    cx,
3037                )
3038                .collect::<Vec<_>>()
3039        });
3040
3041        assert_eq!(
3042            entries,
3043            vec![
3044                None,
3045                None,
3046                Some(blame_entry("3a3a3a", 2..3)),
3047                Some(blame_entry("4c4c4c", 3..4)),
3048            ]
3049        );
3050    });
3051}
3052
3053#[gpui::test(iterations = 30)]
3054async fn test_collaborating_with_editorconfig(
3055    cx_a: &mut TestAppContext,
3056    cx_b: &mut TestAppContext,
3057) {
3058    let mut server = TestServer::start(cx_a.executor()).await;
3059    let client_a = server.create_client(cx_a, "user_a").await;
3060    let client_b = server.create_client(cx_b, "user_b").await;
3061    server
3062        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3063        .await;
3064    let active_call_a = cx_a.read(ActiveCall::global);
3065
3066    cx_b.update(editor::init);
3067
3068    // Set up a fake language server.
3069    client_a.language_registry().add(rust_lang());
3070    client_a
3071        .fs()
3072        .insert_tree(
3073            path!("/a"),
3074            json!({
3075                "src": {
3076                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
3077                    "other_mod": {
3078                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
3079                        ".editorconfig": "",
3080                    },
3081                },
3082                ".editorconfig": "[*]\ntab_width = 2\n",
3083            }),
3084        )
3085        .await;
3086    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
3087    let project_id = active_call_a
3088        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3089        .await
3090        .unwrap();
3091    let main_buffer_a = project_a
3092        .update(cx_a, |p, cx| {
3093            p.open_buffer((worktree_id, "src/main.rs"), cx)
3094        })
3095        .await
3096        .unwrap();
3097    let other_buffer_a = project_a
3098        .update(cx_a, |p, cx| {
3099            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
3100        })
3101        .await
3102        .unwrap();
3103    let cx_a = cx_a.add_empty_window();
3104    let main_editor_a = cx_a.new_window_entity(|window, cx| {
3105        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
3106    });
3107    let other_editor_a = cx_a.new_window_entity(|window, cx| {
3108        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
3109    });
3110    let mut main_editor_cx_a = EditorTestContext {
3111        cx: cx_a.clone(),
3112        window: cx_a.window_handle(),
3113        editor: main_editor_a,
3114        assertion_cx: AssertionContextManager::new(),
3115    };
3116    let mut other_editor_cx_a = EditorTestContext {
3117        cx: cx_a.clone(),
3118        window: cx_a.window_handle(),
3119        editor: other_editor_a,
3120        assertion_cx: AssertionContextManager::new(),
3121    };
3122
3123    // Join the project as client B.
3124    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3125    let main_buffer_b = project_b
3126        .update(cx_b, |p, cx| {
3127            p.open_buffer((worktree_id, "src/main.rs"), cx)
3128        })
3129        .await
3130        .unwrap();
3131    let other_buffer_b = project_b
3132        .update(cx_b, |p, cx| {
3133            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
3134        })
3135        .await
3136        .unwrap();
3137    let cx_b = cx_b.add_empty_window();
3138    let main_editor_b = cx_b.new_window_entity(|window, cx| {
3139        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
3140    });
3141    let other_editor_b = cx_b.new_window_entity(|window, cx| {
3142        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
3143    });
3144    let mut main_editor_cx_b = EditorTestContext {
3145        cx: cx_b.clone(),
3146        window: cx_b.window_handle(),
3147        editor: main_editor_b,
3148        assertion_cx: AssertionContextManager::new(),
3149    };
3150    let mut other_editor_cx_b = EditorTestContext {
3151        cx: cx_b.clone(),
3152        window: cx_b.window_handle(),
3153        editor: other_editor_b,
3154        assertion_cx: AssertionContextManager::new(),
3155    };
3156
3157    let initial_main = indoc! {"
3158ˇmod other;
3159fn main() { let foo = other::foo(); }"};
3160    let initial_other = indoc! {"
3161ˇpub fn foo() -> usize {
3162    4
3163}"};
3164
3165    let first_tabbed_main = indoc! {"
3166  ˇmod other;
3167fn main() { let foo = other::foo(); }"};
3168    tab_undo_assert(
3169        &mut main_editor_cx_a,
3170        &mut main_editor_cx_b,
3171        initial_main,
3172        first_tabbed_main,
3173        true,
3174    );
3175    tab_undo_assert(
3176        &mut main_editor_cx_a,
3177        &mut main_editor_cx_b,
3178        initial_main,
3179        first_tabbed_main,
3180        false,
3181    );
3182
3183    let first_tabbed_other = indoc! {"
3184  ˇpub fn foo() -> usize {
3185    4
3186}"};
3187    tab_undo_assert(
3188        &mut other_editor_cx_a,
3189        &mut other_editor_cx_b,
3190        initial_other,
3191        first_tabbed_other,
3192        true,
3193    );
3194    tab_undo_assert(
3195        &mut other_editor_cx_a,
3196        &mut other_editor_cx_b,
3197        initial_other,
3198        first_tabbed_other,
3199        false,
3200    );
3201
3202    client_a
3203        .fs()
3204        .atomic_write(
3205            PathBuf::from(path!("/a/src/.editorconfig")),
3206            "[*]\ntab_width = 3\n".to_owned(),
3207        )
3208        .await
3209        .unwrap();
3210    cx_a.run_until_parked();
3211    cx_b.run_until_parked();
3212
3213    let second_tabbed_main = indoc! {"
3214   ˇmod other;
3215fn main() { let foo = other::foo(); }"};
3216    tab_undo_assert(
3217        &mut main_editor_cx_a,
3218        &mut main_editor_cx_b,
3219        initial_main,
3220        second_tabbed_main,
3221        true,
3222    );
3223    tab_undo_assert(
3224        &mut main_editor_cx_a,
3225        &mut main_editor_cx_b,
3226        initial_main,
3227        second_tabbed_main,
3228        false,
3229    );
3230
3231    let second_tabbed_other = indoc! {"
3232   ˇpub fn foo() -> usize {
3233    4
3234}"};
3235    tab_undo_assert(
3236        &mut other_editor_cx_a,
3237        &mut other_editor_cx_b,
3238        initial_other,
3239        second_tabbed_other,
3240        true,
3241    );
3242    tab_undo_assert(
3243        &mut other_editor_cx_a,
3244        &mut other_editor_cx_b,
3245        initial_other,
3246        second_tabbed_other,
3247        false,
3248    );
3249
3250    let editorconfig_buffer_b = project_b
3251        .update(cx_b, |p, cx| {
3252            p.open_buffer((worktree_id, "src/other_mod/.editorconfig"), cx)
3253        })
3254        .await
3255        .unwrap();
3256    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
3257        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
3258    });
3259    project_b
3260        .update(cx_b, |project, cx| {
3261            project.save_buffer(editorconfig_buffer_b.clone(), cx)
3262        })
3263        .await
3264        .unwrap();
3265    cx_a.run_until_parked();
3266    cx_b.run_until_parked();
3267
3268    tab_undo_assert(
3269        &mut main_editor_cx_a,
3270        &mut main_editor_cx_b,
3271        initial_main,
3272        second_tabbed_main,
3273        true,
3274    );
3275    tab_undo_assert(
3276        &mut main_editor_cx_a,
3277        &mut main_editor_cx_b,
3278        initial_main,
3279        second_tabbed_main,
3280        false,
3281    );
3282
3283    let third_tabbed_other = indoc! {"
3284      ˇpub fn foo() -> usize {
3285    4
3286}"};
3287    tab_undo_assert(
3288        &mut other_editor_cx_a,
3289        &mut other_editor_cx_b,
3290        initial_other,
3291        third_tabbed_other,
3292        true,
3293    );
3294
3295    tab_undo_assert(
3296        &mut other_editor_cx_a,
3297        &mut other_editor_cx_b,
3298        initial_other,
3299        third_tabbed_other,
3300        false,
3301    );
3302}
3303
3304#[gpui::test]
3305async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3306    let executor = cx_a.executor();
3307    let mut server = TestServer::start(executor.clone()).await;
3308    let client_a = server.create_client(cx_a, "user_a").await;
3309    let client_b = server.create_client(cx_b, "user_b").await;
3310    server
3311        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3312        .await;
3313    let active_call_a = cx_a.read(ActiveCall::global);
3314    let active_call_b = cx_b.read(ActiveCall::global);
3315    cx_a.update(editor::init);
3316    cx_b.update(editor::init);
3317    client_a
3318        .fs()
3319        .insert_tree(
3320            "/a",
3321            json!({
3322                "test.txt": "one\ntwo\nthree\nfour\nfive",
3323            }),
3324        )
3325        .await;
3326    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3327    let project_path = ProjectPath {
3328        worktree_id,
3329        path: Arc::from(Path::new(&"test.txt")),
3330    };
3331    let abs_path = project_a.read_with(cx_a, |project, cx| {
3332        project
3333            .absolute_path(&project_path, cx)
3334            .map(|path_buf| Arc::from(path_buf.to_owned()))
3335            .unwrap()
3336    });
3337
3338    active_call_a
3339        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
3340        .await
3341        .unwrap();
3342    let project_id = active_call_a
3343        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3344        .await
3345        .unwrap();
3346    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3347    active_call_b
3348        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
3349        .await
3350        .unwrap();
3351    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3352    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3353
3354    // Client A opens an editor.
3355    let editor_a = workspace_a
3356        .update_in(cx_a, |workspace, window, cx| {
3357            workspace.open_path(project_path.clone(), None, true, window, cx)
3358        })
3359        .await
3360        .unwrap()
3361        .downcast::<Editor>()
3362        .unwrap();
3363
3364    // Client B opens same editor as A.
3365    let editor_b = workspace_b
3366        .update_in(cx_b, |workspace, window, cx| {
3367            workspace.open_path(project_path.clone(), None, true, window, cx)
3368        })
3369        .await
3370        .unwrap()
3371        .downcast::<Editor>()
3372        .unwrap();
3373
3374    cx_a.run_until_parked();
3375    cx_b.run_until_parked();
3376
3377    // Client A adds breakpoint on line (1)
3378    editor_a.update_in(cx_a, |editor, window, cx| {
3379        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3380    });
3381
3382    cx_a.run_until_parked();
3383    cx_b.run_until_parked();
3384
3385    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3386        editor
3387            .breakpoint_store()
3388            .clone()
3389            .unwrap()
3390            .read(cx)
3391            .all_source_breakpoints(cx)
3392            .clone()
3393    });
3394    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3395        editor
3396            .breakpoint_store()
3397            .clone()
3398            .unwrap()
3399            .read(cx)
3400            .all_source_breakpoints(cx)
3401            .clone()
3402    });
3403
3404    assert_eq!(1, breakpoints_a.len());
3405    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
3406    assert_eq!(breakpoints_a, breakpoints_b);
3407
3408    // Client B adds breakpoint on line(2)
3409    editor_b.update_in(cx_b, |editor, window, cx| {
3410        editor.move_down(&editor::actions::MoveDown, window, cx);
3411        editor.move_down(&editor::actions::MoveDown, window, cx);
3412        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3413    });
3414
3415    cx_a.run_until_parked();
3416    cx_b.run_until_parked();
3417
3418    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3419        editor
3420            .breakpoint_store()
3421            .clone()
3422            .unwrap()
3423            .read(cx)
3424            .all_source_breakpoints(cx)
3425            .clone()
3426    });
3427    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3428        editor
3429            .breakpoint_store()
3430            .clone()
3431            .unwrap()
3432            .read(cx)
3433            .all_source_breakpoints(cx)
3434            .clone()
3435    });
3436
3437    assert_eq!(1, breakpoints_a.len());
3438    assert_eq!(breakpoints_a, breakpoints_b);
3439    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
3440
3441    // Client A removes last added breakpoint from client B
3442    editor_a.update_in(cx_a, |editor, window, cx| {
3443        editor.move_down(&editor::actions::MoveDown, window, cx);
3444        editor.move_down(&editor::actions::MoveDown, window, cx);
3445        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3446    });
3447
3448    cx_a.run_until_parked();
3449    cx_b.run_until_parked();
3450
3451    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3452        editor
3453            .breakpoint_store()
3454            .clone()
3455            .unwrap()
3456            .read(cx)
3457            .all_source_breakpoints(cx)
3458            .clone()
3459    });
3460    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3461        editor
3462            .breakpoint_store()
3463            .clone()
3464            .unwrap()
3465            .read(cx)
3466            .all_source_breakpoints(cx)
3467            .clone()
3468    });
3469
3470    assert_eq!(1, breakpoints_a.len());
3471    assert_eq!(breakpoints_a, breakpoints_b);
3472    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
3473
3474    // Client B removes first added breakpoint by client A
3475    editor_b.update_in(cx_b, |editor, window, cx| {
3476        editor.move_up(&editor::actions::MoveUp, window, cx);
3477        editor.move_up(&editor::actions::MoveUp, window, cx);
3478        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3479    });
3480
3481    cx_a.run_until_parked();
3482    cx_b.run_until_parked();
3483
3484    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3485        editor
3486            .breakpoint_store()
3487            .clone()
3488            .unwrap()
3489            .read(cx)
3490            .all_source_breakpoints(cx)
3491            .clone()
3492    });
3493    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3494        editor
3495            .breakpoint_store()
3496            .clone()
3497            .unwrap()
3498            .read(cx)
3499            .all_source_breakpoints(cx)
3500            .clone()
3501    });
3502
3503    assert_eq!(0, breakpoints_a.len());
3504    assert_eq!(breakpoints_a, breakpoints_b);
3505}
3506
3507#[gpui::test]
3508async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3509    let mut server = TestServer::start(cx_a.executor()).await;
3510    let client_a = server.create_client(cx_a, "user_a").await;
3511    let client_b = server.create_client(cx_b, "user_b").await;
3512    server
3513        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3514        .await;
3515    let active_call_a = cx_a.read(ActiveCall::global);
3516    let active_call_b = cx_b.read(ActiveCall::global);
3517
3518    cx_a.update(editor::init);
3519    cx_b.update(editor::init);
3520
3521    client_a.language_registry().add(rust_lang());
3522    client_b.language_registry().add(rust_lang());
3523    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
3524        "Rust",
3525        FakeLspAdapter {
3526            name: RUST_ANALYZER_NAME,
3527            ..FakeLspAdapter::default()
3528        },
3529    );
3530
3531    client_a
3532        .fs()
3533        .insert_tree(
3534            path!("/a"),
3535            json!({
3536                "main.rs": "fn main() {}",
3537            }),
3538        )
3539        .await;
3540    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
3541    active_call_a
3542        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
3543        .await
3544        .unwrap();
3545    let project_id = active_call_a
3546        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3547        .await
3548        .unwrap();
3549
3550    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3551    active_call_b
3552        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
3553        .await
3554        .unwrap();
3555
3556    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3557    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3558
3559    let editor_a = workspace_a
3560        .update_in(cx_a, |workspace, window, cx| {
3561            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
3562        })
3563        .await
3564        .unwrap()
3565        .downcast::<Editor>()
3566        .unwrap();
3567
3568    let editor_b = workspace_b
3569        .update_in(cx_b, |workspace, window, cx| {
3570            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
3571        })
3572        .await
3573        .unwrap()
3574        .downcast::<Editor>()
3575        .unwrap();
3576
3577    let fake_language_server = fake_language_servers.next().await.unwrap();
3578
3579    // host
3580    let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
3581        |params, _| async move {
3582            assert_eq!(
3583                params.text_document.uri,
3584                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
3585            );
3586            assert_eq!(params.position, lsp::Position::new(0, 0));
3587            Ok(Some(ExpandedMacro {
3588                name: "test_macro_name".to_string(),
3589                expansion: "test_macro_expansion on the host".to_string(),
3590            }))
3591        },
3592    );
3593
3594    editor_a.update_in(cx_a, |editor, window, cx| {
3595        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
3596    });
3597    expand_request_a.next().await.unwrap();
3598    cx_a.run_until_parked();
3599
3600    workspace_a.update(cx_a, |workspace, cx| {
3601        workspace.active_pane().update(cx, |pane, cx| {
3602            assert_eq!(
3603                pane.items_len(),
3604                2,
3605                "Should have added a macro expansion to the host's pane"
3606            );
3607            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
3608            new_editor.update(cx, |editor, cx| {
3609                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
3610            });
3611        })
3612    });
3613
3614    // client
3615    let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
3616        |params, _| async move {
3617            assert_eq!(
3618                params.text_document.uri,
3619                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
3620            );
3621            assert_eq!(
3622                params.position,
3623                lsp::Position::new(0, 12),
3624                "editor_b has selected the entire text and should query for a different position"
3625            );
3626            Ok(Some(ExpandedMacro {
3627                name: "test_macro_name".to_string(),
3628                expansion: "test_macro_expansion on the client".to_string(),
3629            }))
3630        },
3631    );
3632
3633    editor_b.update_in(cx_b, |editor, window, cx| {
3634        editor.select_all(&SelectAll, window, cx);
3635        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
3636    });
3637    expand_request_b.next().await.unwrap();
3638    cx_b.run_until_parked();
3639
3640    workspace_b.update(cx_b, |workspace, cx| {
3641        workspace.active_pane().update(cx, |pane, cx| {
3642            assert_eq!(
3643                pane.items_len(),
3644                2,
3645                "Should have added a macro expansion to the client's pane"
3646            );
3647            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
3648            new_editor.update(cx, |editor, cx| {
3649                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
3650            });
3651        })
3652    });
3653}
3654
3655#[track_caller]
3656fn tab_undo_assert(
3657    cx_a: &mut EditorTestContext,
3658    cx_b: &mut EditorTestContext,
3659    expected_initial: &str,
3660    expected_tabbed: &str,
3661    a_tabs: bool,
3662) {
3663    cx_a.assert_editor_state(expected_initial);
3664    cx_b.assert_editor_state(expected_initial);
3665
3666    if a_tabs {
3667        cx_a.update_editor(|editor, window, cx| {
3668            editor.tab(&editor::actions::Tab, window, cx);
3669        });
3670    } else {
3671        cx_b.update_editor(|editor, window, cx| {
3672            editor.tab(&editor::actions::Tab, window, cx);
3673        });
3674    }
3675
3676    cx_a.run_until_parked();
3677    cx_b.run_until_parked();
3678
3679    cx_a.assert_editor_state(expected_tabbed);
3680    cx_b.assert_editor_state(expected_tabbed);
3681
3682    if a_tabs {
3683        cx_a.update_editor(|editor, window, cx| {
3684            editor.undo(&editor::actions::Undo, window, cx);
3685        });
3686    } else {
3687        cx_b.update_editor(|editor, window, cx| {
3688            editor.undo(&editor::actions::Undo, window, cx);
3689        });
3690    }
3691    cx_a.run_until_parked();
3692    cx_b.run_until_parked();
3693    cx_a.assert_editor_state(expected_initial);
3694    cx_b.assert_editor_state(expected_initial);
3695}
3696
3697fn extract_hint_labels(editor: &Editor) -> Vec<String> {
3698    let mut labels = Vec::new();
3699    for hint in editor.inlay_hint_cache().hints() {
3700        match hint.label {
3701            project::InlayHintLabel::String(s) => labels.push(s),
3702            _ => unreachable!(),
3703        }
3704    }
3705    labels
3706}
3707
3708#[track_caller]
3709fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
3710    editor
3711        .all_inlays(cx)
3712        .into_iter()
3713        .filter_map(|inlay| inlay.get_color())
3714        .map(Rgba::from)
3715        .collect()
3716}
3717
3718fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
3719    git::blame::BlameEntry {
3720        sha: sha.parse().unwrap(),
3721        range,
3722        ..Default::default()
3723    }
3724}