editor_tests.rs

   1use crate::{
   2    rpc::RECONNECT_TIMEOUT,
   3    tests::{TestServer, rust_lang},
   4};
   5use call::ActiveCall;
   6use editor::{
   7    Editor, RowInfo,
   8    actions::{
   9        ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst,
  10        ExpandMacroRecursively, Redo, Rename, ToggleCodeActions, Undo,
  11    },
  12    test::{
  13        editor_test_context::{AssertionContextManager, EditorTestContext},
  14        expand_macro_recursively,
  15    },
  16};
  17use fs::Fs;
  18use futures::StreamExt;
  19use gpui::{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    ops::Range,
  39    path::{Path, PathBuf},
  40    sync::{
  41        Arc,
  42        atomic::{self, AtomicBool, AtomicUsize},
  43    },
  44};
  45use text::Point;
  46use util::{path, uri};
  47use workspace::{CloseIntent, Workspace};
  48
  49#[gpui::test(iterations = 10)]
  50async fn test_host_disconnect(
  51    cx_a: &mut TestAppContext,
  52    cx_b: &mut TestAppContext,
  53    cx_c: &mut TestAppContext,
  54) {
  55    let mut server = TestServer::start(cx_a.executor()).await;
  56    let client_a = server.create_client(cx_a, "user_a").await;
  57    let client_b = server.create_client(cx_b, "user_b").await;
  58    let client_c = server.create_client(cx_c, "user_c").await;
  59    server
  60        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
  61        .await;
  62
  63    cx_b.update(editor::init);
  64    cx_b.update(recent_projects::init);
  65
  66    client_a
  67        .fs()
  68        .insert_tree(
  69            "/a",
  70            json!({
  71                "a.txt": "a-contents",
  72                "b.txt": "b-contents",
  73            }),
  74        )
  75        .await;
  76
  77    let active_call_a = cx_a.read(ActiveCall::global);
  78    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
  79
  80    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
  81    let project_id = active_call_a
  82        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
  83        .await
  84        .unwrap();
  85
  86    let project_b = client_b.join_remote_project(project_id, cx_b).await;
  87    cx_a.background_executor.run_until_parked();
  88
  89    assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
  90
  91    let workspace_b = cx_b.add_window(|window, cx| {
  92        Workspace::new(
  93            None,
  94            project_b.clone(),
  95            client_b.app_state.clone(),
  96            window,
  97            cx,
  98        )
  99    });
 100    let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
 101    let workspace_b_view = workspace_b.root(cx_b).unwrap();
 102
 103    let editor_b = workspace_b
 104        .update(cx_b, |workspace, window, cx| {
 105            workspace.open_path((worktree_id, "b.txt"), None, true, window, cx)
 106        })
 107        .unwrap()
 108        .await
 109        .unwrap()
 110        .downcast::<Editor>()
 111        .unwrap();
 112
 113    //TODO: focus
 114    assert!(cx_b.update_window_entity(&editor_b, |editor, window, _| editor.is_focused(window)));
 115    editor_b.update_in(cx_b, |editor, window, cx| editor.insert("X", window, cx));
 116
 117    cx_b.update(|_, cx| {
 118        assert!(workspace_b_view.read(cx).is_edited());
 119    });
 120
 121    // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
 122    server.forbid_connections();
 123    server.disconnect_client(client_a.peer_id().unwrap());
 124    cx_a.background_executor
 125        .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 126
 127    project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());
 128
 129    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 130
 131    project_b.read_with(cx_b, |project, cx| project.is_read_only(cx));
 132
 133    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
 134
 135    // Ensure client B's edited state is reset and that the whole window is blurred.
 136    workspace_b
 137        .update(cx_b, |workspace, _, cx| {
 138            assert!(workspace.active_modal::<DisconnectedOverlay>(cx).is_some());
 139            assert!(!workspace.is_edited());
 140        })
 141        .unwrap();
 142
 143    // Ensure client B is not prompted to save edits when closing window after disconnecting.
 144    let can_close = workspace_b
 145        .update(cx_b, |workspace, window, cx| {
 146            workspace.prepare_to_close(CloseIntent::Quit, window, cx)
 147        })
 148        .unwrap()
 149        .await
 150        .unwrap();
 151    assert!(can_close);
 152
 153    // Allow client A to reconnect to the server.
 154    server.allow_connections();
 155    cx_a.background_executor.advance_clock(RECEIVE_TIMEOUT);
 156
 157    // Client B calls client A again after they reconnected.
 158    let active_call_b = cx_b.read(ActiveCall::global);
 159    active_call_b
 160        .update(cx_b, |call, cx| {
 161            call.invite(client_a.user_id().unwrap(), None, cx)
 162        })
 163        .await
 164        .unwrap();
 165    cx_a.background_executor.run_until_parked();
 166    active_call_a
 167        .update(cx_a, |call, cx| call.accept_incoming(cx))
 168        .await
 169        .unwrap();
 170
 171    active_call_a
 172        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 173        .await
 174        .unwrap();
 175
 176    // Drop client A's connection again. We should still unshare it successfully.
 177    server.forbid_connections();
 178    server.disconnect_client(client_a.peer_id().unwrap());
 179    cx_a.background_executor
 180        .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 181
 182    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 183}
 184
 185#[gpui::test]
 186async fn test_newline_above_or_below_does_not_move_guest_cursor(
 187    cx_a: &mut TestAppContext,
 188    cx_b: &mut TestAppContext,
 189) {
 190    let mut server = TestServer::start(cx_a.executor()).await;
 191    let client_a = server.create_client(cx_a, "user_a").await;
 192    let client_b = server.create_client(cx_b, "user_b").await;
 193    let executor = cx_a.executor();
 194    server
 195        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 196        .await;
 197    let active_call_a = cx_a.read(ActiveCall::global);
 198
 199    client_a
 200        .fs()
 201        .insert_tree(path!("/dir"), json!({ "a.txt": "Some text\n" }))
 202        .await;
 203    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
 204    let project_id = active_call_a
 205        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 206        .await
 207        .unwrap();
 208
 209    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 210
 211    // Open a buffer as client A
 212    let buffer_a = project_a
 213        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 214        .await
 215        .unwrap();
 216    let cx_a = cx_a.add_empty_window();
 217    let editor_a = cx_a
 218        .new_window_entity(|window, cx| Editor::for_buffer(buffer_a, Some(project_a), window, cx));
 219
 220    let mut editor_cx_a = EditorTestContext {
 221        cx: cx_a.clone(),
 222        window: cx_a.window_handle(),
 223        editor: editor_a,
 224        assertion_cx: AssertionContextManager::new(),
 225    };
 226
 227    let cx_b = cx_b.add_empty_window();
 228    // Open a buffer as client B
 229    let buffer_b = project_b
 230        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 231        .await
 232        .unwrap();
 233    let editor_b = cx_b
 234        .new_window_entity(|window, cx| Editor::for_buffer(buffer_b, Some(project_b), window, cx));
 235
 236    let mut editor_cx_b = EditorTestContext {
 237        cx: cx_b.clone(),
 238        window: cx_b.window_handle(),
 239        editor: editor_b,
 240        assertion_cx: AssertionContextManager::new(),
 241    };
 242
 243    // Test newline above
 244    editor_cx_a.set_selections_state(indoc! {"
 245        Some textˇ
 246    "});
 247    editor_cx_b.set_selections_state(indoc! {"
 248        Some textˇ
 249    "});
 250    editor_cx_a.update_editor(|editor, window, cx| {
 251        editor.newline_above(&editor::actions::NewlineAbove, window, cx)
 252    });
 253    executor.run_until_parked();
 254    editor_cx_a.assert_editor_state(indoc! {"
 255        ˇ
 256        Some text
 257    "});
 258    editor_cx_b.assert_editor_state(indoc! {"
 259
 260        Some textˇ
 261    "});
 262
 263    // Test newline below
 264    editor_cx_a.set_selections_state(indoc! {"
 265
 266        Some textˇ
 267    "});
 268    editor_cx_b.set_selections_state(indoc! {"
 269
 270        Some textˇ
 271    "});
 272    editor_cx_a.update_editor(|editor, window, cx| {
 273        editor.newline_below(&editor::actions::NewlineBelow, window, cx)
 274    });
 275    executor.run_until_parked();
 276    editor_cx_a.assert_editor_state(indoc! {"
 277
 278        Some text
 279        ˇ
 280    "});
 281    editor_cx_b.assert_editor_state(indoc! {"
 282
 283        Some textˇ
 284
 285    "});
 286}
 287
 288#[gpui::test(iterations = 10)]
 289async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 290    let mut server = TestServer::start(cx_a.executor()).await;
 291    let client_a = server.create_client(cx_a, "user_a").await;
 292    let client_b = server.create_client(cx_b, "user_b").await;
 293    server
 294        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 295        .await;
 296    let active_call_a = cx_a.read(ActiveCall::global);
 297
 298    client_a.language_registry().add(rust_lang());
 299    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
 300        "Rust",
 301        FakeLspAdapter {
 302            capabilities: lsp::ServerCapabilities {
 303                completion_provider: Some(lsp::CompletionOptions {
 304                    trigger_characters: Some(vec![".".to_string()]),
 305                    resolve_provider: Some(true),
 306                    ..Default::default()
 307                }),
 308                ..Default::default()
 309            },
 310            ..Default::default()
 311        },
 312    );
 313
 314    client_a
 315        .fs()
 316        .insert_tree(
 317            path!("/a"),
 318            json!({
 319                "main.rs": "fn main() { a }",
 320                "other.rs": "",
 321            }),
 322        )
 323        .await;
 324    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
 325    let project_id = active_call_a
 326        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 327        .await
 328        .unwrap();
 329    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 330
 331    // Open a file in an editor as the guest.
 332    let buffer_b = project_b
 333        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
 334        .await
 335        .unwrap();
 336    let cx_b = cx_b.add_empty_window();
 337    let editor_b = cx_b.new_window_entity(|window, cx| {
 338        Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), window, cx)
 339    });
 340
 341    let fake_language_server = fake_language_servers.next().await.unwrap();
 342    cx_a.background_executor.run_until_parked();
 343
 344    buffer_b.read_with(cx_b, |buffer, _| {
 345        assert!(!buffer.completion_triggers().is_empty())
 346    });
 347
 348    // Type a completion trigger character as the guest.
 349    editor_b.update_in(cx_b, |editor, window, cx| {
 350        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
 351        editor.handle_input(".", window, cx);
 352    });
 353    cx_b.focus(&editor_b);
 354
 355    // Receive a completion request as the host's language server.
 356    // Return some completions from the host's language server.
 357    cx_a.executor().start_waiting();
 358    fake_language_server
 359        .set_request_handler::<lsp::request::Completion, _, _>(|params, _| async move {
 360            assert_eq!(
 361                params.text_document_position.text_document.uri,
 362                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 363            );
 364            assert_eq!(
 365                params.text_document_position.position,
 366                lsp::Position::new(0, 14),
 367            );
 368
 369            Ok(Some(lsp::CompletionResponse::Array(vec![
 370                lsp::CompletionItem {
 371                    label: "first_method(…)".into(),
 372                    detail: Some("fn(&mut self, B) -> C".into()),
 373                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 374                        new_text: "first_method($1)".to_string(),
 375                        range: lsp::Range::new(
 376                            lsp::Position::new(0, 14),
 377                            lsp::Position::new(0, 14),
 378                        ),
 379                    })),
 380                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 381                    ..Default::default()
 382                },
 383                lsp::CompletionItem {
 384                    label: "second_method(…)".into(),
 385                    detail: Some("fn(&mut self, C) -> D<E>".into()),
 386                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 387                        new_text: "second_method()".to_string(),
 388                        range: lsp::Range::new(
 389                            lsp::Position::new(0, 14),
 390                            lsp::Position::new(0, 14),
 391                        ),
 392                    })),
 393                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 394                    ..Default::default()
 395                },
 396            ])))
 397        })
 398        .next()
 399        .await
 400        .unwrap();
 401    cx_a.executor().finish_waiting();
 402
 403    // Open the buffer on the host.
 404    let buffer_a = project_a
 405        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
 406        .await
 407        .unwrap();
 408    cx_a.executor().run_until_parked();
 409
 410    buffer_a.read_with(cx_a, |buffer, _| {
 411        assert_eq!(buffer.text(), "fn main() { a. }")
 412    });
 413
 414    // Confirm a completion on the guest.
 415    editor_b.update_in(cx_b, |editor, window, cx| {
 416        assert!(editor.context_menu_visible());
 417        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
 418        assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
 419    });
 420
 421    // Return a resolved completion from the host's language server.
 422    // The resolved completion has an additional text edit.
 423    fake_language_server.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
 424        |params, _| async move {
 425            assert_eq!(params.label, "first_method(…)");
 426            Ok(lsp::CompletionItem {
 427                label: "first_method(…)".into(),
 428                detail: Some("fn(&mut self, B) -> C".into()),
 429                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 430                    new_text: "first_method($1)".to_string(),
 431                    range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
 432                })),
 433                additional_text_edits: Some(vec![lsp::TextEdit {
 434                    new_text: "use d::SomeTrait;\n".to_string(),
 435                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 436                }]),
 437                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 438                ..Default::default()
 439            })
 440        },
 441    );
 442
 443    // The additional edit is applied.
 444    cx_a.executor().run_until_parked();
 445
 446    buffer_a.read_with(cx_a, |buffer, _| {
 447        assert_eq!(
 448            buffer.text(),
 449            "use d::SomeTrait;\nfn main() { a.first_method() }"
 450        );
 451    });
 452
 453    buffer_b.read_with(cx_b, |buffer, _| {
 454        assert_eq!(
 455            buffer.text(),
 456            "use d::SomeTrait;\nfn main() { a.first_method() }"
 457        );
 458    });
 459
 460    // Now we do a second completion, this time to ensure that documentation/snippets are
 461    // resolved
 462    editor_b.update_in(cx_b, |editor, window, cx| {
 463        editor.change_selections(None, window, cx, |s| s.select_ranges([46..46]));
 464        editor.handle_input("; a", window, cx);
 465        editor.handle_input(".", window, cx);
 466    });
 467
 468    buffer_b.read_with(cx_b, |buffer, _| {
 469        assert_eq!(
 470            buffer.text(),
 471            "use d::SomeTrait;\nfn main() { a.first_method(); a. }"
 472        );
 473    });
 474
 475    let mut completion_response = fake_language_server
 476        .set_request_handler::<lsp::request::Completion, _, _>(|params, _| async move {
 477            assert_eq!(
 478                params.text_document_position.text_document.uri,
 479                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 480            );
 481            assert_eq!(
 482                params.text_document_position.position,
 483                lsp::Position::new(1, 32),
 484            );
 485
 486            Ok(Some(lsp::CompletionResponse::Array(vec![
 487                lsp::CompletionItem {
 488                    label: "third_method(…)".into(),
 489                    detail: Some("fn(&mut self, B, C, D) -> E".into()),
 490                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 491                        // no snippet placehodlers
 492                        new_text: "third_method".to_string(),
 493                        range: lsp::Range::new(
 494                            lsp::Position::new(1, 32),
 495                            lsp::Position::new(1, 32),
 496                        ),
 497                    })),
 498                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 499                    documentation: None,
 500                    ..Default::default()
 501                },
 502            ])))
 503        });
 504
 505    // The completion now gets a new `text_edit.new_text` when resolving the completion item
 506    let mut resolve_completion_response = fake_language_server
 507        .set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(|params, _| async move {
 508            assert_eq!(params.label, "third_method(…)");
 509            Ok(lsp::CompletionItem {
 510                label: "third_method(…)".into(),
 511                detail: Some("fn(&mut self, B, C, D) -> E".into()),
 512                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 513                    // Now it's a snippet
 514                    new_text: "third_method($1, $2, $3)".to_string(),
 515                    range: lsp::Range::new(lsp::Position::new(1, 32), lsp::Position::new(1, 32)),
 516                })),
 517                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 518                documentation: Some(lsp::Documentation::String(
 519                    "this is the documentation".into(),
 520                )),
 521                ..Default::default()
 522            })
 523        });
 524
 525    cx_b.executor().run_until_parked();
 526
 527    completion_response.next().await.unwrap();
 528
 529    editor_b.update_in(cx_b, |editor, window, cx| {
 530        assert!(editor.context_menu_visible());
 531        editor.context_menu_first(&ContextMenuFirst {}, window, cx);
 532    });
 533
 534    resolve_completion_response.next().await.unwrap();
 535    cx_b.executor().run_until_parked();
 536
 537    // When accepting the completion, the snippet is insert.
 538    editor_b.update_in(cx_b, |editor, window, cx| {
 539        assert!(editor.context_menu_visible());
 540        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
 541        assert_eq!(
 542            editor.text(cx),
 543            "use d::SomeTrait;\nfn main() { a.first_method(); a.third_method(, , ) }"
 544        );
 545    });
 546}
 547
 548#[gpui::test(iterations = 10)]
 549async fn test_collaborating_with_code_actions(
 550    cx_a: &mut TestAppContext,
 551    cx_b: &mut TestAppContext,
 552) {
 553    let mut server = TestServer::start(cx_a.executor()).await;
 554    let client_a = server.create_client(cx_a, "user_a").await;
 555    //
 556    let client_b = server.create_client(cx_b, "user_b").await;
 557    server
 558        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 559        .await;
 560    let active_call_a = cx_a.read(ActiveCall::global);
 561
 562    cx_b.update(editor::init);
 563
 564    // Set up a fake language server.
 565    client_a.language_registry().add(rust_lang());
 566    let mut fake_language_servers = client_a
 567        .language_registry()
 568        .register_fake_lsp("Rust", FakeLspAdapter::default());
 569
 570    client_a
 571        .fs()
 572        .insert_tree(
 573            path!("/a"),
 574            json!({
 575                "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
 576                "other.rs": "pub fn foo() -> usize { 4 }",
 577            }),
 578        )
 579        .await;
 580    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
 581    let project_id = active_call_a
 582        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 583        .await
 584        .unwrap();
 585
 586    // Join the project as client B.
 587    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 588    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 589    let editor_b = workspace_b
 590        .update_in(cx_b, |workspace, window, cx| {
 591            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
 592        })
 593        .await
 594        .unwrap()
 595        .downcast::<Editor>()
 596        .unwrap();
 597
 598    let mut fake_language_server = fake_language_servers.next().await.unwrap();
 599    let mut requests = fake_language_server
 600        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
 601            assert_eq!(
 602                params.text_document.uri,
 603                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 604            );
 605            assert_eq!(params.range.start, lsp::Position::new(0, 0));
 606            assert_eq!(params.range.end, lsp::Position::new(0, 0));
 607            Ok(None)
 608        });
 609    cx_a.background_executor
 610        .advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
 611    requests.next().await;
 612
 613    // Move cursor to a location that contains code actions.
 614    editor_b.update_in(cx_b, |editor, window, cx| {
 615        editor.change_selections(None, window, cx, |s| {
 616            s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
 617        });
 618    });
 619    cx_b.focus(&editor_b);
 620
 621    let mut requests = fake_language_server
 622        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
 623            assert_eq!(
 624                params.text_document.uri,
 625                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 626            );
 627            assert_eq!(params.range.start, lsp::Position::new(1, 31));
 628            assert_eq!(params.range.end, lsp::Position::new(1, 31));
 629
 630            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 631                lsp::CodeAction {
 632                    title: "Inline into all callers".to_string(),
 633                    edit: Some(lsp::WorkspaceEdit {
 634                        changes: Some(
 635                            [
 636                                (
 637                                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 638                                    vec![lsp::TextEdit::new(
 639                                        lsp::Range::new(
 640                                            lsp::Position::new(1, 22),
 641                                            lsp::Position::new(1, 34),
 642                                        ),
 643                                        "4".to_string(),
 644                                    )],
 645                                ),
 646                                (
 647                                    lsp::Url::from_file_path(path!("/a/other.rs")).unwrap(),
 648                                    vec![lsp::TextEdit::new(
 649                                        lsp::Range::new(
 650                                            lsp::Position::new(0, 0),
 651                                            lsp::Position::new(0, 27),
 652                                        ),
 653                                        "".to_string(),
 654                                    )],
 655                                ),
 656                            ]
 657                            .into_iter()
 658                            .collect(),
 659                        ),
 660                        ..Default::default()
 661                    }),
 662                    data: Some(json!({
 663                        "codeActionParams": {
 664                            "range": {
 665                                "start": {"line": 1, "column": 31},
 666                                "end": {"line": 1, "column": 31},
 667                            }
 668                        }
 669                    })),
 670                    ..Default::default()
 671                },
 672            )]))
 673        });
 674    cx_a.background_executor
 675        .advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
 676    requests.next().await;
 677
 678    // Toggle code actions and wait for them to display.
 679    editor_b.update_in(cx_b, |editor, window, cx| {
 680        editor.toggle_code_actions(
 681            &ToggleCodeActions {
 682                deployed_from_indicator: None,
 683                quick_launch: false,
 684            },
 685            window,
 686            cx,
 687        );
 688    });
 689    cx_a.background_executor.run_until_parked();
 690
 691    editor_b.update(cx_b, |editor, _| assert!(editor.context_menu_visible()));
 692
 693    fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
 694
 695    // Confirming the code action will trigger a resolve request.
 696    let confirm_action = editor_b
 697        .update_in(cx_b, |editor, window, cx| {
 698            Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, window, cx)
 699        })
 700        .unwrap();
 701    fake_language_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>(
 702        |_, _| async move {
 703            Ok(lsp::CodeAction {
 704                title: "Inline into all callers".to_string(),
 705                edit: Some(lsp::WorkspaceEdit {
 706                    changes: Some(
 707                        [
 708                            (
 709                                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 710                                vec![lsp::TextEdit::new(
 711                                    lsp::Range::new(
 712                                        lsp::Position::new(1, 22),
 713                                        lsp::Position::new(1, 34),
 714                                    ),
 715                                    "4".to_string(),
 716                                )],
 717                            ),
 718                            (
 719                                lsp::Url::from_file_path(path!("/a/other.rs")).unwrap(),
 720                                vec![lsp::TextEdit::new(
 721                                    lsp::Range::new(
 722                                        lsp::Position::new(0, 0),
 723                                        lsp::Position::new(0, 27),
 724                                    ),
 725                                    "".to_string(),
 726                                )],
 727                            ),
 728                        ]
 729                        .into_iter()
 730                        .collect(),
 731                    ),
 732                    ..Default::default()
 733                }),
 734                ..Default::default()
 735            })
 736        },
 737    );
 738
 739    // After the action is confirmed, an editor containing both modified files is opened.
 740    confirm_action.await.unwrap();
 741
 742    let code_action_editor = workspace_b.update(cx_b, |workspace, cx| {
 743        workspace
 744            .active_item(cx)
 745            .unwrap()
 746            .downcast::<Editor>()
 747            .unwrap()
 748    });
 749    code_action_editor.update_in(cx_b, |editor, window, cx| {
 750        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 751        editor.undo(&Undo, window, cx);
 752        assert_eq!(
 753            editor.text(cx),
 754            "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
 755        );
 756        editor.redo(&Redo, window, cx);
 757        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 758    });
 759}
 760
 761#[gpui::test(iterations = 10)]
 762async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 763    let mut server = TestServer::start(cx_a.executor()).await;
 764    let client_a = server.create_client(cx_a, "user_a").await;
 765    let client_b = server.create_client(cx_b, "user_b").await;
 766    server
 767        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 768        .await;
 769    let active_call_a = cx_a.read(ActiveCall::global);
 770
 771    cx_b.update(editor::init);
 772
 773    // Set up a fake language server.
 774    client_a.language_registry().add(rust_lang());
 775    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
 776        "Rust",
 777        FakeLspAdapter {
 778            capabilities: lsp::ServerCapabilities {
 779                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
 780                    prepare_provider: Some(true),
 781                    work_done_progress_options: Default::default(),
 782                })),
 783                ..Default::default()
 784            },
 785            ..Default::default()
 786        },
 787    );
 788
 789    client_a
 790        .fs()
 791        .insert_tree(
 792            path!("/dir"),
 793            json!({
 794                "one.rs": "const ONE: usize = 1;",
 795                "two.rs": "const TWO: usize = one::ONE + one::ONE;"
 796            }),
 797        )
 798        .await;
 799    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
 800    let project_id = active_call_a
 801        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 802        .await
 803        .unwrap();
 804    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 805
 806    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 807    let editor_b = workspace_b
 808        .update_in(cx_b, |workspace, window, cx| {
 809            workspace.open_path((worktree_id, "one.rs"), None, true, window, cx)
 810        })
 811        .await
 812        .unwrap()
 813        .downcast::<Editor>()
 814        .unwrap();
 815    let fake_language_server = fake_language_servers.next().await.unwrap();
 816
 817    // Move cursor to a location that can be renamed.
 818    let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
 819        editor.change_selections(None, window, cx, |s| s.select_ranges([7..7]));
 820        editor.rename(&Rename, window, cx).unwrap()
 821    });
 822
 823    fake_language_server
 824        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
 825            assert_eq!(
 826                params.text_document.uri.as_str(),
 827                uri!("file:///dir/one.rs")
 828            );
 829            assert_eq!(params.position, lsp::Position::new(0, 7));
 830            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
 831                lsp::Position::new(0, 6),
 832                lsp::Position::new(0, 9),
 833            ))))
 834        })
 835        .next()
 836        .await
 837        .unwrap();
 838    prepare_rename.await.unwrap();
 839    editor_b.update(cx_b, |editor, cx| {
 840        use editor::ToOffset;
 841        let rename = editor.pending_rename().unwrap();
 842        let buffer = editor.buffer().read(cx).snapshot(cx);
 843        assert_eq!(
 844            rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
 845            6..9
 846        );
 847        rename.editor.update(cx, |rename_editor, cx| {
 848            let rename_selection = rename_editor.selections.newest::<usize>(cx);
 849            assert_eq!(
 850                rename_selection.range(),
 851                0..3,
 852                "Rename that was triggered from zero selection caret, should propose the whole word."
 853            );
 854            rename_editor.buffer().update(cx, |rename_buffer, cx| {
 855                rename_buffer.edit([(0..3, "THREE")], None, cx);
 856            });
 857        });
 858    });
 859
 860    // Cancel the rename, and repeat the same, but use selections instead of cursor movement
 861    editor_b.update_in(cx_b, |editor, window, cx| {
 862        editor.cancel(&editor::actions::Cancel, window, cx);
 863    });
 864    let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
 865        editor.change_selections(None, window, cx, |s| s.select_ranges([7..8]));
 866        editor.rename(&Rename, window, cx).unwrap()
 867    });
 868
 869    fake_language_server
 870        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
 871            assert_eq!(
 872                params.text_document.uri.as_str(),
 873                uri!("file:///dir/one.rs")
 874            );
 875            assert_eq!(params.position, lsp::Position::new(0, 8));
 876            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
 877                lsp::Position::new(0, 6),
 878                lsp::Position::new(0, 9),
 879            ))))
 880        })
 881        .next()
 882        .await
 883        .unwrap();
 884    prepare_rename.await.unwrap();
 885    editor_b.update(cx_b, |editor, cx| {
 886        use editor::ToOffset;
 887        let rename = editor.pending_rename().unwrap();
 888        let buffer = editor.buffer().read(cx).snapshot(cx);
 889        let lsp_rename_start = rename.range.start.to_offset(&buffer);
 890        let lsp_rename_end = rename.range.end.to_offset(&buffer);
 891        assert_eq!(lsp_rename_start..lsp_rename_end, 6..9);
 892        rename.editor.update(cx, |rename_editor, cx| {
 893            let rename_selection = rename_editor.selections.newest::<usize>(cx);
 894            assert_eq!(
 895                rename_selection.range(),
 896                1..2,
 897                "Rename that was triggered from a selection, should have the same selection range in the rename proposal"
 898            );
 899            rename_editor.buffer().update(cx, |rename_buffer, cx| {
 900                rename_buffer.edit([(0..lsp_rename_end - lsp_rename_start, "THREE")], None, cx);
 901            });
 902        });
 903    });
 904
 905    let confirm_rename = editor_b.update_in(cx_b, |editor, window, cx| {
 906        Editor::confirm_rename(editor, &ConfirmRename, window, cx).unwrap()
 907    });
 908    fake_language_server
 909        .set_request_handler::<lsp::request::Rename, _, _>(|params, _| async move {
 910            assert_eq!(
 911                params.text_document_position.text_document.uri.as_str(),
 912                uri!("file:///dir/one.rs")
 913            );
 914            assert_eq!(
 915                params.text_document_position.position,
 916                lsp::Position::new(0, 6)
 917            );
 918            assert_eq!(params.new_name, "THREE");
 919            Ok(Some(lsp::WorkspaceEdit {
 920                changes: Some(
 921                    [
 922                        (
 923                            lsp::Url::from_file_path(path!("/dir/one.rs")).unwrap(),
 924                            vec![lsp::TextEdit::new(
 925                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
 926                                "THREE".to_string(),
 927                            )],
 928                        ),
 929                        (
 930                            lsp::Url::from_file_path(path!("/dir/two.rs")).unwrap(),
 931                            vec![
 932                                lsp::TextEdit::new(
 933                                    lsp::Range::new(
 934                                        lsp::Position::new(0, 24),
 935                                        lsp::Position::new(0, 27),
 936                                    ),
 937                                    "THREE".to_string(),
 938                                ),
 939                                lsp::TextEdit::new(
 940                                    lsp::Range::new(
 941                                        lsp::Position::new(0, 35),
 942                                        lsp::Position::new(0, 38),
 943                                    ),
 944                                    "THREE".to_string(),
 945                                ),
 946                            ],
 947                        ),
 948                    ]
 949                    .into_iter()
 950                    .collect(),
 951                ),
 952                ..Default::default()
 953            }))
 954        })
 955        .next()
 956        .await
 957        .unwrap();
 958    confirm_rename.await.unwrap();
 959
 960    let rename_editor = workspace_b.update(cx_b, |workspace, cx| {
 961        workspace.active_item_as::<Editor>(cx).unwrap()
 962    });
 963
 964    rename_editor.update_in(cx_b, |editor, window, cx| {
 965        assert_eq!(
 966            editor.text(cx),
 967            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
 968        );
 969        editor.undo(&Undo, window, cx);
 970        assert_eq!(
 971            editor.text(cx),
 972            "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
 973        );
 974        editor.redo(&Redo, window, cx);
 975        assert_eq!(
 976            editor.text(cx),
 977            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
 978        );
 979    });
 980
 981    // Ensure temporary rename edits cannot be undone/redone.
 982    editor_b.update_in(cx_b, |editor, window, cx| {
 983        editor.undo(&Undo, window, cx);
 984        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
 985        editor.undo(&Undo, window, cx);
 986        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
 987        editor.redo(&Redo, window, cx);
 988        assert_eq!(editor.text(cx), "const THREE: usize = 1;");
 989    })
 990}
 991
 992#[gpui::test(iterations = 10)]
 993async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 994    let mut server = TestServer::start(cx_a.executor()).await;
 995    let executor = cx_a.executor();
 996    let client_a = server.create_client(cx_a, "user_a").await;
 997    let client_b = server.create_client(cx_b, "user_b").await;
 998    server
 999        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1000        .await;
1001    let active_call_a = cx_a.read(ActiveCall::global);
1002
1003    cx_b.update(editor::init);
1004
1005    client_a.language_registry().add(rust_lang());
1006    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1007        "Rust",
1008        FakeLspAdapter {
1009            name: "the-language-server",
1010            ..Default::default()
1011        },
1012    );
1013
1014    client_a
1015        .fs()
1016        .insert_tree(
1017            path!("/dir"),
1018            json!({
1019                "main.rs": "const ONE: usize = 1;",
1020            }),
1021        )
1022        .await;
1023    let (project_a, _) = client_a.build_local_project(path!("/dir"), cx_a).await;
1024
1025    let _buffer_a = project_a
1026        .update(cx_a, |p, cx| {
1027            p.open_local_buffer_with_lsp(path!("/dir/main.rs"), cx)
1028        })
1029        .await
1030        .unwrap();
1031
1032    let fake_language_server = fake_language_servers.next().await.unwrap();
1033    fake_language_server.start_progress("the-token").await;
1034
1035    executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
1036    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
1037        token: lsp::NumberOrString::String("the-token".to_string()),
1038        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
1039            lsp::WorkDoneProgressReport {
1040                message: Some("the-message".to_string()),
1041                ..Default::default()
1042            },
1043        )),
1044    });
1045    executor.run_until_parked();
1046
1047    project_a.read_with(cx_a, |project, cx| {
1048        let status = project.language_server_statuses(cx).next().unwrap().1;
1049        assert_eq!(status.name, "the-language-server");
1050        assert_eq!(status.pending_work.len(), 1);
1051        assert_eq!(
1052            status.pending_work["the-token"].message.as_ref().unwrap(),
1053            "the-message"
1054        );
1055    });
1056
1057    let project_id = active_call_a
1058        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1059        .await
1060        .unwrap();
1061    executor.run_until_parked();
1062    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1063
1064    project_b.read_with(cx_b, |project, cx| {
1065        let status = project.language_server_statuses(cx).next().unwrap().1;
1066        assert_eq!(status.name, "the-language-server");
1067    });
1068
1069    executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
1070    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
1071        token: lsp::NumberOrString::String("the-token".to_string()),
1072        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
1073            lsp::WorkDoneProgressReport {
1074                message: Some("the-message-2".to_string()),
1075                ..Default::default()
1076            },
1077        )),
1078    });
1079    executor.run_until_parked();
1080
1081    project_a.read_with(cx_a, |project, cx| {
1082        let status = project.language_server_statuses(cx).next().unwrap().1;
1083        assert_eq!(status.name, "the-language-server");
1084        assert_eq!(status.pending_work.len(), 1);
1085        assert_eq!(
1086            status.pending_work["the-token"].message.as_ref().unwrap(),
1087            "the-message-2"
1088        );
1089    });
1090
1091    project_b.read_with(cx_b, |project, cx| {
1092        let status = project.language_server_statuses(cx).next().unwrap().1;
1093        assert_eq!(status.name, "the-language-server");
1094        assert_eq!(status.pending_work.len(), 1);
1095        assert_eq!(
1096            status.pending_work["the-token"].message.as_ref().unwrap(),
1097            "the-message-2"
1098        );
1099    });
1100}
1101
1102#[gpui::test(iterations = 10)]
1103async fn test_share_project(
1104    cx_a: &mut TestAppContext,
1105    cx_b: &mut TestAppContext,
1106    cx_c: &mut TestAppContext,
1107) {
1108    let executor = cx_a.executor();
1109    let cx_b = cx_b.add_empty_window();
1110    let mut server = TestServer::start(executor.clone()).await;
1111    let client_a = server.create_client(cx_a, "user_a").await;
1112    let client_b = server.create_client(cx_b, "user_b").await;
1113    let client_c = server.create_client(cx_c, "user_c").await;
1114    server
1115        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1116        .await;
1117    let active_call_a = cx_a.read(ActiveCall::global);
1118    let active_call_b = cx_b.read(ActiveCall::global);
1119    let active_call_c = cx_c.read(ActiveCall::global);
1120
1121    client_a
1122        .fs()
1123        .insert_tree(
1124            path!("/a"),
1125            json!({
1126                ".gitignore": "ignored-dir",
1127                "a.txt": "a-contents",
1128                "b.txt": "b-contents",
1129                "ignored-dir": {
1130                    "c.txt": "",
1131                    "d.txt": "",
1132                }
1133            }),
1134        )
1135        .await;
1136
1137    // Invite client B to collaborate on a project
1138    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1139    active_call_a
1140        .update(cx_a, |call, cx| {
1141            call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
1142        })
1143        .await
1144        .unwrap();
1145
1146    // Join that project as client B
1147
1148    let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
1149    executor.run_until_parked();
1150    let call = incoming_call_b.borrow().clone().unwrap();
1151    assert_eq!(call.calling_user.github_login, "user_a");
1152    let initial_project = call.initial_project.unwrap();
1153    active_call_b
1154        .update(cx_b, |call, cx| call.accept_incoming(cx))
1155        .await
1156        .unwrap();
1157    let client_b_peer_id = client_b.peer_id().unwrap();
1158    let project_b = client_b.join_remote_project(initial_project.id, cx_b).await;
1159
1160    let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
1161
1162    executor.run_until_parked();
1163
1164    project_a.read_with(cx_a, |project, _| {
1165        let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
1166        assert_eq!(client_b_collaborator.replica_id, replica_id_b);
1167    });
1168
1169    project_b.read_with(cx_b, |project, cx| {
1170        let worktree = project.worktrees(cx).next().unwrap().read(cx);
1171        assert_eq!(
1172            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1173            [
1174                Path::new(".gitignore"),
1175                Path::new("a.txt"),
1176                Path::new("b.txt"),
1177                Path::new("ignored-dir"),
1178            ]
1179        );
1180    });
1181
1182    project_b
1183        .update(cx_b, |project, cx| {
1184            let worktree = project.worktrees(cx).next().unwrap();
1185            let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
1186            project.expand_entry(worktree_id, entry.id, cx).unwrap()
1187        })
1188        .await
1189        .unwrap();
1190
1191    project_b.read_with(cx_b, |project, cx| {
1192        let worktree = project.worktrees(cx).next().unwrap().read(cx);
1193        assert_eq!(
1194            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1195            [
1196                Path::new(".gitignore"),
1197                Path::new("a.txt"),
1198                Path::new("b.txt"),
1199                Path::new("ignored-dir"),
1200                Path::new("ignored-dir/c.txt"),
1201                Path::new("ignored-dir/d.txt"),
1202            ]
1203        );
1204    });
1205
1206    // Open the same file as client B and client A.
1207    let buffer_b = project_b
1208        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1209        .await
1210        .unwrap();
1211
1212    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
1213
1214    project_a.read_with(cx_a, |project, cx| {
1215        assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
1216    });
1217    let buffer_a = project_a
1218        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1219        .await
1220        .unwrap();
1221
1222    let editor_b =
1223        cx_b.new_window_entity(|window, cx| Editor::for_buffer(buffer_b, None, window, cx));
1224
1225    // Client A sees client B's selection
1226    executor.run_until_parked();
1227
1228    buffer_a.read_with(cx_a, |buffer, _| {
1229        buffer
1230            .snapshot()
1231            .selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
1232            .count()
1233            == 1
1234    });
1235
1236    // Edit the buffer as client B and see that edit as client A.
1237    editor_b.update_in(cx_b, |editor, window, cx| {
1238        editor.handle_input("ok, ", window, cx)
1239    });
1240    executor.run_until_parked();
1241
1242    buffer_a.read_with(cx_a, |buffer, _| {
1243        assert_eq!(buffer.text(), "ok, b-contents")
1244    });
1245
1246    // Client B can invite client C on a project shared by client A.
1247    active_call_b
1248        .update(cx_b, |call, cx| {
1249            call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
1250        })
1251        .await
1252        .unwrap();
1253
1254    let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
1255    executor.run_until_parked();
1256    let call = incoming_call_c.borrow().clone().unwrap();
1257    assert_eq!(call.calling_user.github_login, "user_b");
1258    let initial_project = call.initial_project.unwrap();
1259    active_call_c
1260        .update(cx_c, |call, cx| call.accept_incoming(cx))
1261        .await
1262        .unwrap();
1263    let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
1264
1265    // Client B closes the editor, and client A sees client B's selections removed.
1266    cx_b.update(move |_, _| drop(editor_b));
1267    executor.run_until_parked();
1268
1269    buffer_a.read_with(cx_a, |buffer, _| {
1270        buffer
1271            .snapshot()
1272            .selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
1273            .count()
1274            == 0
1275    });
1276}
1277
1278#[gpui::test(iterations = 10)]
1279async fn test_on_input_format_from_host_to_guest(
1280    cx_a: &mut TestAppContext,
1281    cx_b: &mut TestAppContext,
1282) {
1283    let mut server = TestServer::start(cx_a.executor()).await;
1284    let executor = cx_a.executor();
1285    let client_a = server.create_client(cx_a, "user_a").await;
1286    let client_b = server.create_client(cx_b, "user_b").await;
1287    server
1288        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1289        .await;
1290    let active_call_a = cx_a.read(ActiveCall::global);
1291
1292    client_a.language_registry().add(rust_lang());
1293    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1294        "Rust",
1295        FakeLspAdapter {
1296            capabilities: lsp::ServerCapabilities {
1297                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1298                    first_trigger_character: ":".to_string(),
1299                    more_trigger_character: Some(vec![">".to_string()]),
1300                }),
1301                ..Default::default()
1302            },
1303            ..Default::default()
1304        },
1305    );
1306
1307    client_a
1308        .fs()
1309        .insert_tree(
1310            path!("/a"),
1311            json!({
1312                "main.rs": "fn main() { a }",
1313                "other.rs": "// Test file",
1314            }),
1315        )
1316        .await;
1317    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1318    let project_id = active_call_a
1319        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1320        .await
1321        .unwrap();
1322    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1323
1324    // Open a file in an editor as the host.
1325    let buffer_a = project_a
1326        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1327        .await
1328        .unwrap();
1329    let cx_a = cx_a.add_empty_window();
1330    let editor_a = cx_a.new_window_entity(|window, cx| {
1331        Editor::for_buffer(buffer_a, Some(project_a.clone()), window, cx)
1332    });
1333
1334    let fake_language_server = fake_language_servers.next().await.unwrap();
1335    executor.run_until_parked();
1336
1337    // Receive an OnTypeFormatting request as the host's language server.
1338    // Return some formatting from the host's language server.
1339    fake_language_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
1340        |params, _| async move {
1341            assert_eq!(
1342                params.text_document_position.text_document.uri,
1343                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1344            );
1345            assert_eq!(
1346                params.text_document_position.position,
1347                lsp::Position::new(0, 14),
1348            );
1349
1350            Ok(Some(vec![lsp::TextEdit {
1351                new_text: "~<".to_string(),
1352                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1353            }]))
1354        },
1355    );
1356
1357    // Open the buffer on the guest and see that the formatting worked
1358    let buffer_b = project_b
1359        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1360        .await
1361        .unwrap();
1362
1363    // Type a on type formatting trigger character as the guest.
1364    cx_a.focus(&editor_a);
1365    editor_a.update_in(cx_a, |editor, window, cx| {
1366        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1367        editor.handle_input(">", window, cx);
1368    });
1369
1370    executor.run_until_parked();
1371
1372    buffer_b.read_with(cx_b, |buffer, _| {
1373        assert_eq!(buffer.text(), "fn main() { a>~< }")
1374    });
1375
1376    // Undo should remove LSP edits first
1377    editor_a.update_in(cx_a, |editor, window, cx| {
1378        assert_eq!(editor.text(cx), "fn main() { a>~< }");
1379        editor.undo(&Undo, window, cx);
1380        assert_eq!(editor.text(cx), "fn main() { a> }");
1381    });
1382    executor.run_until_parked();
1383
1384    buffer_b.read_with(cx_b, |buffer, _| {
1385        assert_eq!(buffer.text(), "fn main() { a> }")
1386    });
1387
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
1400#[gpui::test(iterations = 10)]
1401async fn test_on_input_format_from_guest_to_host(
1402    cx_a: &mut TestAppContext,
1403    cx_b: &mut TestAppContext,
1404) {
1405    let mut server = TestServer::start(cx_a.executor()).await;
1406    let executor = cx_a.executor();
1407    let client_a = server.create_client(cx_a, "user_a").await;
1408    let client_b = server.create_client(cx_b, "user_b").await;
1409    server
1410        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1411        .await;
1412    let active_call_a = cx_a.read(ActiveCall::global);
1413
1414    client_a.language_registry().add(rust_lang());
1415    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1416        "Rust",
1417        FakeLspAdapter {
1418            capabilities: lsp::ServerCapabilities {
1419                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1420                    first_trigger_character: ":".to_string(),
1421                    more_trigger_character: Some(vec![">".to_string()]),
1422                }),
1423                ..Default::default()
1424            },
1425            ..Default::default()
1426        },
1427    );
1428
1429    client_a
1430        .fs()
1431        .insert_tree(
1432            path!("/a"),
1433            json!({
1434                "main.rs": "fn main() { a }",
1435                "other.rs": "// Test file",
1436            }),
1437        )
1438        .await;
1439    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1440    let project_id = active_call_a
1441        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1442        .await
1443        .unwrap();
1444    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1445
1446    // Open a file in an editor as the guest.
1447    let buffer_b = project_b
1448        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1449        .await
1450        .unwrap();
1451    let cx_b = cx_b.add_empty_window();
1452    let editor_b = cx_b.new_window_entity(|window, cx| {
1453        Editor::for_buffer(buffer_b, Some(project_b.clone()), window, cx)
1454    });
1455
1456    let fake_language_server = fake_language_servers.next().await.unwrap();
1457    executor.run_until_parked();
1458
1459    // Type a on type formatting trigger character as the guest.
1460    cx_b.focus(&editor_b);
1461    editor_b.update_in(cx_b, |editor, window, cx| {
1462        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1463        editor.handle_input(":", window, cx);
1464    });
1465
1466    // Receive an OnTypeFormatting request as the host's language server.
1467    // Return some formatting from the host's language server.
1468    executor.start_waiting();
1469    fake_language_server
1470        .set_request_handler::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
1471            assert_eq!(
1472                params.text_document_position.text_document.uri,
1473                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1474            );
1475            assert_eq!(
1476                params.text_document_position.position,
1477                lsp::Position::new(0, 14),
1478            );
1479
1480            Ok(Some(vec![lsp::TextEdit {
1481                new_text: "~:".to_string(),
1482                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1483            }]))
1484        })
1485        .next()
1486        .await
1487        .unwrap();
1488    executor.finish_waiting();
1489
1490    // Open the buffer on the host and see that the formatting worked
1491    let buffer_a = project_a
1492        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1493        .await
1494        .unwrap();
1495    executor.run_until_parked();
1496
1497    buffer_a.read_with(cx_a, |buffer, _| {
1498        assert_eq!(buffer.text(), "fn main() { a:~: }")
1499    });
1500
1501    // Undo should remove LSP edits first
1502    editor_b.update_in(cx_b, |editor, window, cx| {
1503        assert_eq!(editor.text(cx), "fn main() { a:~: }");
1504        editor.undo(&Undo, window, cx);
1505        assert_eq!(editor.text(cx), "fn main() { a: }");
1506    });
1507    executor.run_until_parked();
1508
1509    buffer_a.read_with(cx_a, |buffer, _| {
1510        assert_eq!(buffer.text(), "fn main() { a: }")
1511    });
1512
1513    editor_b.update_in(cx_b, |editor, window, cx| {
1514        assert_eq!(editor.text(cx), "fn main() { a: }");
1515        editor.undo(&Undo, window, cx);
1516        assert_eq!(editor.text(cx), "fn main() { a }");
1517    });
1518    executor.run_until_parked();
1519
1520    buffer_a.read_with(cx_a, |buffer, _| {
1521        assert_eq!(buffer.text(), "fn main() { a }")
1522    });
1523}
1524
1525#[gpui::test(iterations = 10)]
1526async fn test_mutual_editor_inlay_hint_cache_update(
1527    cx_a: &mut TestAppContext,
1528    cx_b: &mut TestAppContext,
1529) {
1530    let mut server = TestServer::start(cx_a.executor()).await;
1531    let executor = cx_a.executor();
1532    let client_a = server.create_client(cx_a, "user_a").await;
1533    let client_b = server.create_client(cx_b, "user_b").await;
1534    server
1535        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1536        .await;
1537    let active_call_a = cx_a.read(ActiveCall::global);
1538    let active_call_b = cx_b.read(ActiveCall::global);
1539
1540    cx_a.update(editor::init);
1541    cx_b.update(editor::init);
1542
1543    cx_a.update(|cx| {
1544        SettingsStore::update_global(cx, |store, cx| {
1545            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1546                settings.defaults.inlay_hints = Some(InlayHintSettings {
1547                    enabled: true,
1548                    show_value_hints: true,
1549                    edit_debounce_ms: 0,
1550                    scroll_debounce_ms: 0,
1551                    show_type_hints: true,
1552                    show_parameter_hints: false,
1553                    show_other_hints: true,
1554                    show_background: false,
1555                    toggle_on_modifiers_press: None,
1556                })
1557            });
1558        });
1559    });
1560    cx_b.update(|cx| {
1561        SettingsStore::update_global(cx, |store, cx| {
1562            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1563                settings.defaults.inlay_hints = Some(InlayHintSettings {
1564                    show_value_hints: true,
1565                    enabled: true,
1566                    edit_debounce_ms: 0,
1567                    scroll_debounce_ms: 0,
1568                    show_type_hints: true,
1569                    show_parameter_hints: false,
1570                    show_other_hints: true,
1571                    show_background: false,
1572                    toggle_on_modifiers_press: None,
1573                })
1574            });
1575        });
1576    });
1577
1578    client_a.language_registry().add(rust_lang());
1579    client_b.language_registry().add(rust_lang());
1580    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1581        "Rust",
1582        FakeLspAdapter {
1583            capabilities: lsp::ServerCapabilities {
1584                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1585                ..Default::default()
1586            },
1587            ..Default::default()
1588        },
1589    );
1590
1591    // Client A opens a project.
1592    client_a
1593        .fs()
1594        .insert_tree(
1595            path!("/a"),
1596            json!({
1597                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1598                "other.rs": "// Test file",
1599            }),
1600        )
1601        .await;
1602    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1603    active_call_a
1604        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1605        .await
1606        .unwrap();
1607    let project_id = active_call_a
1608        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1609        .await
1610        .unwrap();
1611
1612    // Client B joins the project
1613    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1614    active_call_b
1615        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1616        .await
1617        .unwrap();
1618
1619    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
1620    executor.start_waiting();
1621
1622    // The host opens a rust file.
1623    let _buffer_a = project_a
1624        .update(cx_a, |project, cx| {
1625            project.open_local_buffer(path!("/a/main.rs"), cx)
1626        })
1627        .await
1628        .unwrap();
1629    let editor_a = workspace_a
1630        .update_in(cx_a, |workspace, window, cx| {
1631            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1632        })
1633        .await
1634        .unwrap()
1635        .downcast::<Editor>()
1636        .unwrap();
1637
1638    let fake_language_server = fake_language_servers.next().await.unwrap();
1639
1640    // Set up the language server to return an additional inlay hint on each request.
1641    let edits_made = Arc::new(AtomicUsize::new(0));
1642    let closure_edits_made = Arc::clone(&edits_made);
1643    fake_language_server
1644        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1645            let task_edits_made = Arc::clone(&closure_edits_made);
1646            async move {
1647                assert_eq!(
1648                    params.text_document.uri,
1649                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1650                );
1651                let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
1652                Ok(Some(vec![lsp::InlayHint {
1653                    position: lsp::Position::new(0, edits_made as u32),
1654                    label: lsp::InlayHintLabel::String(edits_made.to_string()),
1655                    kind: None,
1656                    text_edits: None,
1657                    tooltip: None,
1658                    padding_left: None,
1659                    padding_right: None,
1660                    data: None,
1661                }]))
1662            }
1663        })
1664        .next()
1665        .await
1666        .unwrap();
1667
1668    executor.run_until_parked();
1669
1670    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
1671    editor_a.update(cx_a, |editor, _| {
1672        assert_eq!(
1673            vec![initial_edit.to_string()],
1674            extract_hint_labels(editor),
1675            "Host should get its first hints when opens an editor"
1676        );
1677    });
1678    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1679    let editor_b = workspace_b
1680        .update_in(cx_b, |workspace, window, cx| {
1681            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1682        })
1683        .await
1684        .unwrap()
1685        .downcast::<Editor>()
1686        .unwrap();
1687
1688    executor.run_until_parked();
1689    editor_b.update(cx_b, |editor, _| {
1690        assert_eq!(
1691            vec![initial_edit.to_string()],
1692            extract_hint_labels(editor),
1693            "Client should get its first hints when opens an editor"
1694        );
1695    });
1696
1697    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1698    editor_b.update_in(cx_b, |editor, window, cx| {
1699        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
1700        editor.handle_input(":", window, cx);
1701    });
1702    cx_b.focus(&editor_b);
1703
1704    executor.run_until_parked();
1705    editor_a.update(cx_a, |editor, _| {
1706        assert_eq!(
1707            vec![after_client_edit.to_string()],
1708            extract_hint_labels(editor),
1709        );
1710    });
1711    editor_b.update(cx_b, |editor, _| {
1712        assert_eq!(
1713            vec![after_client_edit.to_string()],
1714            extract_hint_labels(editor),
1715        );
1716    });
1717
1718    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1719    editor_a.update_in(cx_a, |editor, window, cx| {
1720        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1721        editor.handle_input("a change to increment both buffers' versions", window, cx);
1722    });
1723    cx_a.focus(&editor_a);
1724
1725    executor.run_until_parked();
1726    editor_a.update(cx_a, |editor, _| {
1727        assert_eq!(
1728            vec![after_host_edit.to_string()],
1729            extract_hint_labels(editor),
1730        );
1731    });
1732    editor_b.update(cx_b, |editor, _| {
1733        assert_eq!(
1734            vec![after_host_edit.to_string()],
1735            extract_hint_labels(editor),
1736        );
1737    });
1738
1739    let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1740    fake_language_server
1741        .request::<lsp::request::InlayHintRefreshRequest>(())
1742        .await
1743        .expect("inlay refresh request failed");
1744
1745    executor.run_until_parked();
1746    editor_a.update(cx_a, |editor, _| {
1747        assert_eq!(
1748            vec![after_special_edit_for_refresh.to_string()],
1749            extract_hint_labels(editor),
1750            "Host should react to /refresh LSP request"
1751        );
1752    });
1753    editor_b.update(cx_b, |editor, _| {
1754        assert_eq!(
1755            vec![after_special_edit_for_refresh.to_string()],
1756            extract_hint_labels(editor),
1757            "Guest should get a /refresh LSP request propagated by host"
1758        );
1759    });
1760}
1761
1762#[gpui::test(iterations = 10)]
1763async fn test_inlay_hint_refresh_is_forwarded(
1764    cx_a: &mut TestAppContext,
1765    cx_b: &mut TestAppContext,
1766) {
1767    let mut server = TestServer::start(cx_a.executor()).await;
1768    let executor = cx_a.executor();
1769    let client_a = server.create_client(cx_a, "user_a").await;
1770    let client_b = server.create_client(cx_b, "user_b").await;
1771    server
1772        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1773        .await;
1774    let active_call_a = cx_a.read(ActiveCall::global);
1775    let active_call_b = cx_b.read(ActiveCall::global);
1776
1777    cx_a.update(editor::init);
1778    cx_b.update(editor::init);
1779
1780    cx_a.update(|cx| {
1781        SettingsStore::update_global(cx, |store, cx| {
1782            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1783                settings.defaults.inlay_hints = Some(InlayHintSettings {
1784                    show_value_hints: true,
1785                    enabled: false,
1786                    edit_debounce_ms: 0,
1787                    scroll_debounce_ms: 0,
1788                    show_type_hints: false,
1789                    show_parameter_hints: false,
1790                    show_other_hints: false,
1791                    show_background: false,
1792                    toggle_on_modifiers_press: None,
1793                })
1794            });
1795        });
1796    });
1797    cx_b.update(|cx| {
1798        SettingsStore::update_global(cx, |store, cx| {
1799            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1800                settings.defaults.inlay_hints = Some(InlayHintSettings {
1801                    show_value_hints: true,
1802                    enabled: true,
1803                    edit_debounce_ms: 0,
1804                    scroll_debounce_ms: 0,
1805                    show_type_hints: true,
1806                    show_parameter_hints: true,
1807                    show_other_hints: true,
1808                    show_background: false,
1809                    toggle_on_modifiers_press: None,
1810                })
1811            });
1812        });
1813    });
1814
1815    client_a.language_registry().add(rust_lang());
1816    client_b.language_registry().add(rust_lang());
1817    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1818        "Rust",
1819        FakeLspAdapter {
1820            capabilities: lsp::ServerCapabilities {
1821                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1822                ..Default::default()
1823            },
1824            ..Default::default()
1825        },
1826    );
1827
1828    client_a
1829        .fs()
1830        .insert_tree(
1831            path!("/a"),
1832            json!({
1833                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1834                "other.rs": "// Test file",
1835            }),
1836        )
1837        .await;
1838    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1839    active_call_a
1840        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1841        .await
1842        .unwrap();
1843    let project_id = active_call_a
1844        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1845        .await
1846        .unwrap();
1847
1848    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1849    active_call_b
1850        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1851        .await
1852        .unwrap();
1853
1854    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
1855    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1856
1857    cx_a.background_executor.start_waiting();
1858
1859    let editor_a = workspace_a
1860        .update_in(cx_a, |workspace, window, cx| {
1861            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1862        })
1863        .await
1864        .unwrap()
1865        .downcast::<Editor>()
1866        .unwrap();
1867
1868    let editor_b = workspace_b
1869        .update_in(cx_b, |workspace, window, cx| {
1870            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1871        })
1872        .await
1873        .unwrap()
1874        .downcast::<Editor>()
1875        .unwrap();
1876
1877    let other_hints = Arc::new(AtomicBool::new(false));
1878    let fake_language_server = fake_language_servers.next().await.unwrap();
1879    let closure_other_hints = Arc::clone(&other_hints);
1880    fake_language_server
1881        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1882            let task_other_hints = Arc::clone(&closure_other_hints);
1883            async move {
1884                assert_eq!(
1885                    params.text_document.uri,
1886                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1887                );
1888                let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
1889                let character = if other_hints { 0 } else { 2 };
1890                let label = if other_hints {
1891                    "other hint"
1892                } else {
1893                    "initial hint"
1894                };
1895                Ok(Some(vec![lsp::InlayHint {
1896                    position: lsp::Position::new(0, character),
1897                    label: lsp::InlayHintLabel::String(label.to_string()),
1898                    kind: None,
1899                    text_edits: None,
1900                    tooltip: None,
1901                    padding_left: None,
1902                    padding_right: None,
1903                    data: None,
1904                }]))
1905            }
1906        })
1907        .next()
1908        .await
1909        .unwrap();
1910    executor.finish_waiting();
1911
1912    executor.run_until_parked();
1913    editor_a.update(cx_a, |editor, _| {
1914        assert!(
1915            extract_hint_labels(editor).is_empty(),
1916            "Host should get no hints due to them turned off"
1917        );
1918    });
1919
1920    executor.run_until_parked();
1921    editor_b.update(cx_b, |editor, _| {
1922        assert_eq!(
1923            vec!["initial hint".to_string()],
1924            extract_hint_labels(editor),
1925            "Client should get its first hints when opens an editor"
1926        );
1927    });
1928
1929    other_hints.fetch_or(true, atomic::Ordering::Release);
1930    fake_language_server
1931        .request::<lsp::request::InlayHintRefreshRequest>(())
1932        .await
1933        .expect("inlay refresh request failed");
1934    executor.run_until_parked();
1935    editor_a.update(cx_a, |editor, _| {
1936        assert!(
1937            extract_hint_labels(editor).is_empty(),
1938            "Host should get no hints due to them turned off, even after the /refresh"
1939        );
1940    });
1941
1942    executor.run_until_parked();
1943    editor_b.update(cx_b, |editor, _| {
1944        assert_eq!(
1945            vec!["other hint".to_string()],
1946            extract_hint_labels(editor),
1947            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
1948        );
1949    });
1950}
1951
1952#[gpui::test(iterations = 10)]
1953async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1954    let mut server = TestServer::start(cx_a.executor()).await;
1955    let client_a = server.create_client(cx_a, "user_a").await;
1956    let client_b = server.create_client(cx_b, "user_b").await;
1957    server
1958        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1959        .await;
1960    let active_call_a = cx_a.read(ActiveCall::global);
1961
1962    cx_a.update(editor::init);
1963    cx_b.update(editor::init);
1964    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
1965    let inline_blame_off_settings = Some(InlineBlameSettings {
1966        enabled: false,
1967        delay_ms: None,
1968        min_column: None,
1969        show_commit_summary: false,
1970    });
1971    cx_a.update(|cx| {
1972        SettingsStore::update_global(cx, |store, cx| {
1973            store.update_user_settings::<ProjectSettings>(cx, |settings| {
1974                settings.git.inline_blame = inline_blame_off_settings;
1975            });
1976        });
1977    });
1978    cx_b.update(|cx| {
1979        SettingsStore::update_global(cx, |store, cx| {
1980            store.update_user_settings::<ProjectSettings>(cx, |settings| {
1981                settings.git.inline_blame = inline_blame_off_settings;
1982            });
1983        });
1984    });
1985
1986    client_a
1987        .fs()
1988        .insert_tree(
1989            path!("/my-repo"),
1990            json!({
1991                ".git": {},
1992                "file.txt": "line1\nline2\nline3\nline\n",
1993            }),
1994        )
1995        .await;
1996
1997    let blame = git::blame::Blame {
1998        entries: vec![
1999            blame_entry("1b1b1b", 0..1),
2000            blame_entry("0d0d0d", 1..2),
2001            blame_entry("3a3a3a", 2..3),
2002            blame_entry("4c4c4c", 3..4),
2003        ],
2004        messages: [
2005            ("1b1b1b", "message for idx-0"),
2006            ("0d0d0d", "message for idx-1"),
2007            ("3a3a3a", "message for idx-2"),
2008            ("4c4c4c", "message for idx-3"),
2009        ]
2010        .into_iter()
2011        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
2012        .collect(),
2013        remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
2014    };
2015    client_a.fs().set_blame_for_repo(
2016        Path::new(path!("/my-repo/.git")),
2017        vec![("file.txt".into(), blame)],
2018    );
2019
2020    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
2021    let project_id = active_call_a
2022        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2023        .await
2024        .unwrap();
2025
2026    // Create editor_a
2027    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2028    let editor_a = workspace_a
2029        .update_in(cx_a, |workspace, window, cx| {
2030            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2031        })
2032        .await
2033        .unwrap()
2034        .downcast::<Editor>()
2035        .unwrap();
2036
2037    // Join the project as client B.
2038    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2039    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2040    let editor_b = workspace_b
2041        .update_in(cx_b, |workspace, window, cx| {
2042            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2043        })
2044        .await
2045        .unwrap()
2046        .downcast::<Editor>()
2047        .unwrap();
2048    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
2049        editor_b
2050            .buffer()
2051            .read(cx)
2052            .as_singleton()
2053            .unwrap()
2054            .read(cx)
2055            .remote_id()
2056    });
2057
2058    // client_b now requests git blame for the open buffer
2059    editor_b.update_in(cx_b, |editor_b, window, cx| {
2060        assert!(editor_b.blame().is_none());
2061        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
2062    });
2063
2064    cx_a.executor().run_until_parked();
2065    cx_b.executor().run_until_parked();
2066
2067    editor_b.update(cx_b, |editor_b, cx| {
2068        let blame = editor_b.blame().expect("editor_b should have blame now");
2069        let entries = blame.update(cx, |blame, cx| {
2070            blame
2071                .blame_for_rows(
2072                    &(0..4)
2073                        .map(|row| RowInfo {
2074                            buffer_row: Some(row),
2075                            buffer_id: Some(buffer_id_b),
2076                            ..Default::default()
2077                        })
2078                        .collect::<Vec<_>>(),
2079                    cx,
2080                )
2081                .collect::<Vec<_>>()
2082        });
2083
2084        assert_eq!(
2085            entries,
2086            vec![
2087                Some(blame_entry("1b1b1b", 0..1)),
2088                Some(blame_entry("0d0d0d", 1..2)),
2089                Some(blame_entry("3a3a3a", 2..3)),
2090                Some(blame_entry("4c4c4c", 3..4)),
2091            ]
2092        );
2093
2094        blame.update(cx, |blame, _| {
2095            for (idx, entry) in entries.iter().flatten().enumerate() {
2096                let details = blame.details_for_entry(entry).unwrap();
2097                assert_eq!(details.message, format!("message for idx-{}", idx));
2098                assert_eq!(
2099                    details.permalink.unwrap().to_string(),
2100                    format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
2101                );
2102            }
2103        });
2104    });
2105
2106    // editor_b updates the file, which gets sent to client_a, which updates git blame,
2107    // which gets back to client_b.
2108    editor_b.update_in(cx_b, |editor_b, _, cx| {
2109        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
2110    });
2111
2112    cx_a.executor().run_until_parked();
2113    cx_b.executor().run_until_parked();
2114
2115    editor_b.update(cx_b, |editor_b, cx| {
2116        let blame = editor_b.blame().expect("editor_b should have blame now");
2117        let entries = blame.update(cx, |blame, cx| {
2118            blame
2119                .blame_for_rows(
2120                    &(0..4)
2121                        .map(|row| RowInfo {
2122                            buffer_row: Some(row),
2123                            buffer_id: Some(buffer_id_b),
2124                            ..Default::default()
2125                        })
2126                        .collect::<Vec<_>>(),
2127                    cx,
2128                )
2129                .collect::<Vec<_>>()
2130        });
2131
2132        assert_eq!(
2133            entries,
2134            vec![
2135                None,
2136                Some(blame_entry("0d0d0d", 1..2)),
2137                Some(blame_entry("3a3a3a", 2..3)),
2138                Some(blame_entry("4c4c4c", 3..4)),
2139            ]
2140        );
2141    });
2142
2143    // Now editor_a also updates the file
2144    editor_a.update_in(cx_a, |editor_a, _, cx| {
2145        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
2146    });
2147
2148    cx_a.executor().run_until_parked();
2149    cx_b.executor().run_until_parked();
2150
2151    editor_b.update(cx_b, |editor_b, cx| {
2152        let blame = editor_b.blame().expect("editor_b should have blame now");
2153        let entries = blame.update(cx, |blame, cx| {
2154            blame
2155                .blame_for_rows(
2156                    &(0..4)
2157                        .map(|row| RowInfo {
2158                            buffer_row: Some(row),
2159                            buffer_id: Some(buffer_id_b),
2160                            ..Default::default()
2161                        })
2162                        .collect::<Vec<_>>(),
2163                    cx,
2164                )
2165                .collect::<Vec<_>>()
2166        });
2167
2168        assert_eq!(
2169            entries,
2170            vec![
2171                None,
2172                None,
2173                Some(blame_entry("3a3a3a", 2..3)),
2174                Some(blame_entry("4c4c4c", 3..4)),
2175            ]
2176        );
2177    });
2178}
2179
2180#[gpui::test(iterations = 30)]
2181async fn test_collaborating_with_editorconfig(
2182    cx_a: &mut TestAppContext,
2183    cx_b: &mut TestAppContext,
2184) {
2185    let mut server = TestServer::start(cx_a.executor()).await;
2186    let client_a = server.create_client(cx_a, "user_a").await;
2187    let client_b = server.create_client(cx_b, "user_b").await;
2188    server
2189        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2190        .await;
2191    let active_call_a = cx_a.read(ActiveCall::global);
2192
2193    cx_b.update(editor::init);
2194
2195    // Set up a fake language server.
2196    client_a.language_registry().add(rust_lang());
2197    client_a
2198        .fs()
2199        .insert_tree(
2200            path!("/a"),
2201            json!({
2202                "src": {
2203                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
2204                    "other_mod": {
2205                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
2206                        ".editorconfig": "",
2207                    },
2208                },
2209                ".editorconfig": "[*]\ntab_width = 2\n",
2210            }),
2211        )
2212        .await;
2213    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2214    let project_id = active_call_a
2215        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2216        .await
2217        .unwrap();
2218    let main_buffer_a = project_a
2219        .update(cx_a, |p, cx| {
2220            p.open_buffer((worktree_id, "src/main.rs"), cx)
2221        })
2222        .await
2223        .unwrap();
2224    let other_buffer_a = project_a
2225        .update(cx_a, |p, cx| {
2226            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
2227        })
2228        .await
2229        .unwrap();
2230    let cx_a = cx_a.add_empty_window();
2231    let main_editor_a = cx_a.new_window_entity(|window, cx| {
2232        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
2233    });
2234    let other_editor_a = cx_a.new_window_entity(|window, cx| {
2235        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
2236    });
2237    let mut main_editor_cx_a = EditorTestContext {
2238        cx: cx_a.clone(),
2239        window: cx_a.window_handle(),
2240        editor: main_editor_a,
2241        assertion_cx: AssertionContextManager::new(),
2242    };
2243    let mut other_editor_cx_a = EditorTestContext {
2244        cx: cx_a.clone(),
2245        window: cx_a.window_handle(),
2246        editor: other_editor_a,
2247        assertion_cx: AssertionContextManager::new(),
2248    };
2249
2250    // Join the project as client B.
2251    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2252    let main_buffer_b = project_b
2253        .update(cx_b, |p, cx| {
2254            p.open_buffer((worktree_id, "src/main.rs"), cx)
2255        })
2256        .await
2257        .unwrap();
2258    let other_buffer_b = project_b
2259        .update(cx_b, |p, cx| {
2260            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
2261        })
2262        .await
2263        .unwrap();
2264    let cx_b = cx_b.add_empty_window();
2265    let main_editor_b = cx_b.new_window_entity(|window, cx| {
2266        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
2267    });
2268    let other_editor_b = cx_b.new_window_entity(|window, cx| {
2269        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
2270    });
2271    let mut main_editor_cx_b = EditorTestContext {
2272        cx: cx_b.clone(),
2273        window: cx_b.window_handle(),
2274        editor: main_editor_b,
2275        assertion_cx: AssertionContextManager::new(),
2276    };
2277    let mut other_editor_cx_b = EditorTestContext {
2278        cx: cx_b.clone(),
2279        window: cx_b.window_handle(),
2280        editor: other_editor_b,
2281        assertion_cx: AssertionContextManager::new(),
2282    };
2283
2284    let initial_main = indoc! {"
2285ˇmod other;
2286fn main() { let foo = other::foo(); }"};
2287    let initial_other = indoc! {"
2288ˇpub fn foo() -> usize {
2289    4
2290}"};
2291
2292    let first_tabbed_main = indoc! {"
2293  ˇmod other;
2294fn main() { let foo = other::foo(); }"};
2295    tab_undo_assert(
2296        &mut main_editor_cx_a,
2297        &mut main_editor_cx_b,
2298        initial_main,
2299        first_tabbed_main,
2300        true,
2301    );
2302    tab_undo_assert(
2303        &mut main_editor_cx_a,
2304        &mut main_editor_cx_b,
2305        initial_main,
2306        first_tabbed_main,
2307        false,
2308    );
2309
2310    let first_tabbed_other = indoc! {"
2311  ˇpub fn foo() -> usize {
2312    4
2313}"};
2314    tab_undo_assert(
2315        &mut other_editor_cx_a,
2316        &mut other_editor_cx_b,
2317        initial_other,
2318        first_tabbed_other,
2319        true,
2320    );
2321    tab_undo_assert(
2322        &mut other_editor_cx_a,
2323        &mut other_editor_cx_b,
2324        initial_other,
2325        first_tabbed_other,
2326        false,
2327    );
2328
2329    client_a
2330        .fs()
2331        .atomic_write(
2332            PathBuf::from(path!("/a/src/.editorconfig")),
2333            "[*]\ntab_width = 3\n".to_owned(),
2334        )
2335        .await
2336        .unwrap();
2337    cx_a.run_until_parked();
2338    cx_b.run_until_parked();
2339
2340    let second_tabbed_main = indoc! {"
2341   ˇmod other;
2342fn main() { let foo = other::foo(); }"};
2343    tab_undo_assert(
2344        &mut main_editor_cx_a,
2345        &mut main_editor_cx_b,
2346        initial_main,
2347        second_tabbed_main,
2348        true,
2349    );
2350    tab_undo_assert(
2351        &mut main_editor_cx_a,
2352        &mut main_editor_cx_b,
2353        initial_main,
2354        second_tabbed_main,
2355        false,
2356    );
2357
2358    let second_tabbed_other = indoc! {"
2359   ˇpub fn foo() -> usize {
2360    4
2361}"};
2362    tab_undo_assert(
2363        &mut other_editor_cx_a,
2364        &mut other_editor_cx_b,
2365        initial_other,
2366        second_tabbed_other,
2367        true,
2368    );
2369    tab_undo_assert(
2370        &mut other_editor_cx_a,
2371        &mut other_editor_cx_b,
2372        initial_other,
2373        second_tabbed_other,
2374        false,
2375    );
2376
2377    let editorconfig_buffer_b = project_b
2378        .update(cx_b, |p, cx| {
2379            p.open_buffer((worktree_id, "src/other_mod/.editorconfig"), cx)
2380        })
2381        .await
2382        .unwrap();
2383    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
2384        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
2385    });
2386    project_b
2387        .update(cx_b, |project, cx| {
2388            project.save_buffer(editorconfig_buffer_b.clone(), cx)
2389        })
2390        .await
2391        .unwrap();
2392    cx_a.run_until_parked();
2393    cx_b.run_until_parked();
2394
2395    tab_undo_assert(
2396        &mut main_editor_cx_a,
2397        &mut main_editor_cx_b,
2398        initial_main,
2399        second_tabbed_main,
2400        true,
2401    );
2402    tab_undo_assert(
2403        &mut main_editor_cx_a,
2404        &mut main_editor_cx_b,
2405        initial_main,
2406        second_tabbed_main,
2407        false,
2408    );
2409
2410    let third_tabbed_other = indoc! {"
2411      ˇpub fn foo() -> usize {
2412    4
2413}"};
2414    tab_undo_assert(
2415        &mut other_editor_cx_a,
2416        &mut other_editor_cx_b,
2417        initial_other,
2418        third_tabbed_other,
2419        true,
2420    );
2421
2422    tab_undo_assert(
2423        &mut other_editor_cx_a,
2424        &mut other_editor_cx_b,
2425        initial_other,
2426        third_tabbed_other,
2427        false,
2428    );
2429}
2430
2431#[gpui::test]
2432async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2433    let executor = cx_a.executor();
2434    let mut server = TestServer::start(executor.clone()).await;
2435    let client_a = server.create_client(cx_a, "user_a").await;
2436    let client_b = server.create_client(cx_b, "user_b").await;
2437    server
2438        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2439        .await;
2440    let active_call_a = cx_a.read(ActiveCall::global);
2441    let active_call_b = cx_b.read(ActiveCall::global);
2442    cx_a.update(editor::init);
2443    cx_b.update(editor::init);
2444    client_a
2445        .fs()
2446        .insert_tree(
2447            "/a",
2448            json!({
2449                "test.txt": "one\ntwo\nthree\nfour\nfive",
2450            }),
2451        )
2452        .await;
2453    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
2454    let project_path = ProjectPath {
2455        worktree_id,
2456        path: Arc::from(Path::new(&"test.txt")),
2457    };
2458    let abs_path = project_a.read_with(cx_a, |project, cx| {
2459        project
2460            .absolute_path(&project_path, cx)
2461            .map(|path_buf| Arc::from(path_buf.to_owned()))
2462            .unwrap()
2463    });
2464
2465    active_call_a
2466        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2467        .await
2468        .unwrap();
2469    let project_id = active_call_a
2470        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2471        .await
2472        .unwrap();
2473    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2474    active_call_b
2475        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2476        .await
2477        .unwrap();
2478    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2479    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2480
2481    // Client A opens an editor.
2482    let editor_a = workspace_a
2483        .update_in(cx_a, |workspace, window, cx| {
2484            workspace.open_path(project_path.clone(), None, true, window, cx)
2485        })
2486        .await
2487        .unwrap()
2488        .downcast::<Editor>()
2489        .unwrap();
2490
2491    // Client B opens same editor as A.
2492    let editor_b = workspace_b
2493        .update_in(cx_b, |workspace, window, cx| {
2494            workspace.open_path(project_path.clone(), None, true, window, cx)
2495        })
2496        .await
2497        .unwrap()
2498        .downcast::<Editor>()
2499        .unwrap();
2500
2501    cx_a.run_until_parked();
2502    cx_b.run_until_parked();
2503
2504    // Client A adds breakpoint on line (1)
2505    editor_a.update_in(cx_a, |editor, window, cx| {
2506        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2507    });
2508
2509    cx_a.run_until_parked();
2510    cx_b.run_until_parked();
2511
2512    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2513        editor
2514            .breakpoint_store()
2515            .clone()
2516            .unwrap()
2517            .read(cx)
2518            .all_breakpoints(cx)
2519            .clone()
2520    });
2521    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2522        editor
2523            .breakpoint_store()
2524            .clone()
2525            .unwrap()
2526            .read(cx)
2527            .all_breakpoints(cx)
2528            .clone()
2529    });
2530
2531    assert_eq!(1, breakpoints_a.len());
2532    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
2533    assert_eq!(breakpoints_a, breakpoints_b);
2534
2535    // Client B adds breakpoint on line(2)
2536    editor_b.update_in(cx_b, |editor, window, cx| {
2537        editor.move_down(&editor::actions::MoveDown, window, cx);
2538        editor.move_down(&editor::actions::MoveDown, window, cx);
2539        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2540    });
2541
2542    cx_a.run_until_parked();
2543    cx_b.run_until_parked();
2544
2545    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2546        editor
2547            .breakpoint_store()
2548            .clone()
2549            .unwrap()
2550            .read(cx)
2551            .all_breakpoints(cx)
2552            .clone()
2553    });
2554    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2555        editor
2556            .breakpoint_store()
2557            .clone()
2558            .unwrap()
2559            .read(cx)
2560            .all_breakpoints(cx)
2561            .clone()
2562    });
2563
2564    assert_eq!(1, breakpoints_a.len());
2565    assert_eq!(breakpoints_a, breakpoints_b);
2566    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
2567
2568    // Client A removes last added breakpoint from client B
2569    editor_a.update_in(cx_a, |editor, window, cx| {
2570        editor.move_down(&editor::actions::MoveDown, window, cx);
2571        editor.move_down(&editor::actions::MoveDown, window, cx);
2572        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2573    });
2574
2575    cx_a.run_until_parked();
2576    cx_b.run_until_parked();
2577
2578    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2579        editor
2580            .breakpoint_store()
2581            .clone()
2582            .unwrap()
2583            .read(cx)
2584            .all_breakpoints(cx)
2585            .clone()
2586    });
2587    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2588        editor
2589            .breakpoint_store()
2590            .clone()
2591            .unwrap()
2592            .read(cx)
2593            .all_breakpoints(cx)
2594            .clone()
2595    });
2596
2597    assert_eq!(1, breakpoints_a.len());
2598    assert_eq!(breakpoints_a, breakpoints_b);
2599    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
2600
2601    // Client B removes first added breakpoint by client A
2602    editor_b.update_in(cx_b, |editor, window, cx| {
2603        editor.move_up(&editor::actions::MoveUp, window, cx);
2604        editor.move_up(&editor::actions::MoveUp, window, cx);
2605        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2606    });
2607
2608    cx_a.run_until_parked();
2609    cx_b.run_until_parked();
2610
2611    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2612        editor
2613            .breakpoint_store()
2614            .clone()
2615            .unwrap()
2616            .read(cx)
2617            .all_breakpoints(cx)
2618            .clone()
2619    });
2620    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2621        editor
2622            .breakpoint_store()
2623            .clone()
2624            .unwrap()
2625            .read(cx)
2626            .all_breakpoints(cx)
2627            .clone()
2628    });
2629
2630    assert_eq!(0, breakpoints_a.len());
2631    assert_eq!(breakpoints_a, breakpoints_b);
2632}
2633
2634#[gpui::test]
2635async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2636    let mut server = TestServer::start(cx_a.executor()).await;
2637    let client_a = server.create_client(cx_a, "user_a").await;
2638    let client_b = server.create_client(cx_b, "user_b").await;
2639    server
2640        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2641        .await;
2642    let active_call_a = cx_a.read(ActiveCall::global);
2643    let active_call_b = cx_b.read(ActiveCall::global);
2644
2645    cx_a.update(editor::init);
2646    cx_b.update(editor::init);
2647
2648    client_a.language_registry().add(rust_lang());
2649    client_b.language_registry().add(rust_lang());
2650    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2651        "Rust",
2652        FakeLspAdapter {
2653            name: RUST_ANALYZER_NAME,
2654            ..FakeLspAdapter::default()
2655        },
2656    );
2657
2658    client_a
2659        .fs()
2660        .insert_tree(
2661            path!("/a"),
2662            json!({
2663                "main.rs": "fn main() {}",
2664            }),
2665        )
2666        .await;
2667    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2668    active_call_a
2669        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2670        .await
2671        .unwrap();
2672    let project_id = active_call_a
2673        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2674        .await
2675        .unwrap();
2676
2677    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2678    active_call_b
2679        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2680        .await
2681        .unwrap();
2682
2683    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2684    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2685
2686    let editor_a = workspace_a
2687        .update_in(cx_a, |workspace, window, cx| {
2688            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2689        })
2690        .await
2691        .unwrap()
2692        .downcast::<Editor>()
2693        .unwrap();
2694
2695    let editor_b = workspace_b
2696        .update_in(cx_b, |workspace, window, cx| {
2697            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2698        })
2699        .await
2700        .unwrap()
2701        .downcast::<Editor>()
2702        .unwrap();
2703
2704    let fake_language_server = fake_language_servers.next().await.unwrap();
2705
2706    // host
2707    let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
2708        |params, _| async move {
2709            assert_eq!(
2710                params.text_document.uri,
2711                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2712            );
2713            assert_eq!(params.position, lsp::Position::new(0, 0),);
2714            Ok(Some(ExpandedMacro {
2715                name: "test_macro_name".to_string(),
2716                expansion: "test_macro_expansion on the host".to_string(),
2717            }))
2718        },
2719    );
2720
2721    editor_a.update_in(cx_a, |editor, window, cx| {
2722        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
2723    });
2724    expand_request_a.next().await.unwrap();
2725    cx_a.run_until_parked();
2726
2727    workspace_a.update(cx_a, |workspace, cx| {
2728        workspace.active_pane().update(cx, |pane, cx| {
2729            assert_eq!(
2730                pane.items_len(),
2731                2,
2732                "Should have added a macro expansion to the host's pane"
2733            );
2734            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
2735            new_editor.update(cx, |editor, cx| {
2736                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
2737            });
2738        })
2739    });
2740
2741    // client
2742    let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
2743        |params, _| async move {
2744            assert_eq!(
2745                params.text_document.uri,
2746                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2747            );
2748            assert_eq!(params.position, lsp::Position::new(0, 0),);
2749            Ok(Some(ExpandedMacro {
2750                name: "test_macro_name".to_string(),
2751                expansion: "test_macro_expansion on the client".to_string(),
2752            }))
2753        },
2754    );
2755
2756    editor_b.update_in(cx_b, |editor, window, cx| {
2757        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
2758    });
2759    expand_request_b.next().await.unwrap();
2760    cx_b.run_until_parked();
2761
2762    workspace_b.update(cx_b, |workspace, cx| {
2763        workspace.active_pane().update(cx, |pane, cx| {
2764            assert_eq!(
2765                pane.items_len(),
2766                2,
2767                "Should have added a macro expansion to the client's pane"
2768            );
2769            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
2770            new_editor.update(cx, |editor, cx| {
2771                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
2772            });
2773        })
2774    });
2775}
2776
2777#[track_caller]
2778fn tab_undo_assert(
2779    cx_a: &mut EditorTestContext,
2780    cx_b: &mut EditorTestContext,
2781    expected_initial: &str,
2782    expected_tabbed: &str,
2783    a_tabs: bool,
2784) {
2785    cx_a.assert_editor_state(expected_initial);
2786    cx_b.assert_editor_state(expected_initial);
2787
2788    if a_tabs {
2789        cx_a.update_editor(|editor, window, cx| {
2790            editor.tab(&editor::actions::Tab, window, cx);
2791        });
2792    } else {
2793        cx_b.update_editor(|editor, window, cx| {
2794            editor.tab(&editor::actions::Tab, window, cx);
2795        });
2796    }
2797
2798    cx_a.run_until_parked();
2799    cx_b.run_until_parked();
2800
2801    cx_a.assert_editor_state(expected_tabbed);
2802    cx_b.assert_editor_state(expected_tabbed);
2803
2804    if a_tabs {
2805        cx_a.update_editor(|editor, window, cx| {
2806            editor.undo(&editor::actions::Undo, window, cx);
2807        });
2808    } else {
2809        cx_b.update_editor(|editor, window, cx| {
2810            editor.undo(&editor::actions::Undo, window, cx);
2811        });
2812    }
2813    cx_a.run_until_parked();
2814    cx_b.run_until_parked();
2815    cx_a.assert_editor_state(expected_initial);
2816    cx_b.assert_editor_state(expected_initial);
2817}
2818
2819fn extract_hint_labels(editor: &Editor) -> Vec<String> {
2820    let mut labels = Vec::new();
2821    for hint in editor.inlay_hint_cache().hints() {
2822        match hint.label {
2823            project::InlayHintLabel::String(s) => labels.push(s),
2824            _ => unreachable!(),
2825        }
2826    }
2827    labels
2828}
2829
2830fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
2831    git::blame::BlameEntry {
2832        sha: sha.parse().unwrap(),
2833        range,
2834        ..Default::default()
2835    }
2836}