editor_tests.rs

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