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