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![lsp::InlayHint {
2173                    position: lsp::Position::new(0, character),
2174                    label: lsp::InlayHintLabel::String(label.to_string()),
2175                    kind: None,
2176                    text_edits: None,
2177                    tooltip: None,
2178                    padding_left: None,
2179                    padding_right: None,
2180                    data: None,
2181                }]))
2182            }
2183        })
2184        .next()
2185        .await
2186        .unwrap();
2187    executor.finish_waiting();
2188
2189    executor.run_until_parked();
2190    editor_a.update(cx_a, |editor, cx| {
2191        assert!(
2192            extract_hint_labels(editor, cx).is_empty(),
2193            "Host should get no hints due to them turned off"
2194        );
2195    });
2196
2197    executor.run_until_parked();
2198    editor_b.update(cx_b, |editor, cx| {
2199        assert_eq!(
2200            vec!["initial hint".to_string()],
2201            extract_hint_labels(editor, cx),
2202            "Client should get its first hints when opens an editor"
2203        );
2204    });
2205
2206    other_hints.fetch_or(true, atomic::Ordering::Release);
2207    fake_language_server
2208        .request::<lsp::request::InlayHintRefreshRequest>(())
2209        .await
2210        .into_response()
2211        .expect("inlay refresh request failed");
2212    executor.run_until_parked();
2213    editor_a.update(cx_a, |editor, cx| {
2214        assert!(
2215            extract_hint_labels(editor, cx).is_empty(),
2216            "Host should get no hints due to them turned off, even after the /refresh"
2217        );
2218    });
2219
2220    executor.run_until_parked();
2221    editor_b.update(cx_b, |editor, cx| {
2222        assert_eq!(
2223            vec!["other hint".to_string()],
2224            extract_hint_labels(editor, cx),
2225            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
2226        );
2227    });
2228}
2229
2230#[gpui::test(iterations = 10)]
2231async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2232    let expected_color = Rgba {
2233        r: 0.33,
2234        g: 0.33,
2235        b: 0.33,
2236        a: 0.33,
2237    };
2238    let mut server = TestServer::start(cx_a.executor()).await;
2239    let executor = cx_a.executor();
2240    let client_a = server.create_client(cx_a, "user_a").await;
2241    let client_b = server.create_client(cx_b, "user_b").await;
2242    server
2243        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2244        .await;
2245    let active_call_a = cx_a.read(ActiveCall::global);
2246    let active_call_b = cx_b.read(ActiveCall::global);
2247
2248    cx_a.update(editor::init);
2249    cx_b.update(editor::init);
2250
2251    cx_a.update(|cx| {
2252        SettingsStore::update_global(cx, |store, cx| {
2253            store.update_user_settings(cx, |settings| {
2254                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::None);
2255            });
2256        });
2257    });
2258    cx_b.update(|cx| {
2259        SettingsStore::update_global(cx, |store, cx| {
2260            store.update_user_settings(cx, |settings| {
2261                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2262            });
2263        });
2264    });
2265
2266    let capabilities = lsp::ServerCapabilities {
2267        color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
2268        ..lsp::ServerCapabilities::default()
2269    };
2270    client_a.language_registry().add(rust_lang());
2271    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2272        "Rust",
2273        FakeLspAdapter {
2274            capabilities: capabilities.clone(),
2275            ..FakeLspAdapter::default()
2276        },
2277    );
2278    client_b.language_registry().add(rust_lang());
2279    client_b.language_registry().register_fake_lsp_adapter(
2280        "Rust",
2281        FakeLspAdapter {
2282            capabilities,
2283            ..FakeLspAdapter::default()
2284        },
2285    );
2286
2287    // Client A opens a project.
2288    client_a
2289        .fs()
2290        .insert_tree(
2291            path!("/a"),
2292            json!({
2293                "main.rs": "fn main() { a }",
2294            }),
2295        )
2296        .await;
2297    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2298    active_call_a
2299        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2300        .await
2301        .unwrap();
2302    let project_id = active_call_a
2303        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2304        .await
2305        .unwrap();
2306
2307    // Client B joins the project
2308    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2309    active_call_b
2310        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2311        .await
2312        .unwrap();
2313
2314    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2315
2316    // The host opens a rust file.
2317    let _buffer_a = project_a
2318        .update(cx_a, |project, cx| {
2319            project.open_local_buffer(path!("/a/main.rs"), cx)
2320        })
2321        .await
2322        .unwrap();
2323    let editor_a = workspace_a
2324        .update_in(cx_a, |workspace, window, cx| {
2325            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2326        })
2327        .await
2328        .unwrap()
2329        .downcast::<Editor>()
2330        .unwrap();
2331
2332    let fake_language_server = fake_language_servers.next().await.unwrap();
2333    cx_a.run_until_parked();
2334    cx_b.run_until_parked();
2335
2336    let requests_made = Arc::new(AtomicUsize::new(0));
2337    let closure_requests_made = Arc::clone(&requests_made);
2338    let mut color_request_handle = fake_language_server
2339        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
2340            let requests_made = Arc::clone(&closure_requests_made);
2341            async move {
2342                assert_eq!(
2343                    params.text_document.uri,
2344                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2345                );
2346                requests_made.fetch_add(1, atomic::Ordering::Release);
2347                Ok(vec![lsp::ColorInformation {
2348                    range: lsp::Range {
2349                        start: lsp::Position {
2350                            line: 0,
2351                            character: 0,
2352                        },
2353                        end: lsp::Position {
2354                            line: 0,
2355                            character: 1,
2356                        },
2357                    },
2358                    color: lsp::Color {
2359                        red: 0.33,
2360                        green: 0.33,
2361                        blue: 0.33,
2362                        alpha: 0.33,
2363                    },
2364                }])
2365            }
2366        });
2367    executor.run_until_parked();
2368
2369    assert_eq!(
2370        0,
2371        requests_made.load(atomic::Ordering::Acquire),
2372        "Host did not enable document colors, hence should query for none"
2373    );
2374    editor_a.update(cx_a, |editor, cx| {
2375        assert_eq!(
2376            Vec::<Rgba>::new(),
2377            extract_color_inlays(editor, cx),
2378            "No query colors should result in no hints"
2379        );
2380    });
2381
2382    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2383    let editor_b = workspace_b
2384        .update_in(cx_b, |workspace, window, cx| {
2385            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2386        })
2387        .await
2388        .unwrap()
2389        .downcast::<Editor>()
2390        .unwrap();
2391
2392    color_request_handle.next().await.unwrap();
2393    executor.advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
2394    executor.run_until_parked();
2395
2396    assert_eq!(
2397        1,
2398        requests_made.load(atomic::Ordering::Acquire),
2399        "The client opened the file and got its first colors back"
2400    );
2401    editor_b.update(cx_b, |editor, cx| {
2402        assert_eq!(
2403            vec![expected_color],
2404            extract_color_inlays(editor, cx),
2405            "With document colors as inlays, color inlays should be pushed"
2406        );
2407    });
2408
2409    editor_a.update_in(cx_a, |editor, window, cx| {
2410        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2411            s.select_ranges([13..13].clone())
2412        });
2413        editor.handle_input(":", window, cx);
2414    });
2415    color_request_handle.next().await.unwrap();
2416    executor.run_until_parked();
2417    assert_eq!(
2418        2,
2419        requests_made.load(atomic::Ordering::Acquire),
2420        "After the host edits his file, the client should request the colors again"
2421    );
2422    editor_a.update(cx_a, |editor, cx| {
2423        assert_eq!(
2424            Vec::<Rgba>::new(),
2425            extract_color_inlays(editor, cx),
2426            "Host has no colors still"
2427        );
2428    });
2429    editor_b.update(cx_b, |editor, cx| {
2430        assert_eq!(vec![expected_color], extract_color_inlays(editor, cx),);
2431    });
2432
2433    cx_b.update(|_, cx| {
2434        SettingsStore::update_global(cx, |store, cx| {
2435            store.update_user_settings(cx, |settings| {
2436                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Background);
2437            });
2438        });
2439    });
2440    executor.run_until_parked();
2441    assert_eq!(
2442        2,
2443        requests_made.load(atomic::Ordering::Acquire),
2444        "After the client have changed the colors settings, no extra queries should happen"
2445    );
2446    editor_a.update(cx_a, |editor, cx| {
2447        assert_eq!(
2448            Vec::<Rgba>::new(),
2449            extract_color_inlays(editor, cx),
2450            "Host is unaffected by the client's settings changes"
2451        );
2452    });
2453    editor_b.update(cx_b, |editor, cx| {
2454        assert_eq!(
2455            Vec::<Rgba>::new(),
2456            extract_color_inlays(editor, cx),
2457            "Client should have no colors hints, as in the settings"
2458        );
2459    });
2460
2461    cx_b.update(|_, cx| {
2462        SettingsStore::update_global(cx, |store, cx| {
2463            store.update_user_settings(cx, |settings| {
2464                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2465            });
2466        });
2467    });
2468    executor.run_until_parked();
2469    assert_eq!(
2470        2,
2471        requests_made.load(atomic::Ordering::Acquire),
2472        "After falling back to colors as inlays, no extra LSP queries are made"
2473    );
2474    editor_a.update(cx_a, |editor, cx| {
2475        assert_eq!(
2476            Vec::<Rgba>::new(),
2477            extract_color_inlays(editor, cx),
2478            "Host is unaffected by the client's settings changes, again"
2479        );
2480    });
2481    editor_b.update(cx_b, |editor, cx| {
2482        assert_eq!(
2483            vec![expected_color],
2484            extract_color_inlays(editor, cx),
2485            "Client should have its color hints back"
2486        );
2487    });
2488
2489    cx_a.update(|_, cx| {
2490        SettingsStore::update_global(cx, |store, cx| {
2491            store.update_user_settings(cx, |settings| {
2492                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Border);
2493            });
2494        });
2495    });
2496    color_request_handle.next().await.unwrap();
2497    executor.run_until_parked();
2498    assert_eq!(
2499        3,
2500        requests_made.load(atomic::Ordering::Acquire),
2501        "After the host enables document colors, another LSP query should be made"
2502    );
2503    editor_a.update(cx_a, |editor, cx| {
2504        assert_eq!(
2505            Vec::<Rgba>::new(),
2506            extract_color_inlays(editor, cx),
2507            "Host did not configure document colors as hints hence gets nothing"
2508        );
2509    });
2510    editor_b.update(cx_b, |editor, cx| {
2511        assert_eq!(
2512            vec![expected_color],
2513            extract_color_inlays(editor, cx),
2514            "Client should be unaffected by the host's settings changes"
2515        );
2516    });
2517}
2518
2519async fn test_lsp_pull_diagnostics(
2520    should_stream_workspace_diagnostic: bool,
2521    cx_a: &mut TestAppContext,
2522    cx_b: &mut TestAppContext,
2523) {
2524    let mut server = TestServer::start(cx_a.executor()).await;
2525    let executor = cx_a.executor();
2526    let client_a = server.create_client(cx_a, "user_a").await;
2527    let client_b = server.create_client(cx_b, "user_b").await;
2528    server
2529        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2530        .await;
2531    let active_call_a = cx_a.read(ActiveCall::global);
2532    let active_call_b = cx_b.read(ActiveCall::global);
2533
2534    cx_a.update(editor::init);
2535    cx_b.update(editor::init);
2536
2537    let expected_push_diagnostic_main_message = "pushed main diagnostic";
2538    let expected_push_diagnostic_lib_message = "pushed lib diagnostic";
2539    let expected_pull_diagnostic_main_message = "pulled main diagnostic";
2540    let expected_pull_diagnostic_lib_message = "pulled lib diagnostic";
2541    let expected_workspace_pull_diagnostics_main_message = "pulled workspace main diagnostic";
2542    let expected_workspace_pull_diagnostics_lib_message = "pulled workspace lib diagnostic";
2543
2544    let diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<Option<String>>::new()));
2545    let workspace_diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<String>::new()));
2546    let diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2547    let closure_diagnostics_pulls_made = diagnostics_pulls_made.clone();
2548    let closure_diagnostics_pulls_result_ids = diagnostics_pulls_result_ids.clone();
2549    let workspace_diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2550    let closure_workspace_diagnostics_pulls_made = workspace_diagnostics_pulls_made.clone();
2551    let closure_workspace_diagnostics_pulls_result_ids =
2552        workspace_diagnostics_pulls_result_ids.clone();
2553    let (workspace_diagnostic_cancel_tx, closure_workspace_diagnostic_cancel_rx) =
2554        smol::channel::bounded::<()>(1);
2555    let (closure_workspace_diagnostic_received_tx, workspace_diagnostic_received_rx) =
2556        smol::channel::bounded::<()>(1);
2557
2558    let capabilities = lsp::ServerCapabilities {
2559        diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
2560            lsp::DiagnosticOptions {
2561                identifier: Some("test-pulls".to_string()),
2562                inter_file_dependencies: true,
2563                workspace_diagnostics: true,
2564                work_done_progress_options: lsp::WorkDoneProgressOptions {
2565                    work_done_progress: None,
2566                },
2567            },
2568        )),
2569        ..lsp::ServerCapabilities::default()
2570    };
2571    client_a.language_registry().add(rust_lang());
2572
2573    let pull_diagnostics_handle = Arc::new(parking_lot::Mutex::new(None));
2574    let workspace_diagnostics_pulls_handle = Arc::new(parking_lot::Mutex::new(None));
2575
2576    let closure_pull_diagnostics_handle = pull_diagnostics_handle.clone();
2577    let closure_workspace_diagnostics_pulls_handle = workspace_diagnostics_pulls_handle.clone();
2578    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2579        "Rust",
2580        FakeLspAdapter {
2581            capabilities: capabilities.clone(),
2582            initializer: Some(Box::new(move |fake_language_server| {
2583                let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!(
2584                    "workspace/diagnostic/{}/1",
2585                    fake_language_server.server.server_id()
2586                ));
2587                let closure_workspace_diagnostics_pulls_result_ids = closure_workspace_diagnostics_pulls_result_ids.clone();
2588                let diagnostics_pulls_made = closure_diagnostics_pulls_made.clone();
2589                let diagnostics_pulls_result_ids = closure_diagnostics_pulls_result_ids.clone();
2590                let closure_pull_diagnostics_handle = closure_pull_diagnostics_handle.clone();
2591                let closure_workspace_diagnostics_pulls_handle = closure_workspace_diagnostics_pulls_handle.clone();
2592                let closure_workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone();
2593                let closure_workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone();
2594                let pull_diagnostics_handle = fake_language_server
2595                    .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(
2596                        move |params, _| {
2597                            let requests_made = diagnostics_pulls_made.clone();
2598                            let diagnostics_pulls_result_ids =
2599                                diagnostics_pulls_result_ids.clone();
2600                            async move {
2601                                let message = if lsp::Uri::from_file_path(path!("/a/main.rs"))
2602                                    .unwrap()
2603                                    == params.text_document.uri
2604                                {
2605                                    expected_pull_diagnostic_main_message.to_string()
2606                                } else if lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap()
2607                                    == params.text_document.uri
2608                                {
2609                                    expected_pull_diagnostic_lib_message.to_string()
2610                                } else {
2611                                    panic!("Unexpected document: {}", params.text_document.uri)
2612                                };
2613                                {
2614                                    diagnostics_pulls_result_ids
2615                                        .lock()
2616                                        .await
2617                                        .insert(params.previous_result_id);
2618                                }
2619                                let new_requests_count =
2620                                    requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2621                                Ok(lsp::DocumentDiagnosticReportResult::Report(
2622                                    lsp::DocumentDiagnosticReport::Full(
2623                                        lsp::RelatedFullDocumentDiagnosticReport {
2624                                            related_documents: None,
2625                                            full_document_diagnostic_report:
2626                                                lsp::FullDocumentDiagnosticReport {
2627                                                    result_id: Some(format!(
2628                                                        "pull-{new_requests_count}"
2629                                                    )),
2630                                                    items: vec![lsp::Diagnostic {
2631                                                        range: lsp::Range {
2632                                                            start: lsp::Position {
2633                                                                line: 0,
2634                                                                character: 0,
2635                                                            },
2636                                                            end: lsp::Position {
2637                                                                line: 0,
2638                                                                character: 2,
2639                                                            },
2640                                                        },
2641                                                        severity: Some(
2642                                                            lsp::DiagnosticSeverity::ERROR,
2643                                                        ),
2644                                                        message,
2645                                                        ..lsp::Diagnostic::default()
2646                                                    }],
2647                                                },
2648                                        },
2649                                    ),
2650                                ))
2651                            }
2652                        },
2653                    );
2654                let _ = closure_pull_diagnostics_handle.lock().insert(pull_diagnostics_handle);
2655
2656                let closure_workspace_diagnostics_pulls_made = closure_workspace_diagnostics_pulls_made.clone();
2657                let workspace_diagnostics_pulls_handle = fake_language_server.set_request_handler::<lsp::request::WorkspaceDiagnosticRequest, _, _>(
2658                    move |params, _| {
2659                        let workspace_requests_made = closure_workspace_diagnostics_pulls_made.clone();
2660                        let workspace_diagnostics_pulls_result_ids =
2661                            closure_workspace_diagnostics_pulls_result_ids.clone();
2662                        let workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone();
2663                        let workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone();
2664                        let expected_workspace_diagnostic_token = expected_workspace_diagnostic_token.clone();
2665                        async move {
2666                            let workspace_request_count =
2667                                workspace_requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2668                            {
2669                                workspace_diagnostics_pulls_result_ids
2670                                    .lock()
2671                                    .await
2672                                    .extend(params.previous_result_ids.into_iter().map(|id| id.value));
2673                            }
2674                            if should_stream_workspace_diagnostic && !workspace_diagnostic_cancel_rx.is_closed()
2675                            {
2676                                assert_eq!(
2677                                    params.partial_result_params.partial_result_token,
2678                                    Some(expected_workspace_diagnostic_token)
2679                                );
2680                                workspace_diagnostic_received_tx.send(()).await.unwrap();
2681                                workspace_diagnostic_cancel_rx.recv().await.unwrap();
2682                                workspace_diagnostic_cancel_rx.close();
2683                                // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#partialResults
2684                                // > The final response has to be empty in terms of result values.
2685                                return Ok(lsp::WorkspaceDiagnosticReportResult::Report(
2686                                    lsp::WorkspaceDiagnosticReport { items: Vec::new() },
2687                                ));
2688                            }
2689                            Ok(lsp::WorkspaceDiagnosticReportResult::Report(
2690                                lsp::WorkspaceDiagnosticReport {
2691                                    items: vec![
2692                                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2693                                            lsp::WorkspaceFullDocumentDiagnosticReport {
2694                                                uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2695                                                version: None,
2696                                                full_document_diagnostic_report:
2697                                                    lsp::FullDocumentDiagnosticReport {
2698                                                        result_id: Some(format!(
2699                                                            "workspace_{workspace_request_count}"
2700                                                        )),
2701                                                        items: vec![lsp::Diagnostic {
2702                                                            range: lsp::Range {
2703                                                                start: lsp::Position {
2704                                                                    line: 0,
2705                                                                    character: 1,
2706                                                                },
2707                                                                end: lsp::Position {
2708                                                                    line: 0,
2709                                                                    character: 3,
2710                                                                },
2711                                                            },
2712                                                            severity: Some(lsp::DiagnosticSeverity::WARNING),
2713                                                            message:
2714                                                                expected_workspace_pull_diagnostics_main_message
2715                                                                    .to_string(),
2716                                                            ..lsp::Diagnostic::default()
2717                                                        }],
2718                                                    },
2719                                            },
2720                                        ),
2721                                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2722                                            lsp::WorkspaceFullDocumentDiagnosticReport {
2723                                                uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
2724                                                version: None,
2725                                                full_document_diagnostic_report:
2726                                                    lsp::FullDocumentDiagnosticReport {
2727                                                        result_id: Some(format!(
2728                                                            "workspace_{workspace_request_count}"
2729                                                        )),
2730                                                        items: vec![lsp::Diagnostic {
2731                                                            range: lsp::Range {
2732                                                                start: lsp::Position {
2733                                                                    line: 0,
2734                                                                    character: 1,
2735                                                                },
2736                                                                end: lsp::Position {
2737                                                                    line: 0,
2738                                                                    character: 3,
2739                                                                },
2740                                                            },
2741                                                            severity: Some(lsp::DiagnosticSeverity::WARNING),
2742                                                            message:
2743                                                                expected_workspace_pull_diagnostics_lib_message
2744                                                                    .to_string(),
2745                                                            ..lsp::Diagnostic::default()
2746                                                        }],
2747                                                    },
2748                                            },
2749                                        ),
2750                                    ],
2751                                },
2752                            ))
2753                        }
2754                    });
2755                let _ = closure_workspace_diagnostics_pulls_handle.lock().insert(workspace_diagnostics_pulls_handle);
2756            })),
2757            ..FakeLspAdapter::default()
2758        },
2759    );
2760
2761    client_b.language_registry().add(rust_lang());
2762    client_b.language_registry().register_fake_lsp_adapter(
2763        "Rust",
2764        FakeLspAdapter {
2765            capabilities,
2766            ..FakeLspAdapter::default()
2767        },
2768    );
2769
2770    // Client A opens a project.
2771    client_a
2772        .fs()
2773        .insert_tree(
2774            path!("/a"),
2775            json!({
2776                "main.rs": "fn main() { a }",
2777                "lib.rs": "fn other() {}",
2778            }),
2779        )
2780        .await;
2781    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2782    active_call_a
2783        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2784        .await
2785        .unwrap();
2786    let project_id = active_call_a
2787        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2788        .await
2789        .unwrap();
2790
2791    // Client B joins the project
2792    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2793    active_call_b
2794        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2795        .await
2796        .unwrap();
2797
2798    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2799    executor.start_waiting();
2800
2801    // The host opens a rust file.
2802    let _buffer_a = project_a
2803        .update(cx_a, |project, cx| {
2804            project.open_local_buffer(path!("/a/main.rs"), cx)
2805        })
2806        .await
2807        .unwrap();
2808    let editor_a_main = workspace_a
2809        .update_in(cx_a, |workspace, window, cx| {
2810            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2811        })
2812        .await
2813        .unwrap()
2814        .downcast::<Editor>()
2815        .unwrap();
2816
2817    let fake_language_server = fake_language_servers.next().await.unwrap();
2818    let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!(
2819        "workspace/diagnostic-{}-1",
2820        fake_language_server.server.server_id()
2821    ));
2822    cx_a.run_until_parked();
2823    cx_b.run_until_parked();
2824    let mut pull_diagnostics_handle = pull_diagnostics_handle.lock().take().unwrap();
2825    let mut workspace_diagnostics_pulls_handle =
2826        workspace_diagnostics_pulls_handle.lock().take().unwrap();
2827
2828    if should_stream_workspace_diagnostic {
2829        workspace_diagnostic_received_rx.recv().await.unwrap();
2830    } else {
2831        workspace_diagnostics_pulls_handle.next().await.unwrap();
2832    }
2833    assert_eq!(
2834        1,
2835        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2836        "Workspace diagnostics should be pulled initially on a server startup"
2837    );
2838    pull_diagnostics_handle.next().await.unwrap();
2839    assert_eq!(
2840        1,
2841        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2842        "Host should query pull diagnostics when the editor is opened"
2843    );
2844    executor.run_until_parked();
2845    editor_a_main.update(cx_a, |editor, cx| {
2846        let snapshot = editor.buffer().read(cx).snapshot(cx);
2847        let all_diagnostics = snapshot
2848            .diagnostics_in_range(0..snapshot.len())
2849            .collect::<Vec<_>>();
2850        assert_eq!(
2851            all_diagnostics.len(),
2852            1,
2853            "Expected single diagnostic, but got: {all_diagnostics:?}"
2854        );
2855        let diagnostic = &all_diagnostics[0];
2856        let mut expected_messages = vec![expected_pull_diagnostic_main_message];
2857        if !should_stream_workspace_diagnostic {
2858            expected_messages.push(expected_workspace_pull_diagnostics_main_message);
2859        }
2860        assert!(
2861            expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2862            "Expected {expected_messages:?} on the host, but got: {}",
2863            diagnostic.diagnostic.message
2864        );
2865    });
2866
2867    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2868        lsp::PublishDiagnosticsParams {
2869            uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2870            diagnostics: vec![lsp::Diagnostic {
2871                range: lsp::Range {
2872                    start: lsp::Position {
2873                        line: 0,
2874                        character: 3,
2875                    },
2876                    end: lsp::Position {
2877                        line: 0,
2878                        character: 4,
2879                    },
2880                },
2881                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
2882                message: expected_push_diagnostic_main_message.to_string(),
2883                ..lsp::Diagnostic::default()
2884            }],
2885            version: None,
2886        },
2887    );
2888    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2889        lsp::PublishDiagnosticsParams {
2890            uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
2891            diagnostics: vec![lsp::Diagnostic {
2892                range: lsp::Range {
2893                    start: lsp::Position {
2894                        line: 0,
2895                        character: 3,
2896                    },
2897                    end: lsp::Position {
2898                        line: 0,
2899                        character: 4,
2900                    },
2901                },
2902                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
2903                message: expected_push_diagnostic_lib_message.to_string(),
2904                ..lsp::Diagnostic::default()
2905            }],
2906            version: None,
2907        },
2908    );
2909
2910    if should_stream_workspace_diagnostic {
2911        fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
2912            token: expected_workspace_diagnostic_token.clone(),
2913            value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
2914                lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
2915                    items: vec![
2916                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2917                            lsp::WorkspaceFullDocumentDiagnosticReport {
2918                                uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2919                                version: None,
2920                                full_document_diagnostic_report:
2921                                    lsp::FullDocumentDiagnosticReport {
2922                                        result_id: Some(format!(
2923                                            "workspace_{}",
2924                                            workspace_diagnostics_pulls_made
2925                                                .fetch_add(1, atomic::Ordering::Release)
2926                                                + 1
2927                                        )),
2928                                        items: vec![lsp::Diagnostic {
2929                                            range: lsp::Range {
2930                                                start: lsp::Position {
2931                                                    line: 0,
2932                                                    character: 1,
2933                                                },
2934                                                end: lsp::Position {
2935                                                    line: 0,
2936                                                    character: 2,
2937                                                },
2938                                            },
2939                                            severity: Some(lsp::DiagnosticSeverity::ERROR),
2940                                            message:
2941                                                expected_workspace_pull_diagnostics_main_message
2942                                                    .to_string(),
2943                                            ..lsp::Diagnostic::default()
2944                                        }],
2945                                    },
2946                            },
2947                        ),
2948                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2949                            lsp::WorkspaceFullDocumentDiagnosticReport {
2950                                uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
2951                                version: None,
2952                                full_document_diagnostic_report:
2953                                    lsp::FullDocumentDiagnosticReport {
2954                                        result_id: Some(format!(
2955                                            "workspace_{}",
2956                                            workspace_diagnostics_pulls_made
2957                                                .fetch_add(1, atomic::Ordering::Release)
2958                                                + 1
2959                                        )),
2960                                        items: Vec::new(),
2961                                    },
2962                            },
2963                        ),
2964                    ],
2965                }),
2966            ),
2967        });
2968    };
2969
2970    let mut workspace_diagnostic_start_count =
2971        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
2972
2973    executor.run_until_parked();
2974    editor_a_main.update(cx_a, |editor, cx| {
2975        let snapshot = editor.buffer().read(cx).snapshot(cx);
2976        let all_diagnostics = snapshot
2977            .diagnostics_in_range(0..snapshot.len())
2978            .collect::<Vec<_>>();
2979        assert_eq!(
2980            all_diagnostics.len(),
2981            2,
2982            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
2983        );
2984        let expected_messages = [
2985            expected_workspace_pull_diagnostics_main_message,
2986            expected_pull_diagnostic_main_message,
2987            expected_push_diagnostic_main_message,
2988        ];
2989        for diagnostic in all_diagnostics {
2990            assert!(
2991                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2992                "Expected push and pull messages on the host: {expected_messages:?}, but got: {}",
2993                diagnostic.diagnostic.message
2994            );
2995        }
2996    });
2997
2998    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2999    let editor_b_main = workspace_b
3000        .update_in(cx_b, |workspace, window, cx| {
3001            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
3002        })
3003        .await
3004        .unwrap()
3005        .downcast::<Editor>()
3006        .unwrap();
3007    cx_b.run_until_parked();
3008
3009    pull_diagnostics_handle.next().await.unwrap();
3010    assert_eq!(
3011        2,
3012        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3013        "Client should query pull diagnostics when its editor is opened"
3014    );
3015    executor.run_until_parked();
3016    assert_eq!(
3017        workspace_diagnostic_start_count,
3018        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3019        "Workspace diagnostics should not be changed as the remote client does not initialize the workspace diagnostics pull"
3020    );
3021    editor_b_main.update(cx_b, |editor, cx| {
3022        let snapshot = editor.buffer().read(cx).snapshot(cx);
3023        let all_diagnostics = snapshot
3024            .diagnostics_in_range(0..snapshot.len())
3025            .collect::<Vec<_>>();
3026        assert_eq!(
3027            all_diagnostics.len(),
3028            2,
3029            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
3030        );
3031
3032        // Despite the workspace diagnostics not re-initialized for the remote client, we can still expect its message synced from the host.
3033        let expected_messages = [
3034            expected_workspace_pull_diagnostics_main_message,
3035            expected_pull_diagnostic_main_message,
3036            expected_push_diagnostic_main_message,
3037        ];
3038        for diagnostic in all_diagnostics {
3039            assert!(
3040                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3041                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3042                diagnostic.diagnostic.message
3043            );
3044        }
3045    });
3046
3047    let editor_b_lib = workspace_b
3048        .update_in(cx_b, |workspace, window, cx| {
3049            workspace.open_path((worktree_id, rel_path("lib.rs")), None, true, window, cx)
3050        })
3051        .await
3052        .unwrap()
3053        .downcast::<Editor>()
3054        .unwrap();
3055
3056    pull_diagnostics_handle.next().await.unwrap();
3057    assert_eq!(
3058        3,
3059        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3060        "Client should query pull diagnostics when its another editor is opened"
3061    );
3062    executor.run_until_parked();
3063    assert_eq!(
3064        workspace_diagnostic_start_count,
3065        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3066        "The remote client still did not anything to trigger the workspace diagnostics pull"
3067    );
3068    editor_b_lib.update(cx_b, |editor, cx| {
3069        let snapshot = editor.buffer().read(cx).snapshot(cx);
3070        let all_diagnostics = snapshot
3071            .diagnostics_in_range(0..snapshot.len())
3072            .collect::<Vec<_>>();
3073        let expected_messages = [
3074            expected_pull_diagnostic_lib_message,
3075            // TODO bug: the pushed diagnostics are not being sent to the client when they open the corresponding buffer.
3076            // expected_push_diagnostic_lib_message,
3077        ];
3078        assert_eq!(
3079            all_diagnostics.len(),
3080            1,
3081            "Expected pull diagnostics, but got: {all_diagnostics:?}"
3082        );
3083        for diagnostic in all_diagnostics {
3084            assert!(
3085                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3086                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3087                diagnostic.diagnostic.message
3088            );
3089        }
3090    });
3091
3092    if should_stream_workspace_diagnostic {
3093        fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3094            token: expected_workspace_diagnostic_token.clone(),
3095            value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
3096                lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
3097                    items: vec![lsp::WorkspaceDocumentDiagnosticReport::Full(
3098                        lsp::WorkspaceFullDocumentDiagnosticReport {
3099                            uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3100                            version: None,
3101                            full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
3102                                result_id: Some(format!(
3103                                    "workspace_{}",
3104                                    workspace_diagnostics_pulls_made
3105                                        .fetch_add(1, atomic::Ordering::Release)
3106                                        + 1
3107                                )),
3108                                items: vec![lsp::Diagnostic {
3109                                    range: lsp::Range {
3110                                        start: lsp::Position {
3111                                            line: 0,
3112                                            character: 1,
3113                                        },
3114                                        end: lsp::Position {
3115                                            line: 0,
3116                                            character: 2,
3117                                        },
3118                                    },
3119                                    severity: Some(lsp::DiagnosticSeverity::ERROR),
3120                                    message: expected_workspace_pull_diagnostics_lib_message
3121                                        .to_string(),
3122                                    ..lsp::Diagnostic::default()
3123                                }],
3124                            },
3125                        },
3126                    )],
3127                }),
3128            ),
3129        });
3130        workspace_diagnostic_start_count =
3131            workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
3132        workspace_diagnostic_cancel_tx.send(()).await.unwrap();
3133        workspace_diagnostics_pulls_handle.next().await.unwrap();
3134        executor.run_until_parked();
3135        editor_b_lib.update(cx_b, |editor, cx| {
3136            let snapshot = editor.buffer().read(cx).snapshot(cx);
3137            let all_diagnostics = snapshot
3138                .diagnostics_in_range(0..snapshot.len())
3139                .collect::<Vec<_>>();
3140            let expected_messages = [
3141                expected_workspace_pull_diagnostics_lib_message,
3142                // TODO bug: the pushed diagnostics are not being sent to the client when they open the corresponding buffer.
3143                // expected_push_diagnostic_lib_message,
3144            ];
3145            assert_eq!(
3146                all_diagnostics.len(),
3147                1,
3148                "Expected pull diagnostics, but got: {all_diagnostics:?}"
3149            );
3150            for diagnostic in all_diagnostics {
3151                assert!(
3152                    expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3153                    "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3154                    diagnostic.diagnostic.message
3155                );
3156            }
3157        });
3158    };
3159
3160    {
3161        assert!(
3162            !diagnostics_pulls_result_ids.lock().await.is_empty(),
3163            "Initial diagnostics pulls should report None at least"
3164        );
3165        assert_eq!(
3166            0,
3167            workspace_diagnostics_pulls_result_ids
3168                .lock()
3169                .await
3170                .deref()
3171                .len(),
3172            "After the initial workspace request, opening files should not reuse any result ids"
3173        );
3174    }
3175
3176    editor_b_lib.update_in(cx_b, |editor, window, cx| {
3177        editor.move_to_end(&MoveToEnd, window, cx);
3178        editor.handle_input(":", window, cx);
3179    });
3180    pull_diagnostics_handle.next().await.unwrap();
3181    assert_eq!(
3182        4,
3183        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3184        "Client lib.rs edits should trigger another diagnostics pull for a buffer"
3185    );
3186    workspace_diagnostics_pulls_handle.next().await.unwrap();
3187    assert_eq!(
3188        workspace_diagnostic_start_count + 1,
3189        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3190        "After client lib.rs edits, the workspace diagnostics request should follow"
3191    );
3192    executor.run_until_parked();
3193
3194    editor_b_main.update_in(cx_b, |editor, window, cx| {
3195        editor.move_to_end(&MoveToEnd, window, cx);
3196        editor.handle_input(":", window, cx);
3197    });
3198    pull_diagnostics_handle.next().await.unwrap();
3199    pull_diagnostics_handle.next().await.unwrap();
3200    assert_eq!(
3201        6,
3202        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3203        "Client main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
3204    );
3205    workspace_diagnostics_pulls_handle.next().await.unwrap();
3206    assert_eq!(
3207        workspace_diagnostic_start_count + 2,
3208        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3209        "After client main.rs edits, the workspace diagnostics pull should follow"
3210    );
3211    executor.run_until_parked();
3212
3213    editor_a_main.update_in(cx_a, |editor, window, cx| {
3214        editor.move_to_end(&MoveToEnd, window, cx);
3215        editor.handle_input(":", window, cx);
3216    });
3217    pull_diagnostics_handle.next().await.unwrap();
3218    pull_diagnostics_handle.next().await.unwrap();
3219    assert_eq!(
3220        8,
3221        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3222        "Host main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
3223    );
3224    workspace_diagnostics_pulls_handle.next().await.unwrap();
3225    assert_eq!(
3226        workspace_diagnostic_start_count + 3,
3227        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3228        "After host main.rs edits, the workspace diagnostics pull should follow"
3229    );
3230    executor.run_until_parked();
3231    let diagnostic_pulls_result_ids = diagnostics_pulls_result_ids.lock().await.len();
3232    let workspace_pulls_result_ids = workspace_diagnostics_pulls_result_ids.lock().await.len();
3233    {
3234        assert!(
3235            diagnostic_pulls_result_ids > 1,
3236            "Should have sent result ids when pulling diagnostics"
3237        );
3238        assert!(
3239            workspace_pulls_result_ids > 1,
3240            "Should have sent result ids when pulling workspace diagnostics"
3241        );
3242    }
3243
3244    fake_language_server
3245        .request::<lsp::request::WorkspaceDiagnosticRefresh>(())
3246        .await
3247        .into_response()
3248        .expect("workspace diagnostics refresh request failed");
3249    assert_eq!(
3250        8,
3251        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3252        "No single file pulls should happen after the diagnostics refresh server request"
3253    );
3254    workspace_diagnostics_pulls_handle.next().await.unwrap();
3255    assert_eq!(
3256        workspace_diagnostic_start_count + 4,
3257        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3258        "Another workspace diagnostics pull should happen after the diagnostics refresh server request"
3259    );
3260    {
3261        assert!(
3262            diagnostics_pulls_result_ids.lock().await.len() == diagnostic_pulls_result_ids,
3263            "Pulls should not happen hence no extra ids should appear"
3264        );
3265        assert!(
3266            workspace_diagnostics_pulls_result_ids.lock().await.len() > workspace_pulls_result_ids,
3267            "More workspace diagnostics should be pulled"
3268        );
3269    }
3270    editor_b_lib.update(cx_b, |editor, cx| {
3271        let snapshot = editor.buffer().read(cx).snapshot(cx);
3272        let all_diagnostics = snapshot
3273            .diagnostics_in_range(0..snapshot.len())
3274            .collect::<Vec<_>>();
3275        let expected_messages = [
3276            expected_workspace_pull_diagnostics_lib_message,
3277            expected_pull_diagnostic_lib_message,
3278            expected_push_diagnostic_lib_message,
3279        ];
3280        assert_eq!(all_diagnostics.len(), 1);
3281        for diagnostic in &all_diagnostics {
3282            assert!(
3283                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3284                "Unexpected diagnostics: {all_diagnostics:?}"
3285            );
3286        }
3287    });
3288    editor_b_main.update(cx_b, |editor, cx| {
3289        let snapshot = editor.buffer().read(cx).snapshot(cx);
3290        let all_diagnostics = snapshot
3291            .diagnostics_in_range(0..snapshot.len())
3292            .collect::<Vec<_>>();
3293        assert_eq!(all_diagnostics.len(), 2);
3294
3295        let expected_messages = [
3296            expected_workspace_pull_diagnostics_main_message,
3297            expected_pull_diagnostic_main_message,
3298            expected_push_diagnostic_main_message,
3299        ];
3300        for diagnostic in &all_diagnostics {
3301            assert!(
3302                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3303                "Unexpected diagnostics: {all_diagnostics:?}"
3304            );
3305        }
3306    });
3307    editor_a_main.update(cx_a, |editor, cx| {
3308        let snapshot = editor.buffer().read(cx).snapshot(cx);
3309        let all_diagnostics = snapshot
3310            .diagnostics_in_range(0..snapshot.len())
3311            .collect::<Vec<_>>();
3312        assert_eq!(all_diagnostics.len(), 2);
3313        let expected_messages = [
3314            expected_workspace_pull_diagnostics_main_message,
3315            expected_pull_diagnostic_main_message,
3316            expected_push_diagnostic_main_message,
3317        ];
3318        for diagnostic in &all_diagnostics {
3319            assert!(
3320                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3321                "Unexpected diagnostics: {all_diagnostics:?}"
3322            );
3323        }
3324    });
3325}
3326
3327#[gpui::test(iterations = 10)]
3328async fn test_non_streamed_lsp_pull_diagnostics(
3329    cx_a: &mut TestAppContext,
3330    cx_b: &mut TestAppContext,
3331) {
3332    test_lsp_pull_diagnostics(false, cx_a, cx_b).await;
3333}
3334
3335#[gpui::test(iterations = 10)]
3336async fn test_streamed_lsp_pull_diagnostics(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3337    test_lsp_pull_diagnostics(true, cx_a, cx_b).await;
3338}
3339
3340#[gpui::test(iterations = 10)]
3341async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3342    let mut server = TestServer::start(cx_a.executor()).await;
3343    let client_a = server.create_client(cx_a, "user_a").await;
3344    let client_b = server.create_client(cx_b, "user_b").await;
3345    server
3346        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3347        .await;
3348    let active_call_a = cx_a.read(ActiveCall::global);
3349
3350    cx_a.update(editor::init);
3351    cx_b.update(editor::init);
3352    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
3353    let inline_blame_off_settings = Some(InlineBlameSettings {
3354        enabled: Some(false),
3355        ..Default::default()
3356    });
3357    cx_a.update(|cx| {
3358        SettingsStore::update_global(cx, |store, cx| {
3359            store.update_user_settings(cx, |settings| {
3360                settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
3361            });
3362        });
3363    });
3364    cx_b.update(|cx| {
3365        SettingsStore::update_global(cx, |store, cx| {
3366            store.update_user_settings(cx, |settings| {
3367                settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
3368            });
3369        });
3370    });
3371
3372    client_a
3373        .fs()
3374        .insert_tree(
3375            path!("/my-repo"),
3376            json!({
3377                ".git": {},
3378                "file.txt": "line1\nline2\nline3\nline\n",
3379            }),
3380        )
3381        .await;
3382
3383    let blame = git::blame::Blame {
3384        entries: vec![
3385            blame_entry("1b1b1b", 0..1),
3386            blame_entry("0d0d0d", 1..2),
3387            blame_entry("3a3a3a", 2..3),
3388            blame_entry("4c4c4c", 3..4),
3389        ],
3390        messages: [
3391            ("1b1b1b", "message for idx-0"),
3392            ("0d0d0d", "message for idx-1"),
3393            ("3a3a3a", "message for idx-2"),
3394            ("4c4c4c", "message for idx-3"),
3395        ]
3396        .into_iter()
3397        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
3398        .collect(),
3399        remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
3400    };
3401    client_a.fs().set_blame_for_repo(
3402        Path::new(path!("/my-repo/.git")),
3403        vec![(repo_path("file.txt"), blame)],
3404    );
3405
3406    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
3407    let project_id = active_call_a
3408        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3409        .await
3410        .unwrap();
3411
3412    // Create editor_a
3413    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3414    let editor_a = workspace_a
3415        .update_in(cx_a, |workspace, window, cx| {
3416            workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx)
3417        })
3418        .await
3419        .unwrap()
3420        .downcast::<Editor>()
3421        .unwrap();
3422
3423    // Join the project as client B.
3424    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3425    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3426    let editor_b = workspace_b
3427        .update_in(cx_b, |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    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
3435        editor_b
3436            .buffer()
3437            .read(cx)
3438            .as_singleton()
3439            .unwrap()
3440            .read(cx)
3441            .remote_id()
3442    });
3443
3444    // client_b now requests git blame for the open buffer
3445    editor_b.update_in(cx_b, |editor_b, window, cx| {
3446        assert!(editor_b.blame().is_none());
3447        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
3448    });
3449
3450    cx_a.executor().run_until_parked();
3451    cx_b.executor().run_until_parked();
3452
3453    editor_b.update(cx_b, |editor_b, cx| {
3454        let blame = editor_b.blame().expect("editor_b should have blame now");
3455        let entries = blame.update(cx, |blame, cx| {
3456            blame
3457                .blame_for_rows(
3458                    &(0..4)
3459                        .map(|row| RowInfo {
3460                            buffer_row: Some(row),
3461                            buffer_id: Some(buffer_id_b),
3462                            ..Default::default()
3463                        })
3464                        .collect::<Vec<_>>(),
3465                    cx,
3466                )
3467                .collect::<Vec<_>>()
3468        });
3469
3470        assert_eq!(
3471            entries,
3472            vec![
3473                Some((buffer_id_b, blame_entry("1b1b1b", 0..1))),
3474                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3475                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3476                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3477            ]
3478        );
3479
3480        blame.update(cx, |blame, _| {
3481            for (idx, (buffer, entry)) in entries.iter().flatten().enumerate() {
3482                let details = blame.details_for_entry(*buffer, entry).unwrap();
3483                assert_eq!(details.message, format!("message for idx-{}", idx));
3484                assert_eq!(
3485                    details.permalink.unwrap().to_string(),
3486                    format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
3487                );
3488            }
3489        });
3490    });
3491
3492    // editor_b updates the file, which gets sent to client_a, which updates git blame,
3493    // which gets back to client_b.
3494    editor_b.update_in(cx_b, |editor_b, _, cx| {
3495        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
3496    });
3497
3498    cx_a.executor().run_until_parked();
3499    cx_b.executor().run_until_parked();
3500
3501    editor_b.update(cx_b, |editor_b, cx| {
3502        let blame = editor_b.blame().expect("editor_b should have blame now");
3503        let entries = blame.update(cx, |blame, cx| {
3504            blame
3505                .blame_for_rows(
3506                    &(0..4)
3507                        .map(|row| RowInfo {
3508                            buffer_row: Some(row),
3509                            buffer_id: Some(buffer_id_b),
3510                            ..Default::default()
3511                        })
3512                        .collect::<Vec<_>>(),
3513                    cx,
3514                )
3515                .collect::<Vec<_>>()
3516        });
3517
3518        assert_eq!(
3519            entries,
3520            vec![
3521                None,
3522                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3523                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3524                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3525            ]
3526        );
3527    });
3528
3529    // Now editor_a also updates the file
3530    editor_a.update_in(cx_a, |editor_a, _, cx| {
3531        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
3532    });
3533
3534    cx_a.executor().run_until_parked();
3535    cx_b.executor().run_until_parked();
3536
3537    editor_b.update(cx_b, |editor_b, cx| {
3538        let blame = editor_b.blame().expect("editor_b should have blame now");
3539        let entries = blame.update(cx, |blame, cx| {
3540            blame
3541                .blame_for_rows(
3542                    &(0..4)
3543                        .map(|row| RowInfo {
3544                            buffer_row: Some(row),
3545                            buffer_id: Some(buffer_id_b),
3546                            ..Default::default()
3547                        })
3548                        .collect::<Vec<_>>(),
3549                    cx,
3550                )
3551                .collect::<Vec<_>>()
3552        });
3553
3554        assert_eq!(
3555            entries,
3556            vec![
3557                None,
3558                None,
3559                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3560                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3561            ]
3562        );
3563    });
3564}
3565
3566#[gpui::test(iterations = 30)]
3567async fn test_collaborating_with_editorconfig(
3568    cx_a: &mut TestAppContext,
3569    cx_b: &mut TestAppContext,
3570) {
3571    let mut server = TestServer::start(cx_a.executor()).await;
3572    let client_a = server.create_client(cx_a, "user_a").await;
3573    let client_b = server.create_client(cx_b, "user_b").await;
3574    server
3575        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3576        .await;
3577    let active_call_a = cx_a.read(ActiveCall::global);
3578
3579    cx_b.update(editor::init);
3580
3581    // Set up a fake language server.
3582    client_a.language_registry().add(rust_lang());
3583    client_a
3584        .fs()
3585        .insert_tree(
3586            path!("/a"),
3587            json!({
3588                "src": {
3589                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
3590                    "other_mod": {
3591                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
3592                        ".editorconfig": "",
3593                    },
3594                },
3595                ".editorconfig": "[*]\ntab_width = 2\n",
3596            }),
3597        )
3598        .await;
3599    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
3600    let project_id = active_call_a
3601        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3602        .await
3603        .unwrap();
3604    let main_buffer_a = project_a
3605        .update(cx_a, |p, cx| {
3606            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
3607        })
3608        .await
3609        .unwrap();
3610    let other_buffer_a = project_a
3611        .update(cx_a, |p, cx| {
3612            p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
3613        })
3614        .await
3615        .unwrap();
3616    let cx_a = cx_a.add_empty_window();
3617    let main_editor_a = cx_a.new_window_entity(|window, cx| {
3618        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
3619    });
3620    let other_editor_a = cx_a.new_window_entity(|window, cx| {
3621        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
3622    });
3623    let mut main_editor_cx_a = EditorTestContext {
3624        cx: cx_a.clone(),
3625        window: cx_a.window_handle(),
3626        editor: main_editor_a,
3627        assertion_cx: AssertionContextManager::new(),
3628    };
3629    let mut other_editor_cx_a = EditorTestContext {
3630        cx: cx_a.clone(),
3631        window: cx_a.window_handle(),
3632        editor: other_editor_a,
3633        assertion_cx: AssertionContextManager::new(),
3634    };
3635
3636    // Join the project as client B.
3637    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3638    let main_buffer_b = project_b
3639        .update(cx_b, |p, cx| {
3640            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
3641        })
3642        .await
3643        .unwrap();
3644    let other_buffer_b = project_b
3645        .update(cx_b, |p, cx| {
3646            p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
3647        })
3648        .await
3649        .unwrap();
3650    let cx_b = cx_b.add_empty_window();
3651    let main_editor_b = cx_b.new_window_entity(|window, cx| {
3652        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
3653    });
3654    let other_editor_b = cx_b.new_window_entity(|window, cx| {
3655        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
3656    });
3657    let mut main_editor_cx_b = EditorTestContext {
3658        cx: cx_b.clone(),
3659        window: cx_b.window_handle(),
3660        editor: main_editor_b,
3661        assertion_cx: AssertionContextManager::new(),
3662    };
3663    let mut other_editor_cx_b = EditorTestContext {
3664        cx: cx_b.clone(),
3665        window: cx_b.window_handle(),
3666        editor: other_editor_b,
3667        assertion_cx: AssertionContextManager::new(),
3668    };
3669
3670    let initial_main = indoc! {"
3671ˇmod other;
3672fn main() { let foo = other::foo(); }"};
3673    let initial_other = indoc! {"
3674ˇpub fn foo() -> usize {
3675    4
3676}"};
3677
3678    let first_tabbed_main = indoc! {"
3679  ˇmod other;
3680fn main() { let foo = other::foo(); }"};
3681    tab_undo_assert(
3682        &mut main_editor_cx_a,
3683        &mut main_editor_cx_b,
3684        initial_main,
3685        first_tabbed_main,
3686        true,
3687    );
3688    tab_undo_assert(
3689        &mut main_editor_cx_a,
3690        &mut main_editor_cx_b,
3691        initial_main,
3692        first_tabbed_main,
3693        false,
3694    );
3695
3696    let first_tabbed_other = indoc! {"
3697  ˇpub fn foo() -> usize {
3698    4
3699}"};
3700    tab_undo_assert(
3701        &mut other_editor_cx_a,
3702        &mut other_editor_cx_b,
3703        initial_other,
3704        first_tabbed_other,
3705        true,
3706    );
3707    tab_undo_assert(
3708        &mut other_editor_cx_a,
3709        &mut other_editor_cx_b,
3710        initial_other,
3711        first_tabbed_other,
3712        false,
3713    );
3714
3715    client_a
3716        .fs()
3717        .atomic_write(
3718            PathBuf::from(path!("/a/src/.editorconfig")),
3719            "[*]\ntab_width = 3\n".to_owned(),
3720        )
3721        .await
3722        .unwrap();
3723    cx_a.run_until_parked();
3724    cx_b.run_until_parked();
3725
3726    let second_tabbed_main = indoc! {"
3727   ˇmod other;
3728fn main() { let foo = other::foo(); }"};
3729    tab_undo_assert(
3730        &mut main_editor_cx_a,
3731        &mut main_editor_cx_b,
3732        initial_main,
3733        second_tabbed_main,
3734        true,
3735    );
3736    tab_undo_assert(
3737        &mut main_editor_cx_a,
3738        &mut main_editor_cx_b,
3739        initial_main,
3740        second_tabbed_main,
3741        false,
3742    );
3743
3744    let second_tabbed_other = indoc! {"
3745   ˇpub fn foo() -> usize {
3746    4
3747}"};
3748    tab_undo_assert(
3749        &mut other_editor_cx_a,
3750        &mut other_editor_cx_b,
3751        initial_other,
3752        second_tabbed_other,
3753        true,
3754    );
3755    tab_undo_assert(
3756        &mut other_editor_cx_a,
3757        &mut other_editor_cx_b,
3758        initial_other,
3759        second_tabbed_other,
3760        false,
3761    );
3762
3763    let editorconfig_buffer_b = project_b
3764        .update(cx_b, |p, cx| {
3765            p.open_buffer((worktree_id, rel_path("src/other_mod/.editorconfig")), cx)
3766        })
3767        .await
3768        .unwrap();
3769    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
3770        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
3771    });
3772    project_b
3773        .update(cx_b, |project, cx| {
3774            project.save_buffer(editorconfig_buffer_b.clone(), cx)
3775        })
3776        .await
3777        .unwrap();
3778    cx_a.run_until_parked();
3779    cx_b.run_until_parked();
3780
3781    tab_undo_assert(
3782        &mut main_editor_cx_a,
3783        &mut main_editor_cx_b,
3784        initial_main,
3785        second_tabbed_main,
3786        true,
3787    );
3788    tab_undo_assert(
3789        &mut main_editor_cx_a,
3790        &mut main_editor_cx_b,
3791        initial_main,
3792        second_tabbed_main,
3793        false,
3794    );
3795
3796    let third_tabbed_other = indoc! {"
3797      ˇpub fn foo() -> usize {
3798    4
3799}"};
3800    tab_undo_assert(
3801        &mut other_editor_cx_a,
3802        &mut other_editor_cx_b,
3803        initial_other,
3804        third_tabbed_other,
3805        true,
3806    );
3807
3808    tab_undo_assert(
3809        &mut other_editor_cx_a,
3810        &mut other_editor_cx_b,
3811        initial_other,
3812        third_tabbed_other,
3813        false,
3814    );
3815}
3816
3817#[gpui::test]
3818async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3819    let executor = cx_a.executor();
3820    let mut server = TestServer::start(executor.clone()).await;
3821    let client_a = server.create_client(cx_a, "user_a").await;
3822    let client_b = server.create_client(cx_b, "user_b").await;
3823    server
3824        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3825        .await;
3826    let active_call_a = cx_a.read(ActiveCall::global);
3827    let active_call_b = cx_b.read(ActiveCall::global);
3828    cx_a.update(editor::init);
3829    cx_b.update(editor::init);
3830    client_a
3831        .fs()
3832        .insert_tree(
3833            "/a",
3834            json!({
3835                "test.txt": "one\ntwo\nthree\nfour\nfive",
3836            }),
3837        )
3838        .await;
3839    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3840    let project_path = ProjectPath {
3841        worktree_id,
3842        path: rel_path(&"test.txt").into(),
3843    };
3844    let abs_path = project_a.read_with(cx_a, |project, cx| {
3845        project
3846            .absolute_path(&project_path, cx)
3847            .map(Arc::from)
3848            .unwrap()
3849    });
3850
3851    active_call_a
3852        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
3853        .await
3854        .unwrap();
3855    let project_id = active_call_a
3856        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3857        .await
3858        .unwrap();
3859    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3860    active_call_b
3861        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
3862        .await
3863        .unwrap();
3864    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3865    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3866
3867    // Client A opens an editor.
3868    let editor_a = workspace_a
3869        .update_in(cx_a, |workspace, window, cx| {
3870            workspace.open_path(project_path.clone(), None, true, window, cx)
3871        })
3872        .await
3873        .unwrap()
3874        .downcast::<Editor>()
3875        .unwrap();
3876
3877    // Client B opens same editor as A.
3878    let editor_b = workspace_b
3879        .update_in(cx_b, |workspace, window, cx| {
3880            workspace.open_path(project_path.clone(), None, true, window, cx)
3881        })
3882        .await
3883        .unwrap()
3884        .downcast::<Editor>()
3885        .unwrap();
3886
3887    cx_a.run_until_parked();
3888    cx_b.run_until_parked();
3889
3890    // Client A adds breakpoint on line (1)
3891    editor_a.update_in(cx_a, |editor, window, cx| {
3892        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3893    });
3894
3895    cx_a.run_until_parked();
3896    cx_b.run_until_parked();
3897
3898    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3899        editor
3900            .breakpoint_store()
3901            .unwrap()
3902            .read(cx)
3903            .all_source_breakpoints(cx)
3904    });
3905    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3906        editor
3907            .breakpoint_store()
3908            .unwrap()
3909            .read(cx)
3910            .all_source_breakpoints(cx)
3911    });
3912
3913    assert_eq!(1, breakpoints_a.len());
3914    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
3915    assert_eq!(breakpoints_a, breakpoints_b);
3916
3917    // Client B adds breakpoint on line(2)
3918    editor_b.update_in(cx_b, |editor, window, cx| {
3919        editor.move_down(&editor::actions::MoveDown, window, cx);
3920        editor.move_down(&editor::actions::MoveDown, window, cx);
3921        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3922    });
3923
3924    cx_a.run_until_parked();
3925    cx_b.run_until_parked();
3926
3927    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3928        editor
3929            .breakpoint_store()
3930            .unwrap()
3931            .read(cx)
3932            .all_source_breakpoints(cx)
3933    });
3934    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3935        editor
3936            .breakpoint_store()
3937            .unwrap()
3938            .read(cx)
3939            .all_source_breakpoints(cx)
3940    });
3941
3942    assert_eq!(1, breakpoints_a.len());
3943    assert_eq!(breakpoints_a, breakpoints_b);
3944    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
3945
3946    // Client A removes last added breakpoint from client B
3947    editor_a.update_in(cx_a, |editor, window, cx| {
3948        editor.move_down(&editor::actions::MoveDown, window, cx);
3949        editor.move_down(&editor::actions::MoveDown, window, cx);
3950        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3951    });
3952
3953    cx_a.run_until_parked();
3954    cx_b.run_until_parked();
3955
3956    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3957        editor
3958            .breakpoint_store()
3959            .unwrap()
3960            .read(cx)
3961            .all_source_breakpoints(cx)
3962    });
3963    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3964        editor
3965            .breakpoint_store()
3966            .unwrap()
3967            .read(cx)
3968            .all_source_breakpoints(cx)
3969    });
3970
3971    assert_eq!(1, breakpoints_a.len());
3972    assert_eq!(breakpoints_a, breakpoints_b);
3973    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
3974
3975    // Client B removes first added breakpoint by client A
3976    editor_b.update_in(cx_b, |editor, window, cx| {
3977        editor.move_up(&editor::actions::MoveUp, window, cx);
3978        editor.move_up(&editor::actions::MoveUp, window, cx);
3979        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
3980    });
3981
3982    cx_a.run_until_parked();
3983    cx_b.run_until_parked();
3984
3985    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
3986        editor
3987            .breakpoint_store()
3988            .unwrap()
3989            .read(cx)
3990            .all_source_breakpoints(cx)
3991    });
3992    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
3993        editor
3994            .breakpoint_store()
3995            .unwrap()
3996            .read(cx)
3997            .all_source_breakpoints(cx)
3998    });
3999
4000    assert_eq!(0, breakpoints_a.len());
4001    assert_eq!(breakpoints_a, breakpoints_b);
4002}
4003
4004#[gpui::test]
4005async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4006    let mut server = TestServer::start(cx_a.executor()).await;
4007    let client_a = server.create_client(cx_a, "user_a").await;
4008    let client_b = server.create_client(cx_b, "user_b").await;
4009    server
4010        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4011        .await;
4012    let active_call_a = cx_a.read(ActiveCall::global);
4013    let active_call_b = cx_b.read(ActiveCall::global);
4014
4015    cx_a.update(editor::init);
4016    cx_b.update(editor::init);
4017
4018    client_a.language_registry().add(rust_lang());
4019    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4020        "Rust",
4021        FakeLspAdapter {
4022            name: "rust-analyzer",
4023            ..FakeLspAdapter::default()
4024        },
4025    );
4026    client_b.language_registry().add(rust_lang());
4027    client_b.language_registry().register_fake_lsp_adapter(
4028        "Rust",
4029        FakeLspAdapter {
4030            name: "rust-analyzer",
4031            ..FakeLspAdapter::default()
4032        },
4033    );
4034
4035    client_a
4036        .fs()
4037        .insert_tree(
4038            path!("/a"),
4039            json!({
4040                "main.rs": "fn main() {}",
4041            }),
4042        )
4043        .await;
4044    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4045    active_call_a
4046        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
4047        .await
4048        .unwrap();
4049    let project_id = active_call_a
4050        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4051        .await
4052        .unwrap();
4053
4054    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4055    active_call_b
4056        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
4057        .await
4058        .unwrap();
4059
4060    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4061    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4062
4063    let editor_a = workspace_a
4064        .update_in(cx_a, |workspace, window, cx| {
4065            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
4066        })
4067        .await
4068        .unwrap()
4069        .downcast::<Editor>()
4070        .unwrap();
4071
4072    let editor_b = workspace_b
4073        .update_in(cx_b, |workspace, window, cx| {
4074            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
4075        })
4076        .await
4077        .unwrap()
4078        .downcast::<Editor>()
4079        .unwrap();
4080
4081    let fake_language_server = fake_language_servers.next().await.unwrap();
4082
4083    // host
4084    let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4085        |params, _| async move {
4086            assert_eq!(
4087                params.text_document.uri,
4088                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4089            );
4090            assert_eq!(params.position, lsp::Position::new(0, 0));
4091            Ok(Some(ExpandedMacro {
4092                name: "test_macro_name".to_string(),
4093                expansion: "test_macro_expansion on the host".to_string(),
4094            }))
4095        },
4096    );
4097
4098    editor_a.update_in(cx_a, |editor, window, cx| {
4099        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4100    });
4101    expand_request_a.next().await.unwrap();
4102    cx_a.run_until_parked();
4103
4104    workspace_a.update(cx_a, |workspace, cx| {
4105        workspace.active_pane().update(cx, |pane, cx| {
4106            assert_eq!(
4107                pane.items_len(),
4108                2,
4109                "Should have added a macro expansion to the host's pane"
4110            );
4111            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4112            new_editor.update(cx, |editor, cx| {
4113                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
4114            });
4115        })
4116    });
4117
4118    // client
4119    let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4120        |params, _| async move {
4121            assert_eq!(
4122                params.text_document.uri,
4123                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4124            );
4125            assert_eq!(
4126                params.position,
4127                lsp::Position::new(0, 12),
4128                "editor_b has selected the entire text and should query for a different position"
4129            );
4130            Ok(Some(ExpandedMacro {
4131                name: "test_macro_name".to_string(),
4132                expansion: "test_macro_expansion on the client".to_string(),
4133            }))
4134        },
4135    );
4136
4137    editor_b.update_in(cx_b, |editor, window, cx| {
4138        editor.select_all(&SelectAll, window, cx);
4139        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4140    });
4141    expand_request_b.next().await.unwrap();
4142    cx_b.run_until_parked();
4143
4144    workspace_b.update(cx_b, |workspace, cx| {
4145        workspace.active_pane().update(cx, |pane, cx| {
4146            assert_eq!(
4147                pane.items_len(),
4148                2,
4149                "Should have added a macro expansion to the client's pane"
4150            );
4151            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4152            new_editor.update(cx, |editor, cx| {
4153                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
4154            });
4155        })
4156    });
4157}
4158
4159#[track_caller]
4160fn tab_undo_assert(
4161    cx_a: &mut EditorTestContext,
4162    cx_b: &mut EditorTestContext,
4163    expected_initial: &str,
4164    expected_tabbed: &str,
4165    a_tabs: bool,
4166) {
4167    cx_a.assert_editor_state(expected_initial);
4168    cx_b.assert_editor_state(expected_initial);
4169
4170    if a_tabs {
4171        cx_a.update_editor(|editor, window, cx| {
4172            editor.tab(&editor::actions::Tab, window, cx);
4173        });
4174    } else {
4175        cx_b.update_editor(|editor, window, cx| {
4176            editor.tab(&editor::actions::Tab, window, cx);
4177        });
4178    }
4179
4180    cx_a.run_until_parked();
4181    cx_b.run_until_parked();
4182
4183    cx_a.assert_editor_state(expected_tabbed);
4184    cx_b.assert_editor_state(expected_tabbed);
4185
4186    if a_tabs {
4187        cx_a.update_editor(|editor, window, cx| {
4188            editor.undo(&editor::actions::Undo, window, cx);
4189        });
4190    } else {
4191        cx_b.update_editor(|editor, window, cx| {
4192            editor.undo(&editor::actions::Undo, window, cx);
4193        });
4194    }
4195    cx_a.run_until_parked();
4196    cx_b.run_until_parked();
4197    cx_a.assert_editor_state(expected_initial);
4198    cx_b.assert_editor_state(expected_initial);
4199}
4200
4201fn extract_hint_labels(editor: &Editor, cx: &mut App) -> Vec<String> {
4202    let lsp_store = editor.project().unwrap().read(cx).lsp_store();
4203
4204    let mut all_cached_labels = Vec::new();
4205    let mut all_fetched_hints = Vec::new();
4206    for buffer in editor.buffer().read(cx).all_buffers() {
4207        lsp_store.update(cx, |lsp_store, cx| {
4208            let hints = &lsp_store.latest_lsp_data(&buffer, cx).inlay_hints();
4209            all_cached_labels.extend(hints.all_cached_hints().into_iter().map(|hint| {
4210                let mut label = hint.text().to_string();
4211                if hint.padding_left {
4212                    label.insert(0, ' ');
4213                }
4214                if hint.padding_right {
4215                    label.push_str(" ");
4216                }
4217                label
4218            }));
4219            all_fetched_hints.extend(hints.all_fetched_hints());
4220        });
4221    }
4222
4223    assert!(
4224        all_fetched_hints.is_empty(),
4225        "Did not expect background hints fetch tasks, but got {} of them",
4226        all_fetched_hints.len()
4227    );
4228
4229    all_cached_labels
4230}
4231
4232#[track_caller]
4233fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
4234    editor
4235        .all_inlays(cx)
4236        .into_iter()
4237        .filter_map(|inlay| inlay.get_color())
4238        .map(Rgba::from)
4239        .collect()
4240}
4241
4242fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
4243    git::blame::BlameEntry {
4244        sha: sha.parse().unwrap(),
4245        range,
4246        ..Default::default()
4247    }
4248}