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: 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        .into_response()
1744        .expect("inlay refresh request failed");
1745
1746    executor.run_until_parked();
1747    editor_a.update(cx_a, |editor, _| {
1748        assert_eq!(
1749            vec![after_special_edit_for_refresh.to_string()],
1750            extract_hint_labels(editor),
1751            "Host should react to /refresh LSP request"
1752        );
1753    });
1754    editor_b.update(cx_b, |editor, _| {
1755        assert_eq!(
1756            vec![after_special_edit_for_refresh.to_string()],
1757            extract_hint_labels(editor),
1758            "Guest should get a /refresh LSP request propagated by host"
1759        );
1760    });
1761}
1762
1763#[gpui::test(iterations = 10)]
1764async fn test_inlay_hint_refresh_is_forwarded(
1765    cx_a: &mut TestAppContext,
1766    cx_b: &mut TestAppContext,
1767) {
1768    let mut server = TestServer::start(cx_a.executor()).await;
1769    let executor = cx_a.executor();
1770    let client_a = server.create_client(cx_a, "user_a").await;
1771    let client_b = server.create_client(cx_b, "user_b").await;
1772    server
1773        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1774        .await;
1775    let active_call_a = cx_a.read(ActiveCall::global);
1776    let active_call_b = cx_b.read(ActiveCall::global);
1777
1778    cx_a.update(editor::init);
1779    cx_b.update(editor::init);
1780
1781    cx_a.update(|cx| {
1782        SettingsStore::update_global(cx, |store, cx| {
1783            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1784                settings.defaults.inlay_hints = Some(InlayHintSettings {
1785                    show_value_hints: true,
1786                    enabled: false,
1787                    edit_debounce_ms: 0,
1788                    scroll_debounce_ms: 0,
1789                    show_type_hints: false,
1790                    show_parameter_hints: false,
1791                    show_other_hints: false,
1792                    show_background: false,
1793                    toggle_on_modifiers_press: None,
1794                })
1795            });
1796        });
1797    });
1798    cx_b.update(|cx| {
1799        SettingsStore::update_global(cx, |store, cx| {
1800            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1801                settings.defaults.inlay_hints = Some(InlayHintSettings {
1802                    show_value_hints: true,
1803                    enabled: true,
1804                    edit_debounce_ms: 0,
1805                    scroll_debounce_ms: 0,
1806                    show_type_hints: true,
1807                    show_parameter_hints: true,
1808                    show_other_hints: true,
1809                    show_background: false,
1810                    toggle_on_modifiers_press: None,
1811                })
1812            });
1813        });
1814    });
1815
1816    client_a.language_registry().add(rust_lang());
1817    client_b.language_registry().add(rust_lang());
1818    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1819        "Rust",
1820        FakeLspAdapter {
1821            capabilities: lsp::ServerCapabilities {
1822                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1823                ..Default::default()
1824            },
1825            ..Default::default()
1826        },
1827    );
1828
1829    client_a
1830        .fs()
1831        .insert_tree(
1832            path!("/a"),
1833            json!({
1834                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1835                "other.rs": "// Test file",
1836            }),
1837        )
1838        .await;
1839    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1840    active_call_a
1841        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1842        .await
1843        .unwrap();
1844    let project_id = active_call_a
1845        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1846        .await
1847        .unwrap();
1848
1849    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1850    active_call_b
1851        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1852        .await
1853        .unwrap();
1854
1855    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
1856    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1857
1858    cx_a.background_executor.start_waiting();
1859
1860    let editor_a = workspace_a
1861        .update_in(cx_a, |workspace, window, cx| {
1862            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1863        })
1864        .await
1865        .unwrap()
1866        .downcast::<Editor>()
1867        .unwrap();
1868
1869    let editor_b = workspace_b
1870        .update_in(cx_b, |workspace, window, cx| {
1871            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1872        })
1873        .await
1874        .unwrap()
1875        .downcast::<Editor>()
1876        .unwrap();
1877
1878    let other_hints = Arc::new(AtomicBool::new(false));
1879    let fake_language_server = fake_language_servers.next().await.unwrap();
1880    let closure_other_hints = Arc::clone(&other_hints);
1881    fake_language_server
1882        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1883            let task_other_hints = Arc::clone(&closure_other_hints);
1884            async move {
1885                assert_eq!(
1886                    params.text_document.uri,
1887                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1888                );
1889                let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
1890                let character = if other_hints { 0 } else { 2 };
1891                let label = if other_hints {
1892                    "other hint"
1893                } else {
1894                    "initial hint"
1895                };
1896                Ok(Some(vec![lsp::InlayHint {
1897                    position: lsp::Position::new(0, character),
1898                    label: lsp::InlayHintLabel::String(label.to_string()),
1899                    kind: None,
1900                    text_edits: None,
1901                    tooltip: None,
1902                    padding_left: None,
1903                    padding_right: None,
1904                    data: None,
1905                }]))
1906            }
1907        })
1908        .next()
1909        .await
1910        .unwrap();
1911    executor.finish_waiting();
1912
1913    executor.run_until_parked();
1914    editor_a.update(cx_a, |editor, _| {
1915        assert!(
1916            extract_hint_labels(editor).is_empty(),
1917            "Host should get no hints due to them turned off"
1918        );
1919    });
1920
1921    executor.run_until_parked();
1922    editor_b.update(cx_b, |editor, _| {
1923        assert_eq!(
1924            vec!["initial hint".to_string()],
1925            extract_hint_labels(editor),
1926            "Client should get its first hints when opens an editor"
1927        );
1928    });
1929
1930    other_hints.fetch_or(true, atomic::Ordering::Release);
1931    fake_language_server
1932        .request::<lsp::request::InlayHintRefreshRequest>(())
1933        .await
1934        .into_response()
1935        .expect("inlay refresh request failed");
1936    executor.run_until_parked();
1937    editor_a.update(cx_a, |editor, _| {
1938        assert!(
1939            extract_hint_labels(editor).is_empty(),
1940            "Host should get no hints due to them turned off, even after the /refresh"
1941        );
1942    });
1943
1944    executor.run_until_parked();
1945    editor_b.update(cx_b, |editor, _| {
1946        assert_eq!(
1947            vec!["other hint".to_string()],
1948            extract_hint_labels(editor),
1949            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
1950        );
1951    });
1952}
1953
1954#[gpui::test(iterations = 10)]
1955async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1956    let mut server = TestServer::start(cx_a.executor()).await;
1957    let client_a = server.create_client(cx_a, "user_a").await;
1958    let client_b = server.create_client(cx_b, "user_b").await;
1959    server
1960        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1961        .await;
1962    let active_call_a = cx_a.read(ActiveCall::global);
1963
1964    cx_a.update(editor::init);
1965    cx_b.update(editor::init);
1966    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
1967    let inline_blame_off_settings = Some(InlineBlameSettings {
1968        enabled: false,
1969        delay_ms: None,
1970        min_column: None,
1971        show_commit_summary: false,
1972    });
1973    cx_a.update(|cx| {
1974        SettingsStore::update_global(cx, |store, cx| {
1975            store.update_user_settings::<ProjectSettings>(cx, |settings| {
1976                settings.git.inline_blame = inline_blame_off_settings;
1977            });
1978        });
1979    });
1980    cx_b.update(|cx| {
1981        SettingsStore::update_global(cx, |store, cx| {
1982            store.update_user_settings::<ProjectSettings>(cx, |settings| {
1983                settings.git.inline_blame = inline_blame_off_settings;
1984            });
1985        });
1986    });
1987
1988    client_a
1989        .fs()
1990        .insert_tree(
1991            path!("/my-repo"),
1992            json!({
1993                ".git": {},
1994                "file.txt": "line1\nline2\nline3\nline\n",
1995            }),
1996        )
1997        .await;
1998
1999    let blame = git::blame::Blame {
2000        entries: vec![
2001            blame_entry("1b1b1b", 0..1),
2002            blame_entry("0d0d0d", 1..2),
2003            blame_entry("3a3a3a", 2..3),
2004            blame_entry("4c4c4c", 3..4),
2005        ],
2006        messages: [
2007            ("1b1b1b", "message for idx-0"),
2008            ("0d0d0d", "message for idx-1"),
2009            ("3a3a3a", "message for idx-2"),
2010            ("4c4c4c", "message for idx-3"),
2011        ]
2012        .into_iter()
2013        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
2014        .collect(),
2015        remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
2016    };
2017    client_a.fs().set_blame_for_repo(
2018        Path::new(path!("/my-repo/.git")),
2019        vec![("file.txt".into(), blame)],
2020    );
2021
2022    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
2023    let project_id = active_call_a
2024        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2025        .await
2026        .unwrap();
2027
2028    // Create editor_a
2029    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2030    let editor_a = workspace_a
2031        .update_in(cx_a, |workspace, window, cx| {
2032            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2033        })
2034        .await
2035        .unwrap()
2036        .downcast::<Editor>()
2037        .unwrap();
2038
2039    // Join the project as client B.
2040    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2041    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2042    let editor_b = workspace_b
2043        .update_in(cx_b, |workspace, window, cx| {
2044            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2045        })
2046        .await
2047        .unwrap()
2048        .downcast::<Editor>()
2049        .unwrap();
2050    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
2051        editor_b
2052            .buffer()
2053            .read(cx)
2054            .as_singleton()
2055            .unwrap()
2056            .read(cx)
2057            .remote_id()
2058    });
2059
2060    // client_b now requests git blame for the open buffer
2061    editor_b.update_in(cx_b, |editor_b, window, cx| {
2062        assert!(editor_b.blame().is_none());
2063        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
2064    });
2065
2066    cx_a.executor().run_until_parked();
2067    cx_b.executor().run_until_parked();
2068
2069    editor_b.update(cx_b, |editor_b, cx| {
2070        let blame = editor_b.blame().expect("editor_b should have blame now");
2071        let entries = blame.update(cx, |blame, cx| {
2072            blame
2073                .blame_for_rows(
2074                    &(0..4)
2075                        .map(|row| RowInfo {
2076                            buffer_row: Some(row),
2077                            buffer_id: Some(buffer_id_b),
2078                            ..Default::default()
2079                        })
2080                        .collect::<Vec<_>>(),
2081                    cx,
2082                )
2083                .collect::<Vec<_>>()
2084        });
2085
2086        assert_eq!(
2087            entries,
2088            vec![
2089                Some(blame_entry("1b1b1b", 0..1)),
2090                Some(blame_entry("0d0d0d", 1..2)),
2091                Some(blame_entry("3a3a3a", 2..3)),
2092                Some(blame_entry("4c4c4c", 3..4)),
2093            ]
2094        );
2095
2096        blame.update(cx, |blame, _| {
2097            for (idx, entry) in entries.iter().flatten().enumerate() {
2098                let details = blame.details_for_entry(entry).unwrap();
2099                assert_eq!(details.message, format!("message for idx-{}", idx));
2100                assert_eq!(
2101                    details.permalink.unwrap().to_string(),
2102                    format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
2103                );
2104            }
2105        });
2106    });
2107
2108    // editor_b updates the file, which gets sent to client_a, which updates git blame,
2109    // which gets back to client_b.
2110    editor_b.update_in(cx_b, |editor_b, _, cx| {
2111        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
2112    });
2113
2114    cx_a.executor().run_until_parked();
2115    cx_b.executor().run_until_parked();
2116
2117    editor_b.update(cx_b, |editor_b, cx| {
2118        let blame = editor_b.blame().expect("editor_b should have blame now");
2119        let entries = blame.update(cx, |blame, cx| {
2120            blame
2121                .blame_for_rows(
2122                    &(0..4)
2123                        .map(|row| RowInfo {
2124                            buffer_row: Some(row),
2125                            buffer_id: Some(buffer_id_b),
2126                            ..Default::default()
2127                        })
2128                        .collect::<Vec<_>>(),
2129                    cx,
2130                )
2131                .collect::<Vec<_>>()
2132        });
2133
2134        assert_eq!(
2135            entries,
2136            vec![
2137                None,
2138                Some(blame_entry("0d0d0d", 1..2)),
2139                Some(blame_entry("3a3a3a", 2..3)),
2140                Some(blame_entry("4c4c4c", 3..4)),
2141            ]
2142        );
2143    });
2144
2145    // Now editor_a also updates the file
2146    editor_a.update_in(cx_a, |editor_a, _, cx| {
2147        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
2148    });
2149
2150    cx_a.executor().run_until_parked();
2151    cx_b.executor().run_until_parked();
2152
2153    editor_b.update(cx_b, |editor_b, cx| {
2154        let blame = editor_b.blame().expect("editor_b should have blame now");
2155        let entries = blame.update(cx, |blame, cx| {
2156            blame
2157                .blame_for_rows(
2158                    &(0..4)
2159                        .map(|row| RowInfo {
2160                            buffer_row: Some(row),
2161                            buffer_id: Some(buffer_id_b),
2162                            ..Default::default()
2163                        })
2164                        .collect::<Vec<_>>(),
2165                    cx,
2166                )
2167                .collect::<Vec<_>>()
2168        });
2169
2170        assert_eq!(
2171            entries,
2172            vec![
2173                None,
2174                None,
2175                Some(blame_entry("3a3a3a", 2..3)),
2176                Some(blame_entry("4c4c4c", 3..4)),
2177            ]
2178        );
2179    });
2180}
2181
2182#[gpui::test(iterations = 30)]
2183async fn test_collaborating_with_editorconfig(
2184    cx_a: &mut TestAppContext,
2185    cx_b: &mut TestAppContext,
2186) {
2187    let mut server = TestServer::start(cx_a.executor()).await;
2188    let client_a = server.create_client(cx_a, "user_a").await;
2189    let client_b = server.create_client(cx_b, "user_b").await;
2190    server
2191        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2192        .await;
2193    let active_call_a = cx_a.read(ActiveCall::global);
2194
2195    cx_b.update(editor::init);
2196
2197    // Set up a fake language server.
2198    client_a.language_registry().add(rust_lang());
2199    client_a
2200        .fs()
2201        .insert_tree(
2202            path!("/a"),
2203            json!({
2204                "src": {
2205                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
2206                    "other_mod": {
2207                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
2208                        ".editorconfig": "",
2209                    },
2210                },
2211                ".editorconfig": "[*]\ntab_width = 2\n",
2212            }),
2213        )
2214        .await;
2215    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2216    let project_id = active_call_a
2217        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2218        .await
2219        .unwrap();
2220    let main_buffer_a = project_a
2221        .update(cx_a, |p, cx| {
2222            p.open_buffer((worktree_id, "src/main.rs"), cx)
2223        })
2224        .await
2225        .unwrap();
2226    let other_buffer_a = project_a
2227        .update(cx_a, |p, cx| {
2228            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
2229        })
2230        .await
2231        .unwrap();
2232    let cx_a = cx_a.add_empty_window();
2233    let main_editor_a = cx_a.new_window_entity(|window, cx| {
2234        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
2235    });
2236    let other_editor_a = cx_a.new_window_entity(|window, cx| {
2237        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
2238    });
2239    let mut main_editor_cx_a = EditorTestContext {
2240        cx: cx_a.clone(),
2241        window: cx_a.window_handle(),
2242        editor: main_editor_a,
2243        assertion_cx: AssertionContextManager::new(),
2244    };
2245    let mut other_editor_cx_a = EditorTestContext {
2246        cx: cx_a.clone(),
2247        window: cx_a.window_handle(),
2248        editor: other_editor_a,
2249        assertion_cx: AssertionContextManager::new(),
2250    };
2251
2252    // Join the project as client B.
2253    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2254    let main_buffer_b = project_b
2255        .update(cx_b, |p, cx| {
2256            p.open_buffer((worktree_id, "src/main.rs"), cx)
2257        })
2258        .await
2259        .unwrap();
2260    let other_buffer_b = project_b
2261        .update(cx_b, |p, cx| {
2262            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
2263        })
2264        .await
2265        .unwrap();
2266    let cx_b = cx_b.add_empty_window();
2267    let main_editor_b = cx_b.new_window_entity(|window, cx| {
2268        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
2269    });
2270    let other_editor_b = cx_b.new_window_entity(|window, cx| {
2271        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
2272    });
2273    let mut main_editor_cx_b = EditorTestContext {
2274        cx: cx_b.clone(),
2275        window: cx_b.window_handle(),
2276        editor: main_editor_b,
2277        assertion_cx: AssertionContextManager::new(),
2278    };
2279    let mut other_editor_cx_b = EditorTestContext {
2280        cx: cx_b.clone(),
2281        window: cx_b.window_handle(),
2282        editor: other_editor_b,
2283        assertion_cx: AssertionContextManager::new(),
2284    };
2285
2286    let initial_main = indoc! {"
2287ˇmod other;
2288fn main() { let foo = other::foo(); }"};
2289    let initial_other = indoc! {"
2290ˇpub fn foo() -> usize {
2291    4
2292}"};
2293
2294    let first_tabbed_main = indoc! {"
2295  ˇmod other;
2296fn main() { let foo = other::foo(); }"};
2297    tab_undo_assert(
2298        &mut main_editor_cx_a,
2299        &mut main_editor_cx_b,
2300        initial_main,
2301        first_tabbed_main,
2302        true,
2303    );
2304    tab_undo_assert(
2305        &mut main_editor_cx_a,
2306        &mut main_editor_cx_b,
2307        initial_main,
2308        first_tabbed_main,
2309        false,
2310    );
2311
2312    let first_tabbed_other = indoc! {"
2313  ˇpub fn foo() -> usize {
2314    4
2315}"};
2316    tab_undo_assert(
2317        &mut other_editor_cx_a,
2318        &mut other_editor_cx_b,
2319        initial_other,
2320        first_tabbed_other,
2321        true,
2322    );
2323    tab_undo_assert(
2324        &mut other_editor_cx_a,
2325        &mut other_editor_cx_b,
2326        initial_other,
2327        first_tabbed_other,
2328        false,
2329    );
2330
2331    client_a
2332        .fs()
2333        .atomic_write(
2334            PathBuf::from(path!("/a/src/.editorconfig")),
2335            "[*]\ntab_width = 3\n".to_owned(),
2336        )
2337        .await
2338        .unwrap();
2339    cx_a.run_until_parked();
2340    cx_b.run_until_parked();
2341
2342    let second_tabbed_main = indoc! {"
2343   ˇmod other;
2344fn main() { let foo = other::foo(); }"};
2345    tab_undo_assert(
2346        &mut main_editor_cx_a,
2347        &mut main_editor_cx_b,
2348        initial_main,
2349        second_tabbed_main,
2350        true,
2351    );
2352    tab_undo_assert(
2353        &mut main_editor_cx_a,
2354        &mut main_editor_cx_b,
2355        initial_main,
2356        second_tabbed_main,
2357        false,
2358    );
2359
2360    let second_tabbed_other = indoc! {"
2361   ˇpub fn foo() -> usize {
2362    4
2363}"};
2364    tab_undo_assert(
2365        &mut other_editor_cx_a,
2366        &mut other_editor_cx_b,
2367        initial_other,
2368        second_tabbed_other,
2369        true,
2370    );
2371    tab_undo_assert(
2372        &mut other_editor_cx_a,
2373        &mut other_editor_cx_b,
2374        initial_other,
2375        second_tabbed_other,
2376        false,
2377    );
2378
2379    let editorconfig_buffer_b = project_b
2380        .update(cx_b, |p, cx| {
2381            p.open_buffer((worktree_id, "src/other_mod/.editorconfig"), cx)
2382        })
2383        .await
2384        .unwrap();
2385    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
2386        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
2387    });
2388    project_b
2389        .update(cx_b, |project, cx| {
2390            project.save_buffer(editorconfig_buffer_b.clone(), cx)
2391        })
2392        .await
2393        .unwrap();
2394    cx_a.run_until_parked();
2395    cx_b.run_until_parked();
2396
2397    tab_undo_assert(
2398        &mut main_editor_cx_a,
2399        &mut main_editor_cx_b,
2400        initial_main,
2401        second_tabbed_main,
2402        true,
2403    );
2404    tab_undo_assert(
2405        &mut main_editor_cx_a,
2406        &mut main_editor_cx_b,
2407        initial_main,
2408        second_tabbed_main,
2409        false,
2410    );
2411
2412    let third_tabbed_other = indoc! {"
2413      ˇpub fn foo() -> usize {
2414    4
2415}"};
2416    tab_undo_assert(
2417        &mut other_editor_cx_a,
2418        &mut other_editor_cx_b,
2419        initial_other,
2420        third_tabbed_other,
2421        true,
2422    );
2423
2424    tab_undo_assert(
2425        &mut other_editor_cx_a,
2426        &mut other_editor_cx_b,
2427        initial_other,
2428        third_tabbed_other,
2429        false,
2430    );
2431}
2432
2433#[gpui::test]
2434async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2435    let executor = cx_a.executor();
2436    let mut server = TestServer::start(executor.clone()).await;
2437    let client_a = server.create_client(cx_a, "user_a").await;
2438    let client_b = server.create_client(cx_b, "user_b").await;
2439    server
2440        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2441        .await;
2442    let active_call_a = cx_a.read(ActiveCall::global);
2443    let active_call_b = cx_b.read(ActiveCall::global);
2444    cx_a.update(editor::init);
2445    cx_b.update(editor::init);
2446    client_a
2447        .fs()
2448        .insert_tree(
2449            "/a",
2450            json!({
2451                "test.txt": "one\ntwo\nthree\nfour\nfive",
2452            }),
2453        )
2454        .await;
2455    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
2456    let project_path = ProjectPath {
2457        worktree_id,
2458        path: Arc::from(Path::new(&"test.txt")),
2459    };
2460    let abs_path = project_a.read_with(cx_a, |project, cx| {
2461        project
2462            .absolute_path(&project_path, cx)
2463            .map(|path_buf| Arc::from(path_buf.to_owned()))
2464            .unwrap()
2465    });
2466
2467    active_call_a
2468        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2469        .await
2470        .unwrap();
2471    let project_id = active_call_a
2472        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2473        .await
2474        .unwrap();
2475    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2476    active_call_b
2477        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2478        .await
2479        .unwrap();
2480    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2481    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2482
2483    // Client A opens an editor.
2484    let editor_a = workspace_a
2485        .update_in(cx_a, |workspace, window, cx| {
2486            workspace.open_path(project_path.clone(), None, true, window, cx)
2487        })
2488        .await
2489        .unwrap()
2490        .downcast::<Editor>()
2491        .unwrap();
2492
2493    // Client B opens same editor as A.
2494    let editor_b = workspace_b
2495        .update_in(cx_b, |workspace, window, cx| {
2496            workspace.open_path(project_path.clone(), None, true, window, cx)
2497        })
2498        .await
2499        .unwrap()
2500        .downcast::<Editor>()
2501        .unwrap();
2502
2503    cx_a.run_until_parked();
2504    cx_b.run_until_parked();
2505
2506    // Client A adds breakpoint on line (1)
2507    editor_a.update_in(cx_a, |editor, window, cx| {
2508        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2509    });
2510
2511    cx_a.run_until_parked();
2512    cx_b.run_until_parked();
2513
2514    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2515        editor
2516            .breakpoint_store()
2517            .clone()
2518            .unwrap()
2519            .read(cx)
2520            .all_source_breakpoints(cx)
2521            .clone()
2522    });
2523    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2524        editor
2525            .breakpoint_store()
2526            .clone()
2527            .unwrap()
2528            .read(cx)
2529            .all_source_breakpoints(cx)
2530            .clone()
2531    });
2532
2533    assert_eq!(1, breakpoints_a.len());
2534    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
2535    assert_eq!(breakpoints_a, breakpoints_b);
2536
2537    // Client B adds breakpoint on line(2)
2538    editor_b.update_in(cx_b, |editor, window, cx| {
2539        editor.move_down(&editor::actions::MoveDown, window, cx);
2540        editor.move_down(&editor::actions::MoveDown, window, cx);
2541        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2542    });
2543
2544    cx_a.run_until_parked();
2545    cx_b.run_until_parked();
2546
2547    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2548        editor
2549            .breakpoint_store()
2550            .clone()
2551            .unwrap()
2552            .read(cx)
2553            .all_source_breakpoints(cx)
2554            .clone()
2555    });
2556    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2557        editor
2558            .breakpoint_store()
2559            .clone()
2560            .unwrap()
2561            .read(cx)
2562            .all_source_breakpoints(cx)
2563            .clone()
2564    });
2565
2566    assert_eq!(1, breakpoints_a.len());
2567    assert_eq!(breakpoints_a, breakpoints_b);
2568    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
2569
2570    // Client A removes last added breakpoint from client B
2571    editor_a.update_in(cx_a, |editor, window, cx| {
2572        editor.move_down(&editor::actions::MoveDown, window, cx);
2573        editor.move_down(&editor::actions::MoveDown, window, cx);
2574        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2575    });
2576
2577    cx_a.run_until_parked();
2578    cx_b.run_until_parked();
2579
2580    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2581        editor
2582            .breakpoint_store()
2583            .clone()
2584            .unwrap()
2585            .read(cx)
2586            .all_source_breakpoints(cx)
2587            .clone()
2588    });
2589    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2590        editor
2591            .breakpoint_store()
2592            .clone()
2593            .unwrap()
2594            .read(cx)
2595            .all_source_breakpoints(cx)
2596            .clone()
2597    });
2598
2599    assert_eq!(1, breakpoints_a.len());
2600    assert_eq!(breakpoints_a, breakpoints_b);
2601    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
2602
2603    // Client B removes first added breakpoint by client A
2604    editor_b.update_in(cx_b, |editor, window, cx| {
2605        editor.move_up(&editor::actions::MoveUp, window, cx);
2606        editor.move_up(&editor::actions::MoveUp, window, cx);
2607        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2608    });
2609
2610    cx_a.run_until_parked();
2611    cx_b.run_until_parked();
2612
2613    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2614        editor
2615            .breakpoint_store()
2616            .clone()
2617            .unwrap()
2618            .read(cx)
2619            .all_source_breakpoints(cx)
2620            .clone()
2621    });
2622    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2623        editor
2624            .breakpoint_store()
2625            .clone()
2626            .unwrap()
2627            .read(cx)
2628            .all_source_breakpoints(cx)
2629            .clone()
2630    });
2631
2632    assert_eq!(0, breakpoints_a.len());
2633    assert_eq!(breakpoints_a, breakpoints_b);
2634}
2635
2636#[gpui::test]
2637async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2638    let mut server = TestServer::start(cx_a.executor()).await;
2639    let client_a = server.create_client(cx_a, "user_a").await;
2640    let client_b = server.create_client(cx_b, "user_b").await;
2641    server
2642        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2643        .await;
2644    let active_call_a = cx_a.read(ActiveCall::global);
2645    let active_call_b = cx_b.read(ActiveCall::global);
2646
2647    cx_a.update(editor::init);
2648    cx_b.update(editor::init);
2649
2650    client_a.language_registry().add(rust_lang());
2651    client_b.language_registry().add(rust_lang());
2652    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2653        "Rust",
2654        FakeLspAdapter {
2655            name: RUST_ANALYZER_NAME,
2656            ..FakeLspAdapter::default()
2657        },
2658    );
2659
2660    client_a
2661        .fs()
2662        .insert_tree(
2663            path!("/a"),
2664            json!({
2665                "main.rs": "fn main() {}",
2666            }),
2667        )
2668        .await;
2669    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2670    active_call_a
2671        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2672        .await
2673        .unwrap();
2674    let project_id = active_call_a
2675        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2676        .await
2677        .unwrap();
2678
2679    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2680    active_call_b
2681        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2682        .await
2683        .unwrap();
2684
2685    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2686    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2687
2688    let editor_a = workspace_a
2689        .update_in(cx_a, |workspace, window, cx| {
2690            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2691        })
2692        .await
2693        .unwrap()
2694        .downcast::<Editor>()
2695        .unwrap();
2696
2697    let editor_b = workspace_b
2698        .update_in(cx_b, |workspace, window, cx| {
2699            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2700        })
2701        .await
2702        .unwrap()
2703        .downcast::<Editor>()
2704        .unwrap();
2705
2706    let fake_language_server = fake_language_servers.next().await.unwrap();
2707
2708    // host
2709    let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
2710        |params, _| async move {
2711            assert_eq!(
2712                params.text_document.uri,
2713                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2714            );
2715            assert_eq!(params.position, lsp::Position::new(0, 0),);
2716            Ok(Some(ExpandedMacro {
2717                name: "test_macro_name".to_string(),
2718                expansion: "test_macro_expansion on the host".to_string(),
2719            }))
2720        },
2721    );
2722
2723    editor_a.update_in(cx_a, |editor, window, cx| {
2724        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
2725    });
2726    expand_request_a.next().await.unwrap();
2727    cx_a.run_until_parked();
2728
2729    workspace_a.update(cx_a, |workspace, cx| {
2730        workspace.active_pane().update(cx, |pane, cx| {
2731            assert_eq!(
2732                pane.items_len(),
2733                2,
2734                "Should have added a macro expansion to the host's pane"
2735            );
2736            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
2737            new_editor.update(cx, |editor, cx| {
2738                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
2739            });
2740        })
2741    });
2742
2743    // client
2744    let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
2745        |params, _| async move {
2746            assert_eq!(
2747                params.text_document.uri,
2748                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2749            );
2750            assert_eq!(params.position, lsp::Position::new(0, 0),);
2751            Ok(Some(ExpandedMacro {
2752                name: "test_macro_name".to_string(),
2753                expansion: "test_macro_expansion on the client".to_string(),
2754            }))
2755        },
2756    );
2757
2758    editor_b.update_in(cx_b, |editor, window, cx| {
2759        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
2760    });
2761    expand_request_b.next().await.unwrap();
2762    cx_b.run_until_parked();
2763
2764    workspace_b.update(cx_b, |workspace, cx| {
2765        workspace.active_pane().update(cx, |pane, cx| {
2766            assert_eq!(
2767                pane.items_len(),
2768                2,
2769                "Should have added a macro expansion to the client's pane"
2770            );
2771            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
2772            new_editor.update(cx, |editor, cx| {
2773                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
2774            });
2775        })
2776    });
2777}
2778
2779#[track_caller]
2780fn tab_undo_assert(
2781    cx_a: &mut EditorTestContext,
2782    cx_b: &mut EditorTestContext,
2783    expected_initial: &str,
2784    expected_tabbed: &str,
2785    a_tabs: bool,
2786) {
2787    cx_a.assert_editor_state(expected_initial);
2788    cx_b.assert_editor_state(expected_initial);
2789
2790    if a_tabs {
2791        cx_a.update_editor(|editor, window, cx| {
2792            editor.tab(&editor::actions::Tab, window, cx);
2793        });
2794    } else {
2795        cx_b.update_editor(|editor, window, cx| {
2796            editor.tab(&editor::actions::Tab, window, cx);
2797        });
2798    }
2799
2800    cx_a.run_until_parked();
2801    cx_b.run_until_parked();
2802
2803    cx_a.assert_editor_state(expected_tabbed);
2804    cx_b.assert_editor_state(expected_tabbed);
2805
2806    if a_tabs {
2807        cx_a.update_editor(|editor, window, cx| {
2808            editor.undo(&editor::actions::Undo, window, cx);
2809        });
2810    } else {
2811        cx_b.update_editor(|editor, window, cx| {
2812            editor.undo(&editor::actions::Undo, window, cx);
2813        });
2814    }
2815    cx_a.run_until_parked();
2816    cx_b.run_until_parked();
2817    cx_a.assert_editor_state(expected_initial);
2818    cx_b.assert_editor_state(expected_initial);
2819}
2820
2821fn extract_hint_labels(editor: &Editor) -> Vec<String> {
2822    let mut labels = Vec::new();
2823    for hint in editor.inlay_hint_cache().hints() {
2824        match hint.label {
2825            project::InlayHintLabel::String(s) => labels.push(s),
2826            _ => unreachable!(),
2827        }
2828    }
2829    labels
2830}
2831
2832fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
2833    git::blame::BlameEntry {
2834        sha: sha.parse().unwrap(),
2835        range,
2836        ..Default::default()
2837    }
2838}