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::{InlayHintSettings, 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 = Some(InlayHintSettings {
1790                    enabled: true,
1791                    show_value_hints: true,
1792                    edit_debounce_ms: 0,
1793                    scroll_debounce_ms: 0,
1794                    show_type_hints: true,
1795                    show_parameter_hints: false,
1796                    show_other_hints: true,
1797                    show_background: false,
1798                    toggle_on_modifiers_press: None,
1799                })
1800            });
1801        });
1802    });
1803    cx_b.update(|cx| {
1804        SettingsStore::update_global(cx, |store, cx| {
1805            store.update_user_settings(cx, |settings| {
1806                settings.project.all_languages.defaults.inlay_hints = Some(InlayHintSettings {
1807                    show_value_hints: true,
1808                    enabled: true,
1809                    edit_debounce_ms: 0,
1810                    scroll_debounce_ms: 0,
1811                    show_type_hints: true,
1812                    show_parameter_hints: false,
1813                    show_other_hints: true,
1814                    show_background: false,
1815                    toggle_on_modifiers_press: None,
1816                })
1817            });
1818        });
1819    });
1820
1821    let capabilities = lsp::ServerCapabilities {
1822        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1823        ..lsp::ServerCapabilities::default()
1824    };
1825    client_a.language_registry().add(rust_lang());
1826    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1827        "Rust",
1828        FakeLspAdapter {
1829            capabilities: capabilities.clone(),
1830            ..FakeLspAdapter::default()
1831        },
1832    );
1833    client_b.language_registry().add(rust_lang());
1834    client_b.language_registry().register_fake_lsp_adapter(
1835        "Rust",
1836        FakeLspAdapter {
1837            capabilities,
1838            ..FakeLspAdapter::default()
1839        },
1840    );
1841
1842    // Client A opens a project.
1843    client_a
1844        .fs()
1845        .insert_tree(
1846            path!("/a"),
1847            json!({
1848                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1849                "other.rs": "// Test file",
1850            }),
1851        )
1852        .await;
1853    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1854    active_call_a
1855        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1856        .await
1857        .unwrap();
1858    let project_id = active_call_a
1859        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1860        .await
1861        .unwrap();
1862
1863    // Client B joins the project
1864    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1865    active_call_b
1866        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1867        .await
1868        .unwrap();
1869
1870    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
1871    executor.start_waiting();
1872
1873    // The host opens a rust file.
1874    let _buffer_a = project_a
1875        .update(cx_a, |project, cx| {
1876            project.open_local_buffer(path!("/a/main.rs"), cx)
1877        })
1878        .await
1879        .unwrap();
1880    let editor_a = workspace_a
1881        .update_in(cx_a, |workspace, window, cx| {
1882            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1883        })
1884        .await
1885        .unwrap()
1886        .downcast::<Editor>()
1887        .unwrap();
1888
1889    let fake_language_server = fake_language_servers.next().await.unwrap();
1890
1891    // Set up the language server to return an additional inlay hint on each request.
1892    let edits_made = Arc::new(AtomicUsize::new(0));
1893    let closure_edits_made = Arc::clone(&edits_made);
1894    fake_language_server
1895        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1896            let task_edits_made = Arc::clone(&closure_edits_made);
1897            async move {
1898                assert_eq!(
1899                    params.text_document.uri,
1900                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
1901                );
1902                let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
1903                Ok(Some(vec![lsp::InlayHint {
1904                    position: lsp::Position::new(0, edits_made as u32),
1905                    label: lsp::InlayHintLabel::String(edits_made.to_string()),
1906                    kind: None,
1907                    text_edits: None,
1908                    tooltip: None,
1909                    padding_left: None,
1910                    padding_right: None,
1911                    data: None,
1912                }]))
1913            }
1914        })
1915        .next()
1916        .await
1917        .unwrap();
1918
1919    executor.run_until_parked();
1920
1921    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
1922    editor_a.update(cx_a, |editor, _| {
1923        assert_eq!(
1924            vec![initial_edit.to_string()],
1925            extract_hint_labels(editor),
1926            "Host should get its first hints when opens an editor"
1927        );
1928    });
1929    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1930    let editor_b = workspace_b
1931        .update_in(cx_b, |workspace, window, cx| {
1932            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1933        })
1934        .await
1935        .unwrap()
1936        .downcast::<Editor>()
1937        .unwrap();
1938
1939    executor.run_until_parked();
1940    editor_b.update(cx_b, |editor, _| {
1941        assert_eq!(
1942            vec![initial_edit.to_string()],
1943            extract_hint_labels(editor),
1944            "Client should get its first hints when opens an editor"
1945        );
1946    });
1947
1948    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1949    editor_b.update_in(cx_b, |editor, window, cx| {
1950        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1951            s.select_ranges([13..13].clone())
1952        });
1953        editor.handle_input(":", window, cx);
1954    });
1955    cx_b.focus(&editor_b);
1956
1957    executor.run_until_parked();
1958    editor_a.update(cx_a, |editor, _| {
1959        assert_eq!(
1960            vec![after_client_edit.to_string()],
1961            extract_hint_labels(editor),
1962        );
1963    });
1964    editor_b.update(cx_b, |editor, _| {
1965        assert_eq!(
1966            vec![after_client_edit.to_string()],
1967            extract_hint_labels(editor),
1968        );
1969    });
1970
1971    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1972    editor_a.update_in(cx_a, |editor, window, cx| {
1973        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1974            s.select_ranges([13..13])
1975        });
1976        editor.handle_input("a change to increment both buffers' versions", window, cx);
1977    });
1978    cx_a.focus(&editor_a);
1979
1980    executor.run_until_parked();
1981    editor_a.update(cx_a, |editor, _| {
1982        assert_eq!(
1983            vec![after_host_edit.to_string()],
1984            extract_hint_labels(editor),
1985        );
1986    });
1987    editor_b.update(cx_b, |editor, _| {
1988        assert_eq!(
1989            vec![after_host_edit.to_string()],
1990            extract_hint_labels(editor),
1991        );
1992    });
1993
1994    let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1995    fake_language_server
1996        .request::<lsp::request::InlayHintRefreshRequest>(())
1997        .await
1998        .into_response()
1999        .expect("inlay refresh request failed");
2000
2001    executor.run_until_parked();
2002    editor_a.update(cx_a, |editor, _| {
2003        assert_eq!(
2004            vec![after_special_edit_for_refresh.to_string()],
2005            extract_hint_labels(editor),
2006            "Host should react to /refresh LSP request"
2007        );
2008    });
2009    editor_b.update(cx_b, |editor, _| {
2010        assert_eq!(
2011            vec![after_special_edit_for_refresh.to_string()],
2012            extract_hint_labels(editor),
2013            "Guest should get a /refresh LSP request propagated by host"
2014        );
2015    });
2016}
2017
2018#[gpui::test(iterations = 10)]
2019async fn test_inlay_hint_refresh_is_forwarded(
2020    cx_a: &mut TestAppContext,
2021    cx_b: &mut TestAppContext,
2022) {
2023    let mut server = TestServer::start(cx_a.executor()).await;
2024    let executor = cx_a.executor();
2025    let client_a = server.create_client(cx_a, "user_a").await;
2026    let client_b = server.create_client(cx_b, "user_b").await;
2027    server
2028        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2029        .await;
2030    let active_call_a = cx_a.read(ActiveCall::global);
2031    let active_call_b = cx_b.read(ActiveCall::global);
2032
2033    cx_a.update(editor::init);
2034    cx_b.update(editor::init);
2035
2036    cx_a.update(|cx| {
2037        SettingsStore::update_global(cx, |store, cx| {
2038            store.update_user_settings(cx, |settings| {
2039                settings.project.all_languages.defaults.inlay_hints = Some(InlayHintSettings {
2040                    show_value_hints: true,
2041                    enabled: false,
2042                    edit_debounce_ms: 0,
2043                    scroll_debounce_ms: 0,
2044                    show_type_hints: false,
2045                    show_parameter_hints: false,
2046                    show_other_hints: false,
2047                    show_background: false,
2048                    toggle_on_modifiers_press: None,
2049                })
2050            });
2051        });
2052    });
2053    cx_b.update(|cx| {
2054        SettingsStore::update_global(cx, |store, cx| {
2055            store.update_user_settings(cx, |settings| {
2056                settings.project.all_languages.defaults.inlay_hints = Some(InlayHintSettings {
2057                    show_value_hints: true,
2058                    enabled: true,
2059                    edit_debounce_ms: 0,
2060                    scroll_debounce_ms: 0,
2061                    show_type_hints: true,
2062                    show_parameter_hints: true,
2063                    show_other_hints: true,
2064                    show_background: false,
2065                    toggle_on_modifiers_press: None,
2066                })
2067            });
2068        });
2069    });
2070
2071    let capabilities = lsp::ServerCapabilities {
2072        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2073        ..lsp::ServerCapabilities::default()
2074    };
2075    client_a.language_registry().add(rust_lang());
2076    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2077        "Rust",
2078        FakeLspAdapter {
2079            capabilities: capabilities.clone(),
2080            ..FakeLspAdapter::default()
2081        },
2082    );
2083    client_b.language_registry().add(rust_lang());
2084    client_b.language_registry().register_fake_lsp_adapter(
2085        "Rust",
2086        FakeLspAdapter {
2087            capabilities,
2088            ..FakeLspAdapter::default()
2089        },
2090    );
2091
2092    client_a
2093        .fs()
2094        .insert_tree(
2095            path!("/a"),
2096            json!({
2097                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
2098                "other.rs": "// Test file",
2099            }),
2100        )
2101        .await;
2102    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2103    active_call_a
2104        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2105        .await
2106        .unwrap();
2107    let project_id = active_call_a
2108        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2109        .await
2110        .unwrap();
2111
2112    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2113    active_call_b
2114        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2115        .await
2116        .unwrap();
2117
2118    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2119    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2120
2121    cx_a.background_executor.start_waiting();
2122
2123    let editor_a = workspace_a
2124        .update_in(cx_a, |workspace, window, cx| {
2125            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2126        })
2127        .await
2128        .unwrap()
2129        .downcast::<Editor>()
2130        .unwrap();
2131
2132    let editor_b = workspace_b
2133        .update_in(cx_b, |workspace, window, cx| {
2134            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2135        })
2136        .await
2137        .unwrap()
2138        .downcast::<Editor>()
2139        .unwrap();
2140
2141    let other_hints = Arc::new(AtomicBool::new(false));
2142    let fake_language_server = fake_language_servers.next().await.unwrap();
2143    let closure_other_hints = Arc::clone(&other_hints);
2144    fake_language_server
2145        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2146            let task_other_hints = Arc::clone(&closure_other_hints);
2147            async move {
2148                assert_eq!(
2149                    params.text_document.uri,
2150                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2151                );
2152                let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
2153                let character = if other_hints { 0 } else { 2 };
2154                let label = if other_hints {
2155                    "other hint"
2156                } else {
2157                    "initial hint"
2158                };
2159                Ok(Some(vec![lsp::InlayHint {
2160                    position: lsp::Position::new(0, character),
2161                    label: lsp::InlayHintLabel::String(label.to_string()),
2162                    kind: None,
2163                    text_edits: None,
2164                    tooltip: None,
2165                    padding_left: None,
2166                    padding_right: None,
2167                    data: None,
2168                }]))
2169            }
2170        })
2171        .next()
2172        .await
2173        .unwrap();
2174    executor.finish_waiting();
2175
2176    executor.run_until_parked();
2177    editor_a.update(cx_a, |editor, _| {
2178        assert!(
2179            extract_hint_labels(editor).is_empty(),
2180            "Host should get no hints due to them turned off"
2181        );
2182    });
2183
2184    executor.run_until_parked();
2185    editor_b.update(cx_b, |editor, _| {
2186        assert_eq!(
2187            vec!["initial hint".to_string()],
2188            extract_hint_labels(editor),
2189            "Client should get its first hints when opens an editor"
2190        );
2191    });
2192
2193    other_hints.fetch_or(true, atomic::Ordering::Release);
2194    fake_language_server
2195        .request::<lsp::request::InlayHintRefreshRequest>(())
2196        .await
2197        .into_response()
2198        .expect("inlay refresh request failed");
2199    executor.run_until_parked();
2200    editor_a.update(cx_a, |editor, _| {
2201        assert!(
2202            extract_hint_labels(editor).is_empty(),
2203            "Host should get no hints due to them turned off, even after the /refresh"
2204        );
2205    });
2206
2207    executor.run_until_parked();
2208    editor_b.update(cx_b, |editor, _| {
2209        assert_eq!(
2210            vec!["other hint".to_string()],
2211            extract_hint_labels(editor),
2212            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
2213        );
2214    });
2215}
2216
2217#[gpui::test(iterations = 10)]
2218async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2219    let expected_color = Rgba {
2220        r: 0.33,
2221        g: 0.33,
2222        b: 0.33,
2223        a: 0.33,
2224    };
2225    let mut server = TestServer::start(cx_a.executor()).await;
2226    let executor = cx_a.executor();
2227    let client_a = server.create_client(cx_a, "user_a").await;
2228    let client_b = server.create_client(cx_b, "user_b").await;
2229    server
2230        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2231        .await;
2232    let active_call_a = cx_a.read(ActiveCall::global);
2233    let active_call_b = cx_b.read(ActiveCall::global);
2234
2235    cx_a.update(editor::init);
2236    cx_b.update(editor::init);
2237
2238    cx_a.update(|cx| {
2239        SettingsStore::update_global(cx, |store, cx| {
2240            store.update_user_settings(cx, |settings| {
2241                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::None);
2242            });
2243        });
2244    });
2245    cx_b.update(|cx| {
2246        SettingsStore::update_global(cx, |store, cx| {
2247            store.update_user_settings(cx, |settings| {
2248                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2249            });
2250        });
2251    });
2252
2253    let capabilities = lsp::ServerCapabilities {
2254        color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
2255        ..lsp::ServerCapabilities::default()
2256    };
2257    client_a.language_registry().add(rust_lang());
2258    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2259        "Rust",
2260        FakeLspAdapter {
2261            capabilities: capabilities.clone(),
2262            ..FakeLspAdapter::default()
2263        },
2264    );
2265    client_b.language_registry().add(rust_lang());
2266    client_b.language_registry().register_fake_lsp_adapter(
2267        "Rust",
2268        FakeLspAdapter {
2269            capabilities,
2270            ..FakeLspAdapter::default()
2271        },
2272    );
2273
2274    // Client A opens a project.
2275    client_a
2276        .fs()
2277        .insert_tree(
2278            path!("/a"),
2279            json!({
2280                "main.rs": "fn main() { a }",
2281            }),
2282        )
2283        .await;
2284    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2285    active_call_a
2286        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2287        .await
2288        .unwrap();
2289    let project_id = active_call_a
2290        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2291        .await
2292        .unwrap();
2293
2294    // Client B joins the project
2295    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2296    active_call_b
2297        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2298        .await
2299        .unwrap();
2300
2301    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2302
2303    // The host opens a rust file.
2304    let _buffer_a = project_a
2305        .update(cx_a, |project, cx| {
2306            project.open_local_buffer(path!("/a/main.rs"), cx)
2307        })
2308        .await
2309        .unwrap();
2310    let editor_a = workspace_a
2311        .update_in(cx_a, |workspace, window, cx| {
2312            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2313        })
2314        .await
2315        .unwrap()
2316        .downcast::<Editor>()
2317        .unwrap();
2318
2319    let fake_language_server = fake_language_servers.next().await.unwrap();
2320    cx_a.run_until_parked();
2321    cx_b.run_until_parked();
2322
2323    let requests_made = Arc::new(AtomicUsize::new(0));
2324    let closure_requests_made = Arc::clone(&requests_made);
2325    let mut color_request_handle = fake_language_server
2326        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
2327            let requests_made = Arc::clone(&closure_requests_made);
2328            async move {
2329                assert_eq!(
2330                    params.text_document.uri,
2331                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2332                );
2333                requests_made.fetch_add(1, atomic::Ordering::Release);
2334                Ok(vec![lsp::ColorInformation {
2335                    range: lsp::Range {
2336                        start: lsp::Position {
2337                            line: 0,
2338                            character: 0,
2339                        },
2340                        end: lsp::Position {
2341                            line: 0,
2342                            character: 1,
2343                        },
2344                    },
2345                    color: lsp::Color {
2346                        red: 0.33,
2347                        green: 0.33,
2348                        blue: 0.33,
2349                        alpha: 0.33,
2350                    },
2351                }])
2352            }
2353        });
2354    executor.run_until_parked();
2355
2356    assert_eq!(
2357        0,
2358        requests_made.load(atomic::Ordering::Acquire),
2359        "Host did not enable document colors, hence should query for none"
2360    );
2361    editor_a.update(cx_a, |editor, cx| {
2362        assert_eq!(
2363            Vec::<Rgba>::new(),
2364            extract_color_inlays(editor, cx),
2365            "No query colors should result in no hints"
2366        );
2367    });
2368
2369    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2370    let editor_b = workspace_b
2371        .update_in(cx_b, |workspace, window, cx| {
2372            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2373        })
2374        .await
2375        .unwrap()
2376        .downcast::<Editor>()
2377        .unwrap();
2378
2379    color_request_handle.next().await.unwrap();
2380    executor.run_until_parked();
2381
2382    assert_eq!(
2383        1,
2384        requests_made.load(atomic::Ordering::Acquire),
2385        "The client opened the file and got its first colors back"
2386    );
2387    editor_b.update(cx_b, |editor, cx| {
2388        assert_eq!(
2389            vec![expected_color],
2390            extract_color_inlays(editor, cx),
2391            "With document colors as inlays, color inlays should be pushed"
2392        );
2393    });
2394
2395    editor_a.update_in(cx_a, |editor, window, cx| {
2396        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2397            s.select_ranges([13..13].clone())
2398        });
2399        editor.handle_input(":", window, cx);
2400    });
2401    color_request_handle.next().await.unwrap();
2402    executor.run_until_parked();
2403    assert_eq!(
2404        2,
2405        requests_made.load(atomic::Ordering::Acquire),
2406        "After the host edits his file, the client should request the colors again"
2407    );
2408    editor_a.update(cx_a, |editor, cx| {
2409        assert_eq!(
2410            Vec::<Rgba>::new(),
2411            extract_color_inlays(editor, cx),
2412            "Host has no colors still"
2413        );
2414    });
2415    editor_b.update(cx_b, |editor, cx| {
2416        assert_eq!(vec![expected_color], extract_color_inlays(editor, cx),);
2417    });
2418
2419    cx_b.update(|_, cx| {
2420        SettingsStore::update_global(cx, |store, cx| {
2421            store.update_user_settings(cx, |settings| {
2422                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Background);
2423            });
2424        });
2425    });
2426    executor.run_until_parked();
2427    assert_eq!(
2428        2,
2429        requests_made.load(atomic::Ordering::Acquire),
2430        "After the client have changed the colors settings, no extra queries should happen"
2431    );
2432    editor_a.update(cx_a, |editor, cx| {
2433        assert_eq!(
2434            Vec::<Rgba>::new(),
2435            extract_color_inlays(editor, cx),
2436            "Host is unaffected by the client's settings changes"
2437        );
2438    });
2439    editor_b.update(cx_b, |editor, cx| {
2440        assert_eq!(
2441            Vec::<Rgba>::new(),
2442            extract_color_inlays(editor, cx),
2443            "Client should have no colors hints, as in the settings"
2444        );
2445    });
2446
2447    cx_b.update(|_, cx| {
2448        SettingsStore::update_global(cx, |store, cx| {
2449            store.update_user_settings(cx, |settings| {
2450                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2451            });
2452        });
2453    });
2454    executor.run_until_parked();
2455    assert_eq!(
2456        2,
2457        requests_made.load(atomic::Ordering::Acquire),
2458        "After falling back to colors as inlays, no extra LSP queries are made"
2459    );
2460    editor_a.update(cx_a, |editor, cx| {
2461        assert_eq!(
2462            Vec::<Rgba>::new(),
2463            extract_color_inlays(editor, cx),
2464            "Host is unaffected by the client's settings changes, again"
2465        );
2466    });
2467    editor_b.update(cx_b, |editor, cx| {
2468        assert_eq!(
2469            vec![expected_color],
2470            extract_color_inlays(editor, cx),
2471            "Client should have its color hints back"
2472        );
2473    });
2474
2475    cx_a.update(|_, cx| {
2476        SettingsStore::update_global(cx, |store, cx| {
2477            store.update_user_settings(cx, |settings| {
2478                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Border);
2479            });
2480        });
2481    });
2482    color_request_handle.next().await.unwrap();
2483    executor.run_until_parked();
2484    assert_eq!(
2485        3,
2486        requests_made.load(atomic::Ordering::Acquire),
2487        "After the host enables document colors, another LSP query should be made"
2488    );
2489    editor_a.update(cx_a, |editor, cx| {
2490        assert_eq!(
2491            Vec::<Rgba>::new(),
2492            extract_color_inlays(editor, cx),
2493            "Host did not configure document colors as hints hence gets nothing"
2494        );
2495    });
2496    editor_b.update(cx_b, |editor, cx| {
2497        assert_eq!(
2498            vec![expected_color],
2499            extract_color_inlays(editor, cx),
2500            "Client should be unaffected by the host's settings changes"
2501        );
2502    });
2503}
2504
2505async fn test_lsp_pull_diagnostics(
2506    should_stream_workspace_diagnostic: bool,
2507    cx_a: &mut TestAppContext,
2508    cx_b: &mut TestAppContext,
2509) {
2510    let mut server = TestServer::start(cx_a.executor()).await;
2511    let executor = cx_a.executor();
2512    let client_a = server.create_client(cx_a, "user_a").await;
2513    let client_b = server.create_client(cx_b, "user_b").await;
2514    server
2515        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2516        .await;
2517    let active_call_a = cx_a.read(ActiveCall::global);
2518    let active_call_b = cx_b.read(ActiveCall::global);
2519
2520    cx_a.update(editor::init);
2521    cx_b.update(editor::init);
2522
2523    let capabilities = lsp::ServerCapabilities {
2524        diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
2525            lsp::DiagnosticOptions {
2526                identifier: Some("test-pulls".to_string()),
2527                inter_file_dependencies: true,
2528                workspace_diagnostics: true,
2529                work_done_progress_options: lsp::WorkDoneProgressOptions {
2530                    work_done_progress: None,
2531                },
2532            },
2533        )),
2534        ..lsp::ServerCapabilities::default()
2535    };
2536    client_a.language_registry().add(rust_lang());
2537    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2538        "Rust",
2539        FakeLspAdapter {
2540            capabilities: capabilities.clone(),
2541            ..FakeLspAdapter::default()
2542        },
2543    );
2544    client_b.language_registry().add(rust_lang());
2545    client_b.language_registry().register_fake_lsp_adapter(
2546        "Rust",
2547        FakeLspAdapter {
2548            capabilities,
2549            ..FakeLspAdapter::default()
2550        },
2551    );
2552
2553    // Client A opens a project.
2554    client_a
2555        .fs()
2556        .insert_tree(
2557            path!("/a"),
2558            json!({
2559                "main.rs": "fn main() { a }",
2560                "lib.rs": "fn other() {}",
2561            }),
2562        )
2563        .await;
2564    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2565    active_call_a
2566        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2567        .await
2568        .unwrap();
2569    let project_id = active_call_a
2570        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2571        .await
2572        .unwrap();
2573
2574    // Client B joins the project
2575    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2576    active_call_b
2577        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2578        .await
2579        .unwrap();
2580
2581    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2582    executor.start_waiting();
2583
2584    // The host opens a rust file.
2585    let _buffer_a = project_a
2586        .update(cx_a, |project, cx| {
2587            project.open_local_buffer(path!("/a/main.rs"), cx)
2588        })
2589        .await
2590        .unwrap();
2591    let editor_a_main = workspace_a
2592        .update_in(cx_a, |workspace, window, cx| {
2593            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2594        })
2595        .await
2596        .unwrap()
2597        .downcast::<Editor>()
2598        .unwrap();
2599
2600    let fake_language_server = fake_language_servers.next().await.unwrap();
2601    cx_a.run_until_parked();
2602    cx_b.run_until_parked();
2603    let expected_push_diagnostic_main_message = "pushed main diagnostic";
2604    let expected_push_diagnostic_lib_message = "pushed lib diagnostic";
2605    let expected_pull_diagnostic_main_message = "pulled main diagnostic";
2606    let expected_pull_diagnostic_lib_message = "pulled lib diagnostic";
2607    let expected_workspace_pull_diagnostics_main_message = "pulled workspace main diagnostic";
2608    let expected_workspace_pull_diagnostics_lib_message = "pulled workspace lib diagnostic";
2609
2610    let diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<Option<String>>::new()));
2611    let workspace_diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<String>::new()));
2612    let diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2613    let closure_diagnostics_pulls_made = diagnostics_pulls_made.clone();
2614    let closure_diagnostics_pulls_result_ids = diagnostics_pulls_result_ids.clone();
2615    let mut pull_diagnostics_handle = fake_language_server
2616        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
2617            let requests_made = closure_diagnostics_pulls_made.clone();
2618            let diagnostics_pulls_result_ids = closure_diagnostics_pulls_result_ids.clone();
2619            async move {
2620                let message = if lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap()
2621                    == params.text_document.uri
2622                {
2623                    expected_pull_diagnostic_main_message.to_string()
2624                } else if lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap()
2625                    == params.text_document.uri
2626                {
2627                    expected_pull_diagnostic_lib_message.to_string()
2628                } else {
2629                    panic!("Unexpected document: {}", params.text_document.uri)
2630                };
2631                {
2632                    diagnostics_pulls_result_ids
2633                        .lock()
2634                        .await
2635                        .insert(params.previous_result_id);
2636                }
2637                let new_requests_count = requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2638                Ok(lsp::DocumentDiagnosticReportResult::Report(
2639                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
2640                        related_documents: None,
2641                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
2642                            result_id: Some(format!("pull-{new_requests_count}")),
2643                            items: vec![lsp::Diagnostic {
2644                                range: lsp::Range {
2645                                    start: lsp::Position {
2646                                        line: 0,
2647                                        character: 0,
2648                                    },
2649                                    end: lsp::Position {
2650                                        line: 0,
2651                                        character: 2,
2652                                    },
2653                                },
2654                                severity: Some(lsp::DiagnosticSeverity::ERROR),
2655                                message,
2656                                ..lsp::Diagnostic::default()
2657                            }],
2658                        },
2659                    }),
2660                ))
2661            }
2662        });
2663
2664    let workspace_diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2665    let closure_workspace_diagnostics_pulls_made = workspace_diagnostics_pulls_made.clone();
2666    let closure_workspace_diagnostics_pulls_result_ids =
2667        workspace_diagnostics_pulls_result_ids.clone();
2668    let (workspace_diagnostic_cancel_tx, closure_workspace_diagnostic_cancel_rx) =
2669        smol::channel::bounded::<()>(1);
2670    let (closure_workspace_diagnostic_received_tx, workspace_diagnostic_received_rx) =
2671        smol::channel::bounded::<()>(1);
2672    let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!(
2673        "workspace/diagnostic-{}-1",
2674        fake_language_server.server.server_id()
2675    ));
2676    let closure_expected_workspace_diagnostic_token = expected_workspace_diagnostic_token.clone();
2677    let mut workspace_diagnostics_pulls_handle = fake_language_server
2678        .set_request_handler::<lsp::request::WorkspaceDiagnosticRequest, _, _>(
2679        move |params, _| {
2680            let workspace_requests_made = closure_workspace_diagnostics_pulls_made.clone();
2681            let workspace_diagnostics_pulls_result_ids =
2682                closure_workspace_diagnostics_pulls_result_ids.clone();
2683            let workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone();
2684            let workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone();
2685            let expected_workspace_diagnostic_token =
2686                closure_expected_workspace_diagnostic_token.clone();
2687            async move {
2688                let workspace_request_count =
2689                    workspace_requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2690                {
2691                    workspace_diagnostics_pulls_result_ids
2692                        .lock()
2693                        .await
2694                        .extend(params.previous_result_ids.into_iter().map(|id| id.value));
2695                }
2696                if should_stream_workspace_diagnostic && !workspace_diagnostic_cancel_rx.is_closed()
2697                {
2698                    assert_eq!(
2699                        params.partial_result_params.partial_result_token,
2700                        Some(expected_workspace_diagnostic_token)
2701                    );
2702                    workspace_diagnostic_received_tx.send(()).await.unwrap();
2703                    workspace_diagnostic_cancel_rx.recv().await.unwrap();
2704                    workspace_diagnostic_cancel_rx.close();
2705                    // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#partialResults
2706                    // > The final response has to be empty in terms of result values.
2707                    return Ok(lsp::WorkspaceDiagnosticReportResult::Report(
2708                        lsp::WorkspaceDiagnosticReport { items: Vec::new() },
2709                    ));
2710                }
2711                Ok(lsp::WorkspaceDiagnosticReportResult::Report(
2712                    lsp::WorkspaceDiagnosticReport {
2713                        items: vec![
2714                            lsp::WorkspaceDocumentDiagnosticReport::Full(
2715                                lsp::WorkspaceFullDocumentDiagnosticReport {
2716                                    uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2717                                    version: None,
2718                                    full_document_diagnostic_report:
2719                                        lsp::FullDocumentDiagnosticReport {
2720                                            result_id: Some(format!(
2721                                                "workspace_{workspace_request_count}"
2722                                            )),
2723                                            items: vec![lsp::Diagnostic {
2724                                                range: lsp::Range {
2725                                                    start: lsp::Position {
2726                                                        line: 0,
2727                                                        character: 1,
2728                                                    },
2729                                                    end: lsp::Position {
2730                                                        line: 0,
2731                                                        character: 3,
2732                                                    },
2733                                                },
2734                                                severity: Some(lsp::DiagnosticSeverity::WARNING),
2735                                                message:
2736                                                    expected_workspace_pull_diagnostics_main_message
2737                                                        .to_string(),
2738                                                ..lsp::Diagnostic::default()
2739                                            }],
2740                                        },
2741                                },
2742                            ),
2743                            lsp::WorkspaceDocumentDiagnosticReport::Full(
2744                                lsp::WorkspaceFullDocumentDiagnosticReport {
2745                                    uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
2746                                    version: None,
2747                                    full_document_diagnostic_report:
2748                                        lsp::FullDocumentDiagnosticReport {
2749                                            result_id: Some(format!(
2750                                                "workspace_{workspace_request_count}"
2751                                            )),
2752                                            items: vec![lsp::Diagnostic {
2753                                                range: lsp::Range {
2754                                                    start: lsp::Position {
2755                                                        line: 0,
2756                                                        character: 1,
2757                                                    },
2758                                                    end: lsp::Position {
2759                                                        line: 0,
2760                                                        character: 3,
2761                                                    },
2762                                                },
2763                                                severity: Some(lsp::DiagnosticSeverity::WARNING),
2764                                                message:
2765                                                    expected_workspace_pull_diagnostics_lib_message
2766                                                        .to_string(),
2767                                                ..lsp::Diagnostic::default()
2768                                            }],
2769                                        },
2770                                },
2771                            ),
2772                        ],
2773                    },
2774                ))
2775            }
2776        },
2777    );
2778
2779    if should_stream_workspace_diagnostic {
2780        workspace_diagnostic_received_rx.recv().await.unwrap();
2781    } else {
2782        workspace_diagnostics_pulls_handle.next().await.unwrap();
2783    }
2784    assert_eq!(
2785        1,
2786        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2787        "Workspace diagnostics should be pulled initially on a server startup"
2788    );
2789    pull_diagnostics_handle.next().await.unwrap();
2790    assert_eq!(
2791        1,
2792        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2793        "Host should query pull diagnostics when the editor is opened"
2794    );
2795    executor.run_until_parked();
2796    editor_a_main.update(cx_a, |editor, cx| {
2797        let snapshot = editor.buffer().read(cx).snapshot(cx);
2798        let all_diagnostics = snapshot
2799            .diagnostics_in_range(0..snapshot.len())
2800            .collect::<Vec<_>>();
2801        assert_eq!(
2802            all_diagnostics.len(),
2803            1,
2804            "Expected single diagnostic, but got: {all_diagnostics:?}"
2805        );
2806        let diagnostic = &all_diagnostics[0];
2807        let mut expected_messages = vec![expected_pull_diagnostic_main_message];
2808        if !should_stream_workspace_diagnostic {
2809            expected_messages.push(expected_workspace_pull_diagnostics_main_message);
2810        }
2811        assert!(
2812            expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2813            "Expected {expected_messages:?} on the host, but got: {}",
2814            diagnostic.diagnostic.message
2815        );
2816    });
2817
2818    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2819        &lsp::PublishDiagnosticsParams {
2820            uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2821            diagnostics: vec![lsp::Diagnostic {
2822                range: lsp::Range {
2823                    start: lsp::Position {
2824                        line: 0,
2825                        character: 3,
2826                    },
2827                    end: lsp::Position {
2828                        line: 0,
2829                        character: 4,
2830                    },
2831                },
2832                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
2833                message: expected_push_diagnostic_main_message.to_string(),
2834                ..lsp::Diagnostic::default()
2835            }],
2836            version: None,
2837        },
2838    );
2839    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2840        &lsp::PublishDiagnosticsParams {
2841            uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
2842            diagnostics: vec![lsp::Diagnostic {
2843                range: lsp::Range {
2844                    start: lsp::Position {
2845                        line: 0,
2846                        character: 3,
2847                    },
2848                    end: lsp::Position {
2849                        line: 0,
2850                        character: 4,
2851                    },
2852                },
2853                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
2854                message: expected_push_diagnostic_lib_message.to_string(),
2855                ..lsp::Diagnostic::default()
2856            }],
2857            version: None,
2858        },
2859    );
2860
2861    if should_stream_workspace_diagnostic {
2862        fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
2863            token: expected_workspace_diagnostic_token.clone(),
2864            value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
2865                lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
2866                    items: vec![
2867                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2868                            lsp::WorkspaceFullDocumentDiagnosticReport {
2869                                uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2870                                version: None,
2871                                full_document_diagnostic_report:
2872                                    lsp::FullDocumentDiagnosticReport {
2873                                        result_id: Some(format!(
2874                                            "workspace_{}",
2875                                            workspace_diagnostics_pulls_made
2876                                                .fetch_add(1, atomic::Ordering::Release)
2877                                                + 1
2878                                        )),
2879                                        items: vec![lsp::Diagnostic {
2880                                            range: lsp::Range {
2881                                                start: lsp::Position {
2882                                                    line: 0,
2883                                                    character: 1,
2884                                                },
2885                                                end: lsp::Position {
2886                                                    line: 0,
2887                                                    character: 2,
2888                                                },
2889                                            },
2890                                            severity: Some(lsp::DiagnosticSeverity::ERROR),
2891                                            message:
2892                                                expected_workspace_pull_diagnostics_main_message
2893                                                    .to_string(),
2894                                            ..lsp::Diagnostic::default()
2895                                        }],
2896                                    },
2897                            },
2898                        ),
2899                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2900                            lsp::WorkspaceFullDocumentDiagnosticReport {
2901                                uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
2902                                version: None,
2903                                full_document_diagnostic_report:
2904                                    lsp::FullDocumentDiagnosticReport {
2905                                        result_id: Some(format!(
2906                                            "workspace_{}",
2907                                            workspace_diagnostics_pulls_made
2908                                                .fetch_add(1, atomic::Ordering::Release)
2909                                                + 1
2910                                        )),
2911                                        items: Vec::new(),
2912                                    },
2913                            },
2914                        ),
2915                    ],
2916                }),
2917            ),
2918        });
2919    };
2920
2921    let mut workspace_diagnostic_start_count =
2922        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
2923
2924    executor.run_until_parked();
2925    editor_a_main.update(cx_a, |editor, cx| {
2926        let snapshot = editor.buffer().read(cx).snapshot(cx);
2927        let all_diagnostics = snapshot
2928            .diagnostics_in_range(0..snapshot.len())
2929            .collect::<Vec<_>>();
2930        assert_eq!(
2931            all_diagnostics.len(),
2932            2,
2933            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
2934        );
2935        let expected_messages = [
2936            expected_workspace_pull_diagnostics_main_message,
2937            expected_pull_diagnostic_main_message,
2938            expected_push_diagnostic_main_message,
2939        ];
2940        for diagnostic in all_diagnostics {
2941            assert!(
2942                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2943                "Expected push and pull messages on the host: {expected_messages:?}, but got: {}",
2944                diagnostic.diagnostic.message
2945            );
2946        }
2947    });
2948
2949    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2950    let editor_b_main = workspace_b
2951        .update_in(cx_b, |workspace, window, cx| {
2952            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2953        })
2954        .await
2955        .unwrap()
2956        .downcast::<Editor>()
2957        .unwrap();
2958    cx_b.run_until_parked();
2959
2960    pull_diagnostics_handle.next().await.unwrap();
2961    assert_eq!(
2962        2,
2963        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2964        "Client should query pull diagnostics when its editor is opened"
2965    );
2966    executor.run_until_parked();
2967    assert_eq!(
2968        workspace_diagnostic_start_count,
2969        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2970        "Workspace diagnostics should not be changed as the remote client does not initialize the workspace diagnostics pull"
2971    );
2972    editor_b_main.update(cx_b, |editor, cx| {
2973        let snapshot = editor.buffer().read(cx).snapshot(cx);
2974        let all_diagnostics = snapshot
2975            .diagnostics_in_range(0..snapshot.len())
2976            .collect::<Vec<_>>();
2977        assert_eq!(
2978            all_diagnostics.len(),
2979            2,
2980            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
2981        );
2982
2983        // Despite the workspace diagnostics not re-initialized for the remote client, we can still expect its message synced from the host.
2984        let expected_messages = [
2985            expected_workspace_pull_diagnostics_main_message,
2986            expected_pull_diagnostic_main_message,
2987            expected_push_diagnostic_main_message,
2988        ];
2989        for diagnostic in all_diagnostics {
2990            assert!(
2991                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2992                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
2993                diagnostic.diagnostic.message
2994            );
2995        }
2996    });
2997
2998    let editor_b_lib = workspace_b
2999        .update_in(cx_b, |workspace, window, cx| {
3000            workspace.open_path((worktree_id, "lib.rs"), None, true, window, cx)
3001        })
3002        .await
3003        .unwrap()
3004        .downcast::<Editor>()
3005        .unwrap();
3006
3007    pull_diagnostics_handle.next().await.unwrap();
3008    assert_eq!(
3009        3,
3010        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3011        "Client should query pull diagnostics when its another editor is opened"
3012    );
3013    executor.run_until_parked();
3014    assert_eq!(
3015        workspace_diagnostic_start_count,
3016        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3017        "The remote client still did not anything to trigger the workspace diagnostics pull"
3018    );
3019    editor_b_lib.update(cx_b, |editor, cx| {
3020        let snapshot = editor.buffer().read(cx).snapshot(cx);
3021        let all_diagnostics = snapshot
3022            .diagnostics_in_range(0..snapshot.len())
3023            .collect::<Vec<_>>();
3024        let expected_messages = [
3025            expected_pull_diagnostic_lib_message,
3026            // TODO bug: the pushed diagnostics are not being sent to the client when they open the corresponding buffer.
3027            // expected_push_diagnostic_lib_message,
3028        ];
3029        assert_eq!(
3030            all_diagnostics.len(),
3031            1,
3032            "Expected pull diagnostics, but got: {all_diagnostics:?}"
3033        );
3034        for diagnostic in all_diagnostics {
3035            assert!(
3036                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3037                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3038                diagnostic.diagnostic.message
3039            );
3040        }
3041    });
3042
3043    if should_stream_workspace_diagnostic {
3044        fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
3045            token: expected_workspace_diagnostic_token.clone(),
3046            value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
3047                lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
3048                    items: vec![lsp::WorkspaceDocumentDiagnosticReport::Full(
3049                        lsp::WorkspaceFullDocumentDiagnosticReport {
3050                            uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3051                            version: None,
3052                            full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
3053                                result_id: Some(format!(
3054                                    "workspace_{}",
3055                                    workspace_diagnostics_pulls_made
3056                                        .fetch_add(1, atomic::Ordering::Release)
3057                                        + 1
3058                                )),
3059                                items: vec![lsp::Diagnostic {
3060                                    range: lsp::Range {
3061                                        start: lsp::Position {
3062                                            line: 0,
3063                                            character: 1,
3064                                        },
3065                                        end: lsp::Position {
3066                                            line: 0,
3067                                            character: 2,
3068                                        },
3069                                    },
3070                                    severity: Some(lsp::DiagnosticSeverity::ERROR),
3071                                    message: expected_workspace_pull_diagnostics_lib_message
3072                                        .to_string(),
3073                                    ..lsp::Diagnostic::default()
3074                                }],
3075                            },
3076                        },
3077                    )],
3078                }),
3079            ),
3080        });
3081        workspace_diagnostic_start_count =
3082            workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
3083        workspace_diagnostic_cancel_tx.send(()).await.unwrap();
3084        workspace_diagnostics_pulls_handle.next().await.unwrap();
3085        executor.run_until_parked();
3086        editor_b_lib.update(cx_b, |editor, cx| {
3087            let snapshot = editor.buffer().read(cx).snapshot(cx);
3088            let all_diagnostics = snapshot
3089                .diagnostics_in_range(0..snapshot.len())
3090                .collect::<Vec<_>>();
3091            let expected_messages = [
3092                expected_workspace_pull_diagnostics_lib_message,
3093                // TODO bug: the pushed diagnostics are not being sent to the client when they open the corresponding buffer.
3094                // expected_push_diagnostic_lib_message,
3095            ];
3096            assert_eq!(
3097                all_diagnostics.len(),
3098                1,
3099                "Expected pull diagnostics, but got: {all_diagnostics:?}"
3100            );
3101            for diagnostic in all_diagnostics {
3102                assert!(
3103                    expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3104                    "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3105                    diagnostic.diagnostic.message
3106                );
3107            }
3108        });
3109    };
3110
3111    {
3112        assert!(
3113            !diagnostics_pulls_result_ids.lock().await.is_empty(),
3114            "Initial diagnostics pulls should report None at least"
3115        );
3116        assert_eq!(
3117            0,
3118            workspace_diagnostics_pulls_result_ids
3119                .lock()
3120                .await
3121                .deref()
3122                .len(),
3123            "After the initial workspace request, opening files should not reuse any result ids"
3124        );
3125    }
3126
3127    editor_b_lib.update_in(cx_b, |editor, window, cx| {
3128        editor.move_to_end(&MoveToEnd, window, cx);
3129        editor.handle_input(":", window, cx);
3130    });
3131    pull_diagnostics_handle.next().await.unwrap();
3132    assert_eq!(
3133        4,
3134        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3135        "Client lib.rs edits should trigger another diagnostics pull for a buffer"
3136    );
3137    workspace_diagnostics_pulls_handle.next().await.unwrap();
3138    assert_eq!(
3139        workspace_diagnostic_start_count + 1,
3140        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3141        "After client lib.rs edits, the workspace diagnostics request should follow"
3142    );
3143    executor.run_until_parked();
3144
3145    editor_b_main.update_in(cx_b, |editor, window, cx| {
3146        editor.move_to_end(&MoveToEnd, window, cx);
3147        editor.handle_input(":", window, cx);
3148    });
3149    pull_diagnostics_handle.next().await.unwrap();
3150    pull_diagnostics_handle.next().await.unwrap();
3151    assert_eq!(
3152        6,
3153        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3154        "Client main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
3155    );
3156    workspace_diagnostics_pulls_handle.next().await.unwrap();
3157    assert_eq!(
3158        workspace_diagnostic_start_count + 2,
3159        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3160        "After client main.rs edits, the workspace diagnostics pull should follow"
3161    );
3162    executor.run_until_parked();
3163
3164    editor_a_main.update_in(cx_a, |editor, window, cx| {
3165        editor.move_to_end(&MoveToEnd, window, cx);
3166        editor.handle_input(":", window, cx);
3167    });
3168    pull_diagnostics_handle.next().await.unwrap();
3169    pull_diagnostics_handle.next().await.unwrap();
3170    assert_eq!(
3171        8,
3172        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3173        "Host main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
3174    );
3175    workspace_diagnostics_pulls_handle.next().await.unwrap();
3176    assert_eq!(
3177        workspace_diagnostic_start_count + 3,
3178        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3179        "After host main.rs edits, the workspace diagnostics pull should follow"
3180    );
3181    executor.run_until_parked();
3182    let diagnostic_pulls_result_ids = diagnostics_pulls_result_ids.lock().await.len();
3183    let workspace_pulls_result_ids = workspace_diagnostics_pulls_result_ids.lock().await.len();
3184    {
3185        assert!(
3186            diagnostic_pulls_result_ids > 1,
3187            "Should have sent result ids when pulling diagnostics"
3188        );
3189        assert!(
3190            workspace_pulls_result_ids > 1,
3191            "Should have sent result ids when pulling workspace diagnostics"
3192        );
3193    }
3194
3195    fake_language_server
3196        .request::<lsp::request::WorkspaceDiagnosticRefresh>(())
3197        .await
3198        .into_response()
3199        .expect("workspace diagnostics refresh request failed");
3200    assert_eq!(
3201        8,
3202        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3203        "No single file pulls should happen after the diagnostics refresh server request"
3204    );
3205    workspace_diagnostics_pulls_handle.next().await.unwrap();
3206    assert_eq!(
3207        workspace_diagnostic_start_count + 4,
3208        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3209        "Another workspace diagnostics pull should happen after the diagnostics refresh server request"
3210    );
3211    {
3212        assert!(
3213            diagnostics_pulls_result_ids.lock().await.len() == diagnostic_pulls_result_ids,
3214            "Pulls should not happen hence no extra ids should appear"
3215        );
3216        assert!(
3217            workspace_diagnostics_pulls_result_ids.lock().await.len() > workspace_pulls_result_ids,
3218            "More workspace diagnostics should be pulled"
3219        );
3220    }
3221    editor_b_lib.update(cx_b, |editor, cx| {
3222        let snapshot = editor.buffer().read(cx).snapshot(cx);
3223        let all_diagnostics = snapshot
3224            .diagnostics_in_range(0..snapshot.len())
3225            .collect::<Vec<_>>();
3226        let expected_messages = [
3227            expected_workspace_pull_diagnostics_lib_message,
3228            expected_pull_diagnostic_lib_message,
3229            expected_push_diagnostic_lib_message,
3230        ];
3231        assert_eq!(all_diagnostics.len(), 1);
3232        for diagnostic in &all_diagnostics {
3233            assert!(
3234                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3235                "Unexpected diagnostics: {all_diagnostics:?}"
3236            );
3237        }
3238    });
3239    editor_b_main.update(cx_b, |editor, cx| {
3240        let snapshot = editor.buffer().read(cx).snapshot(cx);
3241        let all_diagnostics = snapshot
3242            .diagnostics_in_range(0..snapshot.len())
3243            .collect::<Vec<_>>();
3244        assert_eq!(all_diagnostics.len(), 2);
3245
3246        let expected_messages = [
3247            expected_workspace_pull_diagnostics_main_message,
3248            expected_pull_diagnostic_main_message,
3249            expected_push_diagnostic_main_message,
3250        ];
3251        for diagnostic in &all_diagnostics {
3252            assert!(
3253                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3254                "Unexpected diagnostics: {all_diagnostics:?}"
3255            );
3256        }
3257    });
3258    editor_a_main.update(cx_a, |editor, cx| {
3259        let snapshot = editor.buffer().read(cx).snapshot(cx);
3260        let all_diagnostics = snapshot
3261            .diagnostics_in_range(0..snapshot.len())
3262            .collect::<Vec<_>>();
3263        assert_eq!(all_diagnostics.len(), 2);
3264        let expected_messages = [
3265            expected_workspace_pull_diagnostics_main_message,
3266            expected_pull_diagnostic_main_message,
3267            expected_push_diagnostic_main_message,
3268        ];
3269        for diagnostic in &all_diagnostics {
3270            assert!(
3271                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3272                "Unexpected diagnostics: {all_diagnostics:?}"
3273            );
3274        }
3275    });
3276}
3277
3278#[gpui::test(iterations = 10)]
3279async fn test_non_streamed_lsp_pull_diagnostics(
3280    cx_a: &mut TestAppContext,
3281    cx_b: &mut TestAppContext,
3282) {
3283    test_lsp_pull_diagnostics(false, cx_a, cx_b).await;
3284}
3285
3286#[gpui::test(iterations = 10)]
3287async fn test_streamed_lsp_pull_diagnostics(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3288    test_lsp_pull_diagnostics(true, cx_a, cx_b).await;
3289}
3290
3291#[gpui::test(iterations = 10)]
3292async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3293    let mut server = TestServer::start(cx_a.executor()).await;
3294    let client_a = server.create_client(cx_a, "user_a").await;
3295    let client_b = server.create_client(cx_b, "user_b").await;
3296    server
3297        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3298        .await;
3299    let active_call_a = cx_a.read(ActiveCall::global);
3300
3301    cx_a.update(editor::init);
3302    cx_b.update(editor::init);
3303    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
3304    let inline_blame_off_settings = Some(InlineBlameSettings {
3305        enabled: Some(false),
3306        ..Default::default()
3307    });
3308    cx_a.update(|cx| {
3309        SettingsStore::update_global(cx, |store, cx| {
3310            store.update_user_settings(cx, |settings| {
3311                settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
3312            });
3313        });
3314    });
3315    cx_b.update(|cx| {
3316        SettingsStore::update_global(cx, |store, cx| {
3317            store.update_user_settings(cx, |settings| {
3318                settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
3319            });
3320        });
3321    });
3322
3323    client_a
3324        .fs()
3325        .insert_tree(
3326            path!("/my-repo"),
3327            json!({
3328                ".git": {},
3329                "file.txt": "line1\nline2\nline3\nline\n",
3330            }),
3331        )
3332        .await;
3333
3334    let blame = git::blame::Blame {
3335        entries: vec![
3336            blame_entry("1b1b1b", 0..1),
3337            blame_entry("0d0d0d", 1..2),
3338            blame_entry("3a3a3a", 2..3),
3339            blame_entry("4c4c4c", 3..4),
3340        ],
3341        messages: [
3342            ("1b1b1b", "message for idx-0"),
3343            ("0d0d0d", "message for idx-1"),
3344            ("3a3a3a", "message for idx-2"),
3345            ("4c4c4c", "message for idx-3"),
3346        ]
3347        .into_iter()
3348        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
3349        .collect(),
3350        remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
3351    };
3352    client_a.fs().set_blame_for_repo(
3353        Path::new(path!("/my-repo/.git")),
3354        vec![("file.txt".into(), blame)],
3355    );
3356
3357    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
3358    let project_id = active_call_a
3359        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3360        .await
3361        .unwrap();
3362
3363    // Create editor_a
3364    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3365    let editor_a = workspace_a
3366        .update_in(cx_a, |workspace, window, cx| {
3367            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
3368        })
3369        .await
3370        .unwrap()
3371        .downcast::<Editor>()
3372        .unwrap();
3373
3374    // Join the project as client B.
3375    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3376    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3377    let editor_b = workspace_b
3378        .update_in(cx_b, |workspace, window, cx| {
3379            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
3380        })
3381        .await
3382        .unwrap()
3383        .downcast::<Editor>()
3384        .unwrap();
3385    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
3386        editor_b
3387            .buffer()
3388            .read(cx)
3389            .as_singleton()
3390            .unwrap()
3391            .read(cx)
3392            .remote_id()
3393    });
3394
3395    // client_b now requests git blame for the open buffer
3396    editor_b.update_in(cx_b, |editor_b, window, cx| {
3397        assert!(editor_b.blame().is_none());
3398        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
3399    });
3400
3401    cx_a.executor().run_until_parked();
3402    cx_b.executor().run_until_parked();
3403
3404    editor_b.update(cx_b, |editor_b, cx| {
3405        let blame = editor_b.blame().expect("editor_b should have blame now");
3406        let entries = blame.update(cx, |blame, cx| {
3407            blame
3408                .blame_for_rows(
3409                    &(0..4)
3410                        .map(|row| RowInfo {
3411                            buffer_row: Some(row),
3412                            buffer_id: Some(buffer_id_b),
3413                            ..Default::default()
3414                        })
3415                        .collect::<Vec<_>>(),
3416                    cx,
3417                )
3418                .collect::<Vec<_>>()
3419        });
3420
3421        assert_eq!(
3422            entries,
3423            vec![
3424                Some((buffer_id_b, blame_entry("1b1b1b", 0..1))),
3425                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3426                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3427                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3428            ]
3429        );
3430
3431        blame.update(cx, |blame, _| {
3432            for (idx, (buffer, entry)) in entries.iter().flatten().enumerate() {
3433                let details = blame.details_for_entry(*buffer, entry).unwrap();
3434                assert_eq!(details.message, format!("message for idx-{}", idx));
3435                assert_eq!(
3436                    details.permalink.unwrap().to_string(),
3437                    format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
3438                );
3439            }
3440        });
3441    });
3442
3443    // editor_b updates the file, which gets sent to client_a, which updates git blame,
3444    // which gets back to client_b.
3445    editor_b.update_in(cx_b, |editor_b, _, cx| {
3446        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
3447    });
3448
3449    cx_a.executor().run_until_parked();
3450    cx_b.executor().run_until_parked();
3451
3452    editor_b.update(cx_b, |editor_b, cx| {
3453        let blame = editor_b.blame().expect("editor_b should have blame now");
3454        let entries = blame.update(cx, |blame, cx| {
3455            blame
3456                .blame_for_rows(
3457                    &(0..4)
3458                        .map(|row| RowInfo {
3459                            buffer_row: Some(row),
3460                            buffer_id: Some(buffer_id_b),
3461                            ..Default::default()
3462                        })
3463                        .collect::<Vec<_>>(),
3464                    cx,
3465                )
3466                .collect::<Vec<_>>()
3467        });
3468
3469        assert_eq!(
3470            entries,
3471            vec![
3472                None,
3473                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3474                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3475                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3476            ]
3477        );
3478    });
3479
3480    // Now editor_a also updates the file
3481    editor_a.update_in(cx_a, |editor_a, _, cx| {
3482        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
3483    });
3484
3485    cx_a.executor().run_until_parked();
3486    cx_b.executor().run_until_parked();
3487
3488    editor_b.update(cx_b, |editor_b, cx| {
3489        let blame = editor_b.blame().expect("editor_b should have blame now");
3490        let entries = blame.update(cx, |blame, cx| {
3491            blame
3492                .blame_for_rows(
3493                    &(0..4)
3494                        .map(|row| RowInfo {
3495                            buffer_row: Some(row),
3496                            buffer_id: Some(buffer_id_b),
3497                            ..Default::default()
3498                        })
3499                        .collect::<Vec<_>>(),
3500                    cx,
3501                )
3502                .collect::<Vec<_>>()
3503        });
3504
3505        assert_eq!(
3506            entries,
3507            vec![
3508                None,
3509                None,
3510                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3511                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3512            ]
3513        );
3514    });
3515}
3516
3517#[gpui::test(iterations = 30)]
3518async fn test_collaborating_with_editorconfig(
3519    cx_a: &mut TestAppContext,
3520    cx_b: &mut TestAppContext,
3521) {
3522    let mut server = TestServer::start(cx_a.executor()).await;
3523    let client_a = server.create_client(cx_a, "user_a").await;
3524    let client_b = server.create_client(cx_b, "user_b").await;
3525    server
3526        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3527        .await;
3528    let active_call_a = cx_a.read(ActiveCall::global);
3529
3530    cx_b.update(editor::init);
3531
3532    // Set up a fake language server.
3533    client_a.language_registry().add(rust_lang());
3534    client_a
3535        .fs()
3536        .insert_tree(
3537            path!("/a"),
3538            json!({
3539                "src": {
3540                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
3541                    "other_mod": {
3542                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
3543                        ".editorconfig": "",
3544                    },
3545                },
3546                ".editorconfig": "[*]\ntab_width = 2\n",
3547            }),
3548        )
3549        .await;
3550    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
3551    let project_id = active_call_a
3552        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3553        .await
3554        .unwrap();
3555    let main_buffer_a = project_a
3556        .update(cx_a, |p, cx| {
3557            p.open_buffer((worktree_id, "src/main.rs"), cx)
3558        })
3559        .await
3560        .unwrap();
3561    let other_buffer_a = project_a
3562        .update(cx_a, |p, cx| {
3563            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
3564        })
3565        .await
3566        .unwrap();
3567    let cx_a = cx_a.add_empty_window();
3568    let main_editor_a = cx_a.new_window_entity(|window, cx| {
3569        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
3570    });
3571    let other_editor_a = cx_a.new_window_entity(|window, cx| {
3572        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
3573    });
3574    let mut main_editor_cx_a = EditorTestContext {
3575        cx: cx_a.clone(),
3576        window: cx_a.window_handle(),
3577        editor: main_editor_a,
3578        assertion_cx: AssertionContextManager::new(),
3579    };
3580    let mut other_editor_cx_a = EditorTestContext {
3581        cx: cx_a.clone(),
3582        window: cx_a.window_handle(),
3583        editor: other_editor_a,
3584        assertion_cx: AssertionContextManager::new(),
3585    };
3586
3587    // Join the project as client B.
3588    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3589    let main_buffer_b = project_b
3590        .update(cx_b, |p, cx| {
3591            p.open_buffer((worktree_id, "src/main.rs"), cx)
3592        })
3593        .await
3594        .unwrap();
3595    let other_buffer_b = project_b
3596        .update(cx_b, |p, cx| {
3597            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
3598        })
3599        .await
3600        .unwrap();
3601    let cx_b = cx_b.add_empty_window();
3602    let main_editor_b = cx_b.new_window_entity(|window, cx| {
3603        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
3604    });
3605    let other_editor_b = cx_b.new_window_entity(|window, cx| {
3606        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
3607    });
3608    let mut main_editor_cx_b = EditorTestContext {
3609        cx: cx_b.clone(),
3610        window: cx_b.window_handle(),
3611        editor: main_editor_b,
3612        assertion_cx: AssertionContextManager::new(),
3613    };
3614    let mut other_editor_cx_b = EditorTestContext {
3615        cx: cx_b.clone(),
3616        window: cx_b.window_handle(),
3617        editor: other_editor_b,
3618        assertion_cx: AssertionContextManager::new(),
3619    };
3620
3621    let initial_main = indoc! {"
3622ˇmod other;
3623fn main() { let foo = other::foo(); }"};
3624    let initial_other = indoc! {"
3625ˇpub fn foo() -> usize {
3626    4
3627}"};
3628
3629    let first_tabbed_main = indoc! {"
3630  ˇmod other;
3631fn main() { let foo = other::foo(); }"};
3632    tab_undo_assert(
3633        &mut main_editor_cx_a,
3634        &mut main_editor_cx_b,
3635        initial_main,
3636        first_tabbed_main,
3637        true,
3638    );
3639    tab_undo_assert(
3640        &mut main_editor_cx_a,
3641        &mut main_editor_cx_b,
3642        initial_main,
3643        first_tabbed_main,
3644        false,
3645    );
3646
3647    let first_tabbed_other = indoc! {"
3648  ˇpub fn foo() -> usize {
3649    4
3650}"};
3651    tab_undo_assert(
3652        &mut other_editor_cx_a,
3653        &mut other_editor_cx_b,
3654        initial_other,
3655        first_tabbed_other,
3656        true,
3657    );
3658    tab_undo_assert(
3659        &mut other_editor_cx_a,
3660        &mut other_editor_cx_b,
3661        initial_other,
3662        first_tabbed_other,
3663        false,
3664    );
3665
3666    client_a
3667        .fs()
3668        .atomic_write(
3669            PathBuf::from(path!("/a/src/.editorconfig")),
3670            "[*]\ntab_width = 3\n".to_owned(),
3671        )
3672        .await
3673        .unwrap();
3674    cx_a.run_until_parked();
3675    cx_b.run_until_parked();
3676
3677    let second_tabbed_main = indoc! {"
3678   ˇmod other;
3679fn main() { let foo = other::foo(); }"};
3680    tab_undo_assert(
3681        &mut main_editor_cx_a,
3682        &mut main_editor_cx_b,
3683        initial_main,
3684        second_tabbed_main,
3685        true,
3686    );
3687    tab_undo_assert(
3688        &mut main_editor_cx_a,
3689        &mut main_editor_cx_b,
3690        initial_main,
3691        second_tabbed_main,
3692        false,
3693    );
3694
3695    let second_tabbed_other = indoc! {"
3696   ˇpub fn foo() -> usize {
3697    4
3698}"};
3699    tab_undo_assert(
3700        &mut other_editor_cx_a,
3701        &mut other_editor_cx_b,
3702        initial_other,
3703        second_tabbed_other,
3704        true,
3705    );
3706    tab_undo_assert(
3707        &mut other_editor_cx_a,
3708        &mut other_editor_cx_b,
3709        initial_other,
3710        second_tabbed_other,
3711        false,
3712    );
3713
3714    let editorconfig_buffer_b = project_b
3715        .update(cx_b, |p, cx| {
3716            p.open_buffer((worktree_id, "src/other_mod/.editorconfig"), cx)
3717        })
3718        .await
3719        .unwrap();
3720    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
3721        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
3722    });
3723    project_b
3724        .update(cx_b, |project, cx| {
3725            project.save_buffer(editorconfig_buffer_b.clone(), cx)
3726        })
3727        .await
3728        .unwrap();
3729    cx_a.run_until_parked();
3730    cx_b.run_until_parked();
3731
3732    tab_undo_assert(
3733        &mut main_editor_cx_a,
3734        &mut main_editor_cx_b,
3735        initial_main,
3736        second_tabbed_main,
3737        true,
3738    );
3739    tab_undo_assert(
3740        &mut main_editor_cx_a,
3741        &mut main_editor_cx_b,
3742        initial_main,
3743        second_tabbed_main,
3744        false,
3745    );
3746
3747    let third_tabbed_other = indoc! {"
3748      ˇpub fn foo() -> usize {
3749    4
3750}"};
3751    tab_undo_assert(
3752        &mut other_editor_cx_a,
3753        &mut other_editor_cx_b,
3754        initial_other,
3755        third_tabbed_other,
3756        true,
3757    );
3758
3759    tab_undo_assert(
3760        &mut other_editor_cx_a,
3761        &mut other_editor_cx_b,
3762        initial_other,
3763        third_tabbed_other,
3764        false,
3765    );
3766}
3767
3768#[gpui::test]
3769async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3770    let executor = cx_a.executor();
3771    let mut server = TestServer::start(executor.clone()).await;
3772    let client_a = server.create_client(cx_a, "user_a").await;
3773    let client_b = server.create_client(cx_b, "user_b").await;
3774    server
3775        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3776        .await;
3777    let active_call_a = cx_a.read(ActiveCall::global);
3778    let active_call_b = cx_b.read(ActiveCall::global);
3779    cx_a.update(editor::init);
3780    cx_b.update(editor::init);
3781    client_a
3782        .fs()
3783        .insert_tree(
3784            "/a",
3785            json!({
3786                "test.txt": "one\ntwo\nthree\nfour\nfive",
3787            }),
3788        )
3789        .await;
3790    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3791    let project_path = ProjectPath {
3792        worktree_id,
3793        path: Arc::from(Path::new(&"test.txt")),
3794    };
3795    let abs_path = project_a.read_with(cx_a, |project, cx| {
3796        project
3797            .absolute_path(&project_path, cx)
3798            .map(Arc::from)
3799            .unwrap()
3800    });
3801
3802    active_call_a
3803        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
3804        .await
3805        .unwrap();
3806    let project_id = active_call_a
3807        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3808        .await
3809        .unwrap();
3810    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3811    active_call_b
3812        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
3813        .await
3814        .unwrap();
3815    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3816    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3817
3818    // Client A opens an editor.
3819    let editor_a = workspace_a
3820        .update_in(cx_a, |workspace, window, cx| {
3821            workspace.open_path(project_path.clone(), None, true, window, cx)
3822        })
3823        .await
3824        .unwrap()
3825        .downcast::<Editor>()
3826        .unwrap();
3827
3828    // Client B opens same editor as A.
3829    let editor_b = workspace_b
3830        .update_in(cx_b, |workspace, window, cx| {
3831            workspace.open_path(project_path.clone(), None, true, window, cx)
3832        })
3833        .await
3834        .unwrap()
3835        .downcast::<Editor>()
3836        .unwrap();
3837
3838    cx_a.run_until_parked();
3839    cx_b.run_until_parked();
3840
3841    // Client A adds breakpoint on line (1)
3842    editor_a.update_in(cx_a, |editor, window, cx| {
3843        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3844    });
3845
3846    cx_a.run_until_parked();
3847    cx_b.run_until_parked();
3848
3849    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3850        editor
3851            .breakpoint_store()
3852            .unwrap()
3853            .read(cx)
3854            .all_source_breakpoints(cx)
3855    });
3856    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3857        editor
3858            .breakpoint_store()
3859            .unwrap()
3860            .read(cx)
3861            .all_source_breakpoints(cx)
3862    });
3863
3864    assert_eq!(1, breakpoints_a.len());
3865    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
3866    assert_eq!(breakpoints_a, breakpoints_b);
3867
3868    // Client B adds breakpoint on line(2)
3869    editor_b.update_in(cx_b, |editor, window, cx| {
3870        editor.move_down(&editor::actions::MoveDown, window, cx);
3871        editor.move_down(&editor::actions::MoveDown, window, cx);
3872        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3873    });
3874
3875    cx_a.run_until_parked();
3876    cx_b.run_until_parked();
3877
3878    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3879        editor
3880            .breakpoint_store()
3881            .unwrap()
3882            .read(cx)
3883            .all_source_breakpoints(cx)
3884    });
3885    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3886        editor
3887            .breakpoint_store()
3888            .unwrap()
3889            .read(cx)
3890            .all_source_breakpoints(cx)
3891    });
3892
3893    assert_eq!(1, breakpoints_a.len());
3894    assert_eq!(breakpoints_a, breakpoints_b);
3895    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
3896
3897    // Client A removes last added breakpoint from client B
3898    editor_a.update_in(cx_a, |editor, window, cx| {
3899        editor.move_down(&editor::actions::MoveDown, window, cx);
3900        editor.move_down(&editor::actions::MoveDown, window, cx);
3901        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3902    });
3903
3904    cx_a.run_until_parked();
3905    cx_b.run_until_parked();
3906
3907    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3908        editor
3909            .breakpoint_store()
3910            .unwrap()
3911            .read(cx)
3912            .all_source_breakpoints(cx)
3913    });
3914    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3915        editor
3916            .breakpoint_store()
3917            .unwrap()
3918            .read(cx)
3919            .all_source_breakpoints(cx)
3920    });
3921
3922    assert_eq!(1, breakpoints_a.len());
3923    assert_eq!(breakpoints_a, breakpoints_b);
3924    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
3925
3926    // Client B removes first added breakpoint by client A
3927    editor_b.update_in(cx_b, |editor, window, cx| {
3928        editor.move_up(&editor::actions::MoveUp, window, cx);
3929        editor.move_up(&editor::actions::MoveUp, window, cx);
3930        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3931    });
3932
3933    cx_a.run_until_parked();
3934    cx_b.run_until_parked();
3935
3936    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3937        editor
3938            .breakpoint_store()
3939            .unwrap()
3940            .read(cx)
3941            .all_source_breakpoints(cx)
3942    });
3943    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3944        editor
3945            .breakpoint_store()
3946            .unwrap()
3947            .read(cx)
3948            .all_source_breakpoints(cx)
3949    });
3950
3951    assert_eq!(0, breakpoints_a.len());
3952    assert_eq!(breakpoints_a, breakpoints_b);
3953}
3954
3955#[gpui::test]
3956async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3957    let mut server = TestServer::start(cx_a.executor()).await;
3958    let client_a = server.create_client(cx_a, "user_a").await;
3959    let client_b = server.create_client(cx_b, "user_b").await;
3960    server
3961        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3962        .await;
3963    let active_call_a = cx_a.read(ActiveCall::global);
3964    let active_call_b = cx_b.read(ActiveCall::global);
3965
3966    cx_a.update(editor::init);
3967    cx_b.update(editor::init);
3968
3969    client_a.language_registry().add(rust_lang());
3970    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
3971        "Rust",
3972        FakeLspAdapter {
3973            name: "rust-analyzer",
3974            ..FakeLspAdapter::default()
3975        },
3976    );
3977    client_b.language_registry().add(rust_lang());
3978    client_b.language_registry().register_fake_lsp_adapter(
3979        "Rust",
3980        FakeLspAdapter {
3981            name: "rust-analyzer",
3982            ..FakeLspAdapter::default()
3983        },
3984    );
3985
3986    client_a
3987        .fs()
3988        .insert_tree(
3989            path!("/a"),
3990            json!({
3991                "main.rs": "fn main() {}",
3992            }),
3993        )
3994        .await;
3995    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
3996    active_call_a
3997        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
3998        .await
3999        .unwrap();
4000    let project_id = active_call_a
4001        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4002        .await
4003        .unwrap();
4004
4005    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4006    active_call_b
4007        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
4008        .await
4009        .unwrap();
4010
4011    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4012    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4013
4014    let editor_a = workspace_a
4015        .update_in(cx_a, |workspace, window, cx| {
4016            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
4017        })
4018        .await
4019        .unwrap()
4020        .downcast::<Editor>()
4021        .unwrap();
4022
4023    let editor_b = workspace_b
4024        .update_in(cx_b, |workspace, window, cx| {
4025            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
4026        })
4027        .await
4028        .unwrap()
4029        .downcast::<Editor>()
4030        .unwrap();
4031
4032    let fake_language_server = fake_language_servers.next().await.unwrap();
4033
4034    // host
4035    let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4036        |params, _| async move {
4037            assert_eq!(
4038                params.text_document.uri,
4039                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4040            );
4041            assert_eq!(params.position, lsp::Position::new(0, 0));
4042            Ok(Some(ExpandedMacro {
4043                name: "test_macro_name".to_string(),
4044                expansion: "test_macro_expansion on the host".to_string(),
4045            }))
4046        },
4047    );
4048
4049    editor_a.update_in(cx_a, |editor, window, cx| {
4050        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4051    });
4052    expand_request_a.next().await.unwrap();
4053    cx_a.run_until_parked();
4054
4055    workspace_a.update(cx_a, |workspace, cx| {
4056        workspace.active_pane().update(cx, |pane, cx| {
4057            assert_eq!(
4058                pane.items_len(),
4059                2,
4060                "Should have added a macro expansion to the host's pane"
4061            );
4062            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4063            new_editor.update(cx, |editor, cx| {
4064                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
4065            });
4066        })
4067    });
4068
4069    // client
4070    let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4071        |params, _| async move {
4072            assert_eq!(
4073                params.text_document.uri,
4074                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4075            );
4076            assert_eq!(
4077                params.position,
4078                lsp::Position::new(0, 12),
4079                "editor_b has selected the entire text and should query for a different position"
4080            );
4081            Ok(Some(ExpandedMacro {
4082                name: "test_macro_name".to_string(),
4083                expansion: "test_macro_expansion on the client".to_string(),
4084            }))
4085        },
4086    );
4087
4088    editor_b.update_in(cx_b, |editor, window, cx| {
4089        editor.select_all(&SelectAll, window, cx);
4090        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4091    });
4092    expand_request_b.next().await.unwrap();
4093    cx_b.run_until_parked();
4094
4095    workspace_b.update(cx_b, |workspace, cx| {
4096        workspace.active_pane().update(cx, |pane, cx| {
4097            assert_eq!(
4098                pane.items_len(),
4099                2,
4100                "Should have added a macro expansion to the client's pane"
4101            );
4102            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4103            new_editor.update(cx, |editor, cx| {
4104                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
4105            });
4106        })
4107    });
4108}
4109
4110#[track_caller]
4111fn tab_undo_assert(
4112    cx_a: &mut EditorTestContext,
4113    cx_b: &mut EditorTestContext,
4114    expected_initial: &str,
4115    expected_tabbed: &str,
4116    a_tabs: bool,
4117) {
4118    cx_a.assert_editor_state(expected_initial);
4119    cx_b.assert_editor_state(expected_initial);
4120
4121    if a_tabs {
4122        cx_a.update_editor(|editor, window, cx| {
4123            editor.tab(&editor::actions::Tab, window, cx);
4124        });
4125    } else {
4126        cx_b.update_editor(|editor, window, cx| {
4127            editor.tab(&editor::actions::Tab, window, cx);
4128        });
4129    }
4130
4131    cx_a.run_until_parked();
4132    cx_b.run_until_parked();
4133
4134    cx_a.assert_editor_state(expected_tabbed);
4135    cx_b.assert_editor_state(expected_tabbed);
4136
4137    if a_tabs {
4138        cx_a.update_editor(|editor, window, cx| {
4139            editor.undo(&editor::actions::Undo, window, cx);
4140        });
4141    } else {
4142        cx_b.update_editor(|editor, window, cx| {
4143            editor.undo(&editor::actions::Undo, window, cx);
4144        });
4145    }
4146    cx_a.run_until_parked();
4147    cx_b.run_until_parked();
4148    cx_a.assert_editor_state(expected_initial);
4149    cx_b.assert_editor_state(expected_initial);
4150}
4151
4152fn extract_hint_labels(editor: &Editor) -> Vec<String> {
4153    let mut labels = Vec::new();
4154    for hint in editor.inlay_hint_cache().hints() {
4155        match hint.label {
4156            project::InlayHintLabel::String(s) => labels.push(s),
4157            _ => unreachable!(),
4158        }
4159    }
4160    labels
4161}
4162
4163#[track_caller]
4164fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
4165    editor
4166        .all_inlays(cx)
4167        .into_iter()
4168        .filter_map(|inlay| inlay.get_color())
4169        .map(Rgba::from)
4170        .collect()
4171}
4172
4173fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
4174    git::blame::BlameEntry {
4175        sha: sha.parse().unwrap(),
4176        range,
4177        ..Default::default()
4178    }
4179}