editor_tests.rs

   1use crate::{
   2    rpc::RECONNECT_TIMEOUT,
   3    tests::{TestServer, rust_lang},
   4};
   5use call::ActiveCall;
   6use editor::{
   7    DocumentColorsRenderMode, Editor, EditorSettings, RowInfo,
   8    actions::{
   9        ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst,
  10        ExpandMacroRecursively, Redo, Rename, SelectAll, ToggleCodeActions, Undo,
  11    },
  12    test::{
  13        editor_test_context::{AssertionContextManager, EditorTestContext},
  14        expand_macro_recursively,
  15    },
  16};
  17use fs::Fs;
  18use futures::StreamExt;
  19use gpui::{App, Rgba, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
  20use indoc::indoc;
  21use language::{
  22    FakeLspAdapter,
  23    language_settings::{AllLanguageSettings, InlayHintSettings},
  24};
  25use project::{
  26    ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT,
  27    lsp_store::{
  28        lsp_ext_command::{ExpandedMacro, LspExtExpandMacro},
  29        rust_analyzer_ext::RUST_ANALYZER_NAME,
  30    },
  31    project_settings::{InlineBlameSettings, ProjectSettings},
  32};
  33use recent_projects::disconnected_overlay::DisconnectedOverlay;
  34use rpc::RECEIVE_TIMEOUT;
  35use serde_json::json;
  36use settings::SettingsStore;
  37use std::{
  38    ops::Range,
  39    path::{Path, PathBuf},
  40    sync::{
  41        Arc,
  42        atomic::{self, AtomicBool, AtomicUsize},
  43    },
  44};
  45use text::Point;
  46use util::{path, uri};
  47use workspace::{CloseIntent, Workspace};
  48
  49#[gpui::test(iterations = 10)]
  50async fn test_host_disconnect(
  51    cx_a: &mut TestAppContext,
  52    cx_b: &mut TestAppContext,
  53    cx_c: &mut TestAppContext,
  54) {
  55    let mut server = TestServer::start(cx_a.executor()).await;
  56    let client_a = server.create_client(cx_a, "user_a").await;
  57    let client_b = server.create_client(cx_b, "user_b").await;
  58    let client_c = server.create_client(cx_c, "user_c").await;
  59    server
  60        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
  61        .await;
  62
  63    cx_b.update(editor::init);
  64    cx_b.update(recent_projects::init);
  65
  66    client_a
  67        .fs()
  68        .insert_tree(
  69            "/a",
  70            json!({
  71                "a.txt": "a-contents",
  72                "b.txt": "b-contents",
  73            }),
  74        )
  75        .await;
  76
  77    let active_call_a = cx_a.read(ActiveCall::global);
  78    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
  79
  80    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
  81    let project_id = active_call_a
  82        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
  83        .await
  84        .unwrap();
  85
  86    let project_b = client_b.join_remote_project(project_id, cx_b).await;
  87    cx_a.background_executor.run_until_parked();
  88
  89    assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
  90
  91    let workspace_b = cx_b.add_window(|window, cx| {
  92        Workspace::new(
  93            None,
  94            project_b.clone(),
  95            client_b.app_state.clone(),
  96            window,
  97            cx,
  98        )
  99    });
 100    let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
 101    let workspace_b_view = workspace_b.root(cx_b).unwrap();
 102
 103    let editor_b = workspace_b
 104        .update(cx_b, |workspace, window, cx| {
 105            workspace.open_path((worktree_id, "b.txt"), None, true, window, cx)
 106        })
 107        .unwrap()
 108        .await
 109        .unwrap()
 110        .downcast::<Editor>()
 111        .unwrap();
 112
 113    //TODO: focus
 114    assert!(cx_b.update_window_entity(&editor_b, |editor, window, _| editor.is_focused(window)));
 115    editor_b.update_in(cx_b, |editor, window, cx| editor.insert("X", window, cx));
 116
 117    cx_b.update(|_, cx| {
 118        assert!(workspace_b_view.read(cx).is_edited());
 119    });
 120
 121    // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
 122    server.forbid_connections();
 123    server.disconnect_client(client_a.peer_id().unwrap());
 124    cx_a.background_executor
 125        .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 126
 127    project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());
 128
 129    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 130
 131    project_b.read_with(cx_b, |project, cx| project.is_read_only(cx));
 132
 133    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
 134
 135    // Ensure client B's edited state is reset and that the whole window is blurred.
 136    workspace_b
 137        .update(cx_b, |workspace, _, cx| {
 138            assert!(workspace.active_modal::<DisconnectedOverlay>(cx).is_some());
 139            assert!(!workspace.is_edited());
 140        })
 141        .unwrap();
 142
 143    // Ensure client B is not prompted to save edits when closing window after disconnecting.
 144    let can_close = workspace_b
 145        .update(cx_b, |workspace, window, cx| {
 146            workspace.prepare_to_close(CloseIntent::Quit, window, cx)
 147        })
 148        .unwrap()
 149        .await
 150        .unwrap();
 151    assert!(can_close);
 152
 153    // Allow client A to reconnect to the server.
 154    server.allow_connections();
 155    cx_a.background_executor.advance_clock(RECEIVE_TIMEOUT);
 156
 157    // Client B calls client A again after they reconnected.
 158    let active_call_b = cx_b.read(ActiveCall::global);
 159    active_call_b
 160        .update(cx_b, |call, cx| {
 161            call.invite(client_a.user_id().unwrap(), None, cx)
 162        })
 163        .await
 164        .unwrap();
 165    cx_a.background_executor.run_until_parked();
 166    active_call_a
 167        .update(cx_a, |call, cx| call.accept_incoming(cx))
 168        .await
 169        .unwrap();
 170
 171    active_call_a
 172        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 173        .await
 174        .unwrap();
 175
 176    // Drop client A's connection again. We should still unshare it successfully.
 177    server.forbid_connections();
 178    server.disconnect_client(client_a.peer_id().unwrap());
 179    cx_a.background_executor
 180        .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 181
 182    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 183}
 184
 185#[gpui::test]
 186async fn test_newline_above_or_below_does_not_move_guest_cursor(
 187    cx_a: &mut TestAppContext,
 188    cx_b: &mut TestAppContext,
 189) {
 190    let mut server = TestServer::start(cx_a.executor()).await;
 191    let client_a = server.create_client(cx_a, "user_a").await;
 192    let client_b = server.create_client(cx_b, "user_b").await;
 193    let executor = cx_a.executor();
 194    server
 195        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 196        .await;
 197    let active_call_a = cx_a.read(ActiveCall::global);
 198
 199    client_a
 200        .fs()
 201        .insert_tree(path!("/dir"), json!({ "a.txt": "Some text\n" }))
 202        .await;
 203    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
 204    let project_id = active_call_a
 205        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 206        .await
 207        .unwrap();
 208
 209    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 210
 211    // Open a buffer as client A
 212    let buffer_a = project_a
 213        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 214        .await
 215        .unwrap();
 216    let cx_a = cx_a.add_empty_window();
 217    let editor_a = cx_a
 218        .new_window_entity(|window, cx| Editor::for_buffer(buffer_a, Some(project_a), window, cx));
 219
 220    let mut editor_cx_a = EditorTestContext {
 221        cx: cx_a.clone(),
 222        window: cx_a.window_handle(),
 223        editor: editor_a,
 224        assertion_cx: AssertionContextManager::new(),
 225    };
 226
 227    let cx_b = cx_b.add_empty_window();
 228    // Open a buffer as client B
 229    let buffer_b = project_b
 230        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 231        .await
 232        .unwrap();
 233    let editor_b = cx_b
 234        .new_window_entity(|window, cx| Editor::for_buffer(buffer_b, Some(project_b), window, cx));
 235
 236    let mut editor_cx_b = EditorTestContext {
 237        cx: cx_b.clone(),
 238        window: cx_b.window_handle(),
 239        editor: editor_b,
 240        assertion_cx: AssertionContextManager::new(),
 241    };
 242
 243    // Test newline above
 244    editor_cx_a.set_selections_state(indoc! {"
 245        Some textˇ
 246    "});
 247    editor_cx_b.set_selections_state(indoc! {"
 248        Some textˇ
 249    "});
 250    editor_cx_a.update_editor(|editor, window, cx| {
 251        editor.newline_above(&editor::actions::NewlineAbove, window, cx)
 252    });
 253    executor.run_until_parked();
 254    editor_cx_a.assert_editor_state(indoc! {"
 255        ˇ
 256        Some text
 257    "});
 258    editor_cx_b.assert_editor_state(indoc! {"
 259
 260        Some textˇ
 261    "});
 262
 263    // Test newline below
 264    editor_cx_a.set_selections_state(indoc! {"
 265
 266        Some textˇ
 267    "});
 268    editor_cx_b.set_selections_state(indoc! {"
 269
 270        Some textˇ
 271    "});
 272    editor_cx_a.update_editor(|editor, window, cx| {
 273        editor.newline_below(&editor::actions::NewlineBelow, window, cx)
 274    });
 275    executor.run_until_parked();
 276    editor_cx_a.assert_editor_state(indoc! {"
 277
 278        Some text
 279        ˇ
 280    "});
 281    editor_cx_b.assert_editor_state(indoc! {"
 282
 283        Some textˇ
 284
 285    "});
 286}
 287
 288#[gpui::test(iterations = 10)]
 289async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 290    let mut server = TestServer::start(cx_a.executor()).await;
 291    let client_a = server.create_client(cx_a, "user_a").await;
 292    let client_b = server.create_client(cx_b, "user_b").await;
 293    server
 294        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 295        .await;
 296    let active_call_a = cx_a.read(ActiveCall::global);
 297
 298    client_a.language_registry().add(rust_lang());
 299    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
 300        "Rust",
 301        FakeLspAdapter {
 302            capabilities: lsp::ServerCapabilities {
 303                completion_provider: Some(lsp::CompletionOptions {
 304                    trigger_characters: Some(vec![".".to_string()]),
 305                    resolve_provider: Some(true),
 306                    ..Default::default()
 307                }),
 308                ..Default::default()
 309            },
 310            ..Default::default()
 311        },
 312    );
 313
 314    client_a
 315        .fs()
 316        .insert_tree(
 317            path!("/a"),
 318            json!({
 319                "main.rs": "fn main() { a }",
 320                "other.rs": "",
 321            }),
 322        )
 323        .await;
 324    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
 325    let project_id = active_call_a
 326        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 327        .await
 328        .unwrap();
 329    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 330
 331    // Open a file in an editor as the guest.
 332    let buffer_b = project_b
 333        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
 334        .await
 335        .unwrap();
 336    let cx_b = cx_b.add_empty_window();
 337    let editor_b = cx_b.new_window_entity(|window, cx| {
 338        Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), window, cx)
 339    });
 340
 341    let fake_language_server = fake_language_servers.next().await.unwrap();
 342    cx_a.background_executor.run_until_parked();
 343
 344    buffer_b.read_with(cx_b, |buffer, _| {
 345        assert!(!buffer.completion_triggers().is_empty())
 346    });
 347
 348    // Type a completion trigger character as the guest.
 349    editor_b.update_in(cx_b, |editor, window, cx| {
 350        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
 351        editor.handle_input(".", window, cx);
 352    });
 353    cx_b.focus(&editor_b);
 354
 355    // Receive a completion request as the host's language server.
 356    // Return some completions from the host's language server.
 357    cx_a.executor().start_waiting();
 358    fake_language_server
 359        .set_request_handler::<lsp::request::Completion, _, _>(|params, _| async move {
 360            assert_eq!(
 361                params.text_document_position.text_document.uri,
 362                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 363            );
 364            assert_eq!(
 365                params.text_document_position.position,
 366                lsp::Position::new(0, 14),
 367            );
 368
 369            Ok(Some(lsp::CompletionResponse::Array(vec![
 370                lsp::CompletionItem {
 371                    label: "first_method(…)".into(),
 372                    detail: Some("fn(&mut self, B) -> C".into()),
 373                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 374                        new_text: "first_method($1)".to_string(),
 375                        range: lsp::Range::new(
 376                            lsp::Position::new(0, 14),
 377                            lsp::Position::new(0, 14),
 378                        ),
 379                    })),
 380                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 381                    ..Default::default()
 382                },
 383                lsp::CompletionItem {
 384                    label: "second_method(…)".into(),
 385                    detail: Some("fn(&mut self, C) -> D<E>".into()),
 386                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 387                        new_text: "second_method()".to_string(),
 388                        range: lsp::Range::new(
 389                            lsp::Position::new(0, 14),
 390                            lsp::Position::new(0, 14),
 391                        ),
 392                    })),
 393                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 394                    ..Default::default()
 395                },
 396            ])))
 397        })
 398        .next()
 399        .await
 400        .unwrap();
 401    cx_a.executor().finish_waiting();
 402
 403    // Open the buffer on the host.
 404    let buffer_a = project_a
 405        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
 406        .await
 407        .unwrap();
 408    cx_a.executor().run_until_parked();
 409
 410    buffer_a.read_with(cx_a, |buffer, _| {
 411        assert_eq!(buffer.text(), "fn main() { a. }")
 412    });
 413
 414    // Confirm a completion on the guest.
 415    editor_b.update_in(cx_b, |editor, window, cx| {
 416        assert!(editor.context_menu_visible());
 417        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
 418        assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
 419    });
 420
 421    // Return a resolved completion from the host's language server.
 422    // The resolved completion has an additional text edit.
 423    fake_language_server.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
 424        |params, _| async move {
 425            assert_eq!(params.label, "first_method(…)");
 426            Ok(lsp::CompletionItem {
 427                label: "first_method(…)".into(),
 428                detail: Some("fn(&mut self, B) -> C".into()),
 429                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 430                    new_text: "first_method($1)".to_string(),
 431                    range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
 432                })),
 433                additional_text_edits: Some(vec![lsp::TextEdit {
 434                    new_text: "use d::SomeTrait;\n".to_string(),
 435                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 436                }]),
 437                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 438                ..Default::default()
 439            })
 440        },
 441    );
 442
 443    // The additional edit is applied.
 444    cx_a.executor().run_until_parked();
 445
 446    buffer_a.read_with(cx_a, |buffer, _| {
 447        assert_eq!(
 448            buffer.text(),
 449            "use d::SomeTrait;\nfn main() { a.first_method() }"
 450        );
 451    });
 452
 453    buffer_b.read_with(cx_b, |buffer, _| {
 454        assert_eq!(
 455            buffer.text(),
 456            "use d::SomeTrait;\nfn main() { a.first_method() }"
 457        );
 458    });
 459
 460    // Now we do a second completion, this time to ensure that documentation/snippets are
 461    // resolved
 462    editor_b.update_in(cx_b, |editor, window, cx| {
 463        editor.change_selections(None, window, cx, |s| s.select_ranges([46..46]));
 464        editor.handle_input("; a", window, cx);
 465        editor.handle_input(".", window, cx);
 466    });
 467
 468    buffer_b.read_with(cx_b, |buffer, _| {
 469        assert_eq!(
 470            buffer.text(),
 471            "use d::SomeTrait;\nfn main() { a.first_method(); a. }"
 472        );
 473    });
 474
 475    let mut completion_response = fake_language_server
 476        .set_request_handler::<lsp::request::Completion, _, _>(|params, _| async move {
 477            assert_eq!(
 478                params.text_document_position.text_document.uri,
 479                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 480            );
 481            assert_eq!(
 482                params.text_document_position.position,
 483                lsp::Position::new(1, 32),
 484            );
 485
 486            Ok(Some(lsp::CompletionResponse::Array(vec![
 487                lsp::CompletionItem {
 488                    label: "third_method(…)".into(),
 489                    detail: Some("fn(&mut self, B, C, D) -> E".into()),
 490                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 491                        // no snippet placehodlers
 492                        new_text: "third_method".to_string(),
 493                        range: lsp::Range::new(
 494                            lsp::Position::new(1, 32),
 495                            lsp::Position::new(1, 32),
 496                        ),
 497                    })),
 498                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 499                    documentation: None,
 500                    ..Default::default()
 501                },
 502            ])))
 503        });
 504
 505    // The completion now gets a new `text_edit.new_text` when resolving the completion item
 506    let mut resolve_completion_response = fake_language_server
 507        .set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(|params, _| async move {
 508            assert_eq!(params.label, "third_method(…)");
 509            Ok(lsp::CompletionItem {
 510                label: "third_method(…)".into(),
 511                detail: Some("fn(&mut self, B, C, D) -> E".into()),
 512                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 513                    // Now it's a snippet
 514                    new_text: "third_method($1, $2, $3)".to_string(),
 515                    range: lsp::Range::new(lsp::Position::new(1, 32), lsp::Position::new(1, 32)),
 516                })),
 517                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 518                documentation: Some(lsp::Documentation::String(
 519                    "this is the documentation".into(),
 520                )),
 521                ..Default::default()
 522            })
 523        });
 524
 525    cx_b.executor().run_until_parked();
 526
 527    completion_response.next().await.unwrap();
 528
 529    editor_b.update_in(cx_b, |editor, window, cx| {
 530        assert!(editor.context_menu_visible());
 531        editor.context_menu_first(&ContextMenuFirst {}, window, cx);
 532    });
 533
 534    resolve_completion_response.next().await.unwrap();
 535    cx_b.executor().run_until_parked();
 536
 537    // When accepting the completion, the snippet is insert.
 538    editor_b.update_in(cx_b, |editor, window, cx| {
 539        assert!(editor.context_menu_visible());
 540        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
 541        assert_eq!(
 542            editor.text(cx),
 543            "use d::SomeTrait;\nfn main() { a.first_method(); a.third_method(, , ) }"
 544        );
 545    });
 546}
 547
 548#[gpui::test(iterations = 10)]
 549async fn test_collaborating_with_code_actions(
 550    cx_a: &mut TestAppContext,
 551    cx_b: &mut TestAppContext,
 552) {
 553    let mut server = TestServer::start(cx_a.executor()).await;
 554    let client_a = server.create_client(cx_a, "user_a").await;
 555    //
 556    let client_b = server.create_client(cx_b, "user_b").await;
 557    server
 558        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 559        .await;
 560    let active_call_a = cx_a.read(ActiveCall::global);
 561
 562    cx_b.update(editor::init);
 563
 564    // Set up a fake language server.
 565    client_a.language_registry().add(rust_lang());
 566    let mut fake_language_servers = client_a
 567        .language_registry()
 568        .register_fake_lsp("Rust", FakeLspAdapter::default());
 569
 570    client_a
 571        .fs()
 572        .insert_tree(
 573            path!("/a"),
 574            json!({
 575                "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
 576                "other.rs": "pub fn foo() -> usize { 4 }",
 577            }),
 578        )
 579        .await;
 580    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
 581    let project_id = active_call_a
 582        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 583        .await
 584        .unwrap();
 585
 586    // Join the project as client B.
 587    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 588    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 589    let editor_b = workspace_b
 590        .update_in(cx_b, |workspace, window, cx| {
 591            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
 592        })
 593        .await
 594        .unwrap()
 595        .downcast::<Editor>()
 596        .unwrap();
 597
 598    let mut fake_language_server = fake_language_servers.next().await.unwrap();
 599    let mut requests = fake_language_server
 600        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
 601            assert_eq!(
 602                params.text_document.uri,
 603                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 604            );
 605            assert_eq!(params.range.start, lsp::Position::new(0, 0));
 606            assert_eq!(params.range.end, lsp::Position::new(0, 0));
 607            Ok(None)
 608        });
 609    cx_a.background_executor
 610        .advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
 611    requests.next().await;
 612
 613    // Move cursor to a location that contains code actions.
 614    editor_b.update_in(cx_b, |editor, window, cx| {
 615        editor.change_selections(None, window, cx, |s| {
 616            s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
 617        });
 618    });
 619    cx_b.focus(&editor_b);
 620
 621    let mut requests = fake_language_server
 622        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
 623            assert_eq!(
 624                params.text_document.uri,
 625                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 626            );
 627            assert_eq!(params.range.start, lsp::Position::new(1, 31));
 628            assert_eq!(params.range.end, lsp::Position::new(1, 31));
 629
 630            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 631                lsp::CodeAction {
 632                    title: "Inline into all callers".to_string(),
 633                    edit: Some(lsp::WorkspaceEdit {
 634                        changes: Some(
 635                            [
 636                                (
 637                                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 638                                    vec![lsp::TextEdit::new(
 639                                        lsp::Range::new(
 640                                            lsp::Position::new(1, 22),
 641                                            lsp::Position::new(1, 34),
 642                                        ),
 643                                        "4".to_string(),
 644                                    )],
 645                                ),
 646                                (
 647                                    lsp::Url::from_file_path(path!("/a/other.rs")).unwrap(),
 648                                    vec![lsp::TextEdit::new(
 649                                        lsp::Range::new(
 650                                            lsp::Position::new(0, 0),
 651                                            lsp::Position::new(0, 27),
 652                                        ),
 653                                        "".to_string(),
 654                                    )],
 655                                ),
 656                            ]
 657                            .into_iter()
 658                            .collect(),
 659                        ),
 660                        ..Default::default()
 661                    }),
 662                    data: Some(json!({
 663                        "codeActionParams": {
 664                            "range": {
 665                                "start": {"line": 1, "column": 31},
 666                                "end": {"line": 1, "column": 31},
 667                            }
 668                        }
 669                    })),
 670                    ..Default::default()
 671                },
 672            )]))
 673        });
 674    cx_a.background_executor
 675        .advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
 676    requests.next().await;
 677
 678    // Toggle code actions and wait for them to display.
 679    editor_b.update_in(cx_b, |editor, window, cx| {
 680        editor.toggle_code_actions(
 681            &ToggleCodeActions {
 682                deployed_from: None,
 683                quick_launch: false,
 684            },
 685            window,
 686            cx,
 687        );
 688    });
 689    cx_a.background_executor.run_until_parked();
 690
 691    editor_b.update(cx_b, |editor, _| assert!(editor.context_menu_visible()));
 692
 693    fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
 694
 695    // Confirming the code action will trigger a resolve request.
 696    let confirm_action = editor_b
 697        .update_in(cx_b, |editor, window, cx| {
 698            Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, window, cx)
 699        })
 700        .unwrap();
 701    fake_language_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>(
 702        |_, _| async move {
 703            Ok(lsp::CodeAction {
 704                title: "Inline into all callers".to_string(),
 705                edit: Some(lsp::WorkspaceEdit {
 706                    changes: Some(
 707                        [
 708                            (
 709                                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
 710                                vec![lsp::TextEdit::new(
 711                                    lsp::Range::new(
 712                                        lsp::Position::new(1, 22),
 713                                        lsp::Position::new(1, 34),
 714                                    ),
 715                                    "4".to_string(),
 716                                )],
 717                            ),
 718                            (
 719                                lsp::Url::from_file_path(path!("/a/other.rs")).unwrap(),
 720                                vec![lsp::TextEdit::new(
 721                                    lsp::Range::new(
 722                                        lsp::Position::new(0, 0),
 723                                        lsp::Position::new(0, 27),
 724                                    ),
 725                                    "".to_string(),
 726                                )],
 727                            ),
 728                        ]
 729                        .into_iter()
 730                        .collect(),
 731                    ),
 732                    ..Default::default()
 733                }),
 734                ..Default::default()
 735            })
 736        },
 737    );
 738
 739    // After the action is confirmed, an editor containing both modified files is opened.
 740    confirm_action.await.unwrap();
 741
 742    let code_action_editor = workspace_b.update(cx_b, |workspace, cx| {
 743        workspace
 744            .active_item(cx)
 745            .unwrap()
 746            .downcast::<Editor>()
 747            .unwrap()
 748    });
 749    code_action_editor.update_in(cx_b, |editor, window, cx| {
 750        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 751        editor.undo(&Undo, window, cx);
 752        assert_eq!(
 753            editor.text(cx),
 754            "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
 755        );
 756        editor.redo(&Redo, window, cx);
 757        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 758    });
 759}
 760
 761#[gpui::test(iterations = 10)]
 762async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 763    let mut server = TestServer::start(cx_a.executor()).await;
 764    let client_a = server.create_client(cx_a, "user_a").await;
 765    let client_b = server.create_client(cx_b, "user_b").await;
 766    server
 767        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 768        .await;
 769    let active_call_a = cx_a.read(ActiveCall::global);
 770
 771    cx_b.update(editor::init);
 772
 773    // Set up a fake language server.
 774    client_a.language_registry().add(rust_lang());
 775    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
 776        "Rust",
 777        FakeLspAdapter {
 778            capabilities: lsp::ServerCapabilities {
 779                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
 780                    prepare_provider: Some(true),
 781                    work_done_progress_options: Default::default(),
 782                })),
 783                ..Default::default()
 784            },
 785            ..Default::default()
 786        },
 787    );
 788
 789    client_a
 790        .fs()
 791        .insert_tree(
 792            path!("/dir"),
 793            json!({
 794                "one.rs": "const ONE: usize = 1;",
 795                "two.rs": "const TWO: usize = one::ONE + one::ONE;"
 796            }),
 797        )
 798        .await;
 799    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
 800    let project_id = active_call_a
 801        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 802        .await
 803        .unwrap();
 804    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 805
 806    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 807    let editor_b = workspace_b
 808        .update_in(cx_b, |workspace, window, cx| {
 809            workspace.open_path((worktree_id, "one.rs"), None, true, window, cx)
 810        })
 811        .await
 812        .unwrap()
 813        .downcast::<Editor>()
 814        .unwrap();
 815    let fake_language_server = fake_language_servers.next().await.unwrap();
 816
 817    // Move cursor to a location that can be renamed.
 818    let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
 819        editor.change_selections(None, window, cx, |s| s.select_ranges([7..7]));
 820        editor.rename(&Rename, window, cx).unwrap()
 821    });
 822
 823    fake_language_server
 824        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
 825            assert_eq!(
 826                params.text_document.uri.as_str(),
 827                uri!("file:///dir/one.rs")
 828            );
 829            assert_eq!(params.position, lsp::Position::new(0, 7));
 830            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
 831                lsp::Position::new(0, 6),
 832                lsp::Position::new(0, 9),
 833            ))))
 834        })
 835        .next()
 836        .await
 837        .unwrap();
 838    prepare_rename.await.unwrap();
 839    editor_b.update(cx_b, |editor, cx| {
 840        use editor::ToOffset;
 841        let rename = editor.pending_rename().unwrap();
 842        let buffer = editor.buffer().read(cx).snapshot(cx);
 843        assert_eq!(
 844            rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
 845            6..9
 846        );
 847        rename.editor.update(cx, |rename_editor, cx| {
 848            let rename_selection = rename_editor.selections.newest::<usize>(cx);
 849            assert_eq!(
 850                rename_selection.range(),
 851                0..3,
 852                "Rename that was triggered from zero selection caret, should propose the whole word."
 853            );
 854            rename_editor.buffer().update(cx, |rename_buffer, cx| {
 855                rename_buffer.edit([(0..3, "THREE")], None, cx);
 856            });
 857        });
 858    });
 859
 860    // Cancel the rename, and repeat the same, but use selections instead of cursor movement
 861    editor_b.update_in(cx_b, |editor, window, cx| {
 862        editor.cancel(&editor::actions::Cancel, window, cx);
 863    });
 864    let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
 865        editor.change_selections(None, window, cx, |s| s.select_ranges([7..8]));
 866        editor.rename(&Rename, window, cx).unwrap()
 867    });
 868
 869    fake_language_server
 870        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
 871            assert_eq!(
 872                params.text_document.uri.as_str(),
 873                uri!("file:///dir/one.rs")
 874            );
 875            assert_eq!(params.position, lsp::Position::new(0, 8));
 876            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
 877                lsp::Position::new(0, 6),
 878                lsp::Position::new(0, 9),
 879            ))))
 880        })
 881        .next()
 882        .await
 883        .unwrap();
 884    prepare_rename.await.unwrap();
 885    editor_b.update(cx_b, |editor, cx| {
 886        use editor::ToOffset;
 887        let rename = editor.pending_rename().unwrap();
 888        let buffer = editor.buffer().read(cx).snapshot(cx);
 889        let lsp_rename_start = rename.range.start.to_offset(&buffer);
 890        let lsp_rename_end = rename.range.end.to_offset(&buffer);
 891        assert_eq!(lsp_rename_start..lsp_rename_end, 6..9);
 892        rename.editor.update(cx, |rename_editor, cx| {
 893            let rename_selection = rename_editor.selections.newest::<usize>(cx);
 894            assert_eq!(
 895                rename_selection.range(),
 896                1..2,
 897                "Rename that was triggered from a selection, should have the same selection range in the rename proposal"
 898            );
 899            rename_editor.buffer().update(cx, |rename_buffer, cx| {
 900                rename_buffer.edit([(0..lsp_rename_end - lsp_rename_start, "THREE")], None, cx);
 901            });
 902        });
 903    });
 904
 905    let confirm_rename = editor_b.update_in(cx_b, |editor, window, cx| {
 906        Editor::confirm_rename(editor, &ConfirmRename, window, cx).unwrap()
 907    });
 908    fake_language_server
 909        .set_request_handler::<lsp::request::Rename, _, _>(|params, _| async move {
 910            assert_eq!(
 911                params.text_document_position.text_document.uri.as_str(),
 912                uri!("file:///dir/one.rs")
 913            );
 914            assert_eq!(
 915                params.text_document_position.position,
 916                lsp::Position::new(0, 6)
 917            );
 918            assert_eq!(params.new_name, "THREE");
 919            Ok(Some(lsp::WorkspaceEdit {
 920                changes: Some(
 921                    [
 922                        (
 923                            lsp::Url::from_file_path(path!("/dir/one.rs")).unwrap(),
 924                            vec![lsp::TextEdit::new(
 925                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
 926                                "THREE".to_string(),
 927                            )],
 928                        ),
 929                        (
 930                            lsp::Url::from_file_path(path!("/dir/two.rs")).unwrap(),
 931                            vec![
 932                                lsp::TextEdit::new(
 933                                    lsp::Range::new(
 934                                        lsp::Position::new(0, 24),
 935                                        lsp::Position::new(0, 27),
 936                                    ),
 937                                    "THREE".to_string(),
 938                                ),
 939                                lsp::TextEdit::new(
 940                                    lsp::Range::new(
 941                                        lsp::Position::new(0, 35),
 942                                        lsp::Position::new(0, 38),
 943                                    ),
 944                                    "THREE".to_string(),
 945                                ),
 946                            ],
 947                        ),
 948                    ]
 949                    .into_iter()
 950                    .collect(),
 951                ),
 952                ..Default::default()
 953            }))
 954        })
 955        .next()
 956        .await
 957        .unwrap();
 958    confirm_rename.await.unwrap();
 959
 960    let rename_editor = workspace_b.update(cx_b, |workspace, cx| {
 961        workspace.active_item_as::<Editor>(cx).unwrap()
 962    });
 963
 964    rename_editor.update_in(cx_b, |editor, window, cx| {
 965        assert_eq!(
 966            editor.text(cx),
 967            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
 968        );
 969        editor.undo(&Undo, window, cx);
 970        assert_eq!(
 971            editor.text(cx),
 972            "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
 973        );
 974        editor.redo(&Redo, window, cx);
 975        assert_eq!(
 976            editor.text(cx),
 977            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
 978        );
 979    });
 980
 981    // Ensure temporary rename edits cannot be undone/redone.
 982    editor_b.update_in(cx_b, |editor, window, cx| {
 983        editor.undo(&Undo, window, cx);
 984        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
 985        editor.undo(&Undo, window, cx);
 986        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
 987        editor.redo(&Redo, window, cx);
 988        assert_eq!(editor.text(cx), "const THREE: usize = 1;");
 989    })
 990}
 991
 992#[gpui::test(iterations = 10)]
 993async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 994    let mut server = TestServer::start(cx_a.executor()).await;
 995    let executor = cx_a.executor();
 996    let client_a = server.create_client(cx_a, "user_a").await;
 997    let client_b = server.create_client(cx_b, "user_b").await;
 998    server
 999        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1000        .await;
1001    let active_call_a = cx_a.read(ActiveCall::global);
1002
1003    cx_b.update(editor::init);
1004
1005    client_a.language_registry().add(rust_lang());
1006    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1007        "Rust",
1008        FakeLspAdapter {
1009            name: "the-language-server",
1010            ..Default::default()
1011        },
1012    );
1013
1014    client_a
1015        .fs()
1016        .insert_tree(
1017            path!("/dir"),
1018            json!({
1019                "main.rs": "const ONE: usize = 1;",
1020            }),
1021        )
1022        .await;
1023    let (project_a, _) = client_a.build_local_project(path!("/dir"), cx_a).await;
1024
1025    let _buffer_a = project_a
1026        .update(cx_a, |p, cx| {
1027            p.open_local_buffer_with_lsp(path!("/dir/main.rs"), cx)
1028        })
1029        .await
1030        .unwrap();
1031
1032    let fake_language_server = fake_language_servers.next().await.unwrap();
1033    fake_language_server.start_progress("the-token").await;
1034
1035    executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
1036    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
1037        token: lsp::NumberOrString::String("the-token".to_string()),
1038        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
1039            lsp::WorkDoneProgressReport {
1040                message: Some("the-message".to_string()),
1041                ..Default::default()
1042            },
1043        )),
1044    });
1045    executor.run_until_parked();
1046
1047    project_a.read_with(cx_a, |project, cx| {
1048        let status = project.language_server_statuses(cx).next().unwrap().1;
1049        assert_eq!(status.name, "the-language-server");
1050        assert_eq!(status.pending_work.len(), 1);
1051        assert_eq!(
1052            status.pending_work["the-token"].message.as_ref().unwrap(),
1053            "the-message"
1054        );
1055    });
1056
1057    let project_id = active_call_a
1058        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1059        .await
1060        .unwrap();
1061    executor.run_until_parked();
1062    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1063
1064    project_b.read_with(cx_b, |project, cx| {
1065        let status = project.language_server_statuses(cx).next().unwrap().1;
1066        assert_eq!(status.name, "the-language-server");
1067    });
1068
1069    executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
1070    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
1071        token: lsp::NumberOrString::String("the-token".to_string()),
1072        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
1073            lsp::WorkDoneProgressReport {
1074                message: Some("the-message-2".to_string()),
1075                ..Default::default()
1076            },
1077        )),
1078    });
1079    executor.run_until_parked();
1080
1081    project_a.read_with(cx_a, |project, cx| {
1082        let status = project.language_server_statuses(cx).next().unwrap().1;
1083        assert_eq!(status.name, "the-language-server");
1084        assert_eq!(status.pending_work.len(), 1);
1085        assert_eq!(
1086            status.pending_work["the-token"].message.as_ref().unwrap(),
1087            "the-message-2"
1088        );
1089    });
1090
1091    project_b.read_with(cx_b, |project, cx| {
1092        let status = project.language_server_statuses(cx).next().unwrap().1;
1093        assert_eq!(status.name, "the-language-server");
1094        assert_eq!(status.pending_work.len(), 1);
1095        assert_eq!(
1096            status.pending_work["the-token"].message.as_ref().unwrap(),
1097            "the-message-2"
1098        );
1099    });
1100}
1101
1102#[gpui::test(iterations = 10)]
1103async fn test_share_project(
1104    cx_a: &mut TestAppContext,
1105    cx_b: &mut TestAppContext,
1106    cx_c: &mut TestAppContext,
1107) {
1108    let executor = cx_a.executor();
1109    let cx_b = cx_b.add_empty_window();
1110    let mut server = TestServer::start(executor.clone()).await;
1111    let client_a = server.create_client(cx_a, "user_a").await;
1112    let client_b = server.create_client(cx_b, "user_b").await;
1113    let client_c = server.create_client(cx_c, "user_c").await;
1114    server
1115        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1116        .await;
1117    let active_call_a = cx_a.read(ActiveCall::global);
1118    let active_call_b = cx_b.read(ActiveCall::global);
1119    let active_call_c = cx_c.read(ActiveCall::global);
1120
1121    client_a
1122        .fs()
1123        .insert_tree(
1124            path!("/a"),
1125            json!({
1126                ".gitignore": "ignored-dir",
1127                "a.txt": "a-contents",
1128                "b.txt": "b-contents",
1129                "ignored-dir": {
1130                    "c.txt": "",
1131                    "d.txt": "",
1132                }
1133            }),
1134        )
1135        .await;
1136
1137    // Invite client B to collaborate on a project
1138    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1139    active_call_a
1140        .update(cx_a, |call, cx| {
1141            call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
1142        })
1143        .await
1144        .unwrap();
1145
1146    // Join that project as client B
1147
1148    let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
1149    executor.run_until_parked();
1150    let call = incoming_call_b.borrow().clone().unwrap();
1151    assert_eq!(call.calling_user.github_login, "user_a");
1152    let initial_project = call.initial_project.unwrap();
1153    active_call_b
1154        .update(cx_b, |call, cx| call.accept_incoming(cx))
1155        .await
1156        .unwrap();
1157    let client_b_peer_id = client_b.peer_id().unwrap();
1158    let project_b = client_b.join_remote_project(initial_project.id, cx_b).await;
1159
1160    let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
1161
1162    executor.run_until_parked();
1163
1164    project_a.read_with(cx_a, |project, _| {
1165        let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
1166        assert_eq!(client_b_collaborator.replica_id, replica_id_b);
1167    });
1168
1169    project_b.read_with(cx_b, |project, cx| {
1170        let worktree = project.worktrees(cx).next().unwrap().read(cx);
1171        assert_eq!(
1172            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1173            [
1174                Path::new(".gitignore"),
1175                Path::new("a.txt"),
1176                Path::new("b.txt"),
1177                Path::new("ignored-dir"),
1178            ]
1179        );
1180    });
1181
1182    project_b
1183        .update(cx_b, |project, cx| {
1184            let worktree = project.worktrees(cx).next().unwrap();
1185            let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
1186            project.expand_entry(worktree_id, entry.id, cx).unwrap()
1187        })
1188        .await
1189        .unwrap();
1190
1191    project_b.read_with(cx_b, |project, cx| {
1192        let worktree = project.worktrees(cx).next().unwrap().read(cx);
1193        assert_eq!(
1194            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1195            [
1196                Path::new(".gitignore"),
1197                Path::new("a.txt"),
1198                Path::new("b.txt"),
1199                Path::new("ignored-dir"),
1200                Path::new("ignored-dir/c.txt"),
1201                Path::new("ignored-dir/d.txt"),
1202            ]
1203        );
1204    });
1205
1206    // Open the same file as client B and client A.
1207    let buffer_b = project_b
1208        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1209        .await
1210        .unwrap();
1211
1212    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
1213
1214    project_a.read_with(cx_a, |project, cx| {
1215        assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
1216    });
1217    let buffer_a = project_a
1218        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1219        .await
1220        .unwrap();
1221
1222    let editor_b =
1223        cx_b.new_window_entity(|window, cx| Editor::for_buffer(buffer_b, None, window, cx));
1224
1225    // Client A sees client B's selection
1226    executor.run_until_parked();
1227
1228    buffer_a.read_with(cx_a, |buffer, _| {
1229        buffer
1230            .snapshot()
1231            .selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
1232            .count()
1233            == 1
1234    });
1235
1236    // Edit the buffer as client B and see that edit as client A.
1237    editor_b.update_in(cx_b, |editor, window, cx| {
1238        editor.handle_input("ok, ", window, cx)
1239    });
1240    executor.run_until_parked();
1241
1242    buffer_a.read_with(cx_a, |buffer, _| {
1243        assert_eq!(buffer.text(), "ok, b-contents")
1244    });
1245
1246    // Client B can invite client C on a project shared by client A.
1247    active_call_b
1248        .update(cx_b, |call, cx| {
1249            call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
1250        })
1251        .await
1252        .unwrap();
1253
1254    let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
1255    executor.run_until_parked();
1256    let call = incoming_call_c.borrow().clone().unwrap();
1257    assert_eq!(call.calling_user.github_login, "user_b");
1258    let initial_project = call.initial_project.unwrap();
1259    active_call_c
1260        .update(cx_c, |call, cx| call.accept_incoming(cx))
1261        .await
1262        .unwrap();
1263    let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
1264
1265    // Client B closes the editor, and client A sees client B's selections removed.
1266    cx_b.update(move |_, _| drop(editor_b));
1267    executor.run_until_parked();
1268
1269    buffer_a.read_with(cx_a, |buffer, _| {
1270        buffer
1271            .snapshot()
1272            .selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
1273            .count()
1274            == 0
1275    });
1276}
1277
1278#[gpui::test(iterations = 10)]
1279async fn test_on_input_format_from_host_to_guest(
1280    cx_a: &mut TestAppContext,
1281    cx_b: &mut TestAppContext,
1282) {
1283    let mut server = TestServer::start(cx_a.executor()).await;
1284    let executor = cx_a.executor();
1285    let client_a = server.create_client(cx_a, "user_a").await;
1286    let client_b = server.create_client(cx_b, "user_b").await;
1287    server
1288        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1289        .await;
1290    let active_call_a = cx_a.read(ActiveCall::global);
1291
1292    client_a.language_registry().add(rust_lang());
1293    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1294        "Rust",
1295        FakeLspAdapter {
1296            capabilities: lsp::ServerCapabilities {
1297                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1298                    first_trigger_character: ":".to_string(),
1299                    more_trigger_character: Some(vec![">".to_string()]),
1300                }),
1301                ..Default::default()
1302            },
1303            ..Default::default()
1304        },
1305    );
1306
1307    client_a
1308        .fs()
1309        .insert_tree(
1310            path!("/a"),
1311            json!({
1312                "main.rs": "fn main() { a }",
1313                "other.rs": "// Test file",
1314            }),
1315        )
1316        .await;
1317    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1318    let project_id = active_call_a
1319        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1320        .await
1321        .unwrap();
1322    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1323
1324    // Open a file in an editor as the host.
1325    let buffer_a = project_a
1326        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1327        .await
1328        .unwrap();
1329    let cx_a = cx_a.add_empty_window();
1330    let editor_a = cx_a.new_window_entity(|window, cx| {
1331        Editor::for_buffer(buffer_a, Some(project_a.clone()), window, cx)
1332    });
1333
1334    let fake_language_server = fake_language_servers.next().await.unwrap();
1335    executor.run_until_parked();
1336
1337    // Receive an OnTypeFormatting request as the host's language server.
1338    // Return some formatting from the host's language server.
1339    fake_language_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
1340        |params, _| async move {
1341            assert_eq!(
1342                params.text_document_position.text_document.uri,
1343                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1344            );
1345            assert_eq!(
1346                params.text_document_position.position,
1347                lsp::Position::new(0, 14),
1348            );
1349
1350            Ok(Some(vec![lsp::TextEdit {
1351                new_text: "~<".to_string(),
1352                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1353            }]))
1354        },
1355    );
1356
1357    // Open the buffer on the guest and see that the formatting worked
1358    let buffer_b = project_b
1359        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1360        .await
1361        .unwrap();
1362
1363    // Type a on type formatting trigger character as the guest.
1364    cx_a.focus(&editor_a);
1365    editor_a.update_in(cx_a, |editor, window, cx| {
1366        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1367        editor.handle_input(">", window, cx);
1368    });
1369
1370    executor.run_until_parked();
1371
1372    buffer_b.read_with(cx_b, |buffer, _| {
1373        assert_eq!(buffer.text(), "fn main() { a>~< }")
1374    });
1375
1376    // Undo should remove LSP edits first
1377    editor_a.update_in(cx_a, |editor, window, cx| {
1378        assert_eq!(editor.text(cx), "fn main() { a>~< }");
1379        editor.undo(&Undo, window, cx);
1380        assert_eq!(editor.text(cx), "fn main() { a> }");
1381    });
1382    executor.run_until_parked();
1383
1384    buffer_b.read_with(cx_b, |buffer, _| {
1385        assert_eq!(buffer.text(), "fn main() { a> }")
1386    });
1387
1388    editor_a.update_in(cx_a, |editor, window, cx| {
1389        assert_eq!(editor.text(cx), "fn main() { a> }");
1390        editor.undo(&Undo, window, cx);
1391        assert_eq!(editor.text(cx), "fn main() { a }");
1392    });
1393    executor.run_until_parked();
1394
1395    buffer_b.read_with(cx_b, |buffer, _| {
1396        assert_eq!(buffer.text(), "fn main() { a }")
1397    });
1398}
1399
1400#[gpui::test(iterations = 10)]
1401async fn test_on_input_format_from_guest_to_host(
1402    cx_a: &mut TestAppContext,
1403    cx_b: &mut TestAppContext,
1404) {
1405    let mut server = TestServer::start(cx_a.executor()).await;
1406    let executor = cx_a.executor();
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
1414    client_a.language_registry().add(rust_lang());
1415    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1416        "Rust",
1417        FakeLspAdapter {
1418            capabilities: lsp::ServerCapabilities {
1419                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1420                    first_trigger_character: ":".to_string(),
1421                    more_trigger_character: Some(vec![">".to_string()]),
1422                }),
1423                ..Default::default()
1424            },
1425            ..Default::default()
1426        },
1427    );
1428
1429    client_a
1430        .fs()
1431        .insert_tree(
1432            path!("/a"),
1433            json!({
1434                "main.rs": "fn main() { a }",
1435                "other.rs": "// Test file",
1436            }),
1437        )
1438        .await;
1439    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1440    let project_id = active_call_a
1441        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1442        .await
1443        .unwrap();
1444    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1445
1446    // Open a file in an editor as the guest.
1447    let buffer_b = project_b
1448        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1449        .await
1450        .unwrap();
1451    let cx_b = cx_b.add_empty_window();
1452    let editor_b = cx_b.new_window_entity(|window, cx| {
1453        Editor::for_buffer(buffer_b, Some(project_b.clone()), window, cx)
1454    });
1455
1456    let fake_language_server = fake_language_servers.next().await.unwrap();
1457    executor.run_until_parked();
1458
1459    // Type a on type formatting trigger character as the guest.
1460    cx_b.focus(&editor_b);
1461    editor_b.update_in(cx_b, |editor, window, cx| {
1462        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1463        editor.handle_input(":", window, cx);
1464    });
1465
1466    // Receive an OnTypeFormatting request as the host's language server.
1467    // Return some formatting from the host's language server.
1468    executor.start_waiting();
1469    fake_language_server
1470        .set_request_handler::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
1471            assert_eq!(
1472                params.text_document_position.text_document.uri,
1473                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1474            );
1475            assert_eq!(
1476                params.text_document_position.position,
1477                lsp::Position::new(0, 14),
1478            );
1479
1480            Ok(Some(vec![lsp::TextEdit {
1481                new_text: "~:".to_string(),
1482                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1483            }]))
1484        })
1485        .next()
1486        .await
1487        .unwrap();
1488    executor.finish_waiting();
1489
1490    // Open the buffer on the host and see that the formatting worked
1491    let buffer_a = project_a
1492        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1493        .await
1494        .unwrap();
1495    executor.run_until_parked();
1496
1497    buffer_a.read_with(cx_a, |buffer, _| {
1498        assert_eq!(buffer.text(), "fn main() { a:~: }")
1499    });
1500
1501    // Undo should remove LSP edits first
1502    editor_b.update_in(cx_b, |editor, window, cx| {
1503        assert_eq!(editor.text(cx), "fn main() { a:~: }");
1504        editor.undo(&Undo, window, cx);
1505        assert_eq!(editor.text(cx), "fn main() { a: }");
1506    });
1507    executor.run_until_parked();
1508
1509    buffer_a.read_with(cx_a, |buffer, _| {
1510        assert_eq!(buffer.text(), "fn main() { a: }")
1511    });
1512
1513    editor_b.update_in(cx_b, |editor, window, cx| {
1514        assert_eq!(editor.text(cx), "fn main() { a: }");
1515        editor.undo(&Undo, window, cx);
1516        assert_eq!(editor.text(cx), "fn main() { a }");
1517    });
1518    executor.run_until_parked();
1519
1520    buffer_a.read_with(cx_a, |buffer, _| {
1521        assert_eq!(buffer.text(), "fn main() { a }")
1522    });
1523}
1524
1525#[gpui::test(iterations = 10)]
1526async fn test_mutual_editor_inlay_hint_cache_update(
1527    cx_a: &mut TestAppContext,
1528    cx_b: &mut TestAppContext,
1529) {
1530    let mut server = TestServer::start(cx_a.executor()).await;
1531    let executor = cx_a.executor();
1532    let client_a = server.create_client(cx_a, "user_a").await;
1533    let client_b = server.create_client(cx_b, "user_b").await;
1534    server
1535        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1536        .await;
1537    let active_call_a = cx_a.read(ActiveCall::global);
1538    let active_call_b = cx_b.read(ActiveCall::global);
1539
1540    cx_a.update(editor::init);
1541    cx_b.update(editor::init);
1542
1543    cx_a.update(|cx| {
1544        SettingsStore::update_global(cx, |store, cx| {
1545            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1546                settings.defaults.inlay_hints = Some(InlayHintSettings {
1547                    enabled: true,
1548                    show_value_hints: true,
1549                    edit_debounce_ms: 0,
1550                    scroll_debounce_ms: 0,
1551                    show_type_hints: true,
1552                    show_parameter_hints: false,
1553                    show_other_hints: true,
1554                    show_background: false,
1555                    toggle_on_modifiers_press: None,
1556                })
1557            });
1558        });
1559    });
1560    cx_b.update(|cx| {
1561        SettingsStore::update_global(cx, |store, cx| {
1562            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1563                settings.defaults.inlay_hints = Some(InlayHintSettings {
1564                    show_value_hints: true,
1565                    enabled: true,
1566                    edit_debounce_ms: 0,
1567                    scroll_debounce_ms: 0,
1568                    show_type_hints: true,
1569                    show_parameter_hints: false,
1570                    show_other_hints: true,
1571                    show_background: false,
1572                    toggle_on_modifiers_press: None,
1573                })
1574            });
1575        });
1576    });
1577
1578    client_a.language_registry().add(rust_lang());
1579    client_b.language_registry().add(rust_lang());
1580    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1581        "Rust",
1582        FakeLspAdapter {
1583            capabilities: lsp::ServerCapabilities {
1584                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1585                ..Default::default()
1586            },
1587            ..Default::default()
1588        },
1589    );
1590
1591    // Client A opens a project.
1592    client_a
1593        .fs()
1594        .insert_tree(
1595            path!("/a"),
1596            json!({
1597                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1598                "other.rs": "// Test file",
1599            }),
1600        )
1601        .await;
1602    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1603    active_call_a
1604        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1605        .await
1606        .unwrap();
1607    let project_id = active_call_a
1608        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1609        .await
1610        .unwrap();
1611
1612    // Client B joins the project
1613    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1614    active_call_b
1615        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1616        .await
1617        .unwrap();
1618
1619    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
1620    executor.start_waiting();
1621
1622    // The host opens a rust file.
1623    let _buffer_a = project_a
1624        .update(cx_a, |project, cx| {
1625            project.open_local_buffer(path!("/a/main.rs"), cx)
1626        })
1627        .await
1628        .unwrap();
1629    let editor_a = workspace_a
1630        .update_in(cx_a, |workspace, window, cx| {
1631            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1632        })
1633        .await
1634        .unwrap()
1635        .downcast::<Editor>()
1636        .unwrap();
1637
1638    let fake_language_server = fake_language_servers.next().await.unwrap();
1639
1640    // Set up the language server to return an additional inlay hint on each request.
1641    let edits_made = Arc::new(AtomicUsize::new(0));
1642    let closure_edits_made = Arc::clone(&edits_made);
1643    fake_language_server
1644        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1645            let task_edits_made = Arc::clone(&closure_edits_made);
1646            async move {
1647                assert_eq!(
1648                    params.text_document.uri,
1649                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1650                );
1651                let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
1652                Ok(Some(vec![lsp::InlayHint {
1653                    position: lsp::Position::new(0, edits_made as u32),
1654                    label: lsp::InlayHintLabel::String(edits_made.to_string()),
1655                    kind: None,
1656                    text_edits: None,
1657                    tooltip: None,
1658                    padding_left: None,
1659                    padding_right: None,
1660                    data: None,
1661                }]))
1662            }
1663        })
1664        .next()
1665        .await
1666        .unwrap();
1667
1668    executor.run_until_parked();
1669
1670    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
1671    editor_a.update(cx_a, |editor, _| {
1672        assert_eq!(
1673            vec![initial_edit.to_string()],
1674            extract_hint_labels(editor),
1675            "Host should get its first hints when opens an editor"
1676        );
1677    });
1678    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1679    let editor_b = workspace_b
1680        .update_in(cx_b, |workspace, window, cx| {
1681            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1682        })
1683        .await
1684        .unwrap()
1685        .downcast::<Editor>()
1686        .unwrap();
1687
1688    executor.run_until_parked();
1689    editor_b.update(cx_b, |editor, _| {
1690        assert_eq!(
1691            vec![initial_edit.to_string()],
1692            extract_hint_labels(editor),
1693            "Client should get its first hints when opens an editor"
1694        );
1695    });
1696
1697    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1698    editor_b.update_in(cx_b, |editor, window, cx| {
1699        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
1700        editor.handle_input(":", window, cx);
1701    });
1702    cx_b.focus(&editor_b);
1703
1704    executor.run_until_parked();
1705    editor_a.update(cx_a, |editor, _| {
1706        assert_eq!(
1707            vec![after_client_edit.to_string()],
1708            extract_hint_labels(editor),
1709        );
1710    });
1711    editor_b.update(cx_b, |editor, _| {
1712        assert_eq!(
1713            vec![after_client_edit.to_string()],
1714            extract_hint_labels(editor),
1715        );
1716    });
1717
1718    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1719    editor_a.update_in(cx_a, |editor, window, cx| {
1720        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1721        editor.handle_input("a change to increment both buffers' versions", window, cx);
1722    });
1723    cx_a.focus(&editor_a);
1724
1725    executor.run_until_parked();
1726    editor_a.update(cx_a, |editor, _| {
1727        assert_eq!(
1728            vec![after_host_edit.to_string()],
1729            extract_hint_labels(editor),
1730        );
1731    });
1732    editor_b.update(cx_b, |editor, _| {
1733        assert_eq!(
1734            vec![after_host_edit.to_string()],
1735            extract_hint_labels(editor),
1736        );
1737    });
1738
1739    let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1740    fake_language_server
1741        .request::<lsp::request::InlayHintRefreshRequest>(())
1742        .await
1743        .into_response()
1744        .expect("inlay refresh request failed");
1745
1746    executor.run_until_parked();
1747    editor_a.update(cx_a, |editor, _| {
1748        assert_eq!(
1749            vec![after_special_edit_for_refresh.to_string()],
1750            extract_hint_labels(editor),
1751            "Host should react to /refresh LSP request"
1752        );
1753    });
1754    editor_b.update(cx_b, |editor, _| {
1755        assert_eq!(
1756            vec![after_special_edit_for_refresh.to_string()],
1757            extract_hint_labels(editor),
1758            "Guest should get a /refresh LSP request propagated by host"
1759        );
1760    });
1761}
1762
1763#[gpui::test(iterations = 10)]
1764async fn test_inlay_hint_refresh_is_forwarded(
1765    cx_a: &mut TestAppContext,
1766    cx_b: &mut TestAppContext,
1767) {
1768    let mut server = TestServer::start(cx_a.executor()).await;
1769    let executor = cx_a.executor();
1770    let client_a = server.create_client(cx_a, "user_a").await;
1771    let client_b = server.create_client(cx_b, "user_b").await;
1772    server
1773        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1774        .await;
1775    let active_call_a = cx_a.read(ActiveCall::global);
1776    let active_call_b = cx_b.read(ActiveCall::global);
1777
1778    cx_a.update(editor::init);
1779    cx_b.update(editor::init);
1780
1781    cx_a.update(|cx| {
1782        SettingsStore::update_global(cx, |store, cx| {
1783            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1784                settings.defaults.inlay_hints = Some(InlayHintSettings {
1785                    show_value_hints: true,
1786                    enabled: false,
1787                    edit_debounce_ms: 0,
1788                    scroll_debounce_ms: 0,
1789                    show_type_hints: false,
1790                    show_parameter_hints: false,
1791                    show_other_hints: false,
1792                    show_background: false,
1793                    toggle_on_modifiers_press: None,
1794                })
1795            });
1796        });
1797    });
1798    cx_b.update(|cx| {
1799        SettingsStore::update_global(cx, |store, cx| {
1800            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1801                settings.defaults.inlay_hints = Some(InlayHintSettings {
1802                    show_value_hints: true,
1803                    enabled: true,
1804                    edit_debounce_ms: 0,
1805                    scroll_debounce_ms: 0,
1806                    show_type_hints: true,
1807                    show_parameter_hints: true,
1808                    show_other_hints: true,
1809                    show_background: false,
1810                    toggle_on_modifiers_press: None,
1811                })
1812            });
1813        });
1814    });
1815
1816    client_a.language_registry().add(rust_lang());
1817    client_b.language_registry().add(rust_lang());
1818    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1819        "Rust",
1820        FakeLspAdapter {
1821            capabilities: lsp::ServerCapabilities {
1822                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1823                ..Default::default()
1824            },
1825            ..Default::default()
1826        },
1827    );
1828
1829    client_a
1830        .fs()
1831        .insert_tree(
1832            path!("/a"),
1833            json!({
1834                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1835                "other.rs": "// Test file",
1836            }),
1837        )
1838        .await;
1839    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1840    active_call_a
1841        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1842        .await
1843        .unwrap();
1844    let project_id = active_call_a
1845        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1846        .await
1847        .unwrap();
1848
1849    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1850    active_call_b
1851        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1852        .await
1853        .unwrap();
1854
1855    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
1856    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1857
1858    cx_a.background_executor.start_waiting();
1859
1860    let editor_a = workspace_a
1861        .update_in(cx_a, |workspace, window, cx| {
1862            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1863        })
1864        .await
1865        .unwrap()
1866        .downcast::<Editor>()
1867        .unwrap();
1868
1869    let editor_b = workspace_b
1870        .update_in(cx_b, |workspace, window, cx| {
1871            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
1872        })
1873        .await
1874        .unwrap()
1875        .downcast::<Editor>()
1876        .unwrap();
1877
1878    let other_hints = Arc::new(AtomicBool::new(false));
1879    let fake_language_server = fake_language_servers.next().await.unwrap();
1880    let closure_other_hints = Arc::clone(&other_hints);
1881    fake_language_server
1882        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1883            let task_other_hints = Arc::clone(&closure_other_hints);
1884            async move {
1885                assert_eq!(
1886                    params.text_document.uri,
1887                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
1888                );
1889                let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
1890                let character = if other_hints { 0 } else { 2 };
1891                let label = if other_hints {
1892                    "other hint"
1893                } else {
1894                    "initial hint"
1895                };
1896                Ok(Some(vec![lsp::InlayHint {
1897                    position: lsp::Position::new(0, character),
1898                    label: lsp::InlayHintLabel::String(label.to_string()),
1899                    kind: None,
1900                    text_edits: None,
1901                    tooltip: None,
1902                    padding_left: None,
1903                    padding_right: None,
1904                    data: None,
1905                }]))
1906            }
1907        })
1908        .next()
1909        .await
1910        .unwrap();
1911    executor.finish_waiting();
1912
1913    executor.run_until_parked();
1914    editor_a.update(cx_a, |editor, _| {
1915        assert!(
1916            extract_hint_labels(editor).is_empty(),
1917            "Host should get no hints due to them turned off"
1918        );
1919    });
1920
1921    executor.run_until_parked();
1922    editor_b.update(cx_b, |editor, _| {
1923        assert_eq!(
1924            vec!["initial hint".to_string()],
1925            extract_hint_labels(editor),
1926            "Client should get its first hints when opens an editor"
1927        );
1928    });
1929
1930    other_hints.fetch_or(true, atomic::Ordering::Release);
1931    fake_language_server
1932        .request::<lsp::request::InlayHintRefreshRequest>(())
1933        .await
1934        .into_response()
1935        .expect("inlay refresh request failed");
1936    executor.run_until_parked();
1937    editor_a.update(cx_a, |editor, _| {
1938        assert!(
1939            extract_hint_labels(editor).is_empty(),
1940            "Host should get no hints due to them turned off, even after the /refresh"
1941        );
1942    });
1943
1944    executor.run_until_parked();
1945    editor_b.update(cx_b, |editor, _| {
1946        assert_eq!(
1947            vec!["other hint".to_string()],
1948            extract_hint_labels(editor),
1949            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
1950        );
1951    });
1952}
1953
1954#[gpui::test(iterations = 10)]
1955async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1956    let expected_color = Rgba {
1957        r: 0.33,
1958        g: 0.33,
1959        b: 0.33,
1960        a: 0.33,
1961    };
1962    let mut server = TestServer::start(cx_a.executor()).await;
1963    let executor = cx_a.executor();
1964    let client_a = server.create_client(cx_a, "user_a").await;
1965    let client_b = server.create_client(cx_b, "user_b").await;
1966    server
1967        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1968        .await;
1969    let active_call_a = cx_a.read(ActiveCall::global);
1970    let active_call_b = cx_b.read(ActiveCall::global);
1971
1972    cx_a.update(editor::init);
1973    cx_b.update(editor::init);
1974
1975    cx_a.update(|cx| {
1976        SettingsStore::update_global(cx, |store, cx| {
1977            store.update_user_settings::<EditorSettings>(cx, |settings| {
1978                settings.lsp_document_colors = Some(DocumentColorsRenderMode::None);
1979            });
1980        });
1981    });
1982    cx_b.update(|cx| {
1983        SettingsStore::update_global(cx, |store, cx| {
1984            store.update_user_settings::<EditorSettings>(cx, |settings| {
1985                settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
1986            });
1987        });
1988    });
1989
1990    client_a.language_registry().add(rust_lang());
1991    client_b.language_registry().add(rust_lang());
1992    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1993        "Rust",
1994        FakeLspAdapter {
1995            capabilities: lsp::ServerCapabilities {
1996                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
1997                ..lsp::ServerCapabilities::default()
1998            },
1999            ..FakeLspAdapter::default()
2000        },
2001    );
2002
2003    // Client A opens a project.
2004    client_a
2005        .fs()
2006        .insert_tree(
2007            path!("/a"),
2008            json!({
2009                "main.rs": "fn main() { a }",
2010            }),
2011        )
2012        .await;
2013    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2014    active_call_a
2015        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2016        .await
2017        .unwrap();
2018    let project_id = active_call_a
2019        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2020        .await
2021        .unwrap();
2022
2023    // Client B joins the project
2024    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2025    active_call_b
2026        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2027        .await
2028        .unwrap();
2029
2030    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2031
2032    // The host opens a rust file.
2033    let _buffer_a = project_a
2034        .update(cx_a, |project, cx| {
2035            project.open_local_buffer(path!("/a/main.rs"), cx)
2036        })
2037        .await
2038        .unwrap();
2039    let editor_a = workspace_a
2040        .update_in(cx_a, |workspace, window, cx| {
2041            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2042        })
2043        .await
2044        .unwrap()
2045        .downcast::<Editor>()
2046        .unwrap();
2047
2048    let fake_language_server = fake_language_servers.next().await.unwrap();
2049
2050    let requests_made = Arc::new(AtomicUsize::new(0));
2051    let closure_requests_made = Arc::clone(&requests_made);
2052    let mut color_request_handle = fake_language_server
2053        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
2054            let requests_made = Arc::clone(&closure_requests_made);
2055            async move {
2056                assert_eq!(
2057                    params.text_document.uri,
2058                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2059                );
2060                requests_made.fetch_add(1, atomic::Ordering::Release);
2061                Ok(vec![lsp::ColorInformation {
2062                    range: lsp::Range {
2063                        start: lsp::Position {
2064                            line: 0,
2065                            character: 0,
2066                        },
2067                        end: lsp::Position {
2068                            line: 0,
2069                            character: 1,
2070                        },
2071                    },
2072                    color: lsp::Color {
2073                        red: 0.33,
2074                        green: 0.33,
2075                        blue: 0.33,
2076                        alpha: 0.33,
2077                    },
2078                }])
2079            }
2080        });
2081    executor.run_until_parked();
2082
2083    assert_eq!(
2084        0,
2085        requests_made.load(atomic::Ordering::Acquire),
2086        "Host did not enable document colors, hence should query for none"
2087    );
2088    editor_a.update(cx_a, |editor, cx| {
2089        assert_eq!(
2090            Vec::<Rgba>::new(),
2091            extract_color_inlays(editor, cx),
2092            "No query colors should result in no hints"
2093        );
2094    });
2095
2096    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2097    let editor_b = workspace_b
2098        .update_in(cx_b, |workspace, window, cx| {
2099            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2100        })
2101        .await
2102        .unwrap()
2103        .downcast::<Editor>()
2104        .unwrap();
2105
2106    color_request_handle.next().await.unwrap();
2107    executor.run_until_parked();
2108
2109    assert_eq!(
2110        1,
2111        requests_made.load(atomic::Ordering::Acquire),
2112        "The client opened the file and got its first colors back"
2113    );
2114    editor_b.update(cx_b, |editor, cx| {
2115        assert_eq!(
2116            vec![expected_color],
2117            extract_color_inlays(editor, cx),
2118            "With document colors as inlays, color inlays should be pushed"
2119        );
2120    });
2121
2122    editor_a.update_in(cx_a, |editor, window, cx| {
2123        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
2124        editor.handle_input(":", window, cx);
2125    });
2126    color_request_handle.next().await.unwrap();
2127    executor.run_until_parked();
2128    assert_eq!(
2129        2,
2130        requests_made.load(atomic::Ordering::Acquire),
2131        "After the host edits his file, the client should request the colors again"
2132    );
2133    editor_a.update(cx_a, |editor, cx| {
2134        assert_eq!(
2135            Vec::<Rgba>::new(),
2136            extract_color_inlays(editor, cx),
2137            "Host has no colors still"
2138        );
2139    });
2140    editor_b.update(cx_b, |editor, cx| {
2141        assert_eq!(vec![expected_color], extract_color_inlays(editor, cx),);
2142    });
2143
2144    cx_b.update(|_, cx| {
2145        SettingsStore::update_global(cx, |store, cx| {
2146            store.update_user_settings::<EditorSettings>(cx, |settings| {
2147                settings.lsp_document_colors = Some(DocumentColorsRenderMode::Background);
2148            });
2149        });
2150    });
2151    executor.run_until_parked();
2152    assert_eq!(
2153        2,
2154        requests_made.load(atomic::Ordering::Acquire),
2155        "After the client have changed the colors settings, no extra queries should happen"
2156    );
2157    editor_a.update(cx_a, |editor, cx| {
2158        assert_eq!(
2159            Vec::<Rgba>::new(),
2160            extract_color_inlays(editor, cx),
2161            "Host is unaffected by the client's settings changes"
2162        );
2163    });
2164    editor_b.update(cx_b, |editor, cx| {
2165        assert_eq!(
2166            Vec::<Rgba>::new(),
2167            extract_color_inlays(editor, cx),
2168            "Client should have no colors hints, as in the settings"
2169        );
2170    });
2171
2172    cx_b.update(|_, cx| {
2173        SettingsStore::update_global(cx, |store, cx| {
2174            store.update_user_settings::<EditorSettings>(cx, |settings| {
2175                settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2176            });
2177        });
2178    });
2179    executor.run_until_parked();
2180    assert_eq!(
2181        2,
2182        requests_made.load(atomic::Ordering::Acquire),
2183        "After falling back to colors as inlays, no extra LSP queries are made"
2184    );
2185    editor_a.update(cx_a, |editor, cx| {
2186        assert_eq!(
2187            Vec::<Rgba>::new(),
2188            extract_color_inlays(editor, cx),
2189            "Host is unaffected by the client's settings changes, again"
2190        );
2191    });
2192    editor_b.update(cx_b, |editor, cx| {
2193        assert_eq!(
2194            vec![expected_color],
2195            extract_color_inlays(editor, cx),
2196            "Client should have its color hints back"
2197        );
2198    });
2199
2200    cx_a.update(|_, cx| {
2201        SettingsStore::update_global(cx, |store, cx| {
2202            store.update_user_settings::<EditorSettings>(cx, |settings| {
2203                settings.lsp_document_colors = Some(DocumentColorsRenderMode::Border);
2204            });
2205        });
2206    });
2207    color_request_handle.next().await.unwrap();
2208    executor.run_until_parked();
2209    assert_eq!(
2210        3,
2211        requests_made.load(atomic::Ordering::Acquire),
2212        "After the host enables document colors, another LSP query should be made"
2213    );
2214    editor_a.update(cx_a, |editor, cx| {
2215        assert_eq!(
2216            Vec::<Rgba>::new(),
2217            extract_color_inlays(editor, cx),
2218            "Host did not configure document colors as hints hence gets nothing"
2219        );
2220    });
2221    editor_b.update(cx_b, |editor, cx| {
2222        assert_eq!(
2223            vec![expected_color],
2224            extract_color_inlays(editor, cx),
2225            "Client should be unaffected by the host's settings changes"
2226        );
2227    });
2228}
2229
2230#[gpui::test(iterations = 10)]
2231async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2232    let mut server = TestServer::start(cx_a.executor()).await;
2233    let client_a = server.create_client(cx_a, "user_a").await;
2234    let client_b = server.create_client(cx_b, "user_b").await;
2235    server
2236        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2237        .await;
2238    let active_call_a = cx_a.read(ActiveCall::global);
2239
2240    cx_a.update(editor::init);
2241    cx_b.update(editor::init);
2242    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
2243    let inline_blame_off_settings = Some(InlineBlameSettings {
2244        enabled: false,
2245        delay_ms: None,
2246        min_column: None,
2247        show_commit_summary: false,
2248    });
2249    cx_a.update(|cx| {
2250        SettingsStore::update_global(cx, |store, cx| {
2251            store.update_user_settings::<ProjectSettings>(cx, |settings| {
2252                settings.git.inline_blame = inline_blame_off_settings;
2253            });
2254        });
2255    });
2256    cx_b.update(|cx| {
2257        SettingsStore::update_global(cx, |store, cx| {
2258            store.update_user_settings::<ProjectSettings>(cx, |settings| {
2259                settings.git.inline_blame = inline_blame_off_settings;
2260            });
2261        });
2262    });
2263
2264    client_a
2265        .fs()
2266        .insert_tree(
2267            path!("/my-repo"),
2268            json!({
2269                ".git": {},
2270                "file.txt": "line1\nline2\nline3\nline\n",
2271            }),
2272        )
2273        .await;
2274
2275    let blame = git::blame::Blame {
2276        entries: vec![
2277            blame_entry("1b1b1b", 0..1),
2278            blame_entry("0d0d0d", 1..2),
2279            blame_entry("3a3a3a", 2..3),
2280            blame_entry("4c4c4c", 3..4),
2281        ],
2282        messages: [
2283            ("1b1b1b", "message for idx-0"),
2284            ("0d0d0d", "message for idx-1"),
2285            ("3a3a3a", "message for idx-2"),
2286            ("4c4c4c", "message for idx-3"),
2287        ]
2288        .into_iter()
2289        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
2290        .collect(),
2291        remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
2292    };
2293    client_a.fs().set_blame_for_repo(
2294        Path::new(path!("/my-repo/.git")),
2295        vec![("file.txt".into(), blame)],
2296    );
2297
2298    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
2299    let project_id = active_call_a
2300        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2301        .await
2302        .unwrap();
2303
2304    // Create editor_a
2305    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2306    let editor_a = workspace_a
2307        .update_in(cx_a, |workspace, window, cx| {
2308            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2309        })
2310        .await
2311        .unwrap()
2312        .downcast::<Editor>()
2313        .unwrap();
2314
2315    // Join the project as client B.
2316    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2317    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2318    let editor_b = workspace_b
2319        .update_in(cx_b, |workspace, window, cx| {
2320            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2321        })
2322        .await
2323        .unwrap()
2324        .downcast::<Editor>()
2325        .unwrap();
2326    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
2327        editor_b
2328            .buffer()
2329            .read(cx)
2330            .as_singleton()
2331            .unwrap()
2332            .read(cx)
2333            .remote_id()
2334    });
2335
2336    // client_b now requests git blame for the open buffer
2337    editor_b.update_in(cx_b, |editor_b, window, cx| {
2338        assert!(editor_b.blame().is_none());
2339        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
2340    });
2341
2342    cx_a.executor().run_until_parked();
2343    cx_b.executor().run_until_parked();
2344
2345    editor_b.update(cx_b, |editor_b, cx| {
2346        let blame = editor_b.blame().expect("editor_b should have blame now");
2347        let entries = blame.update(cx, |blame, cx| {
2348            blame
2349                .blame_for_rows(
2350                    &(0..4)
2351                        .map(|row| RowInfo {
2352                            buffer_row: Some(row),
2353                            buffer_id: Some(buffer_id_b),
2354                            ..Default::default()
2355                        })
2356                        .collect::<Vec<_>>(),
2357                    cx,
2358                )
2359                .collect::<Vec<_>>()
2360        });
2361
2362        assert_eq!(
2363            entries,
2364            vec![
2365                Some(blame_entry("1b1b1b", 0..1)),
2366                Some(blame_entry("0d0d0d", 1..2)),
2367                Some(blame_entry("3a3a3a", 2..3)),
2368                Some(blame_entry("4c4c4c", 3..4)),
2369            ]
2370        );
2371
2372        blame.update(cx, |blame, _| {
2373            for (idx, entry) in entries.iter().flatten().enumerate() {
2374                let details = blame.details_for_entry(entry).unwrap();
2375                assert_eq!(details.message, format!("message for idx-{}", idx));
2376                assert_eq!(
2377                    details.permalink.unwrap().to_string(),
2378                    format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
2379                );
2380            }
2381        });
2382    });
2383
2384    // editor_b updates the file, which gets sent to client_a, which updates git blame,
2385    // which gets back to client_b.
2386    editor_b.update_in(cx_b, |editor_b, _, cx| {
2387        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
2388    });
2389
2390    cx_a.executor().run_until_parked();
2391    cx_b.executor().run_until_parked();
2392
2393    editor_b.update(cx_b, |editor_b, cx| {
2394        let blame = editor_b.blame().expect("editor_b should have blame now");
2395        let entries = blame.update(cx, |blame, cx| {
2396            blame
2397                .blame_for_rows(
2398                    &(0..4)
2399                        .map(|row| RowInfo {
2400                            buffer_row: Some(row),
2401                            buffer_id: Some(buffer_id_b),
2402                            ..Default::default()
2403                        })
2404                        .collect::<Vec<_>>(),
2405                    cx,
2406                )
2407                .collect::<Vec<_>>()
2408        });
2409
2410        assert_eq!(
2411            entries,
2412            vec![
2413                None,
2414                Some(blame_entry("0d0d0d", 1..2)),
2415                Some(blame_entry("3a3a3a", 2..3)),
2416                Some(blame_entry("4c4c4c", 3..4)),
2417            ]
2418        );
2419    });
2420
2421    // Now editor_a also updates the file
2422    editor_a.update_in(cx_a, |editor_a, _, cx| {
2423        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
2424    });
2425
2426    cx_a.executor().run_until_parked();
2427    cx_b.executor().run_until_parked();
2428
2429    editor_b.update(cx_b, |editor_b, cx| {
2430        let blame = editor_b.blame().expect("editor_b should have blame now");
2431        let entries = blame.update(cx, |blame, cx| {
2432            blame
2433                .blame_for_rows(
2434                    &(0..4)
2435                        .map(|row| RowInfo {
2436                            buffer_row: Some(row),
2437                            buffer_id: Some(buffer_id_b),
2438                            ..Default::default()
2439                        })
2440                        .collect::<Vec<_>>(),
2441                    cx,
2442                )
2443                .collect::<Vec<_>>()
2444        });
2445
2446        assert_eq!(
2447            entries,
2448            vec![
2449                None,
2450                None,
2451                Some(blame_entry("3a3a3a", 2..3)),
2452                Some(blame_entry("4c4c4c", 3..4)),
2453            ]
2454        );
2455    });
2456}
2457
2458#[gpui::test(iterations = 30)]
2459async fn test_collaborating_with_editorconfig(
2460    cx_a: &mut TestAppContext,
2461    cx_b: &mut TestAppContext,
2462) {
2463    let mut server = TestServer::start(cx_a.executor()).await;
2464    let client_a = server.create_client(cx_a, "user_a").await;
2465    let client_b = server.create_client(cx_b, "user_b").await;
2466    server
2467        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2468        .await;
2469    let active_call_a = cx_a.read(ActiveCall::global);
2470
2471    cx_b.update(editor::init);
2472
2473    // Set up a fake language server.
2474    client_a.language_registry().add(rust_lang());
2475    client_a
2476        .fs()
2477        .insert_tree(
2478            path!("/a"),
2479            json!({
2480                "src": {
2481                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
2482                    "other_mod": {
2483                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
2484                        ".editorconfig": "",
2485                    },
2486                },
2487                ".editorconfig": "[*]\ntab_width = 2\n",
2488            }),
2489        )
2490        .await;
2491    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2492    let project_id = active_call_a
2493        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2494        .await
2495        .unwrap();
2496    let main_buffer_a = project_a
2497        .update(cx_a, |p, cx| {
2498            p.open_buffer((worktree_id, "src/main.rs"), cx)
2499        })
2500        .await
2501        .unwrap();
2502    let other_buffer_a = project_a
2503        .update(cx_a, |p, cx| {
2504            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
2505        })
2506        .await
2507        .unwrap();
2508    let cx_a = cx_a.add_empty_window();
2509    let main_editor_a = cx_a.new_window_entity(|window, cx| {
2510        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
2511    });
2512    let other_editor_a = cx_a.new_window_entity(|window, cx| {
2513        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
2514    });
2515    let mut main_editor_cx_a = EditorTestContext {
2516        cx: cx_a.clone(),
2517        window: cx_a.window_handle(),
2518        editor: main_editor_a,
2519        assertion_cx: AssertionContextManager::new(),
2520    };
2521    let mut other_editor_cx_a = EditorTestContext {
2522        cx: cx_a.clone(),
2523        window: cx_a.window_handle(),
2524        editor: other_editor_a,
2525        assertion_cx: AssertionContextManager::new(),
2526    };
2527
2528    // Join the project as client B.
2529    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2530    let main_buffer_b = project_b
2531        .update(cx_b, |p, cx| {
2532            p.open_buffer((worktree_id, "src/main.rs"), cx)
2533        })
2534        .await
2535        .unwrap();
2536    let other_buffer_b = project_b
2537        .update(cx_b, |p, cx| {
2538            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
2539        })
2540        .await
2541        .unwrap();
2542    let cx_b = cx_b.add_empty_window();
2543    let main_editor_b = cx_b.new_window_entity(|window, cx| {
2544        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
2545    });
2546    let other_editor_b = cx_b.new_window_entity(|window, cx| {
2547        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
2548    });
2549    let mut main_editor_cx_b = EditorTestContext {
2550        cx: cx_b.clone(),
2551        window: cx_b.window_handle(),
2552        editor: main_editor_b,
2553        assertion_cx: AssertionContextManager::new(),
2554    };
2555    let mut other_editor_cx_b = EditorTestContext {
2556        cx: cx_b.clone(),
2557        window: cx_b.window_handle(),
2558        editor: other_editor_b,
2559        assertion_cx: AssertionContextManager::new(),
2560    };
2561
2562    let initial_main = indoc! {"
2563ˇmod other;
2564fn main() { let foo = other::foo(); }"};
2565    let initial_other = indoc! {"
2566ˇpub fn foo() -> usize {
2567    4
2568}"};
2569
2570    let first_tabbed_main = indoc! {"
2571  ˇmod other;
2572fn main() { let foo = other::foo(); }"};
2573    tab_undo_assert(
2574        &mut main_editor_cx_a,
2575        &mut main_editor_cx_b,
2576        initial_main,
2577        first_tabbed_main,
2578        true,
2579    );
2580    tab_undo_assert(
2581        &mut main_editor_cx_a,
2582        &mut main_editor_cx_b,
2583        initial_main,
2584        first_tabbed_main,
2585        false,
2586    );
2587
2588    let first_tabbed_other = indoc! {"
2589  ˇpub fn foo() -> usize {
2590    4
2591}"};
2592    tab_undo_assert(
2593        &mut other_editor_cx_a,
2594        &mut other_editor_cx_b,
2595        initial_other,
2596        first_tabbed_other,
2597        true,
2598    );
2599    tab_undo_assert(
2600        &mut other_editor_cx_a,
2601        &mut other_editor_cx_b,
2602        initial_other,
2603        first_tabbed_other,
2604        false,
2605    );
2606
2607    client_a
2608        .fs()
2609        .atomic_write(
2610            PathBuf::from(path!("/a/src/.editorconfig")),
2611            "[*]\ntab_width = 3\n".to_owned(),
2612        )
2613        .await
2614        .unwrap();
2615    cx_a.run_until_parked();
2616    cx_b.run_until_parked();
2617
2618    let second_tabbed_main = indoc! {"
2619   ˇmod other;
2620fn main() { let foo = other::foo(); }"};
2621    tab_undo_assert(
2622        &mut main_editor_cx_a,
2623        &mut main_editor_cx_b,
2624        initial_main,
2625        second_tabbed_main,
2626        true,
2627    );
2628    tab_undo_assert(
2629        &mut main_editor_cx_a,
2630        &mut main_editor_cx_b,
2631        initial_main,
2632        second_tabbed_main,
2633        false,
2634    );
2635
2636    let second_tabbed_other = indoc! {"
2637   ˇpub fn foo() -> usize {
2638    4
2639}"};
2640    tab_undo_assert(
2641        &mut other_editor_cx_a,
2642        &mut other_editor_cx_b,
2643        initial_other,
2644        second_tabbed_other,
2645        true,
2646    );
2647    tab_undo_assert(
2648        &mut other_editor_cx_a,
2649        &mut other_editor_cx_b,
2650        initial_other,
2651        second_tabbed_other,
2652        false,
2653    );
2654
2655    let editorconfig_buffer_b = project_b
2656        .update(cx_b, |p, cx| {
2657            p.open_buffer((worktree_id, "src/other_mod/.editorconfig"), cx)
2658        })
2659        .await
2660        .unwrap();
2661    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
2662        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
2663    });
2664    project_b
2665        .update(cx_b, |project, cx| {
2666            project.save_buffer(editorconfig_buffer_b.clone(), cx)
2667        })
2668        .await
2669        .unwrap();
2670    cx_a.run_until_parked();
2671    cx_b.run_until_parked();
2672
2673    tab_undo_assert(
2674        &mut main_editor_cx_a,
2675        &mut main_editor_cx_b,
2676        initial_main,
2677        second_tabbed_main,
2678        true,
2679    );
2680    tab_undo_assert(
2681        &mut main_editor_cx_a,
2682        &mut main_editor_cx_b,
2683        initial_main,
2684        second_tabbed_main,
2685        false,
2686    );
2687
2688    let third_tabbed_other = indoc! {"
2689      ˇpub fn foo() -> usize {
2690    4
2691}"};
2692    tab_undo_assert(
2693        &mut other_editor_cx_a,
2694        &mut other_editor_cx_b,
2695        initial_other,
2696        third_tabbed_other,
2697        true,
2698    );
2699
2700    tab_undo_assert(
2701        &mut other_editor_cx_a,
2702        &mut other_editor_cx_b,
2703        initial_other,
2704        third_tabbed_other,
2705        false,
2706    );
2707}
2708
2709#[gpui::test]
2710async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2711    let executor = cx_a.executor();
2712    let mut server = TestServer::start(executor.clone()).await;
2713    let client_a = server.create_client(cx_a, "user_a").await;
2714    let client_b = server.create_client(cx_b, "user_b").await;
2715    server
2716        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2717        .await;
2718    let active_call_a = cx_a.read(ActiveCall::global);
2719    let active_call_b = cx_b.read(ActiveCall::global);
2720    cx_a.update(editor::init);
2721    cx_b.update(editor::init);
2722    client_a
2723        .fs()
2724        .insert_tree(
2725            "/a",
2726            json!({
2727                "test.txt": "one\ntwo\nthree\nfour\nfive",
2728            }),
2729        )
2730        .await;
2731    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
2732    let project_path = ProjectPath {
2733        worktree_id,
2734        path: Arc::from(Path::new(&"test.txt")),
2735    };
2736    let abs_path = project_a.read_with(cx_a, |project, cx| {
2737        project
2738            .absolute_path(&project_path, cx)
2739            .map(|path_buf| Arc::from(path_buf.to_owned()))
2740            .unwrap()
2741    });
2742
2743    active_call_a
2744        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2745        .await
2746        .unwrap();
2747    let project_id = active_call_a
2748        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2749        .await
2750        .unwrap();
2751    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2752    active_call_b
2753        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2754        .await
2755        .unwrap();
2756    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2757    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2758
2759    // Client A opens an editor.
2760    let editor_a = workspace_a
2761        .update_in(cx_a, |workspace, window, cx| {
2762            workspace.open_path(project_path.clone(), None, true, window, cx)
2763        })
2764        .await
2765        .unwrap()
2766        .downcast::<Editor>()
2767        .unwrap();
2768
2769    // Client B opens same editor as A.
2770    let editor_b = workspace_b
2771        .update_in(cx_b, |workspace, window, cx| {
2772            workspace.open_path(project_path.clone(), None, true, window, cx)
2773        })
2774        .await
2775        .unwrap()
2776        .downcast::<Editor>()
2777        .unwrap();
2778
2779    cx_a.run_until_parked();
2780    cx_b.run_until_parked();
2781
2782    // Client A adds breakpoint on line (1)
2783    editor_a.update_in(cx_a, |editor, window, cx| {
2784        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2785    });
2786
2787    cx_a.run_until_parked();
2788    cx_b.run_until_parked();
2789
2790    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2791        editor
2792            .breakpoint_store()
2793            .clone()
2794            .unwrap()
2795            .read(cx)
2796            .all_source_breakpoints(cx)
2797            .clone()
2798    });
2799    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2800        editor
2801            .breakpoint_store()
2802            .clone()
2803            .unwrap()
2804            .read(cx)
2805            .all_source_breakpoints(cx)
2806            .clone()
2807    });
2808
2809    assert_eq!(1, breakpoints_a.len());
2810    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
2811    assert_eq!(breakpoints_a, breakpoints_b);
2812
2813    // Client B adds breakpoint on line(2)
2814    editor_b.update_in(cx_b, |editor, window, cx| {
2815        editor.move_down(&editor::actions::MoveDown, window, cx);
2816        editor.move_down(&editor::actions::MoveDown, window, cx);
2817        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2818    });
2819
2820    cx_a.run_until_parked();
2821    cx_b.run_until_parked();
2822
2823    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2824        editor
2825            .breakpoint_store()
2826            .clone()
2827            .unwrap()
2828            .read(cx)
2829            .all_source_breakpoints(cx)
2830            .clone()
2831    });
2832    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2833        editor
2834            .breakpoint_store()
2835            .clone()
2836            .unwrap()
2837            .read(cx)
2838            .all_source_breakpoints(cx)
2839            .clone()
2840    });
2841
2842    assert_eq!(1, breakpoints_a.len());
2843    assert_eq!(breakpoints_a, breakpoints_b);
2844    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
2845
2846    // Client A removes last added breakpoint from client B
2847    editor_a.update_in(cx_a, |editor, window, cx| {
2848        editor.move_down(&editor::actions::MoveDown, window, cx);
2849        editor.move_down(&editor::actions::MoveDown, window, cx);
2850        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2851    });
2852
2853    cx_a.run_until_parked();
2854    cx_b.run_until_parked();
2855
2856    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2857        editor
2858            .breakpoint_store()
2859            .clone()
2860            .unwrap()
2861            .read(cx)
2862            .all_source_breakpoints(cx)
2863            .clone()
2864    });
2865    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2866        editor
2867            .breakpoint_store()
2868            .clone()
2869            .unwrap()
2870            .read(cx)
2871            .all_source_breakpoints(cx)
2872            .clone()
2873    });
2874
2875    assert_eq!(1, breakpoints_a.len());
2876    assert_eq!(breakpoints_a, breakpoints_b);
2877    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
2878
2879    // Client B removes first added breakpoint by client A
2880    editor_b.update_in(cx_b, |editor, window, cx| {
2881        editor.move_up(&editor::actions::MoveUp, window, cx);
2882        editor.move_up(&editor::actions::MoveUp, window, cx);
2883        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2884    });
2885
2886    cx_a.run_until_parked();
2887    cx_b.run_until_parked();
2888
2889    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2890        editor
2891            .breakpoint_store()
2892            .clone()
2893            .unwrap()
2894            .read(cx)
2895            .all_source_breakpoints(cx)
2896            .clone()
2897    });
2898    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2899        editor
2900            .breakpoint_store()
2901            .clone()
2902            .unwrap()
2903            .read(cx)
2904            .all_source_breakpoints(cx)
2905            .clone()
2906    });
2907
2908    assert_eq!(0, breakpoints_a.len());
2909    assert_eq!(breakpoints_a, breakpoints_b);
2910}
2911
2912#[gpui::test]
2913async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2914    let mut server = TestServer::start(cx_a.executor()).await;
2915    let client_a = server.create_client(cx_a, "user_a").await;
2916    let client_b = server.create_client(cx_b, "user_b").await;
2917    server
2918        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2919        .await;
2920    let active_call_a = cx_a.read(ActiveCall::global);
2921    let active_call_b = cx_b.read(ActiveCall::global);
2922
2923    cx_a.update(editor::init);
2924    cx_b.update(editor::init);
2925
2926    client_a.language_registry().add(rust_lang());
2927    client_b.language_registry().add(rust_lang());
2928    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2929        "Rust",
2930        FakeLspAdapter {
2931            name: RUST_ANALYZER_NAME,
2932            ..FakeLspAdapter::default()
2933        },
2934    );
2935
2936    client_a
2937        .fs()
2938        .insert_tree(
2939            path!("/a"),
2940            json!({
2941                "main.rs": "fn main() {}",
2942            }),
2943        )
2944        .await;
2945    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2946    active_call_a
2947        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2948        .await
2949        .unwrap();
2950    let project_id = active_call_a
2951        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2952        .await
2953        .unwrap();
2954
2955    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2956    active_call_b
2957        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2958        .await
2959        .unwrap();
2960
2961    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2962    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2963
2964    let editor_a = workspace_a
2965        .update_in(cx_a, |workspace, window, cx| {
2966            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2967        })
2968        .await
2969        .unwrap()
2970        .downcast::<Editor>()
2971        .unwrap();
2972
2973    let editor_b = workspace_b
2974        .update_in(cx_b, |workspace, window, cx| {
2975            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2976        })
2977        .await
2978        .unwrap()
2979        .downcast::<Editor>()
2980        .unwrap();
2981
2982    let fake_language_server = fake_language_servers.next().await.unwrap();
2983
2984    // host
2985    let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
2986        |params, _| async move {
2987            assert_eq!(
2988                params.text_document.uri,
2989                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2990            );
2991            assert_eq!(params.position, lsp::Position::new(0, 0));
2992            Ok(Some(ExpandedMacro {
2993                name: "test_macro_name".to_string(),
2994                expansion: "test_macro_expansion on the host".to_string(),
2995            }))
2996        },
2997    );
2998
2999    editor_a.update_in(cx_a, |editor, window, cx| {
3000        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
3001    });
3002    expand_request_a.next().await.unwrap();
3003    cx_a.run_until_parked();
3004
3005    workspace_a.update(cx_a, |workspace, cx| {
3006        workspace.active_pane().update(cx, |pane, cx| {
3007            assert_eq!(
3008                pane.items_len(),
3009                2,
3010                "Should have added a macro expansion to the host's pane"
3011            );
3012            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
3013            new_editor.update(cx, |editor, cx| {
3014                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
3015            });
3016        })
3017    });
3018
3019    // client
3020    let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
3021        |params, _| async move {
3022            assert_eq!(
3023                params.text_document.uri,
3024                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
3025            );
3026            assert_eq!(
3027                params.position,
3028                lsp::Position::new(0, 12),
3029                "editor_b has selected the entire text and should query for a different position"
3030            );
3031            Ok(Some(ExpandedMacro {
3032                name: "test_macro_name".to_string(),
3033                expansion: "test_macro_expansion on the client".to_string(),
3034            }))
3035        },
3036    );
3037
3038    editor_b.update_in(cx_b, |editor, window, cx| {
3039        editor.select_all(&SelectAll, window, cx);
3040        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
3041    });
3042    expand_request_b.next().await.unwrap();
3043    cx_b.run_until_parked();
3044
3045    workspace_b.update(cx_b, |workspace, cx| {
3046        workspace.active_pane().update(cx, |pane, cx| {
3047            assert_eq!(
3048                pane.items_len(),
3049                2,
3050                "Should have added a macro expansion to the client's pane"
3051            );
3052            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
3053            new_editor.update(cx, |editor, cx| {
3054                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
3055            });
3056        })
3057    });
3058}
3059
3060#[track_caller]
3061fn tab_undo_assert(
3062    cx_a: &mut EditorTestContext,
3063    cx_b: &mut EditorTestContext,
3064    expected_initial: &str,
3065    expected_tabbed: &str,
3066    a_tabs: bool,
3067) {
3068    cx_a.assert_editor_state(expected_initial);
3069    cx_b.assert_editor_state(expected_initial);
3070
3071    if a_tabs {
3072        cx_a.update_editor(|editor, window, cx| {
3073            editor.tab(&editor::actions::Tab, window, cx);
3074        });
3075    } else {
3076        cx_b.update_editor(|editor, window, cx| {
3077            editor.tab(&editor::actions::Tab, window, cx);
3078        });
3079    }
3080
3081    cx_a.run_until_parked();
3082    cx_b.run_until_parked();
3083
3084    cx_a.assert_editor_state(expected_tabbed);
3085    cx_b.assert_editor_state(expected_tabbed);
3086
3087    if a_tabs {
3088        cx_a.update_editor(|editor, window, cx| {
3089            editor.undo(&editor::actions::Undo, window, cx);
3090        });
3091    } else {
3092        cx_b.update_editor(|editor, window, cx| {
3093            editor.undo(&editor::actions::Undo, window, cx);
3094        });
3095    }
3096    cx_a.run_until_parked();
3097    cx_b.run_until_parked();
3098    cx_a.assert_editor_state(expected_initial);
3099    cx_b.assert_editor_state(expected_initial);
3100}
3101
3102fn extract_hint_labels(editor: &Editor) -> Vec<String> {
3103    let mut labels = Vec::new();
3104    for hint in editor.inlay_hint_cache().hints() {
3105        match hint.label {
3106            project::InlayHintLabel::String(s) => labels.push(s),
3107            _ => unreachable!(),
3108        }
3109    }
3110    labels
3111}
3112
3113#[track_caller]
3114fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
3115    editor
3116        .all_inlays(cx)
3117        .into_iter()
3118        .filter_map(|inlay| inlay.get_color())
3119        .map(Rgba::from)
3120        .collect()
3121}
3122
3123fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
3124    git::blame::BlameEntry {
3125        sha: sha.parse().unwrap(),
3126        range,
3127        ..Default::default()
3128    }
3129}