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(editor, &ConfirmCodeAction { item_ix: Some(0) }, window, cx)
 698        })
 699        .unwrap();
 700    fake_language_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>(
 701        |_, _| async move {
 702            Ok(lsp::CodeAction {
 703                title: "Inline into all callers".to_string(),
 704                edit: Some(lsp::WorkspaceEdit {
 705                    changes: Some(
 706                        [
 707                            (
 708                                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 709                                vec![lsp::TextEdit::new(
 710                                    lsp::Range::new(
 711                                        lsp::Position::new(1, 22),
 712                                        lsp::Position::new(1, 34),
 713                                    ),
 714                                    "4".to_string(),
 715                                )],
 716                            ),
 717                            (
 718                                lsp::Url::from_file_path(path!("/a/other.rs")).unwrap(),
 719                                vec![lsp::TextEdit::new(
 720                                    lsp::Range::new(
 721                                        lsp::Position::new(0, 0),
 722                                        lsp::Position::new(0, 27),
 723                                    ),
 724                                    "".to_string(),
 725                                )],
 726                            ),
 727                        ]
 728                        .into_iter()
 729                        .collect(),
 730                    ),
 731                    ..Default::default()
 732                }),
 733                ..Default::default()
 734            })
 735        },
 736    );
 737
 738    // After the action is confirmed, an editor containing both modified files is opened.
 739    confirm_action.await.unwrap();
 740
 741    let code_action_editor = workspace_b.update(cx_b, |workspace, cx| {
 742        workspace
 743            .active_item(cx)
 744            .unwrap()
 745            .downcast::<Editor>()
 746            .unwrap()
 747    });
 748    code_action_editor.update_in(cx_b, |editor, window, cx| {
 749        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 750        editor.undo(&Undo, window, cx);
 751        assert_eq!(
 752            editor.text(cx),
 753            "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
 754        );
 755        editor.redo(&Redo, window, cx);
 756        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 757    });
 758}
 759
 760#[gpui::test(iterations = 10)]
 761async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 762    let mut server = TestServer::start(cx_a.executor()).await;
 763    let client_a = server.create_client(cx_a, "user_a").await;
 764    let client_b = server.create_client(cx_b, "user_b").await;
 765    server
 766        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 767        .await;
 768    let active_call_a = cx_a.read(ActiveCall::global);
 769
 770    cx_b.update(editor::init);
 771
 772    // Set up a fake language server.
 773    client_a.language_registry().add(rust_lang());
 774    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
 775        "Rust",
 776        FakeLspAdapter {
 777            capabilities: lsp::ServerCapabilities {
 778                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
 779                    prepare_provider: Some(true),
 780                    work_done_progress_options: Default::default(),
 781                })),
 782                ..Default::default()
 783            },
 784            ..Default::default()
 785        },
 786    );
 787
 788    client_a
 789        .fs()
 790        .insert_tree(
 791            path!("/dir"),
 792            json!({
 793                "one.rs": "const ONE: usize = 1;",
 794                "two.rs": "const TWO: usize = one::ONE + one::ONE;"
 795            }),
 796        )
 797        .await;
 798    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
 799    let project_id = active_call_a
 800        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 801        .await
 802        .unwrap();
 803    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 804
 805    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 806    let editor_b = workspace_b
 807        .update_in(cx_b, |workspace, window, cx| {
 808            workspace.open_path((worktree_id, "one.rs"), None, true, window, cx)
 809        })
 810        .await
 811        .unwrap()
 812        .downcast::<Editor>()
 813        .unwrap();
 814    let fake_language_server = fake_language_servers.next().await.unwrap();
 815
 816    // Move cursor to a location that can be renamed.
 817    let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
 818        editor.change_selections(None, window, cx, |s| s.select_ranges([7..7]));
 819        editor.rename(&Rename, window, cx).unwrap()
 820    });
 821
 822    fake_language_server
 823        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
 824            assert_eq!(
 825                params.text_document.uri.as_str(),
 826                uri!("file:///dir/one.rs")
 827            );
 828            assert_eq!(params.position, lsp::Position::new(0, 7));
 829            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
 830                lsp::Position::new(0, 6),
 831                lsp::Position::new(0, 9),
 832            ))))
 833        })
 834        .next()
 835        .await
 836        .unwrap();
 837    prepare_rename.await.unwrap();
 838    editor_b.update(cx_b, |editor, cx| {
 839        use editor::ToOffset;
 840        let rename = editor.pending_rename().unwrap();
 841        let buffer = editor.buffer().read(cx).snapshot(cx);
 842        assert_eq!(
 843            rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
 844            6..9
 845        );
 846        rename.editor.update(cx, |rename_editor, cx| {
 847            let rename_selection = rename_editor.selections.newest::<usize>(cx);
 848            assert_eq!(
 849                rename_selection.range(),
 850                0..3,
 851                "Rename that was triggered from zero selection caret, should propose the whole word."
 852            );
 853            rename_editor.buffer().update(cx, |rename_buffer, cx| {
 854                rename_buffer.edit([(0..3, "THREE")], None, cx);
 855            });
 856        });
 857    });
 858
 859    // Cancel the rename, and repeat the same, but use selections instead of cursor movement
 860    editor_b.update_in(cx_b, |editor, window, cx| {
 861        editor.cancel(&editor::actions::Cancel, window, cx);
 862    });
 863    let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
 864        editor.change_selections(None, window, cx, |s| s.select_ranges([7..8]));
 865        editor.rename(&Rename, window, cx).unwrap()
 866    });
 867
 868    fake_language_server
 869        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
 870            assert_eq!(
 871                params.text_document.uri.as_str(),
 872                uri!("file:///dir/one.rs")
 873            );
 874            assert_eq!(params.position, lsp::Position::new(0, 8));
 875            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
 876                lsp::Position::new(0, 6),
 877                lsp::Position::new(0, 9),
 878            ))))
 879        })
 880        .next()
 881        .await
 882        .unwrap();
 883    prepare_rename.await.unwrap();
 884    editor_b.update(cx_b, |editor, cx| {
 885        use editor::ToOffset;
 886        let rename = editor.pending_rename().unwrap();
 887        let buffer = editor.buffer().read(cx).snapshot(cx);
 888        let lsp_rename_start = rename.range.start.to_offset(&buffer);
 889        let lsp_rename_end = rename.range.end.to_offset(&buffer);
 890        assert_eq!(lsp_rename_start..lsp_rename_end, 6..9);
 891        rename.editor.update(cx, |rename_editor, cx| {
 892            let rename_selection = rename_editor.selections.newest::<usize>(cx);
 893            assert_eq!(
 894                rename_selection.range(),
 895                1..2,
 896                "Rename that was triggered from a selection, should have the same selection range in the rename proposal"
 897            );
 898            rename_editor.buffer().update(cx, |rename_buffer, cx| {
 899                rename_buffer.edit([(0..lsp_rename_end - lsp_rename_start, "THREE")], None, cx);
 900            });
 901        });
 902    });
 903
 904    let confirm_rename = editor_b.update_in(cx_b, |editor, window, cx| {
 905        Editor::confirm_rename(editor, &ConfirmRename, window, cx).unwrap()
 906    });
 907    fake_language_server
 908        .set_request_handler::<lsp::request::Rename, _, _>(|params, _| async move {
 909            assert_eq!(
 910                params.text_document_position.text_document.uri.as_str(),
 911                uri!("file:///dir/one.rs")
 912            );
 913            assert_eq!(
 914                params.text_document_position.position,
 915                lsp::Position::new(0, 6)
 916            );
 917            assert_eq!(params.new_name, "THREE");
 918            Ok(Some(lsp::WorkspaceEdit {
 919                changes: Some(
 920                    [
 921                        (
 922                            lsp::Url::from_file_path(path!("/dir/one.rs")).unwrap(),
 923                            vec![lsp::TextEdit::new(
 924                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
 925                                "THREE".to_string(),
 926                            )],
 927                        ),
 928                        (
 929                            lsp::Url::from_file_path(path!("/dir/two.rs")).unwrap(),
 930                            vec![
 931                                lsp::TextEdit::new(
 932                                    lsp::Range::new(
 933                                        lsp::Position::new(0, 24),
 934                                        lsp::Position::new(0, 27),
 935                                    ),
 936                                    "THREE".to_string(),
 937                                ),
 938                                lsp::TextEdit::new(
 939                                    lsp::Range::new(
 940                                        lsp::Position::new(0, 35),
 941                                        lsp::Position::new(0, 38),
 942                                    ),
 943                                    "THREE".to_string(),
 944                                ),
 945                            ],
 946                        ),
 947                    ]
 948                    .into_iter()
 949                    .collect(),
 950                ),
 951                ..Default::default()
 952            }))
 953        })
 954        .next()
 955        .await
 956        .unwrap();
 957    confirm_rename.await.unwrap();
 958
 959    let rename_editor = workspace_b.update(cx_b, |workspace, cx| {
 960        workspace.active_item_as::<Editor>(cx).unwrap()
 961    });
 962
 963    rename_editor.update_in(cx_b, |editor, window, cx| {
 964        assert_eq!(
 965            editor.text(cx),
 966            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
 967        );
 968        editor.undo(&Undo, window, cx);
 969        assert_eq!(
 970            editor.text(cx),
 971            "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
 972        );
 973        editor.redo(&Redo, window, cx);
 974        assert_eq!(
 975            editor.text(cx),
 976            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
 977        );
 978    });
 979
 980    // Ensure temporary rename edits cannot be undone/redone.
 981    editor_b.update_in(cx_b, |editor, window, cx| {
 982        editor.undo(&Undo, window, cx);
 983        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
 984        editor.undo(&Undo, window, cx);
 985        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
 986        editor.redo(&Redo, window, cx);
 987        assert_eq!(editor.text(cx), "const THREE: usize = 1;");
 988    })
 989}
 990
 991#[gpui::test(iterations = 10)]
 992async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 993    let mut server = TestServer::start(cx_a.executor()).await;
 994    let executor = cx_a.executor();
 995    let client_a = server.create_client(cx_a, "user_a").await;
 996    let client_b = server.create_client(cx_b, "user_b").await;
 997    server
 998        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 999        .await;
1000    let active_call_a = cx_a.read(ActiveCall::global);
1001
1002    cx_b.update(editor::init);
1003
1004    client_a.language_registry().add(rust_lang());
1005    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1006        "Rust",
1007        FakeLspAdapter {
1008            name: "the-language-server",
1009            ..Default::default()
1010        },
1011    );
1012
1013    client_a
1014        .fs()
1015        .insert_tree(
1016            path!("/dir"),
1017            json!({
1018                "main.rs": "const ONE: usize = 1;",
1019            }),
1020        )
1021        .await;
1022    let (project_a, _) = client_a.build_local_project(path!("/dir"), cx_a).await;
1023
1024    let _buffer_a = project_a
1025        .update(cx_a, |p, cx| {
1026            p.open_local_buffer_with_lsp(path!("/dir/main.rs"), cx)
1027        })
1028        .await
1029        .unwrap();
1030
1031    let fake_language_server = fake_language_servers.next().await.unwrap();
1032    fake_language_server.start_progress("the-token").await;
1033
1034    executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
1035    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
1036        token: lsp::NumberOrString::String("the-token".to_string()),
1037        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
1038            lsp::WorkDoneProgressReport {
1039                message: Some("the-message".to_string()),
1040                ..Default::default()
1041            },
1042        )),
1043    });
1044    executor.run_until_parked();
1045
1046    project_a.read_with(cx_a, |project, cx| {
1047        let status = project.language_server_statuses(cx).next().unwrap().1;
1048        assert_eq!(status.name, "the-language-server");
1049        assert_eq!(status.pending_work.len(), 1);
1050        assert_eq!(
1051            status.pending_work["the-token"].message.as_ref().unwrap(),
1052            "the-message"
1053        );
1054    });
1055
1056    let project_id = active_call_a
1057        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1058        .await
1059        .unwrap();
1060    executor.run_until_parked();
1061    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1062
1063    project_b.read_with(cx_b, |project, cx| {
1064        let status = project.language_server_statuses(cx).next().unwrap().1;
1065        assert_eq!(status.name, "the-language-server");
1066    });
1067
1068    executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
1069    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
1070        token: lsp::NumberOrString::String("the-token".to_string()),
1071        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
1072            lsp::WorkDoneProgressReport {
1073                message: Some("the-message-2".to_string()),
1074                ..Default::default()
1075            },
1076        )),
1077    });
1078    executor.run_until_parked();
1079
1080    project_a.read_with(cx_a, |project, cx| {
1081        let status = project.language_server_statuses(cx).next().unwrap().1;
1082        assert_eq!(status.name, "the-language-server");
1083        assert_eq!(status.pending_work.len(), 1);
1084        assert_eq!(
1085            status.pending_work["the-token"].message.as_ref().unwrap(),
1086            "the-message-2"
1087        );
1088    });
1089
1090    project_b.read_with(cx_b, |project, cx| {
1091        let status = project.language_server_statuses(cx).next().unwrap().1;
1092        assert_eq!(status.name, "the-language-server");
1093        assert_eq!(status.pending_work.len(), 1);
1094        assert_eq!(
1095            status.pending_work["the-token"].message.as_ref().unwrap(),
1096            "the-message-2"
1097        );
1098    });
1099}
1100
1101#[gpui::test(iterations = 10)]
1102async fn test_share_project(
1103    cx_a: &mut TestAppContext,
1104    cx_b: &mut TestAppContext,
1105    cx_c: &mut TestAppContext,
1106) {
1107    let executor = cx_a.executor();
1108    let cx_b = cx_b.add_empty_window();
1109    let mut server = TestServer::start(executor.clone()).await;
1110    let client_a = server.create_client(cx_a, "user_a").await;
1111    let client_b = server.create_client(cx_b, "user_b").await;
1112    let client_c = server.create_client(cx_c, "user_c").await;
1113    server
1114        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1115        .await;
1116    let active_call_a = cx_a.read(ActiveCall::global);
1117    let active_call_b = cx_b.read(ActiveCall::global);
1118    let active_call_c = cx_c.read(ActiveCall::global);
1119
1120    client_a
1121        .fs()
1122        .insert_tree(
1123            path!("/a"),
1124            json!({
1125                ".gitignore": "ignored-dir",
1126                "a.txt": "a-contents",
1127                "b.txt": "b-contents",
1128                "ignored-dir": {
1129                    "c.txt": "",
1130                    "d.txt": "",
1131                }
1132            }),
1133        )
1134        .await;
1135
1136    // Invite client B to collaborate on a project
1137    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1138    active_call_a
1139        .update(cx_a, |call, cx| {
1140            call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
1141        })
1142        .await
1143        .unwrap();
1144
1145    // Join that project as client B
1146
1147    let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
1148    executor.run_until_parked();
1149    let call = incoming_call_b.borrow().clone().unwrap();
1150    assert_eq!(call.calling_user.github_login, "user_a");
1151    let initial_project = call.initial_project.unwrap();
1152    active_call_b
1153        .update(cx_b, |call, cx| call.accept_incoming(cx))
1154        .await
1155        .unwrap();
1156    let client_b_peer_id = client_b.peer_id().unwrap();
1157    let project_b = client_b.join_remote_project(initial_project.id, cx_b).await;
1158
1159    let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
1160
1161    executor.run_until_parked();
1162
1163    project_a.read_with(cx_a, |project, _| {
1164        let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
1165        assert_eq!(client_b_collaborator.replica_id, replica_id_b);
1166    });
1167
1168    project_b.read_with(cx_b, |project, cx| {
1169        let worktree = project.worktrees(cx).next().unwrap().read(cx);
1170        assert_eq!(
1171            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1172            [
1173                Path::new(".gitignore"),
1174                Path::new("a.txt"),
1175                Path::new("b.txt"),
1176                Path::new("ignored-dir"),
1177            ]
1178        );
1179    });
1180
1181    project_b
1182        .update(cx_b, |project, cx| {
1183            let worktree = project.worktrees(cx).next().unwrap();
1184            let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
1185            project.expand_entry(worktree_id, entry.id, cx).unwrap()
1186        })
1187        .await
1188        .unwrap();
1189
1190    project_b.read_with(cx_b, |project, cx| {
1191        let worktree = project.worktrees(cx).next().unwrap().read(cx);
1192        assert_eq!(
1193            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1194            [
1195                Path::new(".gitignore"),
1196                Path::new("a.txt"),
1197                Path::new("b.txt"),
1198                Path::new("ignored-dir"),
1199                Path::new("ignored-dir/c.txt"),
1200                Path::new("ignored-dir/d.txt"),
1201            ]
1202        );
1203    });
1204
1205    // Open the same file as client B and client A.
1206    let buffer_b = project_b
1207        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1208        .await
1209        .unwrap();
1210
1211    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
1212
1213    project_a.read_with(cx_a, |project, cx| {
1214        assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
1215    });
1216    let buffer_a = project_a
1217        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1218        .await
1219        .unwrap();
1220
1221    let editor_b =
1222        cx_b.new_window_entity(|window, cx| Editor::for_buffer(buffer_b, None, window, cx));
1223
1224    // Client A sees client B's selection
1225    executor.run_until_parked();
1226
1227    buffer_a.read_with(cx_a, |buffer, _| {
1228        buffer
1229            .snapshot()
1230            .selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
1231            .count()
1232            == 1
1233    });
1234
1235    // Edit the buffer as client B and see that edit as client A.
1236    editor_b.update_in(cx_b, |editor, window, cx| {
1237        editor.handle_input("ok, ", window, cx)
1238    });
1239    executor.run_until_parked();
1240
1241    buffer_a.read_with(cx_a, |buffer, _| {
1242        assert_eq!(buffer.text(), "ok, b-contents")
1243    });
1244
1245    // Client B can invite client C on a project shared by client A.
1246    active_call_b
1247        .update(cx_b, |call, cx| {
1248            call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
1249        })
1250        .await
1251        .unwrap();
1252
1253    let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
1254    executor.run_until_parked();
1255    let call = incoming_call_c.borrow().clone().unwrap();
1256    assert_eq!(call.calling_user.github_login, "user_b");
1257    let initial_project = call.initial_project.unwrap();
1258    active_call_c
1259        .update(cx_c, |call, cx| call.accept_incoming(cx))
1260        .await
1261        .unwrap();
1262    let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
1263
1264    // Client B closes the editor, and client A sees client B's selections removed.
1265    cx_b.update(move |_, _| drop(editor_b));
1266    executor.run_until_parked();
1267
1268    buffer_a.read_with(cx_a, |buffer, _| {
1269        buffer
1270            .snapshot()
1271            .selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
1272            .count()
1273            == 0
1274    });
1275}
1276
1277#[gpui::test(iterations = 10)]
1278async fn test_on_input_format_from_host_to_guest(
1279    cx_a: &mut TestAppContext,
1280    cx_b: &mut TestAppContext,
1281) {
1282    let mut server = TestServer::start(cx_a.executor()).await;
1283    let executor = cx_a.executor();
1284    let client_a = server.create_client(cx_a, "user_a").await;
1285    let client_b = server.create_client(cx_b, "user_b").await;
1286    server
1287        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1288        .await;
1289    let active_call_a = cx_a.read(ActiveCall::global);
1290
1291    client_a.language_registry().add(rust_lang());
1292    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1293        "Rust",
1294        FakeLspAdapter {
1295            capabilities: lsp::ServerCapabilities {
1296                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1297                    first_trigger_character: ":".to_string(),
1298                    more_trigger_character: Some(vec![">".to_string()]),
1299                }),
1300                ..Default::default()
1301            },
1302            ..Default::default()
1303        },
1304    );
1305
1306    client_a
1307        .fs()
1308        .insert_tree(
1309            path!("/a"),
1310            json!({
1311                "main.rs": "fn main() { a }",
1312                "other.rs": "// Test file",
1313            }),
1314        )
1315        .await;
1316    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1317    let project_id = active_call_a
1318        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1319        .await
1320        .unwrap();
1321    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1322
1323    // Open a file in an editor as the host.
1324    let buffer_a = project_a
1325        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1326        .await
1327        .unwrap();
1328    let cx_a = cx_a.add_empty_window();
1329    let editor_a = cx_a.new_window_entity(|window, cx| {
1330        Editor::for_buffer(buffer_a, Some(project_a.clone()), window, cx)
1331    });
1332
1333    let fake_language_server = fake_language_servers.next().await.unwrap();
1334    executor.run_until_parked();
1335
1336    // Receive an OnTypeFormatting request as the host's language server.
1337    // Return some formatting from the host's language server.
1338    fake_language_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
1339        |params, _| async move {
1340            assert_eq!(
1341                params.text_document_position.text_document.uri,
1342                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1343            );
1344            assert_eq!(
1345                params.text_document_position.position,
1346                lsp::Position::new(0, 14),
1347            );
1348
1349            Ok(Some(vec![lsp::TextEdit {
1350                new_text: "~<".to_string(),
1351                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1352            }]))
1353        },
1354    );
1355
1356    // Open the buffer on the guest and see that the formatting worked
1357    let buffer_b = project_b
1358        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1359        .await
1360        .unwrap();
1361
1362    // Type a on type formatting trigger character as the guest.
1363    cx_a.focus(&editor_a);
1364    editor_a.update_in(cx_a, |editor, window, cx| {
1365        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1366        editor.handle_input(">", window, cx);
1367    });
1368
1369    executor.run_until_parked();
1370
1371    buffer_b.read_with(cx_b, |buffer, _| {
1372        assert_eq!(buffer.text(), "fn main() { a>~< }")
1373    });
1374
1375    // Undo should remove LSP edits first
1376    editor_a.update_in(cx_a, |editor, window, cx| {
1377        assert_eq!(editor.text(cx), "fn main() { a>~< }");
1378        editor.undo(&Undo, window, cx);
1379        assert_eq!(editor.text(cx), "fn main() { a> }");
1380    });
1381    executor.run_until_parked();
1382
1383    buffer_b.read_with(cx_b, |buffer, _| {
1384        assert_eq!(buffer.text(), "fn main() { a> }")
1385    });
1386
1387    editor_a.update_in(cx_a, |editor, window, cx| {
1388        assert_eq!(editor.text(cx), "fn main() { a> }");
1389        editor.undo(&Undo, window, cx);
1390        assert_eq!(editor.text(cx), "fn main() { a }");
1391    });
1392    executor.run_until_parked();
1393
1394    buffer_b.read_with(cx_b, |buffer, _| {
1395        assert_eq!(buffer.text(), "fn main() { a }")
1396    });
1397}
1398
1399#[gpui::test(iterations = 10)]
1400async fn test_on_input_format_from_guest_to_host(
1401    cx_a: &mut TestAppContext,
1402    cx_b: &mut TestAppContext,
1403) {
1404    let mut server = TestServer::start(cx_a.executor()).await;
1405    let executor = cx_a.executor();
1406    let client_a = server.create_client(cx_a, "user_a").await;
1407    let client_b = server.create_client(cx_b, "user_b").await;
1408    server
1409        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1410        .await;
1411    let active_call_a = cx_a.read(ActiveCall::global);
1412
1413    client_a.language_registry().add(rust_lang());
1414    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1415        "Rust",
1416        FakeLspAdapter {
1417            capabilities: lsp::ServerCapabilities {
1418                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1419                    first_trigger_character: ":".to_string(),
1420                    more_trigger_character: Some(vec![">".to_string()]),
1421                }),
1422                ..Default::default()
1423            },
1424            ..Default::default()
1425        },
1426    );
1427
1428    client_a
1429        .fs()
1430        .insert_tree(
1431            path!("/a"),
1432            json!({
1433                "main.rs": "fn main() { a }",
1434                "other.rs": "// Test file",
1435            }),
1436        )
1437        .await;
1438    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1439    let project_id = active_call_a
1440        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1441        .await
1442        .unwrap();
1443    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1444
1445    // Open a file in an editor as the guest.
1446    let buffer_b = project_b
1447        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1448        .await
1449        .unwrap();
1450    let cx_b = cx_b.add_empty_window();
1451    let editor_b = cx_b.new_window_entity(|window, cx| {
1452        Editor::for_buffer(buffer_b, Some(project_b.clone()), window, cx)
1453    });
1454
1455    let fake_language_server = fake_language_servers.next().await.unwrap();
1456    executor.run_until_parked();
1457
1458    // Type a on type formatting trigger character as the guest.
1459    cx_b.focus(&editor_b);
1460    editor_b.update_in(cx_b, |editor, window, cx| {
1461        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1462        editor.handle_input(":", window, cx);
1463    });
1464
1465    // Receive an OnTypeFormatting request as the host's language server.
1466    // Return some formatting from the host's language server.
1467    executor.start_waiting();
1468    fake_language_server
1469        .set_request_handler::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
1470            assert_eq!(
1471                params.text_document_position.text_document.uri,
1472                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1473            );
1474            assert_eq!(
1475                params.text_document_position.position,
1476                lsp::Position::new(0, 14),
1477            );
1478
1479            Ok(Some(vec![lsp::TextEdit {
1480                new_text: "~:".to_string(),
1481                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1482            }]))
1483        })
1484        .next()
1485        .await
1486        .unwrap();
1487    executor.finish_waiting();
1488
1489    // Open the buffer on the host and see that the formatting worked
1490    let buffer_a = project_a
1491        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1492        .await
1493        .unwrap();
1494    executor.run_until_parked();
1495
1496    buffer_a.read_with(cx_a, |buffer, _| {
1497        assert_eq!(buffer.text(), "fn main() { a:~: }")
1498    });
1499
1500    // Undo should remove LSP edits first
1501    editor_b.update_in(cx_b, |editor, window, cx| {
1502        assert_eq!(editor.text(cx), "fn main() { a:~: }");
1503        editor.undo(&Undo, window, cx);
1504        assert_eq!(editor.text(cx), "fn main() { a: }");
1505    });
1506    executor.run_until_parked();
1507
1508    buffer_a.read_with(cx_a, |buffer, _| {
1509        assert_eq!(buffer.text(), "fn main() { a: }")
1510    });
1511
1512    editor_b.update_in(cx_b, |editor, window, cx| {
1513        assert_eq!(editor.text(cx), "fn main() { a: }");
1514        editor.undo(&Undo, window, cx);
1515        assert_eq!(editor.text(cx), "fn main() { a }");
1516    });
1517    executor.run_until_parked();
1518
1519    buffer_a.read_with(cx_a, |buffer, _| {
1520        assert_eq!(buffer.text(), "fn main() { a }")
1521    });
1522}
1523
1524#[gpui::test(iterations = 10)]
1525async fn test_mutual_editor_inlay_hint_cache_update(
1526    cx_a: &mut TestAppContext,
1527    cx_b: &mut TestAppContext,
1528) {
1529    let mut server = TestServer::start(cx_a.executor()).await;
1530    let executor = cx_a.executor();
1531    let client_a = server.create_client(cx_a, "user_a").await;
1532    let client_b = server.create_client(cx_b, "user_b").await;
1533    server
1534        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1535        .await;
1536    let active_call_a = cx_a.read(ActiveCall::global);
1537    let active_call_b = cx_b.read(ActiveCall::global);
1538
1539    cx_a.update(editor::init);
1540    cx_b.update(editor::init);
1541
1542    cx_a.update(|cx| {
1543        SettingsStore::update_global(cx, |store, cx| {
1544            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1545                settings.defaults.inlay_hints = Some(InlayHintSettings {
1546                    enabled: true,
1547                    edit_debounce_ms: 0,
1548                    scroll_debounce_ms: 0,
1549                    show_type_hints: true,
1550                    show_parameter_hints: false,
1551                    show_other_hints: true,
1552                    show_background: false,
1553                    toggle_on_modifiers_press: None,
1554                })
1555            });
1556        });
1557    });
1558    cx_b.update(|cx| {
1559        SettingsStore::update_global(cx, |store, cx| {
1560            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1561                settings.defaults.inlay_hints = Some(InlayHintSettings {
1562                    enabled: true,
1563                    edit_debounce_ms: 0,
1564                    scroll_debounce_ms: 0,
1565                    show_type_hints: true,
1566                    show_parameter_hints: false,
1567                    show_other_hints: true,
1568                    show_background: false,
1569                    toggle_on_modifiers_press: None,
1570                })
1571            });
1572        });
1573    });
1574
1575    client_a.language_registry().add(rust_lang());
1576    client_b.language_registry().add(rust_lang());
1577    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1578        "Rust",
1579        FakeLspAdapter {
1580            capabilities: lsp::ServerCapabilities {
1581                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1582                ..Default::default()
1583            },
1584            ..Default::default()
1585        },
1586    );
1587
1588    // Client A opens a project.
1589    client_a
1590        .fs()
1591        .insert_tree(
1592            path!("/a"),
1593            json!({
1594                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1595                "other.rs": "// Test file",
1596            }),
1597        )
1598        .await;
1599    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1600    active_call_a
1601        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1602        .await
1603        .unwrap();
1604    let project_id = active_call_a
1605        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1606        .await
1607        .unwrap();
1608
1609    // Client B joins the project
1610    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1611    active_call_b
1612        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1613        .await
1614        .unwrap();
1615
1616    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
1617    executor.start_waiting();
1618
1619    // The host opens a rust file.
1620    let _buffer_a = project_a
1621        .update(cx_a, |project, cx| {
1622            project.open_local_buffer(path!("/a/main.rs"), cx)
1623        })
1624        .await
1625        .unwrap();
1626    let editor_a = workspace_a
1627        .update_in(cx_a, |workspace, window, cx| {
1628            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1629        })
1630        .await
1631        .unwrap()
1632        .downcast::<Editor>()
1633        .unwrap();
1634
1635    let fake_language_server = fake_language_servers.next().await.unwrap();
1636
1637    // Set up the language server to return an additional inlay hint on each request.
1638    let edits_made = Arc::new(AtomicUsize::new(0));
1639    let closure_edits_made = Arc::clone(&edits_made);
1640    fake_language_server
1641        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1642            let task_edits_made = Arc::clone(&closure_edits_made);
1643            async move {
1644                assert_eq!(
1645                    params.text_document.uri,
1646                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1647                );
1648                let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
1649                Ok(Some(vec![lsp::InlayHint {
1650                    position: lsp::Position::new(0, edits_made as u32),
1651                    label: lsp::InlayHintLabel::String(edits_made.to_string()),
1652                    kind: None,
1653                    text_edits: None,
1654                    tooltip: None,
1655                    padding_left: None,
1656                    padding_right: None,
1657                    data: None,
1658                }]))
1659            }
1660        })
1661        .next()
1662        .await
1663        .unwrap();
1664
1665    executor.run_until_parked();
1666
1667    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
1668    editor_a.update(cx_a, |editor, _| {
1669        assert_eq!(
1670            vec![initial_edit.to_string()],
1671            extract_hint_labels(editor),
1672            "Host should get its first hints when opens an editor"
1673        );
1674    });
1675    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1676    let editor_b = workspace_b
1677        .update_in(cx_b, |workspace, window, cx| {
1678            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1679        })
1680        .await
1681        .unwrap()
1682        .downcast::<Editor>()
1683        .unwrap();
1684
1685    executor.run_until_parked();
1686    editor_b.update(cx_b, |editor, _| {
1687        assert_eq!(
1688            vec![initial_edit.to_string()],
1689            extract_hint_labels(editor),
1690            "Client should get its first hints when opens an editor"
1691        );
1692    });
1693
1694    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1695    editor_b.update_in(cx_b, |editor, window, cx| {
1696        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
1697        editor.handle_input(":", window, cx);
1698    });
1699    cx_b.focus(&editor_b);
1700
1701    executor.run_until_parked();
1702    editor_a.update(cx_a, |editor, _| {
1703        assert_eq!(
1704            vec![after_client_edit.to_string()],
1705            extract_hint_labels(editor),
1706        );
1707    });
1708    editor_b.update(cx_b, |editor, _| {
1709        assert_eq!(
1710            vec![after_client_edit.to_string()],
1711            extract_hint_labels(editor),
1712        );
1713    });
1714
1715    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1716    editor_a.update_in(cx_a, |editor, window, cx| {
1717        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1718        editor.handle_input("a change to increment both buffers' versions", window, cx);
1719    });
1720    cx_a.focus(&editor_a);
1721
1722    executor.run_until_parked();
1723    editor_a.update(cx_a, |editor, _| {
1724        assert_eq!(
1725            vec![after_host_edit.to_string()],
1726            extract_hint_labels(editor),
1727        );
1728    });
1729    editor_b.update(cx_b, |editor, _| {
1730        assert_eq!(
1731            vec![after_host_edit.to_string()],
1732            extract_hint_labels(editor),
1733        );
1734    });
1735
1736    let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1737    fake_language_server
1738        .request::<lsp::request::InlayHintRefreshRequest>(())
1739        .await
1740        .expect("inlay refresh request failed");
1741
1742    executor.run_until_parked();
1743    editor_a.update(cx_a, |editor, _| {
1744        assert_eq!(
1745            vec![after_special_edit_for_refresh.to_string()],
1746            extract_hint_labels(editor),
1747            "Host should react to /refresh LSP request"
1748        );
1749    });
1750    editor_b.update(cx_b, |editor, _| {
1751        assert_eq!(
1752            vec![after_special_edit_for_refresh.to_string()],
1753            extract_hint_labels(editor),
1754            "Guest should get a /refresh LSP request propagated by host"
1755        );
1756    });
1757}
1758
1759#[gpui::test(iterations = 10)]
1760async fn test_inlay_hint_refresh_is_forwarded(
1761    cx_a: &mut TestAppContext,
1762    cx_b: &mut TestAppContext,
1763) {
1764    let mut server = TestServer::start(cx_a.executor()).await;
1765    let executor = cx_a.executor();
1766    let client_a = server.create_client(cx_a, "user_a").await;
1767    let client_b = server.create_client(cx_b, "user_b").await;
1768    server
1769        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1770        .await;
1771    let active_call_a = cx_a.read(ActiveCall::global);
1772    let active_call_b = cx_b.read(ActiveCall::global);
1773
1774    cx_a.update(editor::init);
1775    cx_b.update(editor::init);
1776
1777    cx_a.update(|cx| {
1778        SettingsStore::update_global(cx, |store, cx| {
1779            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1780                settings.defaults.inlay_hints = Some(InlayHintSettings {
1781                    enabled: false,
1782                    edit_debounce_ms: 0,
1783                    scroll_debounce_ms: 0,
1784                    show_type_hints: false,
1785                    show_parameter_hints: false,
1786                    show_other_hints: false,
1787                    show_background: false,
1788                    toggle_on_modifiers_press: None,
1789                })
1790            });
1791        });
1792    });
1793    cx_b.update(|cx| {
1794        SettingsStore::update_global(cx, |store, cx| {
1795            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1796                settings.defaults.inlay_hints = Some(InlayHintSettings {
1797                    enabled: true,
1798                    edit_debounce_ms: 0,
1799                    scroll_debounce_ms: 0,
1800                    show_type_hints: true,
1801                    show_parameter_hints: true,
1802                    show_other_hints: true,
1803                    show_background: false,
1804                    toggle_on_modifiers_press: None,
1805                })
1806            });
1807        });
1808    });
1809
1810    client_a.language_registry().add(rust_lang());
1811    client_b.language_registry().add(rust_lang());
1812    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1813        "Rust",
1814        FakeLspAdapter {
1815            capabilities: lsp::ServerCapabilities {
1816                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1817                ..Default::default()
1818            },
1819            ..Default::default()
1820        },
1821    );
1822
1823    client_a
1824        .fs()
1825        .insert_tree(
1826            path!("/a"),
1827            json!({
1828                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1829                "other.rs": "// Test file",
1830            }),
1831        )
1832        .await;
1833    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1834    active_call_a
1835        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1836        .await
1837        .unwrap();
1838    let project_id = active_call_a
1839        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1840        .await
1841        .unwrap();
1842
1843    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1844    active_call_b
1845        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1846        .await
1847        .unwrap();
1848
1849    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
1850    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1851
1852    cx_a.background_executor.start_waiting();
1853
1854    let editor_a = workspace_a
1855        .update_in(cx_a, |workspace, window, cx| {
1856            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1857        })
1858        .await
1859        .unwrap()
1860        .downcast::<Editor>()
1861        .unwrap();
1862
1863    let editor_b = workspace_b
1864        .update_in(cx_b, |workspace, window, cx| {
1865            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1866        })
1867        .await
1868        .unwrap()
1869        .downcast::<Editor>()
1870        .unwrap();
1871
1872    let other_hints = Arc::new(AtomicBool::new(false));
1873    let fake_language_server = fake_language_servers.next().await.unwrap();
1874    let closure_other_hints = Arc::clone(&other_hints);
1875    fake_language_server
1876        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1877            let task_other_hints = Arc::clone(&closure_other_hints);
1878            async move {
1879                assert_eq!(
1880                    params.text_document.uri,
1881                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1882                );
1883                let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
1884                let character = if other_hints { 0 } else { 2 };
1885                let label = if other_hints {
1886                    "other hint"
1887                } else {
1888                    "initial hint"
1889                };
1890                Ok(Some(vec![lsp::InlayHint {
1891                    position: lsp::Position::new(0, character),
1892                    label: lsp::InlayHintLabel::String(label.to_string()),
1893                    kind: None,
1894                    text_edits: None,
1895                    tooltip: None,
1896                    padding_left: None,
1897                    padding_right: None,
1898                    data: None,
1899                }]))
1900            }
1901        })
1902        .next()
1903        .await
1904        .unwrap();
1905    executor.finish_waiting();
1906
1907    executor.run_until_parked();
1908    editor_a.update(cx_a, |editor, _| {
1909        assert!(
1910            extract_hint_labels(editor).is_empty(),
1911            "Host should get no hints due to them turned off"
1912        );
1913    });
1914
1915    executor.run_until_parked();
1916    editor_b.update(cx_b, |editor, _| {
1917        assert_eq!(
1918            vec!["initial hint".to_string()],
1919            extract_hint_labels(editor),
1920            "Client should get its first hints when opens an editor"
1921        );
1922    });
1923
1924    other_hints.fetch_or(true, atomic::Ordering::Release);
1925    fake_language_server
1926        .request::<lsp::request::InlayHintRefreshRequest>(())
1927        .await
1928        .expect("inlay refresh request failed");
1929    executor.run_until_parked();
1930    editor_a.update(cx_a, |editor, _| {
1931        assert!(
1932            extract_hint_labels(editor).is_empty(),
1933            "Host should get no hints due to them turned off, even after the /refresh"
1934        );
1935    });
1936
1937    executor.run_until_parked();
1938    editor_b.update(cx_b, |editor, _| {
1939        assert_eq!(
1940            vec!["other hint".to_string()],
1941            extract_hint_labels(editor),
1942            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
1943        );
1944    });
1945}
1946
1947#[gpui::test(iterations = 10)]
1948async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1949    let mut server = TestServer::start(cx_a.executor()).await;
1950    let client_a = server.create_client(cx_a, "user_a").await;
1951    let client_b = server.create_client(cx_b, "user_b").await;
1952    server
1953        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1954        .await;
1955    let active_call_a = cx_a.read(ActiveCall::global);
1956
1957    cx_a.update(editor::init);
1958    cx_b.update(editor::init);
1959    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
1960    let inline_blame_off_settings = Some(InlineBlameSettings {
1961        enabled: false,
1962        delay_ms: None,
1963        min_column: None,
1964        show_commit_summary: false,
1965    });
1966    cx_a.update(|cx| {
1967        SettingsStore::update_global(cx, |store, cx| {
1968            store.update_user_settings::<ProjectSettings>(cx, |settings| {
1969                settings.git.inline_blame = inline_blame_off_settings;
1970            });
1971        });
1972    });
1973    cx_b.update(|cx| {
1974        SettingsStore::update_global(cx, |store, cx| {
1975            store.update_user_settings::<ProjectSettings>(cx, |settings| {
1976                settings.git.inline_blame = inline_blame_off_settings;
1977            });
1978        });
1979    });
1980
1981    client_a
1982        .fs()
1983        .insert_tree(
1984            path!("/my-repo"),
1985            json!({
1986                ".git": {},
1987                "file.txt": "line1\nline2\nline3\nline\n",
1988            }),
1989        )
1990        .await;
1991
1992    let blame = git::blame::Blame {
1993        entries: vec![
1994            blame_entry("1b1b1b", 0..1),
1995            blame_entry("0d0d0d", 1..2),
1996            blame_entry("3a3a3a", 2..3),
1997            blame_entry("4c4c4c", 3..4),
1998        ],
1999        messages: [
2000            ("1b1b1b", "message for idx-0"),
2001            ("0d0d0d", "message for idx-1"),
2002            ("3a3a3a", "message for idx-2"),
2003            ("4c4c4c", "message for idx-3"),
2004        ]
2005        .into_iter()
2006        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
2007        .collect(),
2008        remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
2009    };
2010    client_a.fs().set_blame_for_repo(
2011        Path::new(path!("/my-repo/.git")),
2012        vec![("file.txt".into(), blame)],
2013    );
2014
2015    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
2016    let project_id = active_call_a
2017        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2018        .await
2019        .unwrap();
2020
2021    // Create editor_a
2022    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2023    let editor_a = workspace_a
2024        .update_in(cx_a, |workspace, window, cx| {
2025            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2026        })
2027        .await
2028        .unwrap()
2029        .downcast::<Editor>()
2030        .unwrap();
2031
2032    // Join the project as client B.
2033    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2034    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2035    let editor_b = workspace_b
2036        .update_in(cx_b, |workspace, window, cx| {
2037            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2038        })
2039        .await
2040        .unwrap()
2041        .downcast::<Editor>()
2042        .unwrap();
2043    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
2044        editor_b
2045            .buffer()
2046            .read(cx)
2047            .as_singleton()
2048            .unwrap()
2049            .read(cx)
2050            .remote_id()
2051    });
2052
2053    // client_b now requests git blame for the open buffer
2054    editor_b.update_in(cx_b, |editor_b, window, cx| {
2055        assert!(editor_b.blame().is_none());
2056        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
2057    });
2058
2059    cx_a.executor().run_until_parked();
2060    cx_b.executor().run_until_parked();
2061
2062    editor_b.update(cx_b, |editor_b, cx| {
2063        let blame = editor_b.blame().expect("editor_b should have blame now");
2064        let entries = blame.update(cx, |blame, cx| {
2065            blame
2066                .blame_for_rows(
2067                    &(0..4)
2068                        .map(|row| RowInfo {
2069                            buffer_row: Some(row),
2070                            buffer_id: Some(buffer_id_b),
2071                            ..Default::default()
2072                        })
2073                        .collect::<Vec<_>>(),
2074                    cx,
2075                )
2076                .collect::<Vec<_>>()
2077        });
2078
2079        assert_eq!(
2080            entries,
2081            vec![
2082                Some(blame_entry("1b1b1b", 0..1)),
2083                Some(blame_entry("0d0d0d", 1..2)),
2084                Some(blame_entry("3a3a3a", 2..3)),
2085                Some(blame_entry("4c4c4c", 3..4)),
2086            ]
2087        );
2088
2089        blame.update(cx, |blame, _| {
2090            for (idx, entry) in entries.iter().flatten().enumerate() {
2091                let details = blame.details_for_entry(entry).unwrap();
2092                assert_eq!(details.message, format!("message for idx-{}", idx));
2093                assert_eq!(
2094                    details.permalink.unwrap().to_string(),
2095                    format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
2096                );
2097            }
2098        });
2099    });
2100
2101    // editor_b updates the file, which gets sent to client_a, which updates git blame,
2102    // which gets back to client_b.
2103    editor_b.update_in(cx_b, |editor_b, _, cx| {
2104        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
2105    });
2106
2107    cx_a.executor().run_until_parked();
2108    cx_b.executor().run_until_parked();
2109
2110    editor_b.update(cx_b, |editor_b, cx| {
2111        let blame = editor_b.blame().expect("editor_b should have blame now");
2112        let entries = blame.update(cx, |blame, cx| {
2113            blame
2114                .blame_for_rows(
2115                    &(0..4)
2116                        .map(|row| RowInfo {
2117                            buffer_row: Some(row),
2118                            buffer_id: Some(buffer_id_b),
2119                            ..Default::default()
2120                        })
2121                        .collect::<Vec<_>>(),
2122                    cx,
2123                )
2124                .collect::<Vec<_>>()
2125        });
2126
2127        assert_eq!(
2128            entries,
2129            vec![
2130                None,
2131                Some(blame_entry("0d0d0d", 1..2)),
2132                Some(blame_entry("3a3a3a", 2..3)),
2133                Some(blame_entry("4c4c4c", 3..4)),
2134            ]
2135        );
2136    });
2137
2138    // Now editor_a also updates the file
2139    editor_a.update_in(cx_a, |editor_a, _, cx| {
2140        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
2141    });
2142
2143    cx_a.executor().run_until_parked();
2144    cx_b.executor().run_until_parked();
2145
2146    editor_b.update(cx_b, |editor_b, cx| {
2147        let blame = editor_b.blame().expect("editor_b should have blame now");
2148        let entries = blame.update(cx, |blame, cx| {
2149            blame
2150                .blame_for_rows(
2151                    &(0..4)
2152                        .map(|row| RowInfo {
2153                            buffer_row: Some(row),
2154                            buffer_id: Some(buffer_id_b),
2155                            ..Default::default()
2156                        })
2157                        .collect::<Vec<_>>(),
2158                    cx,
2159                )
2160                .collect::<Vec<_>>()
2161        });
2162
2163        assert_eq!(
2164            entries,
2165            vec![
2166                None,
2167                None,
2168                Some(blame_entry("3a3a3a", 2..3)),
2169                Some(blame_entry("4c4c4c", 3..4)),
2170            ]
2171        );
2172    });
2173}
2174
2175#[gpui::test(iterations = 30)]
2176async fn test_collaborating_with_editorconfig(
2177    cx_a: &mut TestAppContext,
2178    cx_b: &mut TestAppContext,
2179) {
2180    let mut server = TestServer::start(cx_a.executor()).await;
2181    let client_a = server.create_client(cx_a, "user_a").await;
2182    let client_b = server.create_client(cx_b, "user_b").await;
2183    server
2184        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2185        .await;
2186    let active_call_a = cx_a.read(ActiveCall::global);
2187
2188    cx_b.update(editor::init);
2189
2190    // Set up a fake language server.
2191    client_a.language_registry().add(rust_lang());
2192    client_a
2193        .fs()
2194        .insert_tree(
2195            path!("/a"),
2196            json!({
2197                "src": {
2198                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
2199                    "other_mod": {
2200                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
2201                        ".editorconfig": "",
2202                    },
2203                },
2204                ".editorconfig": "[*]\ntab_width = 2\n",
2205            }),
2206        )
2207        .await;
2208    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2209    let project_id = active_call_a
2210        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2211        .await
2212        .unwrap();
2213    let main_buffer_a = project_a
2214        .update(cx_a, |p, cx| {
2215            p.open_buffer((worktree_id, "src/main.rs"), cx)
2216        })
2217        .await
2218        .unwrap();
2219    let other_buffer_a = project_a
2220        .update(cx_a, |p, cx| {
2221            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
2222        })
2223        .await
2224        .unwrap();
2225    let cx_a = cx_a.add_empty_window();
2226    let main_editor_a = cx_a.new_window_entity(|window, cx| {
2227        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
2228    });
2229    let other_editor_a = cx_a.new_window_entity(|window, cx| {
2230        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
2231    });
2232    let mut main_editor_cx_a = EditorTestContext {
2233        cx: cx_a.clone(),
2234        window: cx_a.window_handle(),
2235        editor: main_editor_a,
2236        assertion_cx: AssertionContextManager::new(),
2237    };
2238    let mut other_editor_cx_a = EditorTestContext {
2239        cx: cx_a.clone(),
2240        window: cx_a.window_handle(),
2241        editor: other_editor_a,
2242        assertion_cx: AssertionContextManager::new(),
2243    };
2244
2245    // Join the project as client B.
2246    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2247    let main_buffer_b = project_b
2248        .update(cx_b, |p, cx| {
2249            p.open_buffer((worktree_id, "src/main.rs"), cx)
2250        })
2251        .await
2252        .unwrap();
2253    let other_buffer_b = project_b
2254        .update(cx_b, |p, cx| {
2255            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
2256        })
2257        .await
2258        .unwrap();
2259    let cx_b = cx_b.add_empty_window();
2260    let main_editor_b = cx_b.new_window_entity(|window, cx| {
2261        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
2262    });
2263    let other_editor_b = cx_b.new_window_entity(|window, cx| {
2264        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
2265    });
2266    let mut main_editor_cx_b = EditorTestContext {
2267        cx: cx_b.clone(),
2268        window: cx_b.window_handle(),
2269        editor: main_editor_b,
2270        assertion_cx: AssertionContextManager::new(),
2271    };
2272    let mut other_editor_cx_b = EditorTestContext {
2273        cx: cx_b.clone(),
2274        window: cx_b.window_handle(),
2275        editor: other_editor_b,
2276        assertion_cx: AssertionContextManager::new(),
2277    };
2278
2279    let initial_main = indoc! {"
2280ˇmod other;
2281fn main() { let foo = other::foo(); }"};
2282    let initial_other = indoc! {"
2283ˇpub fn foo() -> usize {
2284    4
2285}"};
2286
2287    let first_tabbed_main = indoc! {"
2288  ˇmod other;
2289fn main() { let foo = other::foo(); }"};
2290    tab_undo_assert(
2291        &mut main_editor_cx_a,
2292        &mut main_editor_cx_b,
2293        initial_main,
2294        first_tabbed_main,
2295        true,
2296    );
2297    tab_undo_assert(
2298        &mut main_editor_cx_a,
2299        &mut main_editor_cx_b,
2300        initial_main,
2301        first_tabbed_main,
2302        false,
2303    );
2304
2305    let first_tabbed_other = indoc! {"
2306  ˇpub fn foo() -> usize {
2307    4
2308}"};
2309    tab_undo_assert(
2310        &mut other_editor_cx_a,
2311        &mut other_editor_cx_b,
2312        initial_other,
2313        first_tabbed_other,
2314        true,
2315    );
2316    tab_undo_assert(
2317        &mut other_editor_cx_a,
2318        &mut other_editor_cx_b,
2319        initial_other,
2320        first_tabbed_other,
2321        false,
2322    );
2323
2324    client_a
2325        .fs()
2326        .atomic_write(
2327            PathBuf::from(path!("/a/src/.editorconfig")),
2328            "[*]\ntab_width = 3\n".to_owned(),
2329        )
2330        .await
2331        .unwrap();
2332    cx_a.run_until_parked();
2333    cx_b.run_until_parked();
2334
2335    let second_tabbed_main = indoc! {"
2336   ˇmod other;
2337fn main() { let foo = other::foo(); }"};
2338    tab_undo_assert(
2339        &mut main_editor_cx_a,
2340        &mut main_editor_cx_b,
2341        initial_main,
2342        second_tabbed_main,
2343        true,
2344    );
2345    tab_undo_assert(
2346        &mut main_editor_cx_a,
2347        &mut main_editor_cx_b,
2348        initial_main,
2349        second_tabbed_main,
2350        false,
2351    );
2352
2353    let second_tabbed_other = indoc! {"
2354   ˇpub fn foo() -> usize {
2355    4
2356}"};
2357    tab_undo_assert(
2358        &mut other_editor_cx_a,
2359        &mut other_editor_cx_b,
2360        initial_other,
2361        second_tabbed_other,
2362        true,
2363    );
2364    tab_undo_assert(
2365        &mut other_editor_cx_a,
2366        &mut other_editor_cx_b,
2367        initial_other,
2368        second_tabbed_other,
2369        false,
2370    );
2371
2372    let editorconfig_buffer_b = project_b
2373        .update(cx_b, |p, cx| {
2374            p.open_buffer((worktree_id, "src/other_mod/.editorconfig"), cx)
2375        })
2376        .await
2377        .unwrap();
2378    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
2379        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
2380    });
2381    project_b
2382        .update(cx_b, |project, cx| {
2383            project.save_buffer(editorconfig_buffer_b.clone(), cx)
2384        })
2385        .await
2386        .unwrap();
2387    cx_a.run_until_parked();
2388    cx_b.run_until_parked();
2389
2390    tab_undo_assert(
2391        &mut main_editor_cx_a,
2392        &mut main_editor_cx_b,
2393        initial_main,
2394        second_tabbed_main,
2395        true,
2396    );
2397    tab_undo_assert(
2398        &mut main_editor_cx_a,
2399        &mut main_editor_cx_b,
2400        initial_main,
2401        second_tabbed_main,
2402        false,
2403    );
2404
2405    let third_tabbed_other = indoc! {"
2406      ˇpub fn foo() -> usize {
2407    4
2408}"};
2409    tab_undo_assert(
2410        &mut other_editor_cx_a,
2411        &mut other_editor_cx_b,
2412        initial_other,
2413        third_tabbed_other,
2414        true,
2415    );
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        false,
2423    );
2424}
2425
2426#[gpui::test]
2427async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2428    let executor = cx_a.executor();
2429    let mut server = TestServer::start(executor.clone()).await;
2430    let client_a = server.create_client(cx_a, "user_a").await;
2431    let client_b = server.create_client(cx_b, "user_b").await;
2432    server
2433        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2434        .await;
2435    let active_call_a = cx_a.read(ActiveCall::global);
2436    let active_call_b = cx_b.read(ActiveCall::global);
2437    cx_a.update(editor::init);
2438    cx_b.update(editor::init);
2439    client_a
2440        .fs()
2441        .insert_tree(
2442            "/a",
2443            json!({
2444                "test.txt": "one\ntwo\nthree\nfour\nfive",
2445            }),
2446        )
2447        .await;
2448    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
2449    let project_path = ProjectPath {
2450        worktree_id,
2451        path: Arc::from(Path::new(&"test.txt")),
2452    };
2453    let abs_path = project_a.read_with(cx_a, |project, cx| {
2454        project
2455            .absolute_path(&project_path, cx)
2456            .map(|path_buf| Arc::from(path_buf.to_owned()))
2457            .unwrap()
2458    });
2459
2460    active_call_a
2461        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2462        .await
2463        .unwrap();
2464    let project_id = active_call_a
2465        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2466        .await
2467        .unwrap();
2468    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2469    active_call_b
2470        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2471        .await
2472        .unwrap();
2473    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2474    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2475
2476    // Client A opens an editor.
2477    let editor_a = workspace_a
2478        .update_in(cx_a, |workspace, window, cx| {
2479            workspace.open_path(project_path.clone(), None, true, window, cx)
2480        })
2481        .await
2482        .unwrap()
2483        .downcast::<Editor>()
2484        .unwrap();
2485
2486    // Client B opens same editor as A.
2487    let editor_b = workspace_b
2488        .update_in(cx_b, |workspace, window, cx| {
2489            workspace.open_path(project_path.clone(), None, true, window, cx)
2490        })
2491        .await
2492        .unwrap()
2493        .downcast::<Editor>()
2494        .unwrap();
2495
2496    cx_a.run_until_parked();
2497    cx_b.run_until_parked();
2498
2499    // Client A adds breakpoint on line (1)
2500    editor_a.update_in(cx_a, |editor, window, cx| {
2501        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2502    });
2503
2504    cx_a.run_until_parked();
2505    cx_b.run_until_parked();
2506
2507    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2508        editor
2509            .breakpoint_store()
2510            .clone()
2511            .unwrap()
2512            .read(cx)
2513            .all_breakpoints(cx)
2514            .clone()
2515    });
2516    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2517        editor
2518            .breakpoint_store()
2519            .clone()
2520            .unwrap()
2521            .read(cx)
2522            .all_breakpoints(cx)
2523            .clone()
2524    });
2525
2526    assert_eq!(1, breakpoints_a.len());
2527    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
2528    assert_eq!(breakpoints_a, breakpoints_b);
2529
2530    // Client B adds breakpoint on line(2)
2531    editor_b.update_in(cx_b, |editor, window, cx| {
2532        editor.move_down(&editor::actions::MoveDown, window, cx);
2533        editor.move_down(&editor::actions::MoveDown, window, cx);
2534        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2535    });
2536
2537    cx_a.run_until_parked();
2538    cx_b.run_until_parked();
2539
2540    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2541        editor
2542            .breakpoint_store()
2543            .clone()
2544            .unwrap()
2545            .read(cx)
2546            .all_breakpoints(cx)
2547            .clone()
2548    });
2549    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2550        editor
2551            .breakpoint_store()
2552            .clone()
2553            .unwrap()
2554            .read(cx)
2555            .all_breakpoints(cx)
2556            .clone()
2557    });
2558
2559    assert_eq!(1, breakpoints_a.len());
2560    assert_eq!(breakpoints_a, breakpoints_b);
2561    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
2562
2563    // Client A removes last added breakpoint from client B
2564    editor_a.update_in(cx_a, |editor, window, cx| {
2565        editor.move_down(&editor::actions::MoveDown, window, cx);
2566        editor.move_down(&editor::actions::MoveDown, window, cx);
2567        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2568    });
2569
2570    cx_a.run_until_parked();
2571    cx_b.run_until_parked();
2572
2573    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2574        editor
2575            .breakpoint_store()
2576            .clone()
2577            .unwrap()
2578            .read(cx)
2579            .all_breakpoints(cx)
2580            .clone()
2581    });
2582    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2583        editor
2584            .breakpoint_store()
2585            .clone()
2586            .unwrap()
2587            .read(cx)
2588            .all_breakpoints(cx)
2589            .clone()
2590    });
2591
2592    assert_eq!(1, breakpoints_a.len());
2593    assert_eq!(breakpoints_a, breakpoints_b);
2594    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
2595
2596    // Client B removes first added breakpoint by client A
2597    editor_b.update_in(cx_b, |editor, window, cx| {
2598        editor.move_up(&editor::actions::MoveUp, window, cx);
2599        editor.move_up(&editor::actions::MoveUp, window, cx);
2600        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2601    });
2602
2603    cx_a.run_until_parked();
2604    cx_b.run_until_parked();
2605
2606    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2607        editor
2608            .breakpoint_store()
2609            .clone()
2610            .unwrap()
2611            .read(cx)
2612            .all_breakpoints(cx)
2613            .clone()
2614    });
2615    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2616        editor
2617            .breakpoint_store()
2618            .clone()
2619            .unwrap()
2620            .read(cx)
2621            .all_breakpoints(cx)
2622            .clone()
2623    });
2624
2625    assert_eq!(0, breakpoints_a.len());
2626    assert_eq!(breakpoints_a, breakpoints_b);
2627}
2628
2629#[gpui::test]
2630async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2631    let mut server = TestServer::start(cx_a.executor()).await;
2632    let client_a = server.create_client(cx_a, "user_a").await;
2633    let client_b = server.create_client(cx_b, "user_b").await;
2634    server
2635        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2636        .await;
2637    let active_call_a = cx_a.read(ActiveCall::global);
2638    let active_call_b = cx_b.read(ActiveCall::global);
2639
2640    cx_a.update(editor::init);
2641    cx_b.update(editor::init);
2642
2643    client_a.language_registry().add(rust_lang());
2644    client_b.language_registry().add(rust_lang());
2645    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2646        "Rust",
2647        FakeLspAdapter {
2648            name: RUST_ANALYZER_NAME,
2649            ..FakeLspAdapter::default()
2650        },
2651    );
2652
2653    client_a
2654        .fs()
2655        .insert_tree(
2656            path!("/a"),
2657            json!({
2658                "main.rs": "fn main() {}",
2659            }),
2660        )
2661        .await;
2662    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2663    active_call_a
2664        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2665        .await
2666        .unwrap();
2667    let project_id = active_call_a
2668        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2669        .await
2670        .unwrap();
2671
2672    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2673    active_call_b
2674        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2675        .await
2676        .unwrap();
2677
2678    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2679    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2680
2681    let editor_a = workspace_a
2682        .update_in(cx_a, |workspace, window, cx| {
2683            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2684        })
2685        .await
2686        .unwrap()
2687        .downcast::<Editor>()
2688        .unwrap();
2689
2690    let editor_b = workspace_b
2691        .update_in(cx_b, |workspace, window, cx| {
2692            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2693        })
2694        .await
2695        .unwrap()
2696        .downcast::<Editor>()
2697        .unwrap();
2698
2699    let fake_language_server = fake_language_servers.next().await.unwrap();
2700
2701    // host
2702    let mut expand_request_a =
2703        fake_language_server.set_request_handler::<LspExpandMacro, _, _>(|params, _| async move {
2704            assert_eq!(
2705                params.text_document.uri,
2706                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2707            );
2708            assert_eq!(params.position, lsp::Position::new(0, 0),);
2709            Ok(Some(ExpandedMacro {
2710                name: "test_macro_name".to_string(),
2711                expansion: "test_macro_expansion on the host".to_string(),
2712            }))
2713        });
2714
2715    editor_a.update_in(cx_a, |editor, window, cx| {
2716        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
2717    });
2718    expand_request_a.next().await.unwrap();
2719    cx_a.run_until_parked();
2720
2721    workspace_a.update(cx_a, |workspace, cx| {
2722        workspace.active_pane().update(cx, |pane, cx| {
2723            assert_eq!(
2724                pane.items_len(),
2725                2,
2726                "Should have added a macro expansion to the host's pane"
2727            );
2728            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
2729            new_editor.update(cx, |editor, cx| {
2730                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
2731            });
2732        })
2733    });
2734
2735    // client
2736    let mut expand_request_b =
2737        fake_language_server.set_request_handler::<LspExpandMacro, _, _>(|params, _| async move {
2738            assert_eq!(
2739                params.text_document.uri,
2740                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2741            );
2742            assert_eq!(params.position, lsp::Position::new(0, 0),);
2743            Ok(Some(ExpandedMacro {
2744                name: "test_macro_name".to_string(),
2745                expansion: "test_macro_expansion on the client".to_string(),
2746            }))
2747        });
2748
2749    editor_b.update_in(cx_b, |editor, window, cx| {
2750        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
2751    });
2752    expand_request_b.next().await.unwrap();
2753    cx_b.run_until_parked();
2754
2755    workspace_b.update(cx_b, |workspace, cx| {
2756        workspace.active_pane().update(cx, |pane, cx| {
2757            assert_eq!(
2758                pane.items_len(),
2759                2,
2760                "Should have added a macro expansion to the client's pane"
2761            );
2762            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
2763            new_editor.update(cx, |editor, cx| {
2764                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
2765            });
2766        })
2767    });
2768}
2769
2770#[track_caller]
2771fn tab_undo_assert(
2772    cx_a: &mut EditorTestContext,
2773    cx_b: &mut EditorTestContext,
2774    expected_initial: &str,
2775    expected_tabbed: &str,
2776    a_tabs: bool,
2777) {
2778    cx_a.assert_editor_state(expected_initial);
2779    cx_b.assert_editor_state(expected_initial);
2780
2781    if a_tabs {
2782        cx_a.update_editor(|editor, window, cx| {
2783            editor.tab(&editor::actions::Tab, window, cx);
2784        });
2785    } else {
2786        cx_b.update_editor(|editor, window, cx| {
2787            editor.tab(&editor::actions::Tab, window, cx);
2788        });
2789    }
2790
2791    cx_a.run_until_parked();
2792    cx_b.run_until_parked();
2793
2794    cx_a.assert_editor_state(expected_tabbed);
2795    cx_b.assert_editor_state(expected_tabbed);
2796
2797    if a_tabs {
2798        cx_a.update_editor(|editor, window, cx| {
2799            editor.undo(&editor::actions::Undo, window, cx);
2800        });
2801    } else {
2802        cx_b.update_editor(|editor, window, cx| {
2803            editor.undo(&editor::actions::Undo, window, cx);
2804        });
2805    }
2806    cx_a.run_until_parked();
2807    cx_b.run_until_parked();
2808    cx_a.assert_editor_state(expected_initial);
2809    cx_b.assert_editor_state(expected_initial);
2810}
2811
2812fn extract_hint_labels(editor: &Editor) -> Vec<String> {
2813    let mut labels = Vec::new();
2814    for hint in editor.inlay_hint_cache().hints() {
2815        match hint.label {
2816            project::InlayHintLabel::String(s) => labels.push(s),
2817            _ => unreachable!(),
2818        }
2819    }
2820    labels
2821}
2822
2823fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
2824    git::blame::BlameEntry {
2825        sha: sha.parse().unwrap(),
2826        range,
2827        ..Default::default()
2828    }
2829}