editor_tests.rs

   1use std::{
   2    path::Path,
   3    sync::{
   4        atomic::{self, AtomicBool, AtomicUsize},
   5        Arc,
   6    },
   7};
   8
   9use call::ActiveCall;
  10use editor::{
  11    test::editor_test_context::{AssertionContextManager, EditorTestContext},
  12    ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename, ToggleCodeActions,
  13    Undo,
  14};
  15use futures::StreamExt;
  16use gpui::{TestAppContext, VisualContext, VisualTestContext};
  17use indoc::indoc;
  18use language::{
  19    language_settings::{AllLanguageSettings, InlayHintSettings},
  20    tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig,
  21};
  22use rpc::RECEIVE_TIMEOUT;
  23use serde_json::json;
  24use settings::SettingsStore;
  25use text::Point;
  26use workspace::Workspace;
  27
  28use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
  29
  30#[gpui::test(iterations = 10)]
  31async fn test_host_disconnect(
  32    cx_a: &mut TestAppContext,
  33    cx_b: &mut TestAppContext,
  34    cx_c: &mut TestAppContext,
  35) {
  36    let mut server = TestServer::start(cx_a.executor()).await;
  37    let client_a = server.create_client(cx_a, "user_a").await;
  38    let client_b = server.create_client(cx_b, "user_b").await;
  39    let client_c = server.create_client(cx_c, "user_c").await;
  40    server
  41        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
  42        .await;
  43
  44    cx_b.update(editor::init);
  45
  46    client_a
  47        .fs()
  48        .insert_tree(
  49            "/a",
  50            serde_json::json!({
  51                "a.txt": "a-contents",
  52                "b.txt": "b-contents",
  53            }),
  54        )
  55        .await;
  56
  57    let active_call_a = cx_a.read(ActiveCall::global);
  58    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
  59
  60    let worktree_a = project_a.read_with(cx_a, |project, _| project.worktrees().next().unwrap());
  61    let project_id = active_call_a
  62        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
  63        .await
  64        .unwrap();
  65
  66    let project_b = client_b.build_remote_project(project_id, cx_b).await;
  67    cx_a.background_executor.run_until_parked();
  68
  69    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
  70
  71    let workspace_b =
  72        cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
  73    let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
  74
  75    let editor_b = workspace_b
  76        .update(cx_b, |workspace, cx| {
  77            workspace.open_path((worktree_id, "b.txt"), None, true, cx)
  78        })
  79        .unwrap()
  80        .await
  81        .unwrap()
  82        .downcast::<Editor>()
  83        .unwrap();
  84
  85    //TODO: focus
  86    assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx)));
  87    editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
  88    //todo(is_edited)
  89    // assert!(workspace_b.is_edited(cx_b));
  90
  91    // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
  92    server.forbid_connections();
  93    server.disconnect_client(client_a.peer_id().unwrap());
  94    cx_a.background_executor
  95        .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
  96
  97    project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());
  98
  99    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 100
 101    project_b.read_with(cx_b, |project, _| project.is_read_only());
 102
 103    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
 104
 105    // Ensure client B's edited state is reset and that the whole window is blurred.
 106
 107    workspace_b
 108        .update(cx_b, |_, cx| {
 109            assert_eq!(cx.focused(), None);
 110        })
 111        .unwrap();
 112    // assert!(!workspace_b.is_edited(cx_b));
 113
 114    // Ensure client B is not prompted to save edits when closing window after disconnecting.
 115    let can_close = workspace_b
 116        .update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx))
 117        .unwrap()
 118        .await
 119        .unwrap();
 120    assert!(can_close);
 121
 122    // Allow client A to reconnect to the server.
 123    server.allow_connections();
 124    cx_a.background_executor.advance_clock(RECEIVE_TIMEOUT);
 125
 126    // Client B calls client A again after they reconnected.
 127    let active_call_b = cx_b.read(ActiveCall::global);
 128    active_call_b
 129        .update(cx_b, |call, cx| {
 130            call.invite(client_a.user_id().unwrap(), None, cx)
 131        })
 132        .await
 133        .unwrap();
 134    cx_a.background_executor.run_until_parked();
 135    active_call_a
 136        .update(cx_a, |call, cx| call.accept_incoming(cx))
 137        .await
 138        .unwrap();
 139
 140    active_call_a
 141        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 142        .await
 143        .unwrap();
 144
 145    // Drop client A's connection again. We should still unshare it successfully.
 146    server.forbid_connections();
 147    server.disconnect_client(client_a.peer_id().unwrap());
 148    cx_a.background_executor
 149        .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 150
 151    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 152}
 153
 154#[gpui::test]
 155async fn test_newline_above_or_below_does_not_move_guest_cursor(
 156    cx_a: &mut TestAppContext,
 157    cx_b: &mut TestAppContext,
 158) {
 159    let mut server = TestServer::start(cx_a.executor()).await;
 160    let client_a = server.create_client(cx_a, "user_a").await;
 161    let client_b = server.create_client(cx_b, "user_b").await;
 162    let executor = cx_a.executor();
 163    server
 164        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 165        .await;
 166    let active_call_a = cx_a.read(ActiveCall::global);
 167
 168    client_a
 169        .fs()
 170        .insert_tree("/dir", json!({ "a.txt": "Some text\n" }))
 171        .await;
 172    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
 173    let project_id = active_call_a
 174        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 175        .await
 176        .unwrap();
 177
 178    let project_b = client_b.build_remote_project(project_id, cx_b).await;
 179
 180    // Open a buffer as client A
 181    let buffer_a = project_a
 182        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 183        .await
 184        .unwrap();
 185    let window_a = cx_a.add_empty_window();
 186    let editor_a =
 187        window_a.build_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
 188
 189    let mut editor_cx_a = EditorTestContext {
 190        cx: VisualTestContext::from_window(window_a, cx_a),
 191        window: window_a.into(),
 192        editor: editor_a,
 193        assertion_cx: AssertionContextManager::new(),
 194    };
 195
 196    let window_b = cx_b.add_empty_window();
 197    let mut cx_b = VisualTestContext::from_window(window_b, cx_b);
 198
 199    // Open a buffer as client B
 200    let buffer_b = project_b
 201        .update(&mut cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 202        .await
 203        .unwrap();
 204    let editor_b = window_b.build_view(&mut cx_b, |cx| {
 205        Editor::for_buffer(buffer_b, Some(project_b), cx)
 206    });
 207    let mut editor_cx_b = EditorTestContext {
 208        cx: cx_b,
 209        window: window_b.into(),
 210        editor: editor_b,
 211        assertion_cx: AssertionContextManager::new(),
 212    };
 213
 214    // Test newline above
 215    editor_cx_a.set_selections_state(indoc! {"
 216        Some textˇ
 217    "});
 218    editor_cx_b.set_selections_state(indoc! {"
 219        Some textˇ
 220    "});
 221    editor_cx_a.update_editor(|editor, cx| editor.newline_above(&editor::NewlineAbove, cx));
 222    executor.run_until_parked();
 223    editor_cx_a.assert_editor_state(indoc! {"
 224        ˇ
 225        Some text
 226    "});
 227    editor_cx_b.assert_editor_state(indoc! {"
 228
 229        Some textˇ
 230    "});
 231
 232    // Test newline below
 233    editor_cx_a.set_selections_state(indoc! {"
 234
 235        Some textˇ
 236    "});
 237    editor_cx_b.set_selections_state(indoc! {"
 238
 239        Some textˇ
 240    "});
 241    editor_cx_a.update_editor(|editor, cx| editor.newline_below(&editor::NewlineBelow, cx));
 242    executor.run_until_parked();
 243    editor_cx_a.assert_editor_state(indoc! {"
 244
 245        Some text
 246        ˇ
 247    "});
 248    editor_cx_b.assert_editor_state(indoc! {"
 249
 250        Some textˇ
 251
 252    "});
 253}
 254
 255#[gpui::test(iterations = 10)]
 256async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 257    let mut server = TestServer::start(cx_a.executor()).await;
 258    let client_a = server.create_client(cx_a, "user_a").await;
 259    let client_b = server.create_client(cx_b, "user_b").await;
 260    server
 261        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 262        .await;
 263    let active_call_a = cx_a.read(ActiveCall::global);
 264
 265    // Set up a fake language server.
 266    let mut language = Language::new(
 267        LanguageConfig {
 268            name: "Rust".into(),
 269            path_suffixes: vec!["rs".to_string()],
 270            ..Default::default()
 271        },
 272        Some(tree_sitter_rust::language()),
 273    );
 274    let mut fake_language_servers = language
 275        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 276            capabilities: lsp::ServerCapabilities {
 277                completion_provider: Some(lsp::CompletionOptions {
 278                    trigger_characters: Some(vec![".".to_string()]),
 279                    resolve_provider: Some(true),
 280                    ..Default::default()
 281                }),
 282                ..Default::default()
 283            },
 284            ..Default::default()
 285        }))
 286        .await;
 287    client_a.language_registry().add(Arc::new(language));
 288
 289    client_a
 290        .fs()
 291        .insert_tree(
 292            "/a",
 293            json!({
 294                "main.rs": "fn main() { a }",
 295                "other.rs": "",
 296            }),
 297        )
 298        .await;
 299    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 300    let project_id = active_call_a
 301        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 302        .await
 303        .unwrap();
 304    let project_b = client_b.build_remote_project(project_id, cx_b).await;
 305
 306    // Open a file in an editor as the guest.
 307    let buffer_b = project_b
 308        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
 309        .await
 310        .unwrap();
 311    let window_b = cx_b.add_empty_window();
 312    let editor_b = window_b.build_view(cx_b, |cx| {
 313        Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
 314    });
 315
 316    let fake_language_server = fake_language_servers.next().await.unwrap();
 317    cx_a.background_executor.run_until_parked();
 318
 319    buffer_b.read_with(cx_b, |buffer, _| {
 320        assert!(!buffer.completion_triggers().is_empty())
 321    });
 322
 323    let mut cx_b = VisualTestContext::from_window(window_b, cx_b);
 324
 325    // Type a completion trigger character as the guest.
 326    editor_b.update(&mut cx_b, |editor, cx| {
 327        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
 328        editor.handle_input(".", cx);
 329    });
 330    cx_b.focus_view(&editor_b);
 331
 332    // Receive a completion request as the host's language server.
 333    // Return some completions from the host's language server.
 334    cx_a.executor().start_waiting();
 335    fake_language_server
 336        .handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
 337            assert_eq!(
 338                params.text_document_position.text_document.uri,
 339                lsp::Url::from_file_path("/a/main.rs").unwrap(),
 340            );
 341            assert_eq!(
 342                params.text_document_position.position,
 343                lsp::Position::new(0, 14),
 344            );
 345
 346            Ok(Some(lsp::CompletionResponse::Array(vec![
 347                lsp::CompletionItem {
 348                    label: "first_method(…)".into(),
 349                    detail: Some("fn(&mut self, B) -> C".into()),
 350                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 351                        new_text: "first_method($1)".to_string(),
 352                        range: lsp::Range::new(
 353                            lsp::Position::new(0, 14),
 354                            lsp::Position::new(0, 14),
 355                        ),
 356                    })),
 357                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 358                    ..Default::default()
 359                },
 360                lsp::CompletionItem {
 361                    label: "second_method(…)".into(),
 362                    detail: Some("fn(&mut self, C) -> D<E>".into()),
 363                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 364                        new_text: "second_method()".to_string(),
 365                        range: lsp::Range::new(
 366                            lsp::Position::new(0, 14),
 367                            lsp::Position::new(0, 14),
 368                        ),
 369                    })),
 370                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 371                    ..Default::default()
 372                },
 373            ])))
 374        })
 375        .next()
 376        .await
 377        .unwrap();
 378    cx_a.executor().finish_waiting();
 379
 380    // Open the buffer on the host.
 381    let buffer_a = project_a
 382        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
 383        .await
 384        .unwrap();
 385    cx_a.executor().run_until_parked();
 386
 387    buffer_a.read_with(cx_a, |buffer, _| {
 388        assert_eq!(buffer.text(), "fn main() { a. }")
 389    });
 390
 391    // Confirm a completion on the guest.
 392
 393    editor_b.update(&mut cx_b, |editor, cx| {
 394        assert!(editor.context_menu_visible());
 395        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
 396        assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
 397    });
 398
 399    // Return a resolved completion from the host's language server.
 400    // The resolved completion has an additional text edit.
 401    fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
 402        |params, _| async move {
 403            assert_eq!(params.label, "first_method(…)");
 404            Ok(lsp::CompletionItem {
 405                label: "first_method(…)".into(),
 406                detail: Some("fn(&mut self, B) -> C".into()),
 407                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 408                    new_text: "first_method($1)".to_string(),
 409                    range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
 410                })),
 411                additional_text_edits: Some(vec![lsp::TextEdit {
 412                    new_text: "use d::SomeTrait;\n".to_string(),
 413                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 414                }]),
 415                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 416                ..Default::default()
 417            })
 418        },
 419    );
 420
 421    // The additional edit is applied.
 422    cx_a.executor().run_until_parked();
 423
 424    buffer_a.read_with(cx_a, |buffer, _| {
 425        assert_eq!(
 426            buffer.text(),
 427            "use d::SomeTrait;\nfn main() { a.first_method() }"
 428        );
 429    });
 430
 431    buffer_b.read_with(&mut cx_b, |buffer, _| {
 432        assert_eq!(
 433            buffer.text(),
 434            "use d::SomeTrait;\nfn main() { a.first_method() }"
 435        );
 436    });
 437}
 438
 439#[gpui::test(iterations = 10)]
 440async fn test_collaborating_with_code_actions(
 441    cx_a: &mut TestAppContext,
 442    cx_b: &mut TestAppContext,
 443) {
 444    let mut server = TestServer::start(cx_a.executor()).await;
 445    let client_a = server.create_client(cx_a, "user_a").await;
 446    //
 447    let client_b = server.create_client(cx_b, "user_b").await;
 448    server
 449        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 450        .await;
 451    let active_call_a = cx_a.read(ActiveCall::global);
 452
 453    cx_b.update(editor::init);
 454
 455    // Set up a fake language server.
 456    let mut language = Language::new(
 457        LanguageConfig {
 458            name: "Rust".into(),
 459            path_suffixes: vec!["rs".to_string()],
 460            ..Default::default()
 461        },
 462        Some(tree_sitter_rust::language()),
 463    );
 464    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
 465    client_a.language_registry().add(Arc::new(language));
 466
 467    client_a
 468        .fs()
 469        .insert_tree(
 470            "/a",
 471            json!({
 472                "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
 473                "other.rs": "pub fn foo() -> usize { 4 }",
 474            }),
 475        )
 476        .await;
 477    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 478    let project_id = active_call_a
 479        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 480        .await
 481        .unwrap();
 482
 483    // Join the project as client B.
 484    let project_b = client_b.build_remote_project(project_id, cx_b).await;
 485    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 486    let editor_b = workspace_b
 487        .update(cx_b, |workspace, cx| {
 488            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
 489        })
 490        .await
 491        .unwrap()
 492        .downcast::<Editor>()
 493        .unwrap();
 494
 495    let mut fake_language_server = fake_language_servers.next().await.unwrap();
 496    let mut requests = fake_language_server
 497        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
 498            assert_eq!(
 499                params.text_document.uri,
 500                lsp::Url::from_file_path("/a/main.rs").unwrap(),
 501            );
 502            assert_eq!(params.range.start, lsp::Position::new(0, 0));
 503            assert_eq!(params.range.end, lsp::Position::new(0, 0));
 504            Ok(None)
 505        });
 506    cx_a.background_executor
 507        .advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
 508    requests.next().await;
 509
 510    // Move cursor to a location that contains code actions.
 511    editor_b.update(cx_b, |editor, cx| {
 512        editor.change_selections(None, cx, |s| {
 513            s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
 514        });
 515    });
 516    cx_b.focus_view(&editor_b);
 517
 518    let mut requests = fake_language_server
 519        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
 520            assert_eq!(
 521                params.text_document.uri,
 522                lsp::Url::from_file_path("/a/main.rs").unwrap(),
 523            );
 524            assert_eq!(params.range.start, lsp::Position::new(1, 31));
 525            assert_eq!(params.range.end, lsp::Position::new(1, 31));
 526
 527            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 528                lsp::CodeAction {
 529                    title: "Inline into all callers".to_string(),
 530                    edit: Some(lsp::WorkspaceEdit {
 531                        changes: Some(
 532                            [
 533                                (
 534                                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
 535                                    vec![lsp::TextEdit::new(
 536                                        lsp::Range::new(
 537                                            lsp::Position::new(1, 22),
 538                                            lsp::Position::new(1, 34),
 539                                        ),
 540                                        "4".to_string(),
 541                                    )],
 542                                ),
 543                                (
 544                                    lsp::Url::from_file_path("/a/other.rs").unwrap(),
 545                                    vec![lsp::TextEdit::new(
 546                                        lsp::Range::new(
 547                                            lsp::Position::new(0, 0),
 548                                            lsp::Position::new(0, 27),
 549                                        ),
 550                                        "".to_string(),
 551                                    )],
 552                                ),
 553                            ]
 554                            .into_iter()
 555                            .collect(),
 556                        ),
 557                        ..Default::default()
 558                    }),
 559                    data: Some(json!({
 560                        "codeActionParams": {
 561                            "range": {
 562                                "start": {"line": 1, "column": 31},
 563                                "end": {"line": 1, "column": 31},
 564                            }
 565                        }
 566                    })),
 567                    ..Default::default()
 568                },
 569            )]))
 570        });
 571    cx_a.background_executor
 572        .advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
 573    requests.next().await;
 574
 575    // Toggle code actions and wait for them to display.
 576    editor_b.update(cx_b, |editor, cx| {
 577        editor.toggle_code_actions(
 578            &ToggleCodeActions {
 579                deployed_from_indicator: false,
 580            },
 581            cx,
 582        );
 583    });
 584    cx_a.background_executor.run_until_parked();
 585
 586    editor_b.update(cx_b, |editor, _| assert!(editor.context_menu_visible()));
 587
 588    fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
 589
 590    // Confirming the code action will trigger a resolve request.
 591    let confirm_action = editor_b
 592        .update(cx_b, |editor, cx| {
 593            Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, cx)
 594        })
 595        .unwrap();
 596    fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
 597        |_, _| async move {
 598            Ok(lsp::CodeAction {
 599                title: "Inline into all callers".to_string(),
 600                edit: Some(lsp::WorkspaceEdit {
 601                    changes: Some(
 602                        [
 603                            (
 604                                lsp::Url::from_file_path("/a/main.rs").unwrap(),
 605                                vec![lsp::TextEdit::new(
 606                                    lsp::Range::new(
 607                                        lsp::Position::new(1, 22),
 608                                        lsp::Position::new(1, 34),
 609                                    ),
 610                                    "4".to_string(),
 611                                )],
 612                            ),
 613                            (
 614                                lsp::Url::from_file_path("/a/other.rs").unwrap(),
 615                                vec![lsp::TextEdit::new(
 616                                    lsp::Range::new(
 617                                        lsp::Position::new(0, 0),
 618                                        lsp::Position::new(0, 27),
 619                                    ),
 620                                    "".to_string(),
 621                                )],
 622                            ),
 623                        ]
 624                        .into_iter()
 625                        .collect(),
 626                    ),
 627                    ..Default::default()
 628                }),
 629                ..Default::default()
 630            })
 631        },
 632    );
 633
 634    // After the action is confirmed, an editor containing both modified files is opened.
 635    confirm_action.await.unwrap();
 636
 637    let code_action_editor = workspace_b.update(cx_b, |workspace, cx| {
 638        workspace
 639            .active_item(cx)
 640            .unwrap()
 641            .downcast::<Editor>()
 642            .unwrap()
 643    });
 644    code_action_editor.update(cx_b, |editor, cx| {
 645        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 646        editor.undo(&Undo, cx);
 647        assert_eq!(
 648            editor.text(cx),
 649            "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
 650        );
 651        editor.redo(&Redo, cx);
 652        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 653    });
 654}
 655
 656#[gpui::test(iterations = 10)]
 657async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 658    let mut server = TestServer::start(cx_a.executor()).await;
 659    let client_a = server.create_client(cx_a, "user_a").await;
 660    let client_b = server.create_client(cx_b, "user_b").await;
 661    server
 662        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 663        .await;
 664    let active_call_a = cx_a.read(ActiveCall::global);
 665
 666    cx_b.update(editor::init);
 667
 668    // Set up a fake language server.
 669    let mut language = Language::new(
 670        LanguageConfig {
 671            name: "Rust".into(),
 672            path_suffixes: vec!["rs".to_string()],
 673            ..Default::default()
 674        },
 675        Some(tree_sitter_rust::language()),
 676    );
 677    let mut fake_language_servers = language
 678        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 679            capabilities: lsp::ServerCapabilities {
 680                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
 681                    prepare_provider: Some(true),
 682                    work_done_progress_options: Default::default(),
 683                })),
 684                ..Default::default()
 685            },
 686            ..Default::default()
 687        }))
 688        .await;
 689    client_a.language_registry().add(Arc::new(language));
 690
 691    client_a
 692        .fs()
 693        .insert_tree(
 694            "/dir",
 695            json!({
 696                "one.rs": "const ONE: usize = 1;",
 697                "two.rs": "const TWO: usize = one::ONE + one::ONE;"
 698            }),
 699        )
 700        .await;
 701    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
 702    let project_id = active_call_a
 703        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 704        .await
 705        .unwrap();
 706    let project_b = client_b.build_remote_project(project_id, cx_b).await;
 707
 708    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 709    let editor_b = workspace_b
 710        .update(cx_b, |workspace, cx| {
 711            workspace.open_path((worktree_id, "one.rs"), None, true, cx)
 712        })
 713        .await
 714        .unwrap()
 715        .downcast::<Editor>()
 716        .unwrap();
 717    let fake_language_server = fake_language_servers.next().await.unwrap();
 718
 719    // Move cursor to a location that can be renamed.
 720    let prepare_rename = editor_b.update(cx_b, |editor, cx| {
 721        editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
 722        editor.rename(&Rename, cx).unwrap()
 723    });
 724
 725    fake_language_server
 726        .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
 727            assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
 728            assert_eq!(params.position, lsp::Position::new(0, 7));
 729            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
 730                lsp::Position::new(0, 6),
 731                lsp::Position::new(0, 9),
 732            ))))
 733        })
 734        .next()
 735        .await
 736        .unwrap();
 737    prepare_rename.await.unwrap();
 738    editor_b.update(cx_b, |editor, cx| {
 739        use editor::ToOffset;
 740        let rename = editor.pending_rename().unwrap();
 741        let buffer = editor.buffer().read(cx).snapshot(cx);
 742        assert_eq!(
 743            rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
 744            6..9
 745        );
 746        rename.editor.update(cx, |rename_editor, cx| {
 747            rename_editor.buffer().update(cx, |rename_buffer, cx| {
 748                rename_buffer.edit([(0..3, "THREE")], None, cx);
 749            });
 750        });
 751    });
 752
 753    let confirm_rename = editor_b.update(cx_b, |editor, cx| {
 754        Editor::confirm_rename(editor, &ConfirmRename, cx).unwrap()
 755    });
 756    fake_language_server
 757        .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
 758            assert_eq!(
 759                params.text_document_position.text_document.uri.as_str(),
 760                "file:///dir/one.rs"
 761            );
 762            assert_eq!(
 763                params.text_document_position.position,
 764                lsp::Position::new(0, 6)
 765            );
 766            assert_eq!(params.new_name, "THREE");
 767            Ok(Some(lsp::WorkspaceEdit {
 768                changes: Some(
 769                    [
 770                        (
 771                            lsp::Url::from_file_path("/dir/one.rs").unwrap(),
 772                            vec![lsp::TextEdit::new(
 773                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
 774                                "THREE".to_string(),
 775                            )],
 776                        ),
 777                        (
 778                            lsp::Url::from_file_path("/dir/two.rs").unwrap(),
 779                            vec![
 780                                lsp::TextEdit::new(
 781                                    lsp::Range::new(
 782                                        lsp::Position::new(0, 24),
 783                                        lsp::Position::new(0, 27),
 784                                    ),
 785                                    "THREE".to_string(),
 786                                ),
 787                                lsp::TextEdit::new(
 788                                    lsp::Range::new(
 789                                        lsp::Position::new(0, 35),
 790                                        lsp::Position::new(0, 38),
 791                                    ),
 792                                    "THREE".to_string(),
 793                                ),
 794                            ],
 795                        ),
 796                    ]
 797                    .into_iter()
 798                    .collect(),
 799                ),
 800                ..Default::default()
 801            }))
 802        })
 803        .next()
 804        .await
 805        .unwrap();
 806    confirm_rename.await.unwrap();
 807
 808    let rename_editor = workspace_b.update(cx_b, |workspace, cx| {
 809        workspace.active_item_as::<Editor>(cx).unwrap()
 810    });
 811
 812    rename_editor.update(cx_b, |editor, cx| {
 813        assert_eq!(
 814            editor.text(cx),
 815            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
 816        );
 817        editor.undo(&Undo, cx);
 818        assert_eq!(
 819            editor.text(cx),
 820            "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
 821        );
 822        editor.redo(&Redo, cx);
 823        assert_eq!(
 824            editor.text(cx),
 825            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
 826        );
 827    });
 828
 829    // Ensure temporary rename edits cannot be undone/redone.
 830    editor_b.update(cx_b, |editor, cx| {
 831        editor.undo(&Undo, cx);
 832        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
 833        editor.undo(&Undo, cx);
 834        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
 835        editor.redo(&Redo, cx);
 836        assert_eq!(editor.text(cx), "const THREE: usize = 1;");
 837    })
 838}
 839
 840#[gpui::test(iterations = 10)]
 841async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 842    let mut server = TestServer::start(cx_a.executor()).await;
 843    let executor = cx_a.executor();
 844    let client_a = server.create_client(cx_a, "user_a").await;
 845    let client_b = server.create_client(cx_b, "user_b").await;
 846    server
 847        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 848        .await;
 849    let active_call_a = cx_a.read(ActiveCall::global);
 850
 851    cx_b.update(editor::init);
 852
 853    // Set up a fake language server.
 854    let mut language = Language::new(
 855        LanguageConfig {
 856            name: "Rust".into(),
 857            path_suffixes: vec!["rs".to_string()],
 858            ..Default::default()
 859        },
 860        Some(tree_sitter_rust::language()),
 861    );
 862    let mut fake_language_servers = language
 863        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 864            name: "the-language-server",
 865            ..Default::default()
 866        }))
 867        .await;
 868    client_a.language_registry().add(Arc::new(language));
 869
 870    client_a
 871        .fs()
 872        .insert_tree(
 873            "/dir",
 874            json!({
 875                "main.rs": "const ONE: usize = 1;",
 876            }),
 877        )
 878        .await;
 879    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
 880
 881    let _buffer_a = project_a
 882        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
 883        .await
 884        .unwrap();
 885
 886    let fake_language_server = fake_language_servers.next().await.unwrap();
 887    fake_language_server.start_progress("the-token").await;
 888    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
 889        token: lsp::NumberOrString::String("the-token".to_string()),
 890        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
 891            lsp::WorkDoneProgressReport {
 892                message: Some("the-message".to_string()),
 893                ..Default::default()
 894            },
 895        )),
 896    });
 897    executor.run_until_parked();
 898
 899    project_a.read_with(cx_a, |project, _| {
 900        let status = project.language_server_statuses().next().unwrap();
 901        assert_eq!(status.name, "the-language-server");
 902        assert_eq!(status.pending_work.len(), 1);
 903        assert_eq!(
 904            status.pending_work["the-token"].message.as_ref().unwrap(),
 905            "the-message"
 906        );
 907    });
 908
 909    let project_id = active_call_a
 910        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 911        .await
 912        .unwrap();
 913    executor.run_until_parked();
 914    let project_b = client_b.build_remote_project(project_id, cx_b).await;
 915
 916    project_b.read_with(cx_b, |project, _| {
 917        let status = project.language_server_statuses().next().unwrap();
 918        assert_eq!(status.name, "the-language-server");
 919    });
 920
 921    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
 922        token: lsp::NumberOrString::String("the-token".to_string()),
 923        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
 924            lsp::WorkDoneProgressReport {
 925                message: Some("the-message-2".to_string()),
 926                ..Default::default()
 927            },
 928        )),
 929    });
 930    executor.run_until_parked();
 931
 932    project_a.read_with(cx_a, |project, _| {
 933        let status = project.language_server_statuses().next().unwrap();
 934        assert_eq!(status.name, "the-language-server");
 935        assert_eq!(status.pending_work.len(), 1);
 936        assert_eq!(
 937            status.pending_work["the-token"].message.as_ref().unwrap(),
 938            "the-message-2"
 939        );
 940    });
 941
 942    project_b.read_with(cx_b, |project, _| {
 943        let status = project.language_server_statuses().next().unwrap();
 944        assert_eq!(status.name, "the-language-server");
 945        assert_eq!(status.pending_work.len(), 1);
 946        assert_eq!(
 947            status.pending_work["the-token"].message.as_ref().unwrap(),
 948            "the-message-2"
 949        );
 950    });
 951}
 952
 953#[gpui::test(iterations = 10)]
 954async fn test_share_project(
 955    cx_a: &mut TestAppContext,
 956    cx_b: &mut TestAppContext,
 957    cx_c: &mut TestAppContext,
 958) {
 959    let executor = cx_a.executor();
 960    let window_b = cx_b.add_empty_window();
 961    let mut server = TestServer::start(executor.clone()).await;
 962    let client_a = server.create_client(cx_a, "user_a").await;
 963    let client_b = server.create_client(cx_b, "user_b").await;
 964    let client_c = server.create_client(cx_c, "user_c").await;
 965    server
 966        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
 967        .await;
 968    let active_call_a = cx_a.read(ActiveCall::global);
 969    let active_call_b = cx_b.read(ActiveCall::global);
 970    let active_call_c = cx_c.read(ActiveCall::global);
 971
 972    client_a
 973        .fs()
 974        .insert_tree(
 975            "/a",
 976            json!({
 977                ".gitignore": "ignored-dir",
 978                "a.txt": "a-contents",
 979                "b.txt": "b-contents",
 980                "ignored-dir": {
 981                    "c.txt": "",
 982                    "d.txt": "",
 983                }
 984            }),
 985        )
 986        .await;
 987
 988    // Invite client B to collaborate on a project
 989    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 990    active_call_a
 991        .update(cx_a, |call, cx| {
 992            call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
 993        })
 994        .await
 995        .unwrap();
 996
 997    // Join that project as client B
 998
 999    let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
1000    executor.run_until_parked();
1001    let call = incoming_call_b.borrow().clone().unwrap();
1002    assert_eq!(call.calling_user.github_login, "user_a");
1003    let initial_project = call.initial_project.unwrap();
1004    active_call_b
1005        .update(cx_b, |call, cx| call.accept_incoming(cx))
1006        .await
1007        .unwrap();
1008    let client_b_peer_id = client_b.peer_id().unwrap();
1009    let project_b = client_b
1010        .build_remote_project(initial_project.id, cx_b)
1011        .await;
1012
1013    let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
1014
1015    executor.run_until_parked();
1016
1017    project_a.read_with(cx_a, |project, _| {
1018        let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
1019        assert_eq!(client_b_collaborator.replica_id, replica_id_b);
1020    });
1021
1022    project_b.read_with(cx_b, |project, cx| {
1023        let worktree = project.worktrees().next().unwrap().read(cx);
1024        assert_eq!(
1025            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1026            [
1027                Path::new(".gitignore"),
1028                Path::new("a.txt"),
1029                Path::new("b.txt"),
1030                Path::new("ignored-dir"),
1031            ]
1032        );
1033    });
1034
1035    project_b
1036        .update(cx_b, |project, cx| {
1037            let worktree = project.worktrees().next().unwrap();
1038            let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
1039            project.expand_entry(worktree_id, entry.id, cx).unwrap()
1040        })
1041        .await
1042        .unwrap();
1043
1044    project_b.read_with(cx_b, |project, cx| {
1045        let worktree = project.worktrees().next().unwrap().read(cx);
1046        assert_eq!(
1047            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1048            [
1049                Path::new(".gitignore"),
1050                Path::new("a.txt"),
1051                Path::new("b.txt"),
1052                Path::new("ignored-dir"),
1053                Path::new("ignored-dir/c.txt"),
1054                Path::new("ignored-dir/d.txt"),
1055            ]
1056        );
1057    });
1058
1059    // Open the same file as client B and client A.
1060    let buffer_b = project_b
1061        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1062        .await
1063        .unwrap();
1064
1065    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
1066
1067    project_a.read_with(cx_a, |project, cx| {
1068        assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
1069    });
1070    let buffer_a = project_a
1071        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1072        .await
1073        .unwrap();
1074
1075    let editor_b = window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
1076
1077    // Client A sees client B's selection
1078    executor.run_until_parked();
1079
1080    buffer_a.read_with(cx_a, |buffer, _| {
1081        buffer
1082            .snapshot()
1083            .remote_selections_in_range(text::Anchor::MIN..text::Anchor::MAX)
1084            .count()
1085            == 1
1086    });
1087
1088    // Edit the buffer as client B and see that edit as client A.
1089    let mut cx_b = VisualTestContext::from_window(window_b, cx_b);
1090    editor_b.update(&mut cx_b, |editor, cx| editor.handle_input("ok, ", cx));
1091    executor.run_until_parked();
1092
1093    buffer_a.read_with(cx_a, |buffer, _| {
1094        assert_eq!(buffer.text(), "ok, b-contents")
1095    });
1096
1097    // Client B can invite client C on a project shared by client A.
1098    active_call_b
1099        .update(&mut cx_b, |call, cx| {
1100            call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
1101        })
1102        .await
1103        .unwrap();
1104
1105    let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
1106    executor.run_until_parked();
1107    let call = incoming_call_c.borrow().clone().unwrap();
1108    assert_eq!(call.calling_user.github_login, "user_b");
1109    let initial_project = call.initial_project.unwrap();
1110    active_call_c
1111        .update(cx_c, |call, cx| call.accept_incoming(cx))
1112        .await
1113        .unwrap();
1114    let _project_c = client_c
1115        .build_remote_project(initial_project.id, cx_c)
1116        .await;
1117
1118    // Client B closes the editor, and client A sees client B's selections removed.
1119    cx_b.update(move |_| drop(editor_b));
1120    executor.run_until_parked();
1121
1122    buffer_a.read_with(cx_a, |buffer, _| {
1123        buffer
1124            .snapshot()
1125            .remote_selections_in_range(text::Anchor::MIN..text::Anchor::MAX)
1126            .count()
1127            == 0
1128    });
1129}
1130
1131#[gpui::test(iterations = 10)]
1132async fn test_on_input_format_from_host_to_guest(
1133    cx_a: &mut TestAppContext,
1134    cx_b: &mut TestAppContext,
1135) {
1136    let mut server = TestServer::start(cx_a.executor()).await;
1137    let executor = cx_a.executor();
1138    let client_a = server.create_client(cx_a, "user_a").await;
1139    let client_b = server.create_client(cx_b, "user_b").await;
1140    server
1141        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1142        .await;
1143    let active_call_a = cx_a.read(ActiveCall::global);
1144
1145    // Set up a fake language server.
1146    let mut language = Language::new(
1147        LanguageConfig {
1148            name: "Rust".into(),
1149            path_suffixes: vec!["rs".to_string()],
1150            ..Default::default()
1151        },
1152        Some(tree_sitter_rust::language()),
1153    );
1154    let mut fake_language_servers = language
1155        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1156            capabilities: lsp::ServerCapabilities {
1157                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1158                    first_trigger_character: ":".to_string(),
1159                    more_trigger_character: Some(vec![">".to_string()]),
1160                }),
1161                ..Default::default()
1162            },
1163            ..Default::default()
1164        }))
1165        .await;
1166    client_a.language_registry().add(Arc::new(language));
1167
1168    client_a
1169        .fs()
1170        .insert_tree(
1171            "/a",
1172            json!({
1173                "main.rs": "fn main() { a }",
1174                "other.rs": "// Test file",
1175            }),
1176        )
1177        .await;
1178    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1179    let project_id = active_call_a
1180        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1181        .await
1182        .unwrap();
1183    let project_b = client_b.build_remote_project(project_id, cx_b).await;
1184
1185    // Open a file in an editor as the host.
1186    let buffer_a = project_a
1187        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1188        .await
1189        .unwrap();
1190    let window_a = cx_a.add_empty_window();
1191    let editor_a = window_a
1192        .update(cx_a, |_, cx| {
1193            cx.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx))
1194        })
1195        .unwrap();
1196
1197    let fake_language_server = fake_language_servers.next().await.unwrap();
1198    executor.run_until_parked();
1199
1200    // Receive an OnTypeFormatting request as the host's language server.
1201    // Return some formattings from the host's language server.
1202    fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
1203        |params, _| async move {
1204            assert_eq!(
1205                params.text_document_position.text_document.uri,
1206                lsp::Url::from_file_path("/a/main.rs").unwrap(),
1207            );
1208            assert_eq!(
1209                params.text_document_position.position,
1210                lsp::Position::new(0, 14),
1211            );
1212
1213            Ok(Some(vec![lsp::TextEdit {
1214                new_text: "~<".to_string(),
1215                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1216            }]))
1217        },
1218    );
1219
1220    // Open the buffer on the guest and see that the formattings worked
1221    let buffer_b = project_b
1222        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1223        .await
1224        .unwrap();
1225
1226    let mut cx_a = VisualTestContext::from_window(window_a, cx_a);
1227    // Type a on type formatting trigger character as the guest.
1228    cx_a.focus_view(&editor_a);
1229    editor_a.update(&mut cx_a, |editor, cx| {
1230        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1231        editor.handle_input(">", cx);
1232    });
1233
1234    executor.run_until_parked();
1235
1236    buffer_b.read_with(cx_b, |buffer, _| {
1237        assert_eq!(buffer.text(), "fn main() { a>~< }")
1238    });
1239
1240    // Undo should remove LSP edits first
1241    editor_a.update(&mut cx_a, |editor, cx| {
1242        assert_eq!(editor.text(cx), "fn main() { a>~< }");
1243        editor.undo(&Undo, cx);
1244        assert_eq!(editor.text(cx), "fn main() { a> }");
1245    });
1246    executor.run_until_parked();
1247
1248    buffer_b.read_with(cx_b, |buffer, _| {
1249        assert_eq!(buffer.text(), "fn main() { a> }")
1250    });
1251
1252    editor_a.update(&mut cx_a, |editor, cx| {
1253        assert_eq!(editor.text(cx), "fn main() { a> }");
1254        editor.undo(&Undo, cx);
1255        assert_eq!(editor.text(cx), "fn main() { a }");
1256    });
1257    executor.run_until_parked();
1258
1259    buffer_b.read_with(cx_b, |buffer, _| {
1260        assert_eq!(buffer.text(), "fn main() { a }")
1261    });
1262}
1263
1264#[gpui::test(iterations = 10)]
1265async fn test_on_input_format_from_guest_to_host(
1266    cx_a: &mut TestAppContext,
1267    cx_b: &mut TestAppContext,
1268) {
1269    let mut server = TestServer::start(cx_a.executor()).await;
1270    let executor = cx_a.executor();
1271    let client_a = server.create_client(cx_a, "user_a").await;
1272    let client_b = server.create_client(cx_b, "user_b").await;
1273    server
1274        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1275        .await;
1276    let active_call_a = cx_a.read(ActiveCall::global);
1277
1278    // Set up a fake language server.
1279    let mut language = Language::new(
1280        LanguageConfig {
1281            name: "Rust".into(),
1282            path_suffixes: vec!["rs".to_string()],
1283            ..Default::default()
1284        },
1285        Some(tree_sitter_rust::language()),
1286    );
1287    let mut fake_language_servers = language
1288        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1289            capabilities: lsp::ServerCapabilities {
1290                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1291                    first_trigger_character: ":".to_string(),
1292                    more_trigger_character: Some(vec![">".to_string()]),
1293                }),
1294                ..Default::default()
1295            },
1296            ..Default::default()
1297        }))
1298        .await;
1299    client_a.language_registry().add(Arc::new(language));
1300
1301    client_a
1302        .fs()
1303        .insert_tree(
1304            "/a",
1305            json!({
1306                "main.rs": "fn main() { a }",
1307                "other.rs": "// Test file",
1308            }),
1309        )
1310        .await;
1311    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1312    let project_id = active_call_a
1313        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1314        .await
1315        .unwrap();
1316    let project_b = client_b.build_remote_project(project_id, cx_b).await;
1317
1318    // Open a file in an editor as the guest.
1319    let buffer_b = project_b
1320        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1321        .await
1322        .unwrap();
1323    let window_b = cx_b.add_empty_window();
1324    let editor_b = window_b.build_view(cx_b, |cx| {
1325        Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
1326    });
1327
1328    let fake_language_server = fake_language_servers.next().await.unwrap();
1329    executor.run_until_parked();
1330    let mut cx_b = VisualTestContext::from_window(window_b, cx_b);
1331    // Type a on type formatting trigger character as the guest.
1332    cx_b.focus_view(&editor_b);
1333    editor_b.update(&mut cx_b, |editor, cx| {
1334        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1335        editor.handle_input(":", cx);
1336    });
1337
1338    // Receive an OnTypeFormatting request as the host's language server.
1339    // Return some formattings from the host's language server.
1340    executor.start_waiting();
1341    fake_language_server
1342        .handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
1343            assert_eq!(
1344                params.text_document_position.text_document.uri,
1345                lsp::Url::from_file_path("/a/main.rs").unwrap(),
1346            );
1347            assert_eq!(
1348                params.text_document_position.position,
1349                lsp::Position::new(0, 14),
1350            );
1351
1352            Ok(Some(vec![lsp::TextEdit {
1353                new_text: "~:".to_string(),
1354                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1355            }]))
1356        })
1357        .next()
1358        .await
1359        .unwrap();
1360    executor.finish_waiting();
1361
1362    // Open the buffer on the host and see that the formattings worked
1363    let buffer_a = project_a
1364        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1365        .await
1366        .unwrap();
1367    executor.run_until_parked();
1368
1369    buffer_a.read_with(cx_a, |buffer, _| {
1370        assert_eq!(buffer.text(), "fn main() { a:~: }")
1371    });
1372
1373    // Undo should remove LSP edits first
1374    editor_b.update(&mut cx_b, |editor, cx| {
1375        assert_eq!(editor.text(cx), "fn main() { a:~: }");
1376        editor.undo(&Undo, cx);
1377        assert_eq!(editor.text(cx), "fn main() { a: }");
1378    });
1379    executor.run_until_parked();
1380
1381    buffer_a.read_with(cx_a, |buffer, _| {
1382        assert_eq!(buffer.text(), "fn main() { a: }")
1383    });
1384
1385    editor_b.update(&mut cx_b, |editor, cx| {
1386        assert_eq!(editor.text(cx), "fn main() { a: }");
1387        editor.undo(&Undo, cx);
1388        assert_eq!(editor.text(cx), "fn main() { a }");
1389    });
1390    executor.run_until_parked();
1391
1392    buffer_a.read_with(cx_a, |buffer, _| {
1393        assert_eq!(buffer.text(), "fn main() { a }")
1394    });
1395}
1396
1397#[gpui::test(iterations = 10)]
1398async fn test_mutual_editor_inlay_hint_cache_update(
1399    cx_a: &mut TestAppContext,
1400    cx_b: &mut TestAppContext,
1401) {
1402    let mut server = TestServer::start(cx_a.executor()).await;
1403    let executor = cx_a.executor();
1404    let client_a = server.create_client(cx_a, "user_a").await;
1405    let client_b = server.create_client(cx_b, "user_b").await;
1406    server
1407        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1408        .await;
1409    let active_call_a = cx_a.read(ActiveCall::global);
1410    let active_call_b = cx_b.read(ActiveCall::global);
1411
1412    cx_a.update(editor::init);
1413    cx_b.update(editor::init);
1414
1415    cx_a.update(|cx| {
1416        cx.update_global(|store: &mut SettingsStore, cx| {
1417            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1418                settings.defaults.inlay_hints = Some(InlayHintSettings {
1419                    enabled: true,
1420                    show_type_hints: true,
1421                    show_parameter_hints: false,
1422                    show_other_hints: true,
1423                })
1424            });
1425        });
1426    });
1427    cx_b.update(|cx| {
1428        cx.update_global(|store: &mut SettingsStore, cx| {
1429            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1430                settings.defaults.inlay_hints = Some(InlayHintSettings {
1431                    enabled: true,
1432                    show_type_hints: true,
1433                    show_parameter_hints: false,
1434                    show_other_hints: true,
1435                })
1436            });
1437        });
1438    });
1439
1440    let mut language = Language::new(
1441        LanguageConfig {
1442            name: "Rust".into(),
1443            path_suffixes: vec!["rs".to_string()],
1444            ..Default::default()
1445        },
1446        Some(tree_sitter_rust::language()),
1447    );
1448    let mut fake_language_servers = language
1449        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1450            capabilities: lsp::ServerCapabilities {
1451                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1452                ..Default::default()
1453            },
1454            ..Default::default()
1455        }))
1456        .await;
1457    let language = Arc::new(language);
1458    client_a.language_registry().add(Arc::clone(&language));
1459    client_b.language_registry().add(language);
1460
1461    // Client A opens a project.
1462    client_a
1463        .fs()
1464        .insert_tree(
1465            "/a",
1466            json!({
1467                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1468                "other.rs": "// Test file",
1469            }),
1470        )
1471        .await;
1472    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1473    active_call_a
1474        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1475        .await
1476        .unwrap();
1477    let project_id = active_call_a
1478        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1479        .await
1480        .unwrap();
1481
1482    // Client B joins the project
1483    let project_b = client_b.build_remote_project(project_id, cx_b).await;
1484    active_call_b
1485        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1486        .await
1487        .unwrap();
1488
1489    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
1490    executor.start_waiting();
1491
1492    // The host opens a rust file.
1493    let _buffer_a = project_a
1494        .update(cx_a, |project, cx| {
1495            project.open_local_buffer("/a/main.rs", cx)
1496        })
1497        .await
1498        .unwrap();
1499    let fake_language_server = fake_language_servers.next().await.unwrap();
1500    let editor_a = workspace_a
1501        .update(cx_a, |workspace, cx| {
1502            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1503        })
1504        .await
1505        .unwrap()
1506        .downcast::<Editor>()
1507        .unwrap();
1508
1509    // Set up the language server to return an additional inlay hint on each request.
1510    let edits_made = Arc::new(AtomicUsize::new(0));
1511    let closure_edits_made = Arc::clone(&edits_made);
1512    fake_language_server
1513        .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1514            let task_edits_made = Arc::clone(&closure_edits_made);
1515            async move {
1516                assert_eq!(
1517                    params.text_document.uri,
1518                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
1519                );
1520                let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
1521                Ok(Some(vec![lsp::InlayHint {
1522                    position: lsp::Position::new(0, edits_made as u32),
1523                    label: lsp::InlayHintLabel::String(edits_made.to_string()),
1524                    kind: None,
1525                    text_edits: None,
1526                    tooltip: None,
1527                    padding_left: None,
1528                    padding_right: None,
1529                    data: None,
1530                }]))
1531            }
1532        })
1533        .next()
1534        .await
1535        .unwrap();
1536
1537    executor.run_until_parked();
1538
1539    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
1540    editor_a.update(cx_a, |editor, _| {
1541        assert_eq!(
1542            vec![initial_edit.to_string()],
1543            extract_hint_labels(editor),
1544            "Host should get its first hints when opens an editor"
1545        );
1546        let inlay_cache = editor.inlay_hint_cache();
1547        assert_eq!(
1548            inlay_cache.version(),
1549            1,
1550            "Host editor update the cache version after every cache/view change",
1551        );
1552    });
1553    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1554    let editor_b = workspace_b
1555        .update(cx_b, |workspace, cx| {
1556            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1557        })
1558        .await
1559        .unwrap()
1560        .downcast::<Editor>()
1561        .unwrap();
1562
1563    executor.run_until_parked();
1564    editor_b.update(cx_b, |editor, _| {
1565        assert_eq!(
1566            vec![initial_edit.to_string()],
1567            extract_hint_labels(editor),
1568            "Client should get its first hints when opens an editor"
1569        );
1570        let inlay_cache = editor.inlay_hint_cache();
1571        assert_eq!(
1572            inlay_cache.version(),
1573            1,
1574            "Guest editor update the cache version after every cache/view change"
1575        );
1576    });
1577
1578    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1579    editor_b.update(cx_b, |editor, cx| {
1580        editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
1581        editor.handle_input(":", cx);
1582    });
1583    cx_b.focus_view(&editor_b);
1584
1585    executor.run_until_parked();
1586    editor_a.update(cx_a, |editor, _| {
1587        assert_eq!(
1588            vec![after_client_edit.to_string()],
1589            extract_hint_labels(editor),
1590        );
1591        let inlay_cache = editor.inlay_hint_cache();
1592        assert_eq!(inlay_cache.version(), 2);
1593    });
1594    editor_b.update(cx_b, |editor, _| {
1595        assert_eq!(
1596            vec![after_client_edit.to_string()],
1597            extract_hint_labels(editor),
1598        );
1599        let inlay_cache = editor.inlay_hint_cache();
1600        assert_eq!(inlay_cache.version(), 2);
1601    });
1602
1603    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1604    editor_a.update(cx_a, |editor, cx| {
1605        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1606        editor.handle_input("a change to increment both buffers' versions", cx);
1607    });
1608    cx_a.focus_view(&editor_a);
1609
1610    executor.run_until_parked();
1611    editor_a.update(cx_a, |editor, _| {
1612        assert_eq!(
1613            vec![after_host_edit.to_string()],
1614            extract_hint_labels(editor),
1615        );
1616        let inlay_cache = editor.inlay_hint_cache();
1617        assert_eq!(inlay_cache.version(), 3);
1618    });
1619    editor_b.update(cx_b, |editor, _| {
1620        assert_eq!(
1621            vec![after_host_edit.to_string()],
1622            extract_hint_labels(editor),
1623        );
1624        let inlay_cache = editor.inlay_hint_cache();
1625        assert_eq!(inlay_cache.version(), 3);
1626    });
1627
1628    let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1629    fake_language_server
1630        .request::<lsp::request::InlayHintRefreshRequest>(())
1631        .await
1632        .expect("inlay refresh request failed");
1633
1634    executor.run_until_parked();
1635    editor_a.update(cx_a, |editor, _| {
1636        assert_eq!(
1637            vec![after_special_edit_for_refresh.to_string()],
1638            extract_hint_labels(editor),
1639            "Host should react to /refresh LSP request"
1640        );
1641        let inlay_cache = editor.inlay_hint_cache();
1642        assert_eq!(
1643            inlay_cache.version(),
1644            4,
1645            "Host should accepted all edits and bump its cache version every time"
1646        );
1647    });
1648    editor_b.update(cx_b, |editor, _| {
1649        assert_eq!(
1650            vec![after_special_edit_for_refresh.to_string()],
1651            extract_hint_labels(editor),
1652            "Guest should get a /refresh LSP request propagated by host"
1653        );
1654        let inlay_cache = editor.inlay_hint_cache();
1655        assert_eq!(
1656            inlay_cache.version(),
1657            4,
1658            "Guest should accepted all edits and bump its cache version every time"
1659        );
1660    });
1661}
1662
1663#[gpui::test(iterations = 10)]
1664async fn test_inlay_hint_refresh_is_forwarded(
1665    cx_a: &mut TestAppContext,
1666    cx_b: &mut TestAppContext,
1667) {
1668    let mut server = TestServer::start(cx_a.executor()).await;
1669    let executor = cx_a.executor();
1670    let client_a = server.create_client(cx_a, "user_a").await;
1671    let client_b = server.create_client(cx_b, "user_b").await;
1672    server
1673        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1674        .await;
1675    let active_call_a = cx_a.read(ActiveCall::global);
1676    let active_call_b = cx_b.read(ActiveCall::global);
1677
1678    cx_a.update(editor::init);
1679    cx_b.update(editor::init);
1680
1681    cx_a.update(|cx| {
1682        cx.update_global(|store: &mut SettingsStore, cx| {
1683            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1684                settings.defaults.inlay_hints = Some(InlayHintSettings {
1685                    enabled: false,
1686                    show_type_hints: false,
1687                    show_parameter_hints: false,
1688                    show_other_hints: false,
1689                })
1690            });
1691        });
1692    });
1693    cx_b.update(|cx| {
1694        cx.update_global(|store: &mut SettingsStore, cx| {
1695            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1696                settings.defaults.inlay_hints = Some(InlayHintSettings {
1697                    enabled: true,
1698                    show_type_hints: true,
1699                    show_parameter_hints: true,
1700                    show_other_hints: true,
1701                })
1702            });
1703        });
1704    });
1705
1706    let mut language = Language::new(
1707        LanguageConfig {
1708            name: "Rust".into(),
1709            path_suffixes: vec!["rs".to_string()],
1710            ..Default::default()
1711        },
1712        Some(tree_sitter_rust::language()),
1713    );
1714    let mut fake_language_servers = language
1715        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1716            capabilities: lsp::ServerCapabilities {
1717                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1718                ..Default::default()
1719            },
1720            ..Default::default()
1721        }))
1722        .await;
1723    let language = Arc::new(language);
1724    client_a.language_registry().add(Arc::clone(&language));
1725    client_b.language_registry().add(language);
1726
1727    client_a
1728        .fs()
1729        .insert_tree(
1730            "/a",
1731            json!({
1732                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1733                "other.rs": "// Test file",
1734            }),
1735        )
1736        .await;
1737    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1738    active_call_a
1739        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1740        .await
1741        .unwrap();
1742    let project_id = active_call_a
1743        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1744        .await
1745        .unwrap();
1746
1747    let project_b = client_b.build_remote_project(project_id, cx_b).await;
1748    active_call_b
1749        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1750        .await
1751        .unwrap();
1752
1753    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
1754    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1755
1756    cx_a.background_executor.start_waiting();
1757
1758    let editor_a = workspace_a
1759        .update(cx_a, |workspace, cx| {
1760            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1761        })
1762        .await
1763        .unwrap()
1764        .downcast::<Editor>()
1765        .unwrap();
1766
1767    let editor_b = workspace_b
1768        .update(cx_b, |workspace, cx| {
1769            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1770        })
1771        .await
1772        .unwrap()
1773        .downcast::<Editor>()
1774        .unwrap();
1775
1776    let other_hints = Arc::new(AtomicBool::new(false));
1777    let fake_language_server = fake_language_servers.next().await.unwrap();
1778    let closure_other_hints = Arc::clone(&other_hints);
1779    fake_language_server
1780        .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1781            let task_other_hints = Arc::clone(&closure_other_hints);
1782            async move {
1783                assert_eq!(
1784                    params.text_document.uri,
1785                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
1786                );
1787                let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
1788                let character = if other_hints { 0 } else { 2 };
1789                let label = if other_hints {
1790                    "other hint"
1791                } else {
1792                    "initial hint"
1793                };
1794                Ok(Some(vec![lsp::InlayHint {
1795                    position: lsp::Position::new(0, character),
1796                    label: lsp::InlayHintLabel::String(label.to_string()),
1797                    kind: None,
1798                    text_edits: None,
1799                    tooltip: None,
1800                    padding_left: None,
1801                    padding_right: None,
1802                    data: None,
1803                }]))
1804            }
1805        })
1806        .next()
1807        .await
1808        .unwrap();
1809    executor.finish_waiting();
1810
1811    executor.run_until_parked();
1812    editor_a.update(cx_a, |editor, _| {
1813        assert!(
1814            extract_hint_labels(editor).is_empty(),
1815            "Host should get no hints due to them turned off"
1816        );
1817        let inlay_cache = editor.inlay_hint_cache();
1818        assert_eq!(
1819            inlay_cache.version(),
1820            0,
1821            "Turned off hints should not generate version updates"
1822        );
1823    });
1824
1825    executor.run_until_parked();
1826    editor_b.update(cx_b, |editor, _| {
1827        assert_eq!(
1828            vec!["initial hint".to_string()],
1829            extract_hint_labels(editor),
1830            "Client should get its first hints when opens an editor"
1831        );
1832        let inlay_cache = editor.inlay_hint_cache();
1833        assert_eq!(
1834            inlay_cache.version(),
1835            1,
1836            "Should update cache verison after first hints"
1837        );
1838    });
1839
1840    other_hints.fetch_or(true, atomic::Ordering::Release);
1841    fake_language_server
1842        .request::<lsp::request::InlayHintRefreshRequest>(())
1843        .await
1844        .expect("inlay refresh request failed");
1845    executor.run_until_parked();
1846    editor_a.update(cx_a, |editor, _| {
1847        assert!(
1848            extract_hint_labels(editor).is_empty(),
1849            "Host should get nop hints due to them turned off, even after the /refresh"
1850        );
1851        let inlay_cache = editor.inlay_hint_cache();
1852        assert_eq!(
1853            inlay_cache.version(),
1854            0,
1855            "Turned off hints should not generate version updates, again"
1856        );
1857    });
1858
1859    executor.run_until_parked();
1860    editor_b.update(cx_b, |editor, _| {
1861        assert_eq!(
1862            vec!["other hint".to_string()],
1863            extract_hint_labels(editor),
1864            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
1865        );
1866        let inlay_cache = editor.inlay_hint_cache();
1867        assert_eq!(
1868            inlay_cache.version(),
1869            2,
1870            "Guest should accepted all edits and bump its cache version every time"
1871        );
1872    });
1873}
1874
1875fn extract_hint_labels(editor: &Editor) -> Vec<String> {
1876    let mut labels = Vec::new();
1877    for hint in editor.inlay_hint_cache().hints() {
1878        match hint.label {
1879            project::InlayHintLabel::String(s) => labels.push(s),
1880            _ => unreachable!(),
1881        }
1882    }
1883    labels
1884}