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