editor_tests.rs

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