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