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