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