editor_tests.rs

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