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