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