editor_tests.rs

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