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