editor_tests.rs

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