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