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