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    executor.start_waiting();
2032
2033    // The host opens a rust file.
2034    let _buffer_a = project_a
2035        .update(cx_a, |project, cx| {
2036            project.open_local_buffer(path!("/a/main.rs"), cx)
2037        })
2038        .await
2039        .unwrap();
2040    let editor_a = workspace_a
2041        .update_in(cx_a, |workspace, window, cx| {
2042            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2043        })
2044        .await
2045        .unwrap()
2046        .downcast::<Editor>()
2047        .unwrap();
2048
2049    let fake_language_server = fake_language_servers.next().await.unwrap();
2050
2051    let requests_made = Arc::new(AtomicUsize::new(0));
2052    let closure_requests_made = Arc::clone(&requests_made);
2053    let mut color_request_handle = fake_language_server
2054        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
2055            let requests_made = Arc::clone(&closure_requests_made);
2056            async move {
2057                assert_eq!(
2058                    params.text_document.uri,
2059                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2060                );
2061                requests_made.fetch_add(1, atomic::Ordering::Release);
2062                Ok(vec![lsp::ColorInformation {
2063                    range: lsp::Range {
2064                        start: lsp::Position {
2065                            line: 0,
2066                            character: 0,
2067                        },
2068                        end: lsp::Position {
2069                            line: 0,
2070                            character: 1,
2071                        },
2072                    },
2073                    color: lsp::Color {
2074                        red: 0.33,
2075                        green: 0.33,
2076                        blue: 0.33,
2077                        alpha: 0.33,
2078                    },
2079                }])
2080            }
2081        });
2082    executor.run_until_parked();
2083
2084    assert_eq!(
2085        0,
2086        requests_made.load(atomic::Ordering::Acquire),
2087        "Host did not enable document colors, hence should query for none"
2088    );
2089    editor_a.update(cx_a, |editor, cx| {
2090        assert_eq!(
2091            Vec::<Rgba>::new(),
2092            extract_color_inlays(editor, cx),
2093            "No query colors should result in no hints"
2094        );
2095    });
2096
2097    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2098    let editor_b = workspace_b
2099        .update_in(cx_b, |workspace, window, cx| {
2100            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2101        })
2102        .await
2103        .unwrap()
2104        .downcast::<Editor>()
2105        .unwrap();
2106
2107    color_request_handle.next().await.unwrap();
2108    executor.run_until_parked();
2109
2110    assert_eq!(
2111        1,
2112        requests_made.load(atomic::Ordering::Acquire),
2113        "The client opened the file and got its first colors back"
2114    );
2115    editor_b.update(cx_b, |editor, cx| {
2116        assert_eq!(
2117            vec![expected_color],
2118            extract_color_inlays(editor, cx),
2119            "With document colors as inlays, color inlays should be pushed"
2120        );
2121    });
2122
2123    editor_a.update_in(cx_a, |editor, window, cx| {
2124        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
2125        editor.handle_input(":", window, cx);
2126    });
2127    color_request_handle.next().await.unwrap();
2128    executor.run_until_parked();
2129    assert_eq!(
2130        2,
2131        requests_made.load(atomic::Ordering::Acquire),
2132        "After the host edits his file, the client should request the colors again"
2133    );
2134    editor_a.update(cx_a, |editor, cx| {
2135        assert_eq!(
2136            Vec::<Rgba>::new(),
2137            extract_color_inlays(editor, cx),
2138            "Host has no colors still"
2139        );
2140    });
2141    editor_b.update(cx_b, |editor, cx| {
2142        assert_eq!(vec![expected_color], extract_color_inlays(editor, cx),);
2143    });
2144
2145    cx_b.update(|_, cx| {
2146        SettingsStore::update_global(cx, |store, cx| {
2147            store.update_user_settings::<EditorSettings>(cx, |settings| {
2148                settings.lsp_document_colors = Some(DocumentColorsRenderMode::Background);
2149            });
2150        });
2151    });
2152    executor.run_until_parked();
2153    assert_eq!(
2154        2,
2155        requests_made.load(atomic::Ordering::Acquire),
2156        "After the client have changed the colors settings, no extra queries should happen"
2157    );
2158    editor_a.update(cx_a, |editor, cx| {
2159        assert_eq!(
2160            Vec::<Rgba>::new(),
2161            extract_color_inlays(editor, cx),
2162            "Host is unaffected by the client's settings changes"
2163        );
2164    });
2165    editor_b.update(cx_b, |editor, cx| {
2166        assert_eq!(
2167            Vec::<Rgba>::new(),
2168            extract_color_inlays(editor, cx),
2169            "Client should have no colors hints, as in the settings"
2170        );
2171    });
2172
2173    cx_b.update(|_, cx| {
2174        SettingsStore::update_global(cx, |store, cx| {
2175            store.update_user_settings::<EditorSettings>(cx, |settings| {
2176                settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2177            });
2178        });
2179    });
2180    executor.run_until_parked();
2181    assert_eq!(
2182        2,
2183        requests_made.load(atomic::Ordering::Acquire),
2184        "After falling back to colors as inlays, no extra LSP queries are made"
2185    );
2186    editor_a.update(cx_a, |editor, cx| {
2187        assert_eq!(
2188            Vec::<Rgba>::new(),
2189            extract_color_inlays(editor, cx),
2190            "Host is unaffected by the client's settings changes, again"
2191        );
2192    });
2193    editor_b.update(cx_b, |editor, cx| {
2194        assert_eq!(
2195            vec![expected_color],
2196            extract_color_inlays(editor, cx),
2197            "Client should have its color hints back"
2198        );
2199    });
2200
2201    cx_a.update(|_, cx| {
2202        SettingsStore::update_global(cx, |store, cx| {
2203            store.update_user_settings::<EditorSettings>(cx, |settings| {
2204                settings.lsp_document_colors = Some(DocumentColorsRenderMode::Border);
2205            });
2206        });
2207    });
2208    color_request_handle.next().await.unwrap();
2209    executor.run_until_parked();
2210    assert_eq!(
2211        3,
2212        requests_made.load(atomic::Ordering::Acquire),
2213        "After the host enables document colors, another LSP query should be made"
2214    );
2215    editor_a.update(cx_a, |editor, cx| {
2216        assert_eq!(
2217            Vec::<Rgba>::new(),
2218            extract_color_inlays(editor, cx),
2219            "Host did not configure document colors as hints hence gets nothing"
2220        );
2221    });
2222    editor_b.update(cx_b, |editor, cx| {
2223        assert_eq!(
2224            vec![expected_color],
2225            extract_color_inlays(editor, cx),
2226            "Client should be unaffected by the host's settings changes"
2227        );
2228    });
2229}
2230
2231#[gpui::test(iterations = 10)]
2232async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2233    let mut server = TestServer::start(cx_a.executor()).await;
2234    let client_a = server.create_client(cx_a, "user_a").await;
2235    let client_b = server.create_client(cx_b, "user_b").await;
2236    server
2237        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2238        .await;
2239    let active_call_a = cx_a.read(ActiveCall::global);
2240
2241    cx_a.update(editor::init);
2242    cx_b.update(editor::init);
2243    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
2244    let inline_blame_off_settings = Some(InlineBlameSettings {
2245        enabled: false,
2246        delay_ms: None,
2247        min_column: None,
2248        show_commit_summary: false,
2249    });
2250    cx_a.update(|cx| {
2251        SettingsStore::update_global(cx, |store, cx| {
2252            store.update_user_settings::<ProjectSettings>(cx, |settings| {
2253                settings.git.inline_blame = inline_blame_off_settings;
2254            });
2255        });
2256    });
2257    cx_b.update(|cx| {
2258        SettingsStore::update_global(cx, |store, cx| {
2259            store.update_user_settings::<ProjectSettings>(cx, |settings| {
2260                settings.git.inline_blame = inline_blame_off_settings;
2261            });
2262        });
2263    });
2264
2265    client_a
2266        .fs()
2267        .insert_tree(
2268            path!("/my-repo"),
2269            json!({
2270                ".git": {},
2271                "file.txt": "line1\nline2\nline3\nline\n",
2272            }),
2273        )
2274        .await;
2275
2276    let blame = git::blame::Blame {
2277        entries: vec![
2278            blame_entry("1b1b1b", 0..1),
2279            blame_entry("0d0d0d", 1..2),
2280            blame_entry("3a3a3a", 2..3),
2281            blame_entry("4c4c4c", 3..4),
2282        ],
2283        messages: [
2284            ("1b1b1b", "message for idx-0"),
2285            ("0d0d0d", "message for idx-1"),
2286            ("3a3a3a", "message for idx-2"),
2287            ("4c4c4c", "message for idx-3"),
2288        ]
2289        .into_iter()
2290        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
2291        .collect(),
2292        remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
2293    };
2294    client_a.fs().set_blame_for_repo(
2295        Path::new(path!("/my-repo/.git")),
2296        vec![("file.txt".into(), blame)],
2297    );
2298
2299    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
2300    let project_id = active_call_a
2301        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2302        .await
2303        .unwrap();
2304
2305    // Create editor_a
2306    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2307    let editor_a = workspace_a
2308        .update_in(cx_a, |workspace, window, cx| {
2309            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2310        })
2311        .await
2312        .unwrap()
2313        .downcast::<Editor>()
2314        .unwrap();
2315
2316    // Join the project as client B.
2317    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2318    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2319    let editor_b = workspace_b
2320        .update_in(cx_b, |workspace, window, cx| {
2321            workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
2322        })
2323        .await
2324        .unwrap()
2325        .downcast::<Editor>()
2326        .unwrap();
2327    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
2328        editor_b
2329            .buffer()
2330            .read(cx)
2331            .as_singleton()
2332            .unwrap()
2333            .read(cx)
2334            .remote_id()
2335    });
2336
2337    // client_b now requests git blame for the open buffer
2338    editor_b.update_in(cx_b, |editor_b, window, cx| {
2339        assert!(editor_b.blame().is_none());
2340        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
2341    });
2342
2343    cx_a.executor().run_until_parked();
2344    cx_b.executor().run_until_parked();
2345
2346    editor_b.update(cx_b, |editor_b, cx| {
2347        let blame = editor_b.blame().expect("editor_b should have blame now");
2348        let entries = blame.update(cx, |blame, cx| {
2349            blame
2350                .blame_for_rows(
2351                    &(0..4)
2352                        .map(|row| RowInfo {
2353                            buffer_row: Some(row),
2354                            buffer_id: Some(buffer_id_b),
2355                            ..Default::default()
2356                        })
2357                        .collect::<Vec<_>>(),
2358                    cx,
2359                )
2360                .collect::<Vec<_>>()
2361        });
2362
2363        assert_eq!(
2364            entries,
2365            vec![
2366                Some(blame_entry("1b1b1b", 0..1)),
2367                Some(blame_entry("0d0d0d", 1..2)),
2368                Some(blame_entry("3a3a3a", 2..3)),
2369                Some(blame_entry("4c4c4c", 3..4)),
2370            ]
2371        );
2372
2373        blame.update(cx, |blame, _| {
2374            for (idx, entry) in entries.iter().flatten().enumerate() {
2375                let details = blame.details_for_entry(entry).unwrap();
2376                assert_eq!(details.message, format!("message for idx-{}", idx));
2377                assert_eq!(
2378                    details.permalink.unwrap().to_string(),
2379                    format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
2380                );
2381            }
2382        });
2383    });
2384
2385    // editor_b updates the file, which gets sent to client_a, which updates git blame,
2386    // which gets back to client_b.
2387    editor_b.update_in(cx_b, |editor_b, _, cx| {
2388        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
2389    });
2390
2391    cx_a.executor().run_until_parked();
2392    cx_b.executor().run_until_parked();
2393
2394    editor_b.update(cx_b, |editor_b, cx| {
2395        let blame = editor_b.blame().expect("editor_b should have blame now");
2396        let entries = blame.update(cx, |blame, cx| {
2397            blame
2398                .blame_for_rows(
2399                    &(0..4)
2400                        .map(|row| RowInfo {
2401                            buffer_row: Some(row),
2402                            buffer_id: Some(buffer_id_b),
2403                            ..Default::default()
2404                        })
2405                        .collect::<Vec<_>>(),
2406                    cx,
2407                )
2408                .collect::<Vec<_>>()
2409        });
2410
2411        assert_eq!(
2412            entries,
2413            vec![
2414                None,
2415                Some(blame_entry("0d0d0d", 1..2)),
2416                Some(blame_entry("3a3a3a", 2..3)),
2417                Some(blame_entry("4c4c4c", 3..4)),
2418            ]
2419        );
2420    });
2421
2422    // Now editor_a also updates the file
2423    editor_a.update_in(cx_a, |editor_a, _, cx| {
2424        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
2425    });
2426
2427    cx_a.executor().run_until_parked();
2428    cx_b.executor().run_until_parked();
2429
2430    editor_b.update(cx_b, |editor_b, cx| {
2431        let blame = editor_b.blame().expect("editor_b should have blame now");
2432        let entries = blame.update(cx, |blame, cx| {
2433            blame
2434                .blame_for_rows(
2435                    &(0..4)
2436                        .map(|row| RowInfo {
2437                            buffer_row: Some(row),
2438                            buffer_id: Some(buffer_id_b),
2439                            ..Default::default()
2440                        })
2441                        .collect::<Vec<_>>(),
2442                    cx,
2443                )
2444                .collect::<Vec<_>>()
2445        });
2446
2447        assert_eq!(
2448            entries,
2449            vec![
2450                None,
2451                None,
2452                Some(blame_entry("3a3a3a", 2..3)),
2453                Some(blame_entry("4c4c4c", 3..4)),
2454            ]
2455        );
2456    });
2457}
2458
2459#[gpui::test(iterations = 30)]
2460async fn test_collaborating_with_editorconfig(
2461    cx_a: &mut TestAppContext,
2462    cx_b: &mut TestAppContext,
2463) {
2464    let mut server = TestServer::start(cx_a.executor()).await;
2465    let client_a = server.create_client(cx_a, "user_a").await;
2466    let client_b = server.create_client(cx_b, "user_b").await;
2467    server
2468        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2469        .await;
2470    let active_call_a = cx_a.read(ActiveCall::global);
2471
2472    cx_b.update(editor::init);
2473
2474    // Set up a fake language server.
2475    client_a.language_registry().add(rust_lang());
2476    client_a
2477        .fs()
2478        .insert_tree(
2479            path!("/a"),
2480            json!({
2481                "src": {
2482                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
2483                    "other_mod": {
2484                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
2485                        ".editorconfig": "",
2486                    },
2487                },
2488                ".editorconfig": "[*]\ntab_width = 2\n",
2489            }),
2490        )
2491        .await;
2492    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2493    let project_id = active_call_a
2494        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2495        .await
2496        .unwrap();
2497    let main_buffer_a = project_a
2498        .update(cx_a, |p, cx| {
2499            p.open_buffer((worktree_id, "src/main.rs"), cx)
2500        })
2501        .await
2502        .unwrap();
2503    let other_buffer_a = project_a
2504        .update(cx_a, |p, cx| {
2505            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
2506        })
2507        .await
2508        .unwrap();
2509    let cx_a = cx_a.add_empty_window();
2510    let main_editor_a = cx_a.new_window_entity(|window, cx| {
2511        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
2512    });
2513    let other_editor_a = cx_a.new_window_entity(|window, cx| {
2514        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
2515    });
2516    let mut main_editor_cx_a = EditorTestContext {
2517        cx: cx_a.clone(),
2518        window: cx_a.window_handle(),
2519        editor: main_editor_a,
2520        assertion_cx: AssertionContextManager::new(),
2521    };
2522    let mut other_editor_cx_a = EditorTestContext {
2523        cx: cx_a.clone(),
2524        window: cx_a.window_handle(),
2525        editor: other_editor_a,
2526        assertion_cx: AssertionContextManager::new(),
2527    };
2528
2529    // Join the project as client B.
2530    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2531    let main_buffer_b = project_b
2532        .update(cx_b, |p, cx| {
2533            p.open_buffer((worktree_id, "src/main.rs"), cx)
2534        })
2535        .await
2536        .unwrap();
2537    let other_buffer_b = project_b
2538        .update(cx_b, |p, cx| {
2539            p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
2540        })
2541        .await
2542        .unwrap();
2543    let cx_b = cx_b.add_empty_window();
2544    let main_editor_b = cx_b.new_window_entity(|window, cx| {
2545        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
2546    });
2547    let other_editor_b = cx_b.new_window_entity(|window, cx| {
2548        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
2549    });
2550    let mut main_editor_cx_b = EditorTestContext {
2551        cx: cx_b.clone(),
2552        window: cx_b.window_handle(),
2553        editor: main_editor_b,
2554        assertion_cx: AssertionContextManager::new(),
2555    };
2556    let mut other_editor_cx_b = EditorTestContext {
2557        cx: cx_b.clone(),
2558        window: cx_b.window_handle(),
2559        editor: other_editor_b,
2560        assertion_cx: AssertionContextManager::new(),
2561    };
2562
2563    let initial_main = indoc! {"
2564ˇmod other;
2565fn main() { let foo = other::foo(); }"};
2566    let initial_other = indoc! {"
2567ˇpub fn foo() -> usize {
2568    4
2569}"};
2570
2571    let first_tabbed_main = indoc! {"
2572  ˇmod other;
2573fn main() { let foo = other::foo(); }"};
2574    tab_undo_assert(
2575        &mut main_editor_cx_a,
2576        &mut main_editor_cx_b,
2577        initial_main,
2578        first_tabbed_main,
2579        true,
2580    );
2581    tab_undo_assert(
2582        &mut main_editor_cx_a,
2583        &mut main_editor_cx_b,
2584        initial_main,
2585        first_tabbed_main,
2586        false,
2587    );
2588
2589    let first_tabbed_other = indoc! {"
2590  ˇpub fn foo() -> usize {
2591    4
2592}"};
2593    tab_undo_assert(
2594        &mut other_editor_cx_a,
2595        &mut other_editor_cx_b,
2596        initial_other,
2597        first_tabbed_other,
2598        true,
2599    );
2600    tab_undo_assert(
2601        &mut other_editor_cx_a,
2602        &mut other_editor_cx_b,
2603        initial_other,
2604        first_tabbed_other,
2605        false,
2606    );
2607
2608    client_a
2609        .fs()
2610        .atomic_write(
2611            PathBuf::from(path!("/a/src/.editorconfig")),
2612            "[*]\ntab_width = 3\n".to_owned(),
2613        )
2614        .await
2615        .unwrap();
2616    cx_a.run_until_parked();
2617    cx_b.run_until_parked();
2618
2619    let second_tabbed_main = indoc! {"
2620   ˇmod other;
2621fn main() { let foo = other::foo(); }"};
2622    tab_undo_assert(
2623        &mut main_editor_cx_a,
2624        &mut main_editor_cx_b,
2625        initial_main,
2626        second_tabbed_main,
2627        true,
2628    );
2629    tab_undo_assert(
2630        &mut main_editor_cx_a,
2631        &mut main_editor_cx_b,
2632        initial_main,
2633        second_tabbed_main,
2634        false,
2635    );
2636
2637    let second_tabbed_other = indoc! {"
2638   ˇpub fn foo() -> usize {
2639    4
2640}"};
2641    tab_undo_assert(
2642        &mut other_editor_cx_a,
2643        &mut other_editor_cx_b,
2644        initial_other,
2645        second_tabbed_other,
2646        true,
2647    );
2648    tab_undo_assert(
2649        &mut other_editor_cx_a,
2650        &mut other_editor_cx_b,
2651        initial_other,
2652        second_tabbed_other,
2653        false,
2654    );
2655
2656    let editorconfig_buffer_b = project_b
2657        .update(cx_b, |p, cx| {
2658            p.open_buffer((worktree_id, "src/other_mod/.editorconfig"), cx)
2659        })
2660        .await
2661        .unwrap();
2662    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
2663        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
2664    });
2665    project_b
2666        .update(cx_b, |project, cx| {
2667            project.save_buffer(editorconfig_buffer_b.clone(), cx)
2668        })
2669        .await
2670        .unwrap();
2671    cx_a.run_until_parked();
2672    cx_b.run_until_parked();
2673
2674    tab_undo_assert(
2675        &mut main_editor_cx_a,
2676        &mut main_editor_cx_b,
2677        initial_main,
2678        second_tabbed_main,
2679        true,
2680    );
2681    tab_undo_assert(
2682        &mut main_editor_cx_a,
2683        &mut main_editor_cx_b,
2684        initial_main,
2685        second_tabbed_main,
2686        false,
2687    );
2688
2689    let third_tabbed_other = indoc! {"
2690      ˇpub fn foo() -> usize {
2691    4
2692}"};
2693    tab_undo_assert(
2694        &mut other_editor_cx_a,
2695        &mut other_editor_cx_b,
2696        initial_other,
2697        third_tabbed_other,
2698        true,
2699    );
2700
2701    tab_undo_assert(
2702        &mut other_editor_cx_a,
2703        &mut other_editor_cx_b,
2704        initial_other,
2705        third_tabbed_other,
2706        false,
2707    );
2708}
2709
2710#[gpui::test]
2711async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2712    let executor = cx_a.executor();
2713    let mut server = TestServer::start(executor.clone()).await;
2714    let client_a = server.create_client(cx_a, "user_a").await;
2715    let client_b = server.create_client(cx_b, "user_b").await;
2716    server
2717        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2718        .await;
2719    let active_call_a = cx_a.read(ActiveCall::global);
2720    let active_call_b = cx_b.read(ActiveCall::global);
2721    cx_a.update(editor::init);
2722    cx_b.update(editor::init);
2723    client_a
2724        .fs()
2725        .insert_tree(
2726            "/a",
2727            json!({
2728                "test.txt": "one\ntwo\nthree\nfour\nfive",
2729            }),
2730        )
2731        .await;
2732    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
2733    let project_path = ProjectPath {
2734        worktree_id,
2735        path: Arc::from(Path::new(&"test.txt")),
2736    };
2737    let abs_path = project_a.read_with(cx_a, |project, cx| {
2738        project
2739            .absolute_path(&project_path, cx)
2740            .map(|path_buf| Arc::from(path_buf.to_owned()))
2741            .unwrap()
2742    });
2743
2744    active_call_a
2745        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2746        .await
2747        .unwrap();
2748    let project_id = active_call_a
2749        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2750        .await
2751        .unwrap();
2752    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2753    active_call_b
2754        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2755        .await
2756        .unwrap();
2757    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2758    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2759
2760    // Client A opens an editor.
2761    let editor_a = workspace_a
2762        .update_in(cx_a, |workspace, window, cx| {
2763            workspace.open_path(project_path.clone(), None, true, window, cx)
2764        })
2765        .await
2766        .unwrap()
2767        .downcast::<Editor>()
2768        .unwrap();
2769
2770    // Client B opens same editor as A.
2771    let editor_b = workspace_b
2772        .update_in(cx_b, |workspace, window, cx| {
2773            workspace.open_path(project_path.clone(), None, true, window, cx)
2774        })
2775        .await
2776        .unwrap()
2777        .downcast::<Editor>()
2778        .unwrap();
2779
2780    cx_a.run_until_parked();
2781    cx_b.run_until_parked();
2782
2783    // Client A adds breakpoint on line (1)
2784    editor_a.update_in(cx_a, |editor, window, cx| {
2785        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2786    });
2787
2788    cx_a.run_until_parked();
2789    cx_b.run_until_parked();
2790
2791    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2792        editor
2793            .breakpoint_store()
2794            .clone()
2795            .unwrap()
2796            .read(cx)
2797            .all_source_breakpoints(cx)
2798            .clone()
2799    });
2800    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2801        editor
2802            .breakpoint_store()
2803            .clone()
2804            .unwrap()
2805            .read(cx)
2806            .all_source_breakpoints(cx)
2807            .clone()
2808    });
2809
2810    assert_eq!(1, breakpoints_a.len());
2811    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
2812    assert_eq!(breakpoints_a, breakpoints_b);
2813
2814    // Client B adds breakpoint on line(2)
2815    editor_b.update_in(cx_b, |editor, window, cx| {
2816        editor.move_down(&editor::actions::MoveDown, window, cx);
2817        editor.move_down(&editor::actions::MoveDown, window, cx);
2818        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2819    });
2820
2821    cx_a.run_until_parked();
2822    cx_b.run_until_parked();
2823
2824    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2825        editor
2826            .breakpoint_store()
2827            .clone()
2828            .unwrap()
2829            .read(cx)
2830            .all_source_breakpoints(cx)
2831            .clone()
2832    });
2833    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2834        editor
2835            .breakpoint_store()
2836            .clone()
2837            .unwrap()
2838            .read(cx)
2839            .all_source_breakpoints(cx)
2840            .clone()
2841    });
2842
2843    assert_eq!(1, breakpoints_a.len());
2844    assert_eq!(breakpoints_a, breakpoints_b);
2845    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
2846
2847    // Client A removes last added breakpoint from client B
2848    editor_a.update_in(cx_a, |editor, window, cx| {
2849        editor.move_down(&editor::actions::MoveDown, window, cx);
2850        editor.move_down(&editor::actions::MoveDown, window, cx);
2851        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2852    });
2853
2854    cx_a.run_until_parked();
2855    cx_b.run_until_parked();
2856
2857    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2858        editor
2859            .breakpoint_store()
2860            .clone()
2861            .unwrap()
2862            .read(cx)
2863            .all_source_breakpoints(cx)
2864            .clone()
2865    });
2866    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2867        editor
2868            .breakpoint_store()
2869            .clone()
2870            .unwrap()
2871            .read(cx)
2872            .all_source_breakpoints(cx)
2873            .clone()
2874    });
2875
2876    assert_eq!(1, breakpoints_a.len());
2877    assert_eq!(breakpoints_a, breakpoints_b);
2878    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
2879
2880    // Client B removes first added breakpoint by client A
2881    editor_b.update_in(cx_b, |editor, window, cx| {
2882        editor.move_up(&editor::actions::MoveUp, window, cx);
2883        editor.move_up(&editor::actions::MoveUp, window, cx);
2884        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
2885    });
2886
2887    cx_a.run_until_parked();
2888    cx_b.run_until_parked();
2889
2890    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
2891        editor
2892            .breakpoint_store()
2893            .clone()
2894            .unwrap()
2895            .read(cx)
2896            .all_source_breakpoints(cx)
2897            .clone()
2898    });
2899    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
2900        editor
2901            .breakpoint_store()
2902            .clone()
2903            .unwrap()
2904            .read(cx)
2905            .all_source_breakpoints(cx)
2906            .clone()
2907    });
2908
2909    assert_eq!(0, breakpoints_a.len());
2910    assert_eq!(breakpoints_a, breakpoints_b);
2911}
2912
2913#[gpui::test]
2914async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2915    let mut server = TestServer::start(cx_a.executor()).await;
2916    let client_a = server.create_client(cx_a, "user_a").await;
2917    let client_b = server.create_client(cx_b, "user_b").await;
2918    server
2919        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2920        .await;
2921    let active_call_a = cx_a.read(ActiveCall::global);
2922    let active_call_b = cx_b.read(ActiveCall::global);
2923
2924    cx_a.update(editor::init);
2925    cx_b.update(editor::init);
2926
2927    client_a.language_registry().add(rust_lang());
2928    client_b.language_registry().add(rust_lang());
2929    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2930        "Rust",
2931        FakeLspAdapter {
2932            name: RUST_ANALYZER_NAME,
2933            ..FakeLspAdapter::default()
2934        },
2935    );
2936
2937    client_a
2938        .fs()
2939        .insert_tree(
2940            path!("/a"),
2941            json!({
2942                "main.rs": "fn main() {}",
2943            }),
2944        )
2945        .await;
2946    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2947    active_call_a
2948        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2949        .await
2950        .unwrap();
2951    let project_id = active_call_a
2952        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2953        .await
2954        .unwrap();
2955
2956    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2957    active_call_b
2958        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2959        .await
2960        .unwrap();
2961
2962    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2963    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2964
2965    let editor_a = workspace_a
2966        .update_in(cx_a, |workspace, window, cx| {
2967            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2968        })
2969        .await
2970        .unwrap()
2971        .downcast::<Editor>()
2972        .unwrap();
2973
2974    let editor_b = workspace_b
2975        .update_in(cx_b, |workspace, window, cx| {
2976            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
2977        })
2978        .await
2979        .unwrap()
2980        .downcast::<Editor>()
2981        .unwrap();
2982
2983    let fake_language_server = fake_language_servers.next().await.unwrap();
2984
2985    // host
2986    let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
2987        |params, _| async move {
2988            assert_eq!(
2989                params.text_document.uri,
2990                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2991            );
2992            assert_eq!(params.position, lsp::Position::new(0, 0));
2993            Ok(Some(ExpandedMacro {
2994                name: "test_macro_name".to_string(),
2995                expansion: "test_macro_expansion on the host".to_string(),
2996            }))
2997        },
2998    );
2999
3000    editor_a.update_in(cx_a, |editor, window, cx| {
3001        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
3002    });
3003    expand_request_a.next().await.unwrap();
3004    cx_a.run_until_parked();
3005
3006    workspace_a.update(cx_a, |workspace, cx| {
3007        workspace.active_pane().update(cx, |pane, cx| {
3008            assert_eq!(
3009                pane.items_len(),
3010                2,
3011                "Should have added a macro expansion to the host's pane"
3012            );
3013            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
3014            new_editor.update(cx, |editor, cx| {
3015                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
3016            });
3017        })
3018    });
3019
3020    // client
3021    let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
3022        |params, _| async move {
3023            assert_eq!(
3024                params.text_document.uri,
3025                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
3026            );
3027            assert_eq!(
3028                params.position,
3029                lsp::Position::new(0, 12),
3030                "editor_b has selected the entire text and should query for a different position"
3031            );
3032            Ok(Some(ExpandedMacro {
3033                name: "test_macro_name".to_string(),
3034                expansion: "test_macro_expansion on the client".to_string(),
3035            }))
3036        },
3037    );
3038
3039    editor_b.update_in(cx_b, |editor, window, cx| {
3040        editor.select_all(&SelectAll, window, cx);
3041        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
3042    });
3043    expand_request_b.next().await.unwrap();
3044    cx_b.run_until_parked();
3045
3046    workspace_b.update(cx_b, |workspace, cx| {
3047        workspace.active_pane().update(cx, |pane, cx| {
3048            assert_eq!(
3049                pane.items_len(),
3050                2,
3051                "Should have added a macro expansion to the client's pane"
3052            );
3053            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
3054            new_editor.update(cx, |editor, cx| {
3055                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
3056            });
3057        })
3058    });
3059}
3060
3061#[track_caller]
3062fn tab_undo_assert(
3063    cx_a: &mut EditorTestContext,
3064    cx_b: &mut EditorTestContext,
3065    expected_initial: &str,
3066    expected_tabbed: &str,
3067    a_tabs: bool,
3068) {
3069    cx_a.assert_editor_state(expected_initial);
3070    cx_b.assert_editor_state(expected_initial);
3071
3072    if a_tabs {
3073        cx_a.update_editor(|editor, window, cx| {
3074            editor.tab(&editor::actions::Tab, window, cx);
3075        });
3076    } else {
3077        cx_b.update_editor(|editor, window, cx| {
3078            editor.tab(&editor::actions::Tab, window, cx);
3079        });
3080    }
3081
3082    cx_a.run_until_parked();
3083    cx_b.run_until_parked();
3084
3085    cx_a.assert_editor_state(expected_tabbed);
3086    cx_b.assert_editor_state(expected_tabbed);
3087
3088    if a_tabs {
3089        cx_a.update_editor(|editor, window, cx| {
3090            editor.undo(&editor::actions::Undo, window, cx);
3091        });
3092    } else {
3093        cx_b.update_editor(|editor, window, cx| {
3094            editor.undo(&editor::actions::Undo, window, cx);
3095        });
3096    }
3097    cx_a.run_until_parked();
3098    cx_b.run_until_parked();
3099    cx_a.assert_editor_state(expected_initial);
3100    cx_b.assert_editor_state(expected_initial);
3101}
3102
3103fn extract_hint_labels(editor: &Editor) -> Vec<String> {
3104    let mut labels = Vec::new();
3105    for hint in editor.inlay_hint_cache().hints() {
3106        match hint.label {
3107            project::InlayHintLabel::String(s) => labels.push(s),
3108            _ => unreachable!(),
3109        }
3110    }
3111    labels
3112}
3113
3114#[track_caller]
3115fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
3116    editor
3117        .all_inlays(cx)
3118        .into_iter()
3119        .filter_map(|inlay| inlay.get_color())
3120        .map(Rgba::from)
3121        .collect()
3122}
3123
3124fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
3125    git::blame::BlameEntry {
3126        sha: sha.parse().unwrap(),
3127        range,
3128        ..Default::default()
3129    }
3130}