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