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