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