editor_tests.rs

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