editor_tests.rs

   1use crate::TestServer;
   2use call::ActiveCall;
   3use collab::rpc::RECONNECT_TIMEOUT;
   4use collections::{HashMap, HashSet};
   5use editor::{
   6    DocumentColorsRenderMode, Editor, LSP_REQUEST_DEBOUNCE_TIMEOUT, MultiBufferOffset, RowInfo,
   7    SelectionEffects,
   8    actions::{
   9        ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, CopyFileLocation,
  10        CopyFileName, CopyFileNameWithoutExtension, ExpandMacroRecursively, MoveToEnd, Redo,
  11        Rename, SelectAll, ToggleCodeActions, Undo,
  12    },
  13    test::{
  14        editor_test_context::{AssertionContextManager, EditorTestContext},
  15        expand_macro_recursively,
  16    },
  17};
  18use fs::Fs;
  19use futures::{SinkExt, StreamExt, channel::mpsc, lock::Mutex};
  20use git::repository::repo_path;
  21use gpui::{
  22    App, AppContext as _, Entity, Rgba, SharedString, TestAppContext, UpdateGlobal, VisualContext,
  23    VisualTestContext,
  24};
  25use indoc::indoc;
  26use language::{FakeLspAdapter, language_settings::LanguageSettings, rust_lang};
  27use lsp::DEFAULT_LSP_REQUEST_TIMEOUT;
  28use multi_buffer::{AnchorRangeExt as _, MultiBufferRow};
  29use pretty_assertions::assert_eq;
  30use project::{
  31    ProgressToken, ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT,
  32    lsp_store::lsp_ext_command::{ExpandedMacro, LspExtExpandMacro},
  33    trusted_worktrees::{PathTrust, TrustedWorktrees},
  34};
  35use recent_projects::disconnected_overlay::DisconnectedOverlay;
  36use rpc::RECEIVE_TIMEOUT;
  37use serde_json::json;
  38use settings::{
  39    DocumentFoldingRanges, DocumentSymbols, InlayHintSettingsContent, InlineBlameSettings,
  40    SemanticTokens, SettingsStore,
  41};
  42use std::{
  43    collections::BTreeSet,
  44    num::NonZeroU32,
  45    ops::{Deref as _, Range},
  46    path::{Path, PathBuf},
  47    sync::{
  48        Arc,
  49        atomic::{self, AtomicBool, AtomicUsize},
  50    },
  51    time::Duration,
  52};
  53use text::Point;
  54use util::{path, rel_path::rel_path, uri};
  55use workspace::item::Item as _;
  56use workspace::{CloseIntent, MultiWorkspace, Workspace};
  57
  58#[gpui::test(iterations = 10)]
  59async fn test_host_disconnect(
  60    cx_a: &mut TestAppContext,
  61    cx_b: &mut TestAppContext,
  62    cx_c: &mut TestAppContext,
  63) {
  64    let mut server = TestServer::start(cx_a.executor()).await;
  65    let client_a = server.create_client(cx_a, "user_a").await;
  66    let client_b = server.create_client(cx_b, "user_b").await;
  67    let client_c = server.create_client(cx_c, "user_c").await;
  68    server
  69        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
  70        .await;
  71
  72    cx_b.update(editor::init);
  73    cx_b.update(recent_projects::init);
  74
  75    client_a
  76        .fs()
  77        .insert_tree(
  78            path!("/a"),
  79            json!({
  80                "a.txt": "a-contents",
  81                "b.txt": "b-contents",
  82            }),
  83        )
  84        .await;
  85
  86    let active_call_a = cx_a.read(ActiveCall::global);
  87    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
  88
  89    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
  90    let project_id = active_call_a
  91        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
  92        .await
  93        .unwrap();
  94
  95    let project_b = client_b.join_remote_project(project_id, cx_b).await;
  96    cx_a.background_executor.run_until_parked();
  97
  98    assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
  99
 100    let window_b = cx_b.add_window(|window, cx| {
 101        let workspace = cx.new(|cx| {
 102            Workspace::new(
 103                None,
 104                project_b.clone(),
 105                client_b.app_state.clone(),
 106                window,
 107                cx,
 108            )
 109        });
 110        MultiWorkspace::new(workspace, window, cx)
 111    });
 112    let cx_b = &mut VisualTestContext::from_window(*window_b, cx_b);
 113    let workspace_b = window_b
 114        .root(cx_b)
 115        .unwrap()
 116        .read_with(cx_b, |multi_workspace, _| {
 117            multi_workspace.workspace().clone()
 118        });
 119
 120    let editor_b: Entity<Editor> = workspace_b
 121        .update_in(cx_b, |workspace, window, cx| {
 122            workspace.open_path((worktree_id, rel_path("b.txt")), None, true, window, cx)
 123        })
 124        .await
 125        .unwrap()
 126        .downcast::<Editor>()
 127        .unwrap();
 128
 129    //TODO: focus
 130    assert!(
 131        cx_b.update_window_entity(&editor_b, |editor: &mut Editor, window, _| editor
 132            .is_focused(window))
 133    );
 134    editor_b.update_in(cx_b, |editor: &mut Editor, window, cx| {
 135        editor.insert("X", window, cx)
 136    });
 137
 138    cx_b.update(|_, cx| {
 139        assert!(workspace_b.read(cx).is_edited());
 140    });
 141
 142    // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
 143    server.forbid_connections();
 144    server.disconnect_client(client_a.peer_id().unwrap());
 145    cx_a.background_executor
 146        .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 147
 148    project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());
 149
 150    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 151
 152    project_b.read_with(cx_b, |project, cx| project.is_read_only(cx));
 153
 154    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
 155
 156    // Ensure client B's edited state is reset and that the whole window is blurred.
 157    workspace_b.update(cx_b, |workspace, cx| {
 158        assert!(workspace.active_modal::<DisconnectedOverlay>(cx).is_some());
 159        assert!(!workspace.is_edited());
 160    });
 161
 162    // Ensure client B is not prompted to save edits when closing window after disconnecting.
 163    let can_close: bool = workspace_b
 164        .update_in(cx_b, |workspace, window, cx| {
 165            workspace.prepare_to_close(CloseIntent::Quit, window, cx)
 166        })
 167        .await
 168        .unwrap();
 169    assert!(can_close);
 170
 171    // Allow client A to reconnect to the server.
 172    server.allow_connections();
 173    cx_a.background_executor.advance_clock(RECONNECT_TIMEOUT);
 174
 175    // Client B calls client A again after they reconnected.
 176    let active_call_b = cx_b.read(ActiveCall::global);
 177    active_call_b
 178        .update(cx_b, |call, cx| {
 179            call.invite(client_a.user_id().unwrap(), None, cx)
 180        })
 181        .await
 182        .unwrap();
 183    cx_a.background_executor.run_until_parked();
 184    active_call_a
 185        .update(cx_a, |call, cx| call.accept_incoming(cx))
 186        .await
 187        .unwrap();
 188
 189    active_call_a
 190        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 191        .await
 192        .unwrap();
 193
 194    // Drop client A's connection again. We should still unshare it successfully.
 195    server.forbid_connections();
 196    server.disconnect_client(client_a.peer_id().unwrap());
 197    cx_a.background_executor
 198        .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 199
 200    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 201}
 202
 203#[gpui::test]
 204async fn test_newline_above_or_below_does_not_move_guest_cursor(
 205    cx_a: &mut TestAppContext,
 206    cx_b: &mut TestAppContext,
 207) {
 208    let mut server = TestServer::start(cx_a.executor()).await;
 209    let client_a = server.create_client(cx_a, "user_a").await;
 210    let client_b = server.create_client(cx_b, "user_b").await;
 211    let executor = cx_a.executor();
 212    server
 213        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 214        .await;
 215    let active_call_a = cx_a.read(ActiveCall::global);
 216
 217    client_a
 218        .fs()
 219        .insert_tree(path!("/dir"), json!({ "a.txt": "Some text\n" }))
 220        .await;
 221    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
 222    let project_id = active_call_a
 223        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 224        .await
 225        .unwrap();
 226
 227    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 228
 229    // Open a buffer as client A
 230    let buffer_a = project_a
 231        .update(cx_a, |p, cx| {
 232            p.open_buffer((worktree_id, rel_path("a.txt")), cx)
 233        })
 234        .await
 235        .unwrap();
 236    let cx_a = cx_a.add_empty_window();
 237    let editor_a = cx_a
 238        .new_window_entity(|window, cx| Editor::for_buffer(buffer_a, Some(project_a), window, cx));
 239
 240    let mut editor_cx_a = EditorTestContext {
 241        cx: cx_a.clone(),
 242        window: cx_a.window_handle(),
 243        editor: editor_a,
 244        assertion_cx: AssertionContextManager::new(),
 245    };
 246
 247    let cx_b = cx_b.add_empty_window();
 248    // Open a buffer as client B
 249    let buffer_b = project_b
 250        .update(cx_b, |p, cx| {
 251            p.open_buffer((worktree_id, rel_path("a.txt")), cx)
 252        })
 253        .await
 254        .unwrap();
 255    let editor_b = cx_b
 256        .new_window_entity(|window, cx| Editor::for_buffer(buffer_b, Some(project_b), window, cx));
 257
 258    let mut editor_cx_b = EditorTestContext {
 259        cx: cx_b.clone(),
 260        window: cx_b.window_handle(),
 261        editor: editor_b,
 262        assertion_cx: AssertionContextManager::new(),
 263    };
 264
 265    // Test newline above
 266    editor_cx_a.set_selections_state(indoc! {"
 267        Some textˇ
 268    "});
 269    editor_cx_b.set_selections_state(indoc! {"
 270        Some textˇ
 271    "});
 272    editor_cx_a.update_editor(|editor, window, cx| {
 273        editor.newline_above(&editor::actions::NewlineAbove, window, cx)
 274    });
 275    executor.run_until_parked();
 276    editor_cx_a.assert_editor_state(indoc! {"
 277        ˇ
 278        Some text
 279    "});
 280    editor_cx_b.assert_editor_state(indoc! {"
 281
 282        Some textˇ
 283    "});
 284
 285    // Test newline below
 286    editor_cx_a.set_selections_state(indoc! {"
 287
 288        Some textˇ
 289    "});
 290    editor_cx_b.set_selections_state(indoc! {"
 291
 292        Some textˇ
 293    "});
 294    editor_cx_a.update_editor(|editor, window, cx| {
 295        editor.newline_below(&editor::actions::NewlineBelow, window, cx)
 296    });
 297    executor.run_until_parked();
 298    editor_cx_a.assert_editor_state(indoc! {"
 299
 300        Some text
 301        ˇ
 302    "});
 303    editor_cx_b.assert_editor_state(indoc! {"
 304
 305        Some textˇ
 306
 307    "});
 308}
 309
 310#[gpui::test]
 311async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 312    let mut server = TestServer::start(cx_a.executor()).await;
 313    let client_a = server.create_client(cx_a, "user_a").await;
 314    let client_b = server.create_client(cx_b, "user_b").await;
 315    server
 316        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 317        .await;
 318    let active_call_a = cx_a.read(ActiveCall::global);
 319
 320    let capabilities = lsp::ServerCapabilities {
 321        completion_provider: Some(lsp::CompletionOptions {
 322            trigger_characters: Some(vec![".".to_string()]),
 323            resolve_provider: Some(true),
 324            ..lsp::CompletionOptions::default()
 325        }),
 326        ..lsp::ServerCapabilities::default()
 327    };
 328    client_a.language_registry().add(rust_lang());
 329    let mut fake_language_servers = [
 330        client_a.language_registry().register_fake_lsp(
 331            "Rust",
 332            FakeLspAdapter {
 333                capabilities: capabilities.clone(),
 334                initializer: Some(Box::new(|fake_server| {
 335                    fake_server.set_request_handler::<lsp::request::Completion, _, _>(
 336                        |params, _| async move {
 337                            assert_eq!(
 338                                params.text_document_position.text_document.uri,
 339                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
 340                            );
 341                            assert_eq!(
 342                                params.text_document_position.position,
 343                                lsp::Position::new(0, 14),
 344                            );
 345
 346                            Ok(Some(lsp::CompletionResponse::Array(vec![
 347                                lsp::CompletionItem {
 348                                    label: "first_method(…)".into(),
 349                                    detail: Some("fn(&mut self, B) -> C".into()),
 350                                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 351                                        new_text: "first_method($1)".to_string(),
 352                                        range: lsp::Range::new(
 353                                            lsp::Position::new(0, 14),
 354                                            lsp::Position::new(0, 14),
 355                                        ),
 356                                    })),
 357                                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 358                                    ..Default::default()
 359                                },
 360                                lsp::CompletionItem {
 361                                    label: "second_method(…)".into(),
 362                                    detail: Some("fn(&mut self, C) -> D<E>".into()),
 363                                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 364                                        new_text: "second_method()".to_string(),
 365                                        range: lsp::Range::new(
 366                                            lsp::Position::new(0, 14),
 367                                            lsp::Position::new(0, 14),
 368                                        ),
 369                                    })),
 370                                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 371                                    ..Default::default()
 372                                },
 373                            ])))
 374                        },
 375                    );
 376                })),
 377                ..FakeLspAdapter::default()
 378            },
 379        ),
 380        client_a.language_registry().register_fake_lsp(
 381            "Rust",
 382            FakeLspAdapter {
 383                name: "fake-analyzer",
 384                capabilities: capabilities.clone(),
 385                initializer: Some(Box::new(|fake_server| {
 386                    fake_server.set_request_handler::<lsp::request::Completion, _, _>(
 387                        |_, _| async move { Ok(None) },
 388                    );
 389                })),
 390                ..FakeLspAdapter::default()
 391            },
 392        ),
 393    ];
 394    client_b.language_registry().add(rust_lang());
 395    client_b.language_registry().register_fake_lsp_adapter(
 396        "Rust",
 397        FakeLspAdapter {
 398            capabilities: capabilities.clone(),
 399            ..FakeLspAdapter::default()
 400        },
 401    );
 402    client_b.language_registry().register_fake_lsp_adapter(
 403        "Rust",
 404        FakeLspAdapter {
 405            name: "fake-analyzer",
 406            capabilities,
 407            ..FakeLspAdapter::default()
 408        },
 409    );
 410
 411    client_a
 412        .fs()
 413        .insert_tree(
 414            path!("/a"),
 415            json!({
 416                "main.rs": "fn main() { a }",
 417                "other.rs": "",
 418            }),
 419        )
 420        .await;
 421    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
 422    let project_id = active_call_a
 423        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 424        .await
 425        .unwrap();
 426    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 427
 428    // Open a file in an editor as the guest.
 429    let buffer_b = project_b
 430        .update(cx_b, |p, cx| {
 431            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
 432        })
 433        .await
 434        .unwrap();
 435    let cx_b = cx_b.add_empty_window();
 436    let editor_b = cx_b.new_window_entity(|window, cx| {
 437        Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), window, cx)
 438    });
 439
 440    let fake_language_server = fake_language_servers[0].next().await.unwrap();
 441    let second_fake_language_server = fake_language_servers[1].next().await.unwrap();
 442    cx_a.background_executor.run_until_parked();
 443    cx_b.background_executor.run_until_parked();
 444
 445    buffer_b.read_with(cx_b, |buffer, _| {
 446        assert!(!buffer.completion_triggers().is_empty())
 447    });
 448
 449    // Set up the completion request handlers BEFORE typing the trigger character.
 450    // This is critical - the handlers must be in place when the request arrives,
 451    // otherwise the requests will time out waiting for a response.
 452    let mut first_completion_request = fake_language_server
 453        .set_request_handler::<lsp::request::Completion, _, _>(|params, _| async move {
 454            assert_eq!(
 455                params.text_document_position.text_document.uri,
 456                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
 457            );
 458            assert_eq!(
 459                params.text_document_position.position,
 460                lsp::Position::new(0, 14),
 461            );
 462
 463            Ok(Some(lsp::CompletionResponse::Array(vec![
 464                lsp::CompletionItem {
 465                    label: "first_method(…)".into(),
 466                    detail: Some("fn(&mut self, B) -> C".into()),
 467                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 468                        new_text: "first_method($1)".to_string(),
 469                        range: lsp::Range::new(
 470                            lsp::Position::new(0, 14),
 471                            lsp::Position::new(0, 14),
 472                        ),
 473                    })),
 474                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 475                    ..Default::default()
 476                },
 477                lsp::CompletionItem {
 478                    label: "second_method(…)".into(),
 479                    detail: Some("fn(&mut self, C) -> D<E>".into()),
 480                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 481                        new_text: "second_method()".to_string(),
 482                        range: lsp::Range::new(
 483                            lsp::Position::new(0, 14),
 484                            lsp::Position::new(0, 14),
 485                        ),
 486                    })),
 487                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 488                    ..Default::default()
 489                },
 490            ])))
 491        });
 492    let mut second_completion_request = second_fake_language_server
 493        .set_request_handler::<lsp::request::Completion, _, _>(|_, _| async move { Ok(None) });
 494    // Type a completion trigger character as the guest.
 495    editor_b.update_in(cx_b, |editor, window, cx| {
 496        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 497            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
 498        });
 499        editor.handle_input(".", window, cx);
 500    });
 501    cx_b.focus(&editor_b);
 502
 503    // Allow the completion request to propagate from guest to host to LSP.
 504    cx_b.background_executor.run_until_parked();
 505    cx_a.background_executor.run_until_parked();
 506
 507    // Wait for the completion requests to be received by the fake language servers.
 508    first_completion_request.next().await.unwrap();
 509    second_completion_request.next().await.unwrap();
 510
 511    // Open the buffer on the host.
 512    let buffer_a = project_a
 513        .update(cx_a, |p, cx| {
 514            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
 515        })
 516        .await
 517        .unwrap();
 518    cx_a.executor().run_until_parked();
 519
 520    buffer_a.read_with(cx_a, |buffer, _| {
 521        assert_eq!(buffer.text(), "fn main() { a. }")
 522    });
 523
 524    // Confirm a completion on the guest.
 525    editor_b.update_in(cx_b, |editor, window, cx| {
 526        assert!(editor.context_menu_visible());
 527        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
 528        assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
 529    });
 530
 531    // Return a resolved completion from the host's language server.
 532    // The resolved completion has an additional text edit.
 533    fake_language_server.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
 534        |params, _| async move {
 535            assert_eq!(params.label, "first_method(…)");
 536            Ok(lsp::CompletionItem {
 537                label: "first_method(…)".into(),
 538                detail: Some("fn(&mut self, B) -> C".into()),
 539                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 540                    new_text: "first_method($1)".to_string(),
 541                    range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
 542                })),
 543                additional_text_edits: Some(vec![lsp::TextEdit {
 544                    new_text: "use d::SomeTrait;\n".to_string(),
 545                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 546                }]),
 547                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 548                ..Default::default()
 549            })
 550        },
 551    );
 552
 553    // The additional edit is applied.
 554    cx_a.executor().run_until_parked();
 555    cx_b.executor().run_until_parked();
 556
 557    buffer_a.read_with(cx_a, |buffer, _| {
 558        assert_eq!(
 559            buffer.text(),
 560            "use d::SomeTrait;\nfn main() { a.first_method() }"
 561        );
 562    });
 563
 564    buffer_b.read_with(cx_b, |buffer, _| {
 565        assert_eq!(
 566            buffer.text(),
 567            "use d::SomeTrait;\nfn main() { a.first_method() }"
 568        );
 569    });
 570
 571    // Now we do a second completion, this time to ensure that documentation/snippets are
 572    // resolved
 573    editor_b.update_in(cx_b, |editor, window, cx| {
 574        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 575            s.select_ranges([MultiBufferOffset(46)..MultiBufferOffset(46)])
 576        });
 577        editor.handle_input("; a", window, cx);
 578        editor.handle_input(".", window, cx);
 579    });
 580
 581    buffer_b.read_with(cx_b, |buffer, _| {
 582        assert_eq!(
 583            buffer.text(),
 584            "use d::SomeTrait;\nfn main() { a.first_method(); a. }"
 585        );
 586    });
 587
 588    let mut completion_response = fake_language_server
 589        .set_request_handler::<lsp::request::Completion, _, _>(|params, _| async move {
 590            assert_eq!(
 591                params.text_document_position.text_document.uri,
 592                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
 593            );
 594            assert_eq!(
 595                params.text_document_position.position,
 596                lsp::Position::new(1, 32),
 597            );
 598
 599            Ok(Some(lsp::CompletionResponse::Array(vec![
 600                lsp::CompletionItem {
 601                    label: "third_method(…)".into(),
 602                    detail: Some("fn(&mut self, B, C, D) -> E".into()),
 603                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 604                        // no snippet placeholders
 605                        new_text: "third_method".to_string(),
 606                        range: lsp::Range::new(
 607                            lsp::Position::new(1, 32),
 608                            lsp::Position::new(1, 32),
 609                        ),
 610                    })),
 611                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 612                    documentation: None,
 613                    ..Default::default()
 614                },
 615            ])))
 616        });
 617
 618    // Second language server also needs to handle the request (returns None)
 619    let mut second_completion_response = second_fake_language_server
 620        .set_request_handler::<lsp::request::Completion, _, _>(|_, _| async move { Ok(None) });
 621
 622    // The completion now gets a new `text_edit.new_text` when resolving the completion item
 623    let mut resolve_completion_response = fake_language_server
 624        .set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(|params, _| async move {
 625            assert_eq!(params.label, "third_method(…)");
 626            Ok(lsp::CompletionItem {
 627                label: "third_method(…)".into(),
 628                detail: Some("fn(&mut self, B, C, D) -> E".into()),
 629                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 630                    // Now it's a snippet
 631                    new_text: "third_method($1, $2, $3)".to_string(),
 632                    range: lsp::Range::new(lsp::Position::new(1, 32), lsp::Position::new(1, 32)),
 633                })),
 634                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 635                documentation: Some(lsp::Documentation::String(
 636                    "this is the documentation".into(),
 637                )),
 638                ..Default::default()
 639            })
 640        });
 641
 642    cx_b.executor().run_until_parked();
 643
 644    completion_response.next().await.unwrap();
 645    second_completion_response.next().await.unwrap();
 646
 647    editor_b.update_in(cx_b, |editor, window, cx| {
 648        assert!(editor.context_menu_visible());
 649        editor.context_menu_first(&ContextMenuFirst {}, window, cx);
 650    });
 651
 652    resolve_completion_response.next().await.unwrap();
 653    cx_b.executor().run_until_parked();
 654
 655    // When accepting the completion, the snippet is insert.
 656    editor_b.update_in(cx_b, |editor, window, cx| {
 657        assert!(editor.context_menu_visible());
 658        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
 659        assert_eq!(
 660            editor.text(cx),
 661            "use d::SomeTrait;\nfn main() { a.first_method(); a.third_method(, , ) }"
 662        );
 663    });
 664
 665    // Ensure buffer is synced before proceeding with the next test
 666    cx_a.executor().run_until_parked();
 667    cx_b.executor().run_until_parked();
 668
 669    // Test completions from the second fake language server
 670    // Add another completion trigger to test the second language server
 671    editor_b.update_in(cx_b, |editor, window, cx| {
 672        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 673            s.select_ranges([MultiBufferOffset(68)..MultiBufferOffset(68)])
 674        });
 675        editor.handle_input("; b", window, cx);
 676        editor.handle_input(".", window, cx);
 677    });
 678
 679    buffer_b.read_with(cx_b, |buffer, _| {
 680        assert_eq!(
 681            buffer.text(),
 682            "use d::SomeTrait;\nfn main() { a.first_method(); a.third_method(, , ); b. }"
 683        );
 684    });
 685
 686    // Set up completion handlers for both language servers
 687    let mut first_lsp_completion = fake_language_server
 688        .set_request_handler::<lsp::request::Completion, _, _>(|_, _| async move { Ok(None) });
 689
 690    let mut second_lsp_completion = second_fake_language_server
 691        .set_request_handler::<lsp::request::Completion, _, _>(|params, _| async move {
 692            assert_eq!(
 693                params.text_document_position.text_document.uri,
 694                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
 695            );
 696            assert_eq!(
 697                params.text_document_position.position,
 698                lsp::Position::new(1, 54),
 699            );
 700
 701            Ok(Some(lsp::CompletionResponse::Array(vec![
 702                lsp::CompletionItem {
 703                    label: "analyzer_method(…)".into(),
 704                    detail: Some("fn(&self) -> Result<T>".into()),
 705                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 706                        new_text: "analyzer_method()".to_string(),
 707                        range: lsp::Range::new(
 708                            lsp::Position::new(1, 54),
 709                            lsp::Position::new(1, 54),
 710                        ),
 711                    })),
 712                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 713                    ..lsp::CompletionItem::default()
 714                },
 715            ])))
 716        });
 717
 718    // Await both language server responses
 719    first_lsp_completion.next().await.unwrap();
 720    second_lsp_completion.next().await.unwrap();
 721
 722    cx_b.executor().run_until_parked();
 723
 724    // Confirm the completion from the second language server works
 725    editor_b.update_in(cx_b, |editor, window, cx| {
 726        assert!(editor.context_menu_visible());
 727        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
 728        assert_eq!(
 729            editor.text(cx),
 730            "use d::SomeTrait;\nfn main() { a.first_method(); a.third_method(, , ); b.analyzer_method() }"
 731        );
 732    });
 733}
 734
 735#[gpui::test(iterations = 10)]
 736async fn test_collaborating_with_code_actions(
 737    cx_a: &mut TestAppContext,
 738    cx_b: &mut TestAppContext,
 739) {
 740    let mut server = TestServer::start(cx_a.executor()).await;
 741    let client_a = server.create_client(cx_a, "user_a").await;
 742    //
 743    let client_b = server.create_client(cx_b, "user_b").await;
 744    server
 745        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 746        .await;
 747    let active_call_a = cx_a.read(ActiveCall::global);
 748
 749    cx_b.update(editor::init);
 750
 751    client_a.language_registry().add(rust_lang());
 752    let mut fake_language_servers = client_a
 753        .language_registry()
 754        .register_fake_lsp("Rust", FakeLspAdapter::default());
 755    client_b.language_registry().add(rust_lang());
 756    client_b
 757        .language_registry()
 758        .register_fake_lsp("Rust", FakeLspAdapter::default());
 759
 760    client_a
 761        .fs()
 762        .insert_tree(
 763            path!("/a"),
 764            json!({
 765                "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
 766                "other.rs": "pub fn foo() -> usize { 4 }",
 767            }),
 768        )
 769        .await;
 770    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
 771    let project_id = active_call_a
 772        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 773        .await
 774        .unwrap();
 775
 776    // Join the project as client B.
 777    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 778    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 779    let editor_b = workspace_b
 780        .update_in(cx_b, |workspace, window, cx| {
 781            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
 782        })
 783        .await
 784        .unwrap()
 785        .downcast::<Editor>()
 786        .unwrap();
 787
 788    let mut fake_language_server = fake_language_servers.next().await.unwrap();
 789    let mut requests = fake_language_server
 790        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
 791            assert_eq!(
 792                params.text_document.uri,
 793                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
 794            );
 795            assert_eq!(params.range.start, lsp::Position::new(0, 0));
 796            assert_eq!(params.range.end, lsp::Position::new(0, 0));
 797            Ok(None)
 798        });
 799    cx_a.background_executor
 800        .advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
 801    requests.next().await;
 802
 803    // Move cursor to a location that contains code actions.
 804    editor_b.update_in(cx_b, |editor, window, cx| {
 805        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 806            s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
 807        });
 808    });
 809    cx_b.focus(&editor_b);
 810
 811    let mut requests = fake_language_server
 812        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
 813            assert_eq!(
 814                params.text_document.uri,
 815                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
 816            );
 817            assert_eq!(params.range.start, lsp::Position::new(1, 31));
 818            assert_eq!(params.range.end, lsp::Position::new(1, 31));
 819
 820            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 821                lsp::CodeAction {
 822                    title: "Inline into all callers".to_string(),
 823                    edit: Some(lsp::WorkspaceEdit {
 824                        changes: Some(
 825                            [
 826                                (
 827                                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
 828                                    vec![lsp::TextEdit::new(
 829                                        lsp::Range::new(
 830                                            lsp::Position::new(1, 22),
 831                                            lsp::Position::new(1, 34),
 832                                        ),
 833                                        "4".to_string(),
 834                                    )],
 835                                ),
 836                                (
 837                                    lsp::Uri::from_file_path(path!("/a/other.rs")).unwrap(),
 838                                    vec![lsp::TextEdit::new(
 839                                        lsp::Range::new(
 840                                            lsp::Position::new(0, 0),
 841                                            lsp::Position::new(0, 27),
 842                                        ),
 843                                        "".to_string(),
 844                                    )],
 845                                ),
 846                            ]
 847                            .into_iter()
 848                            .collect(),
 849                        ),
 850                        ..Default::default()
 851                    }),
 852                    data: Some(json!({
 853                        "codeActionParams": {
 854                            "range": {
 855                                "start": {"line": 1, "column": 31},
 856                                "end": {"line": 1, "column": 31},
 857                            }
 858                        }
 859                    })),
 860                    ..Default::default()
 861                },
 862            )]))
 863        });
 864    cx_a.background_executor
 865        .advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
 866    requests.next().await;
 867
 868    // Toggle code actions and wait for them to display.
 869    editor_b.update_in(cx_b, |editor, window, cx| {
 870        editor.toggle_code_actions(
 871            &ToggleCodeActions {
 872                deployed_from: None,
 873                quick_launch: false,
 874            },
 875            window,
 876            cx,
 877        );
 878    });
 879    cx_a.background_executor.run_until_parked();
 880
 881    editor_b.update(cx_b, |editor, _| assert!(editor.context_menu_visible()));
 882
 883    fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
 884
 885    // Confirming the code action will trigger a resolve request.
 886    let confirm_action = editor_b
 887        .update_in(cx_b, |editor, window, cx| {
 888            Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, window, cx)
 889        })
 890        .unwrap();
 891    fake_language_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>(
 892        |_, _| async move {
 893            Ok(lsp::CodeAction {
 894                title: "Inline into all callers".to_string(),
 895                edit: Some(lsp::WorkspaceEdit {
 896                    changes: Some(
 897                        [
 898                            (
 899                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
 900                                vec![lsp::TextEdit::new(
 901                                    lsp::Range::new(
 902                                        lsp::Position::new(1, 22),
 903                                        lsp::Position::new(1, 34),
 904                                    ),
 905                                    "4".to_string(),
 906                                )],
 907                            ),
 908                            (
 909                                lsp::Uri::from_file_path(path!("/a/other.rs")).unwrap(),
 910                                vec![lsp::TextEdit::new(
 911                                    lsp::Range::new(
 912                                        lsp::Position::new(0, 0),
 913                                        lsp::Position::new(0, 27),
 914                                    ),
 915                                    "".to_string(),
 916                                )],
 917                            ),
 918                        ]
 919                        .into_iter()
 920                        .collect(),
 921                    ),
 922                    ..Default::default()
 923                }),
 924                ..Default::default()
 925            })
 926        },
 927    );
 928
 929    // After the action is confirmed, an editor containing both modified files is opened.
 930    confirm_action.await.unwrap();
 931
 932    let code_action_editor = workspace_b.update(cx_b, |workspace, cx| {
 933        workspace
 934            .active_item(cx)
 935            .unwrap()
 936            .downcast::<Editor>()
 937            .unwrap()
 938    });
 939    code_action_editor.update_in(cx_b, |editor, window, cx| {
 940        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 941        editor.undo(&Undo, window, cx);
 942        assert_eq!(
 943            editor.text(cx),
 944            "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
 945        );
 946        editor.redo(&Redo, window, cx);
 947        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 948    });
 949}
 950
 951#[gpui::test(iterations = 10)]
 952async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 953    let mut server = TestServer::start(cx_a.executor()).await;
 954    let client_a = server.create_client(cx_a, "user_a").await;
 955    let client_b = server.create_client(cx_b, "user_b").await;
 956    server
 957        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 958        .await;
 959    let active_call_a = cx_a.read(ActiveCall::global);
 960
 961    cx_b.update(editor::init);
 962
 963    let capabilities = lsp::ServerCapabilities {
 964        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
 965            prepare_provider: Some(true),
 966            work_done_progress_options: Default::default(),
 967        })),
 968        ..lsp::ServerCapabilities::default()
 969    };
 970    client_a.language_registry().add(rust_lang());
 971    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
 972        "Rust",
 973        FakeLspAdapter {
 974            capabilities: capabilities.clone(),
 975            ..FakeLspAdapter::default()
 976        },
 977    );
 978    client_b.language_registry().add(rust_lang());
 979    client_b.language_registry().register_fake_lsp_adapter(
 980        "Rust",
 981        FakeLspAdapter {
 982            capabilities,
 983            ..FakeLspAdapter::default()
 984        },
 985    );
 986
 987    client_a
 988        .fs()
 989        .insert_tree(
 990            path!("/dir"),
 991            json!({
 992                "one.rs": "const ONE: usize = 1;",
 993                "two.rs": "const TWO: usize = one::ONE + one::ONE;"
 994            }),
 995        )
 996        .await;
 997    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
 998    let project_id = active_call_a
 999        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1000        .await
1001        .unwrap();
1002    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1003
1004    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1005    let editor_b = workspace_b
1006        .update_in(cx_b, |workspace, window, cx| {
1007            workspace.open_path((worktree_id, rel_path("one.rs")), None, true, window, cx)
1008        })
1009        .await
1010        .unwrap()
1011        .downcast::<Editor>()
1012        .unwrap();
1013    let fake_language_server = fake_language_servers.next().await.unwrap();
1014    cx_a.run_until_parked();
1015    cx_b.run_until_parked();
1016
1017    // Move cursor to a location that can be renamed.
1018    let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
1019        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1020            s.select_ranges([MultiBufferOffset(7)..MultiBufferOffset(7)])
1021        });
1022        editor.rename(&Rename, window, cx).unwrap()
1023    });
1024
1025    fake_language_server
1026        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
1027            assert_eq!(
1028                params.text_document.uri.as_str(),
1029                uri!("file:///dir/one.rs")
1030            );
1031            assert_eq!(params.position, lsp::Position::new(0, 7));
1032            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
1033                lsp::Position::new(0, 6),
1034                lsp::Position::new(0, 9),
1035            ))))
1036        })
1037        .next()
1038        .await
1039        .unwrap();
1040    prepare_rename.await.unwrap();
1041    editor_b.update(cx_b, |editor, cx| {
1042        use editor::ToOffset;
1043        let rename = editor.pending_rename().unwrap();
1044        let buffer = editor.buffer().read(cx).snapshot(cx);
1045        assert_eq!(
1046            rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
1047            MultiBufferOffset(6)..MultiBufferOffset(9)
1048        );
1049        rename.editor.update(cx, |rename_editor, cx| {
1050            let rename_selection = rename_editor.selections.newest::<MultiBufferOffset>(&rename_editor.display_snapshot(cx));
1051            assert_eq!(
1052                rename_selection.range(),
1053                MultiBufferOffset(0)..MultiBufferOffset(3),
1054                "Rename that was triggered from zero selection caret, should propose the whole word."
1055            );
1056            rename_editor.buffer().update(cx, |rename_buffer, cx| {
1057                rename_buffer.edit([(MultiBufferOffset(0)..MultiBufferOffset(3), "THREE")], None, cx);
1058            });
1059        });
1060    });
1061
1062    // Cancel the rename, and repeat the same, but use selections instead of cursor movement
1063    editor_b.update_in(cx_b, |editor, window, cx| {
1064        editor.cancel(&editor::actions::Cancel, window, cx);
1065    });
1066    let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
1067        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1068            s.select_ranges([MultiBufferOffset(7)..MultiBufferOffset(8)])
1069        });
1070        editor.rename(&Rename, window, cx).unwrap()
1071    });
1072
1073    fake_language_server
1074        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
1075            assert_eq!(
1076                params.text_document.uri.as_str(),
1077                uri!("file:///dir/one.rs")
1078            );
1079            assert_eq!(params.position, lsp::Position::new(0, 8));
1080            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
1081                lsp::Position::new(0, 6),
1082                lsp::Position::new(0, 9),
1083            ))))
1084        })
1085        .next()
1086        .await
1087        .unwrap();
1088    prepare_rename.await.unwrap();
1089    editor_b.update(cx_b, |editor, cx| {
1090        use editor::ToOffset;
1091        let rename = editor.pending_rename().unwrap();
1092        let buffer = editor.buffer().read(cx).snapshot(cx);
1093        let lsp_rename_start = rename.range.start.to_offset(&buffer);
1094        let lsp_rename_end = rename.range.end.to_offset(&buffer);
1095        assert_eq!(lsp_rename_start..lsp_rename_end, MultiBufferOffset(6)..MultiBufferOffset(9));
1096        rename.editor.update(cx, |rename_editor, cx| {
1097            let rename_selection = rename_editor.selections.newest::<MultiBufferOffset>(&rename_editor.display_snapshot(cx));
1098            assert_eq!(
1099                rename_selection.range(),
1100                MultiBufferOffset(1)..MultiBufferOffset(2),
1101                "Rename that was triggered from a selection, should have the same selection range in the rename proposal"
1102            );
1103            rename_editor.buffer().update(cx, |rename_buffer, cx| {
1104                rename_buffer.edit([(MultiBufferOffset(0)..MultiBufferOffset(lsp_rename_end - lsp_rename_start), "THREE")], None, cx);
1105            });
1106        });
1107    });
1108
1109    let confirm_rename = editor_b.update_in(cx_b, |editor, window, cx| {
1110        Editor::confirm_rename(editor, &ConfirmRename, window, cx).unwrap()
1111    });
1112    fake_language_server
1113        .set_request_handler::<lsp::request::Rename, _, _>(|params, _| async move {
1114            assert_eq!(
1115                params.text_document_position.text_document.uri.as_str(),
1116                uri!("file:///dir/one.rs")
1117            );
1118            assert_eq!(
1119                params.text_document_position.position,
1120                lsp::Position::new(0, 6)
1121            );
1122            assert_eq!(params.new_name, "THREE");
1123            Ok(Some(lsp::WorkspaceEdit {
1124                changes: Some(
1125                    [
1126                        (
1127                            lsp::Uri::from_file_path(path!("/dir/one.rs")).unwrap(),
1128                            vec![lsp::TextEdit::new(
1129                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
1130                                "THREE".to_string(),
1131                            )],
1132                        ),
1133                        (
1134                            lsp::Uri::from_file_path(path!("/dir/two.rs")).unwrap(),
1135                            vec![
1136                                lsp::TextEdit::new(
1137                                    lsp::Range::new(
1138                                        lsp::Position::new(0, 24),
1139                                        lsp::Position::new(0, 27),
1140                                    ),
1141                                    "THREE".to_string(),
1142                                ),
1143                                lsp::TextEdit::new(
1144                                    lsp::Range::new(
1145                                        lsp::Position::new(0, 35),
1146                                        lsp::Position::new(0, 38),
1147                                    ),
1148                                    "THREE".to_string(),
1149                                ),
1150                            ],
1151                        ),
1152                    ]
1153                    .into_iter()
1154                    .collect(),
1155                ),
1156                ..Default::default()
1157            }))
1158        })
1159        .next()
1160        .await
1161        .unwrap();
1162    confirm_rename.await.unwrap();
1163
1164    let rename_editor = workspace_b.update(cx_b, |workspace, cx| {
1165        workspace.active_item_as::<Editor>(cx).unwrap()
1166    });
1167
1168    rename_editor.update_in(cx_b, |editor, window, cx| {
1169        assert_eq!(
1170            editor.text(cx),
1171            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
1172        );
1173        editor.undo(&Undo, window, cx);
1174        assert_eq!(
1175            editor.text(cx),
1176            "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
1177        );
1178        editor.redo(&Redo, window, cx);
1179        assert_eq!(
1180            editor.text(cx),
1181            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
1182        );
1183    });
1184
1185    // Ensure temporary rename edits cannot be undone/redone.
1186    editor_b.update_in(cx_b, |editor, window, cx| {
1187        editor.undo(&Undo, window, cx);
1188        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
1189        editor.undo(&Undo, window, cx);
1190        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
1191        editor.redo(&Redo, window, cx);
1192        assert_eq!(editor.text(cx), "const THREE: usize = 1;");
1193    })
1194}
1195
1196#[gpui::test]
1197async fn test_slow_lsp_server(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1198    let mut server = TestServer::start(cx_a.executor()).await;
1199    let client_a = server.create_client(cx_a, "user_a").await;
1200    let client_b = server.create_client(cx_b, "user_b").await;
1201    server
1202        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1203        .await;
1204    let active_call_a = cx_a.read(ActiveCall::global);
1205    cx_b.update(editor::init);
1206
1207    let command_name = "test_command";
1208    let capabilities = lsp::ServerCapabilities {
1209        code_lens_provider: Some(lsp::CodeLensOptions {
1210            resolve_provider: None,
1211        }),
1212        execute_command_provider: Some(lsp::ExecuteCommandOptions {
1213            commands: vec![command_name.to_string()],
1214            ..lsp::ExecuteCommandOptions::default()
1215        }),
1216        ..lsp::ServerCapabilities::default()
1217    };
1218    client_a.language_registry().add(rust_lang());
1219    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1220        "Rust",
1221        FakeLspAdapter {
1222            capabilities: capabilities.clone(),
1223            ..FakeLspAdapter::default()
1224        },
1225    );
1226    client_b.language_registry().add(rust_lang());
1227    client_b.language_registry().register_fake_lsp_adapter(
1228        "Rust",
1229        FakeLspAdapter {
1230            capabilities,
1231            ..FakeLspAdapter::default()
1232        },
1233    );
1234
1235    client_a
1236        .fs()
1237        .insert_tree(
1238            path!("/dir"),
1239            json!({
1240                "one.rs": "const ONE: usize = 1;"
1241            }),
1242        )
1243        .await;
1244    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
1245    let project_id = active_call_a
1246        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1247        .await
1248        .unwrap();
1249    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1250
1251    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1252    let editor_b = workspace_b
1253        .update_in(cx_b, |workspace, window, cx| {
1254            workspace.open_path((worktree_id, rel_path("one.rs")), None, true, window, cx)
1255        })
1256        .await
1257        .unwrap()
1258        .downcast::<Editor>()
1259        .unwrap();
1260    let (lsp_store_b, buffer_b) = editor_b.update(cx_b, |editor, cx| {
1261        let lsp_store = editor.project().unwrap().read(cx).lsp_store();
1262        let buffer = editor.buffer().read(cx).as_singleton().unwrap();
1263        (lsp_store, buffer)
1264    });
1265    let fake_language_server = fake_language_servers.next().await.unwrap();
1266    cx_a.run_until_parked();
1267    cx_b.run_until_parked();
1268
1269    let long_request_time = DEFAULT_LSP_REQUEST_TIMEOUT / 2;
1270    let (request_started_tx, mut request_started_rx) = mpsc::unbounded();
1271    let requests_started = Arc::new(AtomicUsize::new(0));
1272    let requests_completed = Arc::new(AtomicUsize::new(0));
1273    let _lens_requests = fake_language_server
1274        .set_request_handler::<lsp::request::CodeLensRequest, _, _>({
1275            let request_started_tx = request_started_tx.clone();
1276            let requests_started = requests_started.clone();
1277            let requests_completed = requests_completed.clone();
1278            move |params, cx| {
1279                let mut request_started_tx = request_started_tx.clone();
1280                let requests_started = requests_started.clone();
1281                let requests_completed = requests_completed.clone();
1282                async move {
1283                    assert_eq!(
1284                        params.text_document.uri.as_str(),
1285                        uri!("file:///dir/one.rs")
1286                    );
1287                    requests_started.fetch_add(1, atomic::Ordering::Release);
1288                    request_started_tx.send(()).await.unwrap();
1289                    cx.background_executor().timer(long_request_time).await;
1290                    let i = requests_completed.fetch_add(1, atomic::Ordering::Release) + 1;
1291                    Ok(Some(vec![lsp::CodeLens {
1292                        range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 9)),
1293                        command: Some(lsp::Command {
1294                            title: format!("LSP Command {i}"),
1295                            command: command_name.to_string(),
1296                            arguments: None,
1297                        }),
1298                        data: None,
1299                    }]))
1300                }
1301            }
1302        });
1303
1304    // Move cursor to a location, this should trigger the code lens call.
1305    editor_b.update_in(cx_b, |editor, window, cx| {
1306        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1307            s.select_ranges([MultiBufferOffset(7)..MultiBufferOffset(7)])
1308        });
1309    });
1310    let () = request_started_rx.next().await.unwrap();
1311    assert_eq!(
1312        requests_started.load(atomic::Ordering::Acquire),
1313        1,
1314        "Selection change should have initiated the first request"
1315    );
1316    assert_eq!(
1317        requests_completed.load(atomic::Ordering::Acquire),
1318        0,
1319        "Slow requests should be running still"
1320    );
1321    let _first_task = lsp_store_b.update(cx_b, |lsp_store, cx| {
1322        lsp_store
1323            .forget_code_lens_task(buffer_b.read(cx).remote_id())
1324            .expect("Should have the fetch task started")
1325    });
1326
1327    editor_b.update_in(cx_b, |editor, window, cx| {
1328        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1329            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
1330        });
1331    });
1332    let () = request_started_rx.next().await.unwrap();
1333    assert_eq!(
1334        requests_started.load(atomic::Ordering::Acquire),
1335        2,
1336        "Selection change should have initiated the second request"
1337    );
1338    assert_eq!(
1339        requests_completed.load(atomic::Ordering::Acquire),
1340        0,
1341        "Slow requests should be running still"
1342    );
1343    let _second_task = lsp_store_b.update(cx_b, |lsp_store, cx| {
1344        lsp_store
1345            .forget_code_lens_task(buffer_b.read(cx).remote_id())
1346            .expect("Should have the fetch task started for the 2nd time")
1347    });
1348
1349    editor_b.update_in(cx_b, |editor, window, cx| {
1350        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1351            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
1352        });
1353    });
1354    let () = request_started_rx.next().await.unwrap();
1355    assert_eq!(
1356        requests_started.load(atomic::Ordering::Acquire),
1357        3,
1358        "Selection change should have initiated the third request"
1359    );
1360    assert_eq!(
1361        requests_completed.load(atomic::Ordering::Acquire),
1362        0,
1363        "Slow requests should be running still"
1364    );
1365
1366    _first_task.await.unwrap();
1367    _second_task.await.unwrap();
1368    cx_b.run_until_parked();
1369    assert_eq!(
1370        requests_started.load(atomic::Ordering::Acquire),
1371        3,
1372        "No selection changes should trigger no more code lens requests"
1373    );
1374    assert_eq!(
1375        requests_completed.load(atomic::Ordering::Acquire),
1376        1,
1377        "After enough time, a single, deduplicated, LSP request should have been served by the language server"
1378    );
1379    let resulting_lens_actions = editor_b
1380        .update(cx_b, |editor, cx| {
1381            let lsp_store = editor.project().unwrap().read(cx).lsp_store();
1382            lsp_store.update(cx, |lsp_store, cx| {
1383                lsp_store.code_lens_actions(&buffer_b, cx)
1384            })
1385        })
1386        .await
1387        .unwrap()
1388        .unwrap();
1389    assert_eq!(
1390        resulting_lens_actions.len(),
1391        1,
1392        "Should have fetched one code lens action, but got: {resulting_lens_actions:?}"
1393    );
1394    assert_eq!(
1395        resulting_lens_actions.first().unwrap().lsp_action.title(),
1396        "LSP Command 1",
1397        "Only the final code lens action should be in the data"
1398    )
1399}
1400
1401#[gpui::test(iterations = 10)]
1402async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1403    let mut server = TestServer::start(cx_a.executor()).await;
1404    let executor = cx_a.executor();
1405    let client_a = server.create_client(cx_a, "user_a").await;
1406    let client_b = server.create_client(cx_b, "user_b").await;
1407    server
1408        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1409        .await;
1410    let active_call_a = cx_a.read(ActiveCall::global);
1411
1412    cx_b.update(editor::init);
1413
1414    client_a.language_registry().add(rust_lang());
1415    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1416        "Rust",
1417        FakeLspAdapter {
1418            name: "the-language-server",
1419            ..Default::default()
1420        },
1421    );
1422
1423    client_a
1424        .fs()
1425        .insert_tree(
1426            path!("/dir"),
1427            json!({
1428                "main.rs": "const ONE: usize = 1;",
1429            }),
1430        )
1431        .await;
1432    let (project_a, _) = client_a.build_local_project(path!("/dir"), cx_a).await;
1433
1434    let _buffer_a = project_a
1435        .update(cx_a, |p, cx| {
1436            p.open_local_buffer_with_lsp(path!("/dir/main.rs"), cx)
1437        })
1438        .await
1439        .unwrap();
1440
1441    let fake_language_server = fake_language_servers.next().await.unwrap();
1442    executor.run_until_parked();
1443    fake_language_server.start_progress("the-token").await;
1444
1445    executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
1446    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1447        token: lsp::NumberOrString::String("the-token".to_string()),
1448        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
1449            lsp::WorkDoneProgressReport {
1450                message: Some("the-message".to_string()),
1451                ..Default::default()
1452            },
1453        )),
1454    });
1455    executor.run_until_parked();
1456
1457    let token = ProgressToken::String(SharedString::from("the-token"));
1458
1459    project_a.read_with(cx_a, |project, cx| {
1460        let status = project.language_server_statuses(cx).next().unwrap().1;
1461        assert_eq!(status.name.0, "the-language-server");
1462        assert_eq!(status.pending_work.len(), 1);
1463        assert_eq!(
1464            status.pending_work[&token].message.as_ref().unwrap(),
1465            "the-message"
1466        );
1467    });
1468
1469    let project_id = active_call_a
1470        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1471        .await
1472        .unwrap();
1473    executor.run_until_parked();
1474    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1475
1476    project_b.read_with(cx_b, |project, cx| {
1477        let status = project.language_server_statuses(cx).next().unwrap().1;
1478        assert_eq!(status.name.0, "the-language-server");
1479    });
1480
1481    executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
1482    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1483        token: lsp::NumberOrString::String("the-token".to_string()),
1484        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
1485            lsp::WorkDoneProgressReport {
1486                message: Some("the-message-2".to_string()),
1487                ..Default::default()
1488            },
1489        )),
1490    });
1491    executor.run_until_parked();
1492
1493    project_a.read_with(cx_a, |project, cx| {
1494        let status = project.language_server_statuses(cx).next().unwrap().1;
1495        assert_eq!(status.name.0, "the-language-server");
1496        assert_eq!(status.pending_work.len(), 1);
1497        assert_eq!(
1498            status.pending_work[&token].message.as_ref().unwrap(),
1499            "the-message-2"
1500        );
1501    });
1502
1503    project_b.read_with(cx_b, |project, cx| {
1504        let status = project.language_server_statuses(cx).next().unwrap().1;
1505        assert_eq!(status.name.0, "the-language-server");
1506        assert_eq!(status.pending_work.len(), 1);
1507        assert_eq!(
1508            status.pending_work[&token].message.as_ref().unwrap(),
1509            "the-message-2"
1510        );
1511    });
1512}
1513
1514#[gpui::test(iterations = 10)]
1515async fn test_share_project(
1516    cx_a: &mut TestAppContext,
1517    cx_b: &mut TestAppContext,
1518    cx_c: &mut TestAppContext,
1519) {
1520    let executor = cx_a.executor();
1521    let cx_b = cx_b.add_empty_window();
1522    let mut server = TestServer::start(executor.clone()).await;
1523    let client_a = server.create_client(cx_a, "user_a").await;
1524    let client_b = server.create_client(cx_b, "user_b").await;
1525    let client_c = server.create_client(cx_c, "user_c").await;
1526    server
1527        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1528        .await;
1529    let active_call_a = cx_a.read(ActiveCall::global);
1530    let active_call_b = cx_b.read(ActiveCall::global);
1531    let active_call_c = cx_c.read(ActiveCall::global);
1532
1533    client_a
1534        .fs()
1535        .insert_tree(
1536            path!("/a"),
1537            json!({
1538                ".gitignore": "ignored-dir",
1539                "a.txt": "a-contents",
1540                "b.txt": "b-contents",
1541                "ignored-dir": {
1542                    "c.txt": "",
1543                    "d.txt": "",
1544                }
1545            }),
1546        )
1547        .await;
1548
1549    // Invite client B to collaborate on a project
1550    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1551    active_call_a
1552        .update(cx_a, |call, cx| {
1553            call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
1554        })
1555        .await
1556        .unwrap();
1557
1558    // Join that project as client B
1559
1560    let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
1561    executor.run_until_parked();
1562    let call = incoming_call_b.borrow().clone().unwrap();
1563    assert_eq!(call.calling_user.github_login, "user_a");
1564    let initial_project = call.initial_project.unwrap();
1565    active_call_b
1566        .update(cx_b, |call, cx| call.accept_incoming(cx))
1567        .await
1568        .unwrap();
1569    let client_b_peer_id = client_b.peer_id().unwrap();
1570    let project_b = client_b.join_remote_project(initial_project.id, cx_b).await;
1571
1572    let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
1573
1574    executor.run_until_parked();
1575
1576    project_a.read_with(cx_a, |project, _| {
1577        let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
1578        assert_eq!(client_b_collaborator.replica_id, replica_id_b);
1579    });
1580
1581    project_b.read_with(cx_b, |project, cx| {
1582        let worktree = project.worktrees(cx).next().unwrap().read(cx);
1583        assert_eq!(
1584            worktree.paths().collect::<Vec<_>>(),
1585            [
1586                rel_path(".gitignore"),
1587                rel_path("a.txt"),
1588                rel_path("b.txt"),
1589                rel_path("ignored-dir"),
1590            ]
1591        );
1592    });
1593
1594    project_b
1595        .update(cx_b, |project, cx| {
1596            let worktree = project.worktrees(cx).next().unwrap();
1597            let entry = worktree
1598                .read(cx)
1599                .entry_for_path(rel_path("ignored-dir"))
1600                .unwrap();
1601            project.expand_entry(worktree_id, entry.id, cx).unwrap()
1602        })
1603        .await
1604        .unwrap();
1605
1606    project_b.read_with(cx_b, |project, cx| {
1607        let worktree = project.worktrees(cx).next().unwrap().read(cx);
1608        assert_eq!(
1609            worktree.paths().collect::<Vec<_>>(),
1610            [
1611                rel_path(".gitignore"),
1612                rel_path("a.txt"),
1613                rel_path("b.txt"),
1614                rel_path("ignored-dir"),
1615                rel_path("ignored-dir/c.txt"),
1616                rel_path("ignored-dir/d.txt"),
1617            ]
1618        );
1619    });
1620
1621    // Open the same file as client B and client A.
1622    let buffer_b = project_b
1623        .update(cx_b, |p, cx| {
1624            p.open_buffer((worktree_id, rel_path("b.txt")), cx)
1625        })
1626        .await
1627        .unwrap();
1628
1629    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
1630
1631    project_a.read_with(cx_a, |project, cx| {
1632        assert!(project.has_open_buffer((worktree_id, rel_path("b.txt")), cx))
1633    });
1634    let buffer_a = project_a
1635        .update(cx_a, |p, cx| {
1636            p.open_buffer((worktree_id, rel_path("b.txt")), cx)
1637        })
1638        .await
1639        .unwrap();
1640
1641    let editor_b =
1642        cx_b.new_window_entity(|window, cx| Editor::for_buffer(buffer_b, None, window, cx));
1643
1644    // Client A sees client B's selection
1645    executor.run_until_parked();
1646
1647    buffer_a.read_with(cx_a, |buffer, _| {
1648        buffer
1649            .snapshot()
1650            .selections_in_range(
1651                text::Anchor::min_max_range_for_buffer(buffer.remote_id()),
1652                false,
1653            )
1654            .count()
1655            == 1
1656    });
1657
1658    // Edit the buffer as client B and see that edit as client A.
1659    editor_b.update_in(cx_b, |editor, window, cx| {
1660        editor.handle_input("ok, ", window, cx)
1661    });
1662    executor.run_until_parked();
1663
1664    buffer_a.read_with(cx_a, |buffer, _| {
1665        assert_eq!(buffer.text(), "ok, b-contents")
1666    });
1667
1668    // Client B can invite client C on a project shared by client A.
1669    active_call_b
1670        .update(cx_b, |call, cx| {
1671            call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
1672        })
1673        .await
1674        .unwrap();
1675
1676    let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
1677    executor.run_until_parked();
1678    let call = incoming_call_c.borrow().clone().unwrap();
1679    assert_eq!(call.calling_user.github_login, "user_b");
1680    let initial_project = call.initial_project.unwrap();
1681    active_call_c
1682        .update(cx_c, |call, cx| call.accept_incoming(cx))
1683        .await
1684        .unwrap();
1685    let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
1686
1687    // Client B closes the editor, and client A sees client B's selections removed.
1688    cx_b.update(move |_, _| drop(editor_b));
1689    executor.run_until_parked();
1690
1691    buffer_a.read_with(cx_a, |buffer, _| {
1692        buffer
1693            .snapshot()
1694            .selections_in_range(
1695                text::Anchor::min_max_range_for_buffer(buffer.remote_id()),
1696                false,
1697            )
1698            .count()
1699            == 0
1700    });
1701}
1702
1703#[gpui::test(iterations = 10)]
1704async fn test_on_input_format_from_host_to_guest(
1705    cx_a: &mut TestAppContext,
1706    cx_b: &mut TestAppContext,
1707) {
1708    let mut server = TestServer::start(cx_a.executor()).await;
1709    let executor = cx_a.executor();
1710    let client_a = server.create_client(cx_a, "user_a").await;
1711    let client_b = server.create_client(cx_b, "user_b").await;
1712    server
1713        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1714        .await;
1715    let active_call_a = cx_a.read(ActiveCall::global);
1716
1717    client_a.language_registry().add(rust_lang());
1718    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1719        "Rust",
1720        FakeLspAdapter {
1721            capabilities: lsp::ServerCapabilities {
1722                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1723                    first_trigger_character: ":".to_string(),
1724                    more_trigger_character: Some(vec![">".to_string()]),
1725                }),
1726                ..Default::default()
1727            },
1728            ..Default::default()
1729        },
1730    );
1731
1732    client_a
1733        .fs()
1734        .insert_tree(
1735            path!("/a"),
1736            json!({
1737                "main.rs": "fn main() { a }",
1738                "other.rs": "// Test file",
1739            }),
1740        )
1741        .await;
1742    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1743    let project_id = active_call_a
1744        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1745        .await
1746        .unwrap();
1747    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1748
1749    // Open a file in an editor as the host.
1750    let buffer_a = project_a
1751        .update(cx_a, |p, cx| {
1752            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1753        })
1754        .await
1755        .unwrap();
1756    let cx_a = cx_a.add_empty_window();
1757    let editor_a = cx_a.new_window_entity(|window, cx| {
1758        Editor::for_buffer(buffer_a, Some(project_a.clone()), window, cx)
1759    });
1760
1761    let fake_language_server = fake_language_servers.next().await.unwrap();
1762    executor.run_until_parked();
1763
1764    // Receive an OnTypeFormatting request as the host's language server.
1765    // Return some formatting from the host's language server.
1766    fake_language_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
1767        |params, _| async move {
1768            assert_eq!(
1769                params.text_document_position.text_document.uri,
1770                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
1771            );
1772            assert_eq!(
1773                params.text_document_position.position,
1774                lsp::Position::new(0, 14),
1775            );
1776
1777            Ok(Some(vec![lsp::TextEdit {
1778                new_text: "~<".to_string(),
1779                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1780            }]))
1781        },
1782    );
1783
1784    // Open the buffer on the guest and see that the formatting worked
1785    let buffer_b = project_b
1786        .update(cx_b, |p, cx| {
1787            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1788        })
1789        .await
1790        .unwrap();
1791
1792    // Type a on type formatting trigger character as the guest.
1793    cx_a.focus(&editor_a);
1794    editor_a.update_in(cx_a, |editor, window, cx| {
1795        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1796            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
1797        });
1798        editor.handle_input(">", window, cx);
1799    });
1800
1801    executor.run_until_parked();
1802
1803    buffer_b.read_with(cx_b, |buffer, _| {
1804        assert_eq!(buffer.text(), "fn main() { a>~< }")
1805    });
1806
1807    // Undo should remove LSP edits first
1808    editor_a.update_in(cx_a, |editor, window, cx| {
1809        assert_eq!(editor.text(cx), "fn main() { a>~< }");
1810        editor.undo(&Undo, window, cx);
1811        assert_eq!(editor.text(cx), "fn main() { a> }");
1812    });
1813    executor.run_until_parked();
1814
1815    buffer_b.read_with(cx_b, |buffer, _| {
1816        assert_eq!(buffer.text(), "fn main() { a> }")
1817    });
1818
1819    editor_a.update_in(cx_a, |editor, window, cx| {
1820        assert_eq!(editor.text(cx), "fn main() { a> }");
1821        editor.undo(&Undo, window, cx);
1822        assert_eq!(editor.text(cx), "fn main() { a }");
1823    });
1824    executor.run_until_parked();
1825
1826    buffer_b.read_with(cx_b, |buffer, _| {
1827        assert_eq!(buffer.text(), "fn main() { a }")
1828    });
1829}
1830
1831#[gpui::test(iterations = 10)]
1832async fn test_on_input_format_from_guest_to_host(
1833    cx_a: &mut TestAppContext,
1834    cx_b: &mut TestAppContext,
1835) {
1836    let mut server = TestServer::start(cx_a.executor()).await;
1837    let executor = cx_a.executor();
1838    let client_a = server.create_client(cx_a, "user_a").await;
1839    let client_b = server.create_client(cx_b, "user_b").await;
1840    server
1841        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1842        .await;
1843    let active_call_a = cx_a.read(ActiveCall::global);
1844
1845    let capabilities = lsp::ServerCapabilities {
1846        document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1847            first_trigger_character: ":".to_string(),
1848            more_trigger_character: Some(vec![">".to_string()]),
1849        }),
1850        ..lsp::ServerCapabilities::default()
1851    };
1852    client_a.language_registry().add(rust_lang());
1853    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1854        "Rust",
1855        FakeLspAdapter {
1856            capabilities: capabilities.clone(),
1857            ..FakeLspAdapter::default()
1858        },
1859    );
1860    client_b.language_registry().add(rust_lang());
1861    client_b.language_registry().register_fake_lsp_adapter(
1862        "Rust",
1863        FakeLspAdapter {
1864            capabilities,
1865            ..FakeLspAdapter::default()
1866        },
1867    );
1868
1869    client_a
1870        .fs()
1871        .insert_tree(
1872            path!("/a"),
1873            json!({
1874                "main.rs": "fn main() { a }",
1875                "other.rs": "// Test file",
1876            }),
1877        )
1878        .await;
1879    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1880    let project_id = active_call_a
1881        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1882        .await
1883        .unwrap();
1884    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1885
1886    // Open a file in an editor as the guest.
1887    let buffer_b = project_b
1888        .update(cx_b, |p, cx| {
1889            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1890        })
1891        .await
1892        .unwrap();
1893    let cx_b = cx_b.add_empty_window();
1894    let editor_b = cx_b.new_window_entity(|window, cx| {
1895        Editor::for_buffer(buffer_b, Some(project_b.clone()), window, cx)
1896    });
1897
1898    let fake_language_server = fake_language_servers.next().await.unwrap();
1899    executor.run_until_parked();
1900
1901    // Type a on type formatting trigger character as the guest.
1902    cx_b.focus(&editor_b);
1903    editor_b.update_in(cx_b, |editor, window, cx| {
1904        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1905            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
1906        });
1907        editor.handle_input(":", window, cx);
1908    });
1909
1910    // Receive an OnTypeFormatting request as the host's language server.
1911    // Return some formatting from the host's language server.
1912    fake_language_server
1913        .set_request_handler::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
1914            assert_eq!(
1915                params.text_document_position.text_document.uri,
1916                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
1917            );
1918            assert_eq!(
1919                params.text_document_position.position,
1920                lsp::Position::new(0, 14),
1921            );
1922
1923            Ok(Some(vec![lsp::TextEdit {
1924                new_text: "~:".to_string(),
1925                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1926            }]))
1927        })
1928        .next()
1929        .await
1930        .unwrap();
1931
1932    // Open the buffer on the host and see that the formatting worked
1933    let buffer_a = project_a
1934        .update(cx_a, |p, cx| {
1935            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1936        })
1937        .await
1938        .unwrap();
1939    executor.run_until_parked();
1940
1941    buffer_a.read_with(cx_a, |buffer, _| {
1942        assert_eq!(buffer.text(), "fn main() { a:~: }")
1943    });
1944
1945    // Undo should remove LSP edits first
1946    editor_b.update_in(cx_b, |editor, window, cx| {
1947        assert_eq!(editor.text(cx), "fn main() { a:~: }");
1948        editor.undo(&Undo, window, cx);
1949        assert_eq!(editor.text(cx), "fn main() { a: }");
1950    });
1951    executor.run_until_parked();
1952
1953    buffer_a.read_with(cx_a, |buffer, _| {
1954        assert_eq!(buffer.text(), "fn main() { a: }")
1955    });
1956
1957    editor_b.update_in(cx_b, |editor, window, cx| {
1958        assert_eq!(editor.text(cx), "fn main() { a: }");
1959        editor.undo(&Undo, window, cx);
1960        assert_eq!(editor.text(cx), "fn main() { a }");
1961    });
1962    executor.run_until_parked();
1963
1964    buffer_a.read_with(cx_a, |buffer, _| {
1965        assert_eq!(buffer.text(), "fn main() { a }")
1966    });
1967}
1968
1969#[gpui::test(iterations = 10)]
1970async fn test_mutual_editor_inlay_hint_cache_update(
1971    cx_a: &mut TestAppContext,
1972    cx_b: &mut TestAppContext,
1973) {
1974    let mut server = TestServer::start(cx_a.executor()).await;
1975    let executor = cx_a.executor();
1976    let client_a = server.create_client(cx_a, "user_a").await;
1977    let client_b = server.create_client(cx_b, "user_b").await;
1978    server
1979        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1980        .await;
1981    let active_call_a = cx_a.read(ActiveCall::global);
1982    let active_call_b = cx_b.read(ActiveCall::global);
1983
1984    cx_a.update(editor::init);
1985    cx_b.update(editor::init);
1986
1987    cx_a.update(|cx| {
1988        SettingsStore::update_global(cx, |store, cx| {
1989            store.update_user_settings(cx, |settings| {
1990                settings.project.all_languages.defaults.inlay_hints =
1991                    Some(InlayHintSettingsContent {
1992                        enabled: Some(true),
1993                        ..InlayHintSettingsContent::default()
1994                    })
1995            });
1996        });
1997    });
1998    cx_b.update(|cx| {
1999        SettingsStore::update_global(cx, |store, cx| {
2000            store.update_user_settings(cx, |settings| {
2001                settings.project.all_languages.defaults.inlay_hints =
2002                    Some(InlayHintSettingsContent {
2003                        enabled: Some(true),
2004                        ..InlayHintSettingsContent::default()
2005                    })
2006            });
2007        });
2008    });
2009
2010    let capabilities = lsp::ServerCapabilities {
2011        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2012        ..lsp::ServerCapabilities::default()
2013    };
2014    client_a.language_registry().add(rust_lang());
2015
2016    // Set up the language server to return an additional inlay hint on each request.
2017    let edits_made = Arc::new(AtomicUsize::new(0));
2018    let closure_edits_made = Arc::clone(&edits_made);
2019    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2020        "Rust",
2021        FakeLspAdapter {
2022            capabilities: capabilities.clone(),
2023            initializer: Some(Box::new(move |fake_language_server| {
2024                let closure_edits_made = closure_edits_made.clone();
2025                fake_language_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
2026                    move |params, _| {
2027                        let edits_made_2 = Arc::clone(&closure_edits_made);
2028                        async move {
2029                            assert_eq!(
2030                                params.text_document.uri,
2031                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2032                            );
2033                            let edits_made =
2034                                AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire);
2035                            Ok(Some(vec![lsp::InlayHint {
2036                                position: lsp::Position::new(0, edits_made as u32),
2037                                label: lsp::InlayHintLabel::String(edits_made.to_string()),
2038                                kind: None,
2039                                text_edits: None,
2040                                tooltip: None,
2041                                padding_left: None,
2042                                padding_right: None,
2043                                data: None,
2044                            }]))
2045                        }
2046                    },
2047                );
2048            })),
2049            ..FakeLspAdapter::default()
2050        },
2051    );
2052    client_b.language_registry().add(rust_lang());
2053    client_b.language_registry().register_fake_lsp_adapter(
2054        "Rust",
2055        FakeLspAdapter {
2056            capabilities,
2057            ..FakeLspAdapter::default()
2058        },
2059    );
2060
2061    // Client A opens a project.
2062    client_a
2063        .fs()
2064        .insert_tree(
2065            path!("/a"),
2066            json!({
2067                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
2068                "other.rs": "// Test file",
2069            }),
2070        )
2071        .await;
2072    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2073    active_call_a
2074        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2075        .await
2076        .unwrap();
2077    let project_id = active_call_a
2078        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2079        .await
2080        .unwrap();
2081
2082    // Client B joins the project
2083    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2084    active_call_b
2085        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2086        .await
2087        .unwrap();
2088
2089    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2090
2091    // The host opens a rust file.
2092    let file_a = workspace_a.update_in(cx_a, |workspace, window, cx| {
2093        workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2094    });
2095    let fake_language_server = fake_language_servers.next().await.unwrap();
2096    let editor_a = file_a.await.unwrap().downcast::<Editor>().unwrap();
2097    executor.advance_clock(Duration::from_millis(100));
2098    executor.run_until_parked();
2099
2100    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
2101    editor_a.update(cx_a, |editor, cx| {
2102        assert_eq!(
2103            vec![initial_edit.to_string()],
2104            extract_hint_labels(editor, cx),
2105            "Host should get its first hints when opens an editor"
2106        );
2107    });
2108    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2109    let editor_b = workspace_b
2110        .update_in(cx_b, |workspace, window, cx| {
2111            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2112        })
2113        .await
2114        .unwrap()
2115        .downcast::<Editor>()
2116        .unwrap();
2117
2118    executor.advance_clock(Duration::from_millis(100));
2119    executor.run_until_parked();
2120    editor_b.update(cx_b, |editor, cx| {
2121        assert_eq!(
2122            vec![initial_edit.to_string()],
2123            extract_hint_labels(editor, cx),
2124            "Client should get its first hints when opens an editor"
2125        );
2126    });
2127
2128    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
2129    editor_b.update_in(cx_b, |editor, window, cx| {
2130        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2131            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)].clone())
2132        });
2133        editor.handle_input(":", window, cx);
2134    });
2135    cx_b.focus(&editor_b);
2136
2137    executor.advance_clock(Duration::from_secs(1));
2138    executor.run_until_parked();
2139    editor_a.update(cx_a, |editor, cx| {
2140        assert_eq!(
2141            vec![after_client_edit.to_string()],
2142            extract_hint_labels(editor, cx),
2143        );
2144    });
2145    editor_b.update(cx_b, |editor, cx| {
2146        assert_eq!(
2147            vec![after_client_edit.to_string()],
2148            extract_hint_labels(editor, cx),
2149        );
2150    });
2151
2152    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
2153    editor_a.update_in(cx_a, |editor, window, cx| {
2154        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2155            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
2156        });
2157        editor.handle_input("a change to increment both buffers' versions", window, cx);
2158    });
2159    cx_a.focus(&editor_a);
2160
2161    executor.advance_clock(Duration::from_secs(1));
2162    executor.run_until_parked();
2163    editor_a.update(cx_a, |editor, cx| {
2164        assert_eq!(
2165            vec![after_host_edit.to_string()],
2166            extract_hint_labels(editor, cx),
2167        );
2168    });
2169    editor_b.update(cx_b, |editor, cx| {
2170        assert_eq!(
2171            vec![after_host_edit.to_string()],
2172            extract_hint_labels(editor, cx),
2173        );
2174    });
2175
2176    let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
2177    fake_language_server
2178        .request::<lsp::request::InlayHintRefreshRequest>((), DEFAULT_LSP_REQUEST_TIMEOUT)
2179        .await
2180        .into_response()
2181        .expect("inlay refresh request failed");
2182
2183    executor.advance_clock(Duration::from_secs(1));
2184    executor.run_until_parked();
2185    editor_a.update(cx_a, |editor, cx| {
2186        assert_eq!(
2187            vec![after_special_edit_for_refresh.to_string()],
2188            extract_hint_labels(editor, cx),
2189            "Host should react to /refresh LSP request"
2190        );
2191    });
2192    editor_b.update(cx_b, |editor, cx| {
2193        assert_eq!(
2194            vec![after_special_edit_for_refresh.to_string()],
2195            extract_hint_labels(editor, cx),
2196            "Guest should get a /refresh LSP request propagated by host"
2197        );
2198    });
2199}
2200
2201#[gpui::test(iterations = 10)]
2202async fn test_inlay_hint_refresh_is_forwarded(
2203    cx_a: &mut TestAppContext,
2204    cx_b: &mut TestAppContext,
2205) {
2206    let mut server = TestServer::start(cx_a.executor()).await;
2207    let executor = cx_a.executor();
2208    let client_a = server.create_client(cx_a, "user_a").await;
2209    let client_b = server.create_client(cx_b, "user_b").await;
2210    server
2211        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2212        .await;
2213    let active_call_a = cx_a.read(ActiveCall::global);
2214    let active_call_b = cx_b.read(ActiveCall::global);
2215
2216    cx_a.update(editor::init);
2217    cx_b.update(editor::init);
2218
2219    cx_a.update(|cx| {
2220        SettingsStore::update_global(cx, |store, cx| {
2221            store.update_user_settings(cx, |settings| {
2222                settings.project.all_languages.defaults.inlay_hints =
2223                    Some(InlayHintSettingsContent {
2224                        show_value_hints: Some(true),
2225                        enabled: Some(false),
2226                        edit_debounce_ms: Some(0),
2227                        scroll_debounce_ms: Some(0),
2228                        show_type_hints: Some(false),
2229                        show_parameter_hints: Some(false),
2230                        show_other_hints: Some(false),
2231                        show_background: Some(false),
2232                        toggle_on_modifiers_press: None,
2233                    })
2234            });
2235        });
2236    });
2237    cx_b.update(|cx| {
2238        SettingsStore::update_global(cx, |store, cx| {
2239            store.update_user_settings(cx, |settings| {
2240                settings.project.all_languages.defaults.inlay_hints =
2241                    Some(InlayHintSettingsContent {
2242                        show_value_hints: Some(true),
2243                        enabled: Some(true),
2244                        edit_debounce_ms: Some(0),
2245                        scroll_debounce_ms: Some(0),
2246                        show_type_hints: Some(true),
2247                        show_parameter_hints: Some(true),
2248                        show_other_hints: Some(true),
2249                        show_background: Some(false),
2250                        toggle_on_modifiers_press: None,
2251                    })
2252            });
2253        });
2254    });
2255
2256    let capabilities = lsp::ServerCapabilities {
2257        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2258        ..lsp::ServerCapabilities::default()
2259    };
2260    client_a.language_registry().add(rust_lang());
2261    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2262        "Rust",
2263        FakeLspAdapter {
2264            capabilities: capabilities.clone(),
2265            ..FakeLspAdapter::default()
2266        },
2267    );
2268    client_b.language_registry().add(rust_lang());
2269    client_b.language_registry().register_fake_lsp_adapter(
2270        "Rust",
2271        FakeLspAdapter {
2272            capabilities,
2273            ..FakeLspAdapter::default()
2274        },
2275    );
2276
2277    client_a
2278        .fs()
2279        .insert_tree(
2280            path!("/a"),
2281            json!({
2282                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
2283                "other.rs": "// Test file",
2284            }),
2285        )
2286        .await;
2287    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2288    active_call_a
2289        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2290        .await
2291        .unwrap();
2292    let project_id = active_call_a
2293        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2294        .await
2295        .unwrap();
2296
2297    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2298    active_call_b
2299        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2300        .await
2301        .unwrap();
2302
2303    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2304    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2305
2306    let editor_a = workspace_a
2307        .update_in(cx_a, |workspace, window, cx| {
2308            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2309        })
2310        .await
2311        .unwrap()
2312        .downcast::<Editor>()
2313        .unwrap();
2314
2315    let editor_b = workspace_b
2316        .update_in(cx_b, |workspace, window, cx| {
2317            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2318        })
2319        .await
2320        .unwrap()
2321        .downcast::<Editor>()
2322        .unwrap();
2323
2324    let other_hints = Arc::new(AtomicBool::new(false));
2325    let fake_language_server = fake_language_servers.next().await.unwrap();
2326    let closure_other_hints = Arc::clone(&other_hints);
2327    fake_language_server
2328        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2329            let task_other_hints = Arc::clone(&closure_other_hints);
2330            async move {
2331                assert_eq!(
2332                    params.text_document.uri,
2333                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2334                );
2335                let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
2336                let character = if other_hints { 0 } else { 2 };
2337                let label = if other_hints {
2338                    "other hint"
2339                } else {
2340                    "initial hint"
2341                };
2342                Ok(Some(vec![
2343                    lsp::InlayHint {
2344                        position: lsp::Position::new(0, character),
2345                        label: lsp::InlayHintLabel::String(label.to_string()),
2346                        kind: None,
2347                        text_edits: None,
2348                        tooltip: None,
2349                        padding_left: None,
2350                        padding_right: None,
2351                        data: None,
2352                    },
2353                    lsp::InlayHint {
2354                        position: lsp::Position::new(1090, 1090),
2355                        label: lsp::InlayHintLabel::String("out-of-bounds hint".to_string()),
2356                        kind: None,
2357                        text_edits: None,
2358                        tooltip: None,
2359                        padding_left: None,
2360                        padding_right: None,
2361                        data: None,
2362                    },
2363                ]))
2364            }
2365        })
2366        .next()
2367        .await
2368        .unwrap();
2369
2370    executor.run_until_parked();
2371    editor_a.update(cx_a, |editor, cx| {
2372        assert!(
2373            extract_hint_labels(editor, cx).is_empty(),
2374            "Host should get no hints due to them turned off"
2375        );
2376    });
2377
2378    executor.run_until_parked();
2379    editor_b.update(cx_b, |editor, cx| {
2380        assert_eq!(
2381            vec!["initial hint".to_string()],
2382            extract_hint_labels(editor, cx),
2383            "Client should get its first hints when opens an editor"
2384        );
2385    });
2386
2387    other_hints.fetch_or(true, atomic::Ordering::Release);
2388    fake_language_server
2389        .request::<lsp::request::InlayHintRefreshRequest>((), DEFAULT_LSP_REQUEST_TIMEOUT)
2390        .await
2391        .into_response()
2392        .expect("inlay refresh request failed");
2393    executor.run_until_parked();
2394    editor_a.update(cx_a, |editor, cx| {
2395        assert!(
2396            extract_hint_labels(editor, cx).is_empty(),
2397            "Host should get no hints due to them turned off, even after the /refresh"
2398        );
2399    });
2400
2401    executor.run_until_parked();
2402    editor_b.update(cx_b, |editor, cx| {
2403        assert_eq!(
2404            vec!["other hint".to_string()],
2405            extract_hint_labels(editor, cx),
2406            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
2407        );
2408    });
2409}
2410
2411#[gpui::test(iterations = 10)]
2412async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2413    let expected_color = Rgba {
2414        r: 0.33,
2415        g: 0.33,
2416        b: 0.33,
2417        a: 0.33,
2418    };
2419    let mut server = TestServer::start(cx_a.executor()).await;
2420    let executor = cx_a.executor();
2421    let client_a = server.create_client(cx_a, "user_a").await;
2422    let client_b = server.create_client(cx_b, "user_b").await;
2423    server
2424        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2425        .await;
2426    let active_call_a = cx_a.read(ActiveCall::global);
2427    let active_call_b = cx_b.read(ActiveCall::global);
2428
2429    cx_a.update(editor::init);
2430    cx_b.update(editor::init);
2431
2432    cx_a.update(|cx| {
2433        SettingsStore::update_global(cx, |store, cx| {
2434            store.update_user_settings(cx, |settings| {
2435                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::None);
2436            });
2437        });
2438    });
2439    cx_b.update(|cx| {
2440        SettingsStore::update_global(cx, |store, cx| {
2441            store.update_user_settings(cx, |settings| {
2442                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2443            });
2444        });
2445    });
2446
2447    let capabilities = lsp::ServerCapabilities {
2448        color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
2449        ..lsp::ServerCapabilities::default()
2450    };
2451    client_a.language_registry().add(rust_lang());
2452    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2453        "Rust",
2454        FakeLspAdapter {
2455            capabilities: capabilities.clone(),
2456            ..FakeLspAdapter::default()
2457        },
2458    );
2459    client_b.language_registry().add(rust_lang());
2460    client_b.language_registry().register_fake_lsp_adapter(
2461        "Rust",
2462        FakeLspAdapter {
2463            capabilities,
2464            ..FakeLspAdapter::default()
2465        },
2466    );
2467
2468    // Client A opens a project.
2469    client_a
2470        .fs()
2471        .insert_tree(
2472            path!("/a"),
2473            json!({
2474                "main.rs": "fn main() { a }",
2475            }),
2476        )
2477        .await;
2478    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2479    active_call_a
2480        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2481        .await
2482        .unwrap();
2483    let project_id = active_call_a
2484        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2485        .await
2486        .unwrap();
2487
2488    // Client B joins the project
2489    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2490    active_call_b
2491        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2492        .await
2493        .unwrap();
2494
2495    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2496
2497    // The host opens a rust file.
2498    let _buffer_a = project_a
2499        .update(cx_a, |project, cx| {
2500            project.open_local_buffer(path!("/a/main.rs"), cx)
2501        })
2502        .await
2503        .unwrap();
2504    let editor_a = workspace_a
2505        .update_in(cx_a, |workspace, window, cx| {
2506            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2507        })
2508        .await
2509        .unwrap()
2510        .downcast::<Editor>()
2511        .unwrap();
2512
2513    let fake_language_server = fake_language_servers.next().await.unwrap();
2514    cx_a.run_until_parked();
2515    cx_b.run_until_parked();
2516
2517    let requests_made = Arc::new(AtomicUsize::new(0));
2518    let closure_requests_made = Arc::clone(&requests_made);
2519    let mut color_request_handle = fake_language_server
2520        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
2521            let requests_made = Arc::clone(&closure_requests_made);
2522            async move {
2523                assert_eq!(
2524                    params.text_document.uri,
2525                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2526                );
2527                requests_made.fetch_add(1, atomic::Ordering::Release);
2528                Ok(vec![lsp::ColorInformation {
2529                    range: lsp::Range {
2530                        start: lsp::Position {
2531                            line: 0,
2532                            character: 0,
2533                        },
2534                        end: lsp::Position {
2535                            line: 0,
2536                            character: 1,
2537                        },
2538                    },
2539                    color: lsp::Color {
2540                        red: 0.33,
2541                        green: 0.33,
2542                        blue: 0.33,
2543                        alpha: 0.33,
2544                    },
2545                }])
2546            }
2547        });
2548    executor.run_until_parked();
2549
2550    assert_eq!(
2551        0,
2552        requests_made.load(atomic::Ordering::Acquire),
2553        "Host did not enable document colors, hence should query for none"
2554    );
2555    editor_a.update(cx_a, |editor, cx| {
2556        assert_eq!(
2557            Vec::<Rgba>::new(),
2558            extract_color_inlays(editor, cx),
2559            "No query colors should result in no hints"
2560        );
2561    });
2562
2563    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2564    let editor_b = workspace_b
2565        .update_in(cx_b, |workspace, window, cx| {
2566            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2567        })
2568        .await
2569        .unwrap()
2570        .downcast::<Editor>()
2571        .unwrap();
2572
2573    color_request_handle.next().await.unwrap();
2574    executor.advance_clock(LSP_REQUEST_DEBOUNCE_TIMEOUT);
2575    executor.run_until_parked();
2576
2577    assert_eq!(
2578        1,
2579        requests_made.load(atomic::Ordering::Acquire),
2580        "The client opened the file and got its first colors back"
2581    );
2582    editor_b.update(cx_b, |editor, cx| {
2583        assert_eq!(
2584            vec![expected_color],
2585            extract_color_inlays(editor, cx),
2586            "With document colors as inlays, color inlays should be pushed"
2587        );
2588    });
2589
2590    editor_a.update_in(cx_a, |editor, window, cx| {
2591        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2592            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)].clone())
2593        });
2594        editor.handle_input(":", window, cx);
2595    });
2596    color_request_handle.next().await.unwrap();
2597    executor.run_until_parked();
2598    assert_eq!(
2599        2,
2600        requests_made.load(atomic::Ordering::Acquire),
2601        "After the host edits his file, the client should request the colors again"
2602    );
2603    editor_a.update(cx_a, |editor, cx| {
2604        assert_eq!(
2605            Vec::<Rgba>::new(),
2606            extract_color_inlays(editor, cx),
2607            "Host has no colors still"
2608        );
2609    });
2610    editor_b.update(cx_b, |editor, cx| {
2611        assert_eq!(vec![expected_color], extract_color_inlays(editor, cx),);
2612    });
2613
2614    cx_b.update(|_, cx| {
2615        SettingsStore::update_global(cx, |store, cx| {
2616            store.update_user_settings(cx, |settings| {
2617                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Background);
2618            });
2619        });
2620    });
2621    executor.run_until_parked();
2622    assert_eq!(
2623        2,
2624        requests_made.load(atomic::Ordering::Acquire),
2625        "After the client have changed the colors settings, no extra queries should happen"
2626    );
2627    editor_a.update(cx_a, |editor, cx| {
2628        assert_eq!(
2629            Vec::<Rgba>::new(),
2630            extract_color_inlays(editor, cx),
2631            "Host is unaffected by the client's settings changes"
2632        );
2633    });
2634    editor_b.update(cx_b, |editor, cx| {
2635        assert_eq!(
2636            Vec::<Rgba>::new(),
2637            extract_color_inlays(editor, cx),
2638            "Client should have no colors hints, as in the settings"
2639        );
2640    });
2641
2642    cx_b.update(|_, cx| {
2643        SettingsStore::update_global(cx, |store, cx| {
2644            store.update_user_settings(cx, |settings| {
2645                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2646            });
2647        });
2648    });
2649    executor.run_until_parked();
2650    assert_eq!(
2651        2,
2652        requests_made.load(atomic::Ordering::Acquire),
2653        "After falling back to colors as inlays, no extra LSP queries are made"
2654    );
2655    editor_a.update(cx_a, |editor, cx| {
2656        assert_eq!(
2657            Vec::<Rgba>::new(),
2658            extract_color_inlays(editor, cx),
2659            "Host is unaffected by the client's settings changes, again"
2660        );
2661    });
2662    editor_b.update(cx_b, |editor, cx| {
2663        assert_eq!(
2664            vec![expected_color],
2665            extract_color_inlays(editor, cx),
2666            "Client should have its color hints back"
2667        );
2668    });
2669
2670    cx_a.update(|_, cx| {
2671        SettingsStore::update_global(cx, |store, cx| {
2672            store.update_user_settings(cx, |settings| {
2673                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Border);
2674            });
2675        });
2676    });
2677    color_request_handle.next().await.unwrap();
2678    executor.run_until_parked();
2679    assert_eq!(
2680        3,
2681        requests_made.load(atomic::Ordering::Acquire),
2682        "After the host enables document colors, another LSP query should be made"
2683    );
2684    editor_a.update(cx_a, |editor, cx| {
2685        assert_eq!(
2686            Vec::<Rgba>::new(),
2687            extract_color_inlays(editor, cx),
2688            "Host did not configure document colors as hints hence gets nothing"
2689        );
2690    });
2691    editor_b.update(cx_b, |editor, cx| {
2692        assert_eq!(
2693            vec![expected_color],
2694            extract_color_inlays(editor, cx),
2695            "Client should be unaffected by the host's settings changes"
2696        );
2697    });
2698}
2699
2700async fn test_lsp_pull_diagnostics(
2701    should_stream_workspace_diagnostic: bool,
2702    cx_a: &mut TestAppContext,
2703    cx_b: &mut TestAppContext,
2704) {
2705    let mut server = TestServer::start(cx_a.executor()).await;
2706    let executor = cx_a.executor();
2707    let client_a = server.create_client(cx_a, "user_a").await;
2708    let client_b = server.create_client(cx_b, "user_b").await;
2709    server
2710        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2711        .await;
2712    let active_call_a = cx_a.read(ActiveCall::global);
2713    let active_call_b = cx_b.read(ActiveCall::global);
2714
2715    cx_a.update(editor::init);
2716    cx_b.update(editor::init);
2717
2718    let expected_push_diagnostic_main_message = "pushed main diagnostic";
2719    let expected_push_diagnostic_lib_message = "pushed lib diagnostic";
2720    let expected_pull_diagnostic_main_message = "pulled main diagnostic";
2721    let expected_pull_diagnostic_lib_message = "pulled lib diagnostic";
2722    let expected_workspace_pull_diagnostics_main_message = "pulled workspace main diagnostic";
2723    let expected_workspace_pull_diagnostics_lib_message = "pulled workspace lib diagnostic";
2724
2725    let diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<Option<String>>::new()));
2726    let workspace_diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<String>::new()));
2727    let diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2728    let closure_diagnostics_pulls_made = diagnostics_pulls_made.clone();
2729    let closure_diagnostics_pulls_result_ids = diagnostics_pulls_result_ids.clone();
2730    let workspace_diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2731    let closure_workspace_diagnostics_pulls_made = workspace_diagnostics_pulls_made.clone();
2732    let closure_workspace_diagnostics_pulls_result_ids =
2733        workspace_diagnostics_pulls_result_ids.clone();
2734    let (workspace_diagnostic_cancel_tx, closure_workspace_diagnostic_cancel_rx) =
2735        smol::channel::bounded::<()>(1);
2736    let (closure_workspace_diagnostic_received_tx, workspace_diagnostic_received_rx) =
2737        smol::channel::bounded::<()>(1);
2738
2739    let capabilities = lsp::ServerCapabilities {
2740        diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
2741            lsp::DiagnosticOptions {
2742                identifier: Some("test-pulls".to_string()),
2743                inter_file_dependencies: true,
2744                workspace_diagnostics: true,
2745                work_done_progress_options: lsp::WorkDoneProgressOptions {
2746                    work_done_progress: None,
2747                },
2748            },
2749        )),
2750        ..lsp::ServerCapabilities::default()
2751    };
2752    client_a.language_registry().add(rust_lang());
2753
2754    let pull_diagnostics_handle = Arc::new(parking_lot::Mutex::new(None));
2755    let workspace_diagnostics_pulls_handle = Arc::new(parking_lot::Mutex::new(None));
2756
2757    let closure_pull_diagnostics_handle = pull_diagnostics_handle.clone();
2758    let closure_workspace_diagnostics_pulls_handle = workspace_diagnostics_pulls_handle.clone();
2759    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2760        "Rust",
2761        FakeLspAdapter {
2762            capabilities: capabilities.clone(),
2763            initializer: Some(Box::new(move |fake_language_server| {
2764                let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!(
2765                    "workspace/diagnostic/{}/1",
2766                    fake_language_server.server.server_id()
2767                ));
2768                let closure_workspace_diagnostics_pulls_result_ids = closure_workspace_diagnostics_pulls_result_ids.clone();
2769                let diagnostics_pulls_made = closure_diagnostics_pulls_made.clone();
2770                let diagnostics_pulls_result_ids = closure_diagnostics_pulls_result_ids.clone();
2771                let closure_pull_diagnostics_handle = closure_pull_diagnostics_handle.clone();
2772                let closure_workspace_diagnostics_pulls_handle = closure_workspace_diagnostics_pulls_handle.clone();
2773                let closure_workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone();
2774                let closure_workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone();
2775                let pull_diagnostics_handle = fake_language_server
2776                    .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(
2777                        move |params, _| {
2778                            let requests_made = diagnostics_pulls_made.clone();
2779                            let diagnostics_pulls_result_ids =
2780                                diagnostics_pulls_result_ids.clone();
2781                            async move {
2782                                let message = if lsp::Uri::from_file_path(path!("/a/main.rs"))
2783                                    .unwrap()
2784                                    == params.text_document.uri
2785                                {
2786                                    expected_pull_diagnostic_main_message.to_string()
2787                                } else if lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap()
2788                                    == params.text_document.uri
2789                                {
2790                                    expected_pull_diagnostic_lib_message.to_string()
2791                                } else {
2792                                    panic!("Unexpected document: {}", params.text_document.uri)
2793                                };
2794                                {
2795                                    diagnostics_pulls_result_ids
2796                                        .lock()
2797                                        .await
2798                                        .insert(params.previous_result_id);
2799                                }
2800                                let new_requests_count =
2801                                    requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2802                                Ok(lsp::DocumentDiagnosticReportResult::Report(
2803                                    lsp::DocumentDiagnosticReport::Full(
2804                                        lsp::RelatedFullDocumentDiagnosticReport {
2805                                            related_documents: None,
2806                                            full_document_diagnostic_report:
2807                                                lsp::FullDocumentDiagnosticReport {
2808                                                    result_id: Some(format!(
2809                                                        "pull-{new_requests_count}"
2810                                                    )),
2811                                                    items: vec![lsp::Diagnostic {
2812                                                        range: lsp::Range {
2813                                                            start: lsp::Position {
2814                                                                line: 0,
2815                                                                character: 0,
2816                                                            },
2817                                                            end: lsp::Position {
2818                                                                line: 0,
2819                                                                character: 2,
2820                                                            },
2821                                                        },
2822                                                        severity: Some(
2823                                                            lsp::DiagnosticSeverity::ERROR,
2824                                                        ),
2825                                                        message,
2826                                                        ..lsp::Diagnostic::default()
2827                                                    }],
2828                                                },
2829                                        },
2830                                    ),
2831                                ))
2832                            }
2833                        },
2834                    );
2835                let _ = closure_pull_diagnostics_handle.lock().insert(pull_diagnostics_handle);
2836
2837                let closure_workspace_diagnostics_pulls_made = closure_workspace_diagnostics_pulls_made.clone();
2838                let workspace_diagnostics_pulls_handle = fake_language_server.set_request_handler::<lsp::request::WorkspaceDiagnosticRequest, _, _>(
2839                    move |params, _| {
2840                        let workspace_requests_made = closure_workspace_diagnostics_pulls_made.clone();
2841                        let workspace_diagnostics_pulls_result_ids =
2842                            closure_workspace_diagnostics_pulls_result_ids.clone();
2843                        let workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone();
2844                        let workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone();
2845                        let expected_workspace_diagnostic_token = expected_workspace_diagnostic_token.clone();
2846                        async move {
2847                            let workspace_request_count =
2848                                workspace_requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2849                            {
2850                                workspace_diagnostics_pulls_result_ids
2851                                    .lock()
2852                                    .await
2853                                    .extend(params.previous_result_ids.into_iter().map(|id| id.value));
2854                            }
2855                            if should_stream_workspace_diagnostic && !workspace_diagnostic_cancel_rx.is_closed()
2856                            {
2857                                assert_eq!(
2858                                    params.partial_result_params.partial_result_token,
2859                                    Some(expected_workspace_diagnostic_token)
2860                                );
2861                                workspace_diagnostic_received_tx.send(()).await.unwrap();
2862                                workspace_diagnostic_cancel_rx.recv().await.unwrap();
2863                                workspace_diagnostic_cancel_rx.close();
2864                                // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#partialResults
2865                                // > The final response has to be empty in terms of result values.
2866                                return Ok(lsp::WorkspaceDiagnosticReportResult::Report(
2867                                    lsp::WorkspaceDiagnosticReport { items: Vec::new() },
2868                                ));
2869                            }
2870                            Ok(lsp::WorkspaceDiagnosticReportResult::Report(
2871                                lsp::WorkspaceDiagnosticReport {
2872                                    items: vec![
2873                                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2874                                            lsp::WorkspaceFullDocumentDiagnosticReport {
2875                                                uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2876                                                version: None,
2877                                                full_document_diagnostic_report:
2878                                                    lsp::FullDocumentDiagnosticReport {
2879                                                        result_id: Some(format!(
2880                                                            "workspace_{workspace_request_count}"
2881                                                        )),
2882                                                        items: vec![lsp::Diagnostic {
2883                                                            range: lsp::Range {
2884                                                                start: lsp::Position {
2885                                                                    line: 0,
2886                                                                    character: 1,
2887                                                                },
2888                                                                end: lsp::Position {
2889                                                                    line: 0,
2890                                                                    character: 3,
2891                                                                },
2892                                                            },
2893                                                            severity: Some(lsp::DiagnosticSeverity::WARNING),
2894                                                            message:
2895                                                                expected_workspace_pull_diagnostics_main_message
2896                                                                    .to_string(),
2897                                                            ..lsp::Diagnostic::default()
2898                                                        }],
2899                                                    },
2900                                            },
2901                                        ),
2902                                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2903                                            lsp::WorkspaceFullDocumentDiagnosticReport {
2904                                                uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
2905                                                version: None,
2906                                                full_document_diagnostic_report:
2907                                                    lsp::FullDocumentDiagnosticReport {
2908                                                        result_id: Some(format!(
2909                                                            "workspace_{workspace_request_count}"
2910                                                        )),
2911                                                        items: vec![lsp::Diagnostic {
2912                                                            range: lsp::Range {
2913                                                                start: lsp::Position {
2914                                                                    line: 0,
2915                                                                    character: 1,
2916                                                                },
2917                                                                end: lsp::Position {
2918                                                                    line: 0,
2919                                                                    character: 3,
2920                                                                },
2921                                                            },
2922                                                            severity: Some(lsp::DiagnosticSeverity::WARNING),
2923                                                            message:
2924                                                                expected_workspace_pull_diagnostics_lib_message
2925                                                                    .to_string(),
2926                                                            ..lsp::Diagnostic::default()
2927                                                        }],
2928                                                    },
2929                                            },
2930                                        ),
2931                                    ],
2932                                },
2933                            ))
2934                        }
2935                    });
2936                let _ = closure_workspace_diagnostics_pulls_handle.lock().insert(workspace_diagnostics_pulls_handle);
2937            })),
2938            ..FakeLspAdapter::default()
2939        },
2940    );
2941
2942    client_b.language_registry().add(rust_lang());
2943    client_b.language_registry().register_fake_lsp_adapter(
2944        "Rust",
2945        FakeLspAdapter {
2946            capabilities,
2947            ..FakeLspAdapter::default()
2948        },
2949    );
2950
2951    // Client A opens a project.
2952    client_a
2953        .fs()
2954        .insert_tree(
2955            path!("/a"),
2956            json!({
2957                "main.rs": "fn main() { a }",
2958                "lib.rs": "fn other() {}",
2959            }),
2960        )
2961        .await;
2962    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2963    active_call_a
2964        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2965        .await
2966        .unwrap();
2967    let project_id = active_call_a
2968        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2969        .await
2970        .unwrap();
2971
2972    // Client B joins the project
2973    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2974    active_call_b
2975        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2976        .await
2977        .unwrap();
2978
2979    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2980
2981    // The host opens a rust file.
2982    let _buffer_a = project_a
2983        .update(cx_a, |project, cx| {
2984            project.open_local_buffer(path!("/a/main.rs"), cx)
2985        })
2986        .await
2987        .unwrap();
2988    let editor_a_main = workspace_a
2989        .update_in(cx_a, |workspace, window, cx| {
2990            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2991        })
2992        .await
2993        .unwrap()
2994        .downcast::<Editor>()
2995        .unwrap();
2996
2997    let fake_language_server = fake_language_servers.next().await.unwrap();
2998    let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!(
2999        "workspace/diagnostic-{}-1",
3000        fake_language_server.server.server_id()
3001    ));
3002    cx_a.run_until_parked();
3003    cx_b.run_until_parked();
3004    let mut pull_diagnostics_handle = pull_diagnostics_handle.lock().take().unwrap();
3005    let mut workspace_diagnostics_pulls_handle =
3006        workspace_diagnostics_pulls_handle.lock().take().unwrap();
3007
3008    if should_stream_workspace_diagnostic {
3009        workspace_diagnostic_received_rx.recv().await.unwrap();
3010    } else {
3011        workspace_diagnostics_pulls_handle.next().await.unwrap();
3012    }
3013    assert_eq!(
3014        1,
3015        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3016        "Workspace diagnostics should be pulled initially on a server startup"
3017    );
3018    pull_diagnostics_handle.next().await.unwrap();
3019    assert_eq!(
3020        1,
3021        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3022        "Host should query pull diagnostics when the editor is opened"
3023    );
3024    executor.run_until_parked();
3025    editor_a_main.update(cx_a, |editor, cx| {
3026        let snapshot = editor.buffer().read(cx).snapshot(cx);
3027        let all_diagnostics = snapshot
3028            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3029            .collect::<Vec<_>>();
3030        assert_eq!(
3031            all_diagnostics.len(),
3032            1,
3033            "Expected single diagnostic, but got: {all_diagnostics:?}"
3034        );
3035        let diagnostic = &all_diagnostics[0];
3036        let mut expected_messages = vec![expected_pull_diagnostic_main_message];
3037        if !should_stream_workspace_diagnostic {
3038            expected_messages.push(expected_workspace_pull_diagnostics_main_message);
3039        }
3040        assert!(
3041            expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3042            "Expected {expected_messages:?} on the host, but got: {}",
3043            diagnostic.diagnostic.message
3044        );
3045    });
3046
3047    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
3048        lsp::PublishDiagnosticsParams {
3049            uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
3050            diagnostics: vec![lsp::Diagnostic {
3051                range: lsp::Range {
3052                    start: lsp::Position {
3053                        line: 0,
3054                        character: 3,
3055                    },
3056                    end: lsp::Position {
3057                        line: 0,
3058                        character: 4,
3059                    },
3060                },
3061                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
3062                message: expected_push_diagnostic_main_message.to_string(),
3063                ..lsp::Diagnostic::default()
3064            }],
3065            version: None,
3066        },
3067    );
3068    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
3069        lsp::PublishDiagnosticsParams {
3070            uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3071            diagnostics: vec![lsp::Diagnostic {
3072                range: lsp::Range {
3073                    start: lsp::Position {
3074                        line: 0,
3075                        character: 3,
3076                    },
3077                    end: lsp::Position {
3078                        line: 0,
3079                        character: 4,
3080                    },
3081                },
3082                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
3083                message: expected_push_diagnostic_lib_message.to_string(),
3084                ..lsp::Diagnostic::default()
3085            }],
3086            version: None,
3087        },
3088    );
3089
3090    if should_stream_workspace_diagnostic {
3091        fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3092            token: expected_workspace_diagnostic_token.clone(),
3093            value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
3094                lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
3095                    items: vec![
3096                        lsp::WorkspaceDocumentDiagnosticReport::Full(
3097                            lsp::WorkspaceFullDocumentDiagnosticReport {
3098                                uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
3099                                version: None,
3100                                full_document_diagnostic_report:
3101                                    lsp::FullDocumentDiagnosticReport {
3102                                        result_id: Some(format!(
3103                                            "workspace_{}",
3104                                            workspace_diagnostics_pulls_made
3105                                                .fetch_add(1, atomic::Ordering::Release)
3106                                                + 1
3107                                        )),
3108                                        items: vec![lsp::Diagnostic {
3109                                            range: lsp::Range {
3110                                                start: lsp::Position {
3111                                                    line: 0,
3112                                                    character: 1,
3113                                                },
3114                                                end: lsp::Position {
3115                                                    line: 0,
3116                                                    character: 2,
3117                                                },
3118                                            },
3119                                            severity: Some(lsp::DiagnosticSeverity::ERROR),
3120                                            message:
3121                                                expected_workspace_pull_diagnostics_main_message
3122                                                    .to_string(),
3123                                            ..lsp::Diagnostic::default()
3124                                        }],
3125                                    },
3126                            },
3127                        ),
3128                        lsp::WorkspaceDocumentDiagnosticReport::Full(
3129                            lsp::WorkspaceFullDocumentDiagnosticReport {
3130                                uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3131                                version: None,
3132                                full_document_diagnostic_report:
3133                                    lsp::FullDocumentDiagnosticReport {
3134                                        result_id: Some(format!(
3135                                            "workspace_{}",
3136                                            workspace_diagnostics_pulls_made
3137                                                .fetch_add(1, atomic::Ordering::Release)
3138                                                + 1
3139                                        )),
3140                                        items: Vec::new(),
3141                                    },
3142                            },
3143                        ),
3144                    ],
3145                }),
3146            ),
3147        });
3148    };
3149
3150    let mut workspace_diagnostic_start_count =
3151        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
3152
3153    executor.run_until_parked();
3154    editor_a_main.update(cx_a, |editor, cx| {
3155        let snapshot = editor.buffer().read(cx).snapshot(cx);
3156        let all_diagnostics = snapshot
3157            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3158            .collect::<Vec<_>>();
3159        assert_eq!(
3160            all_diagnostics.len(),
3161            2,
3162            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
3163        );
3164        let expected_messages = [
3165            expected_workspace_pull_diagnostics_main_message,
3166            expected_pull_diagnostic_main_message,
3167            expected_push_diagnostic_main_message,
3168        ];
3169        for diagnostic in all_diagnostics {
3170            assert!(
3171                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3172                "Expected push and pull messages on the host: {expected_messages:?}, but got: {}",
3173                diagnostic.diagnostic.message
3174            );
3175        }
3176    });
3177
3178    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3179    let editor_b_main = workspace_b
3180        .update_in(cx_b, |workspace, window, cx| {
3181            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
3182        })
3183        .await
3184        .unwrap()
3185        .downcast::<Editor>()
3186        .unwrap();
3187    cx_b.run_until_parked();
3188
3189    pull_diagnostics_handle.next().await.unwrap();
3190    assert_eq!(
3191        2,
3192        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3193        "Client should query pull diagnostics when its editor is opened"
3194    );
3195    executor.run_until_parked();
3196    assert_eq!(
3197        workspace_diagnostic_start_count,
3198        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3199        "Workspace diagnostics should not be changed as the remote client does not initialize the workspace diagnostics pull"
3200    );
3201    editor_b_main.update(cx_b, |editor, cx| {
3202        let snapshot = editor.buffer().read(cx).snapshot(cx);
3203        let all_diagnostics = snapshot
3204            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3205            .collect::<Vec<_>>();
3206        assert_eq!(
3207            all_diagnostics.len(),
3208            2,
3209            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
3210        );
3211
3212        // Despite the workspace diagnostics not re-initialized for the remote client, we can still expect its message synced from the host.
3213        let expected_messages = [
3214            expected_workspace_pull_diagnostics_main_message,
3215            expected_pull_diagnostic_main_message,
3216            expected_push_diagnostic_main_message,
3217        ];
3218        for diagnostic in all_diagnostics {
3219            assert!(
3220                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3221                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3222                diagnostic.diagnostic.message
3223            );
3224        }
3225    });
3226
3227    let editor_b_lib = workspace_b
3228        .update_in(cx_b, |workspace, window, cx| {
3229            workspace.open_path((worktree_id, rel_path("lib.rs")), None, true, window, cx)
3230        })
3231        .await
3232        .unwrap()
3233        .downcast::<Editor>()
3234        .unwrap();
3235
3236    pull_diagnostics_handle.next().await.unwrap();
3237    assert_eq!(
3238        3,
3239        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3240        "Client should query pull diagnostics when its another editor is opened"
3241    );
3242    executor.run_until_parked();
3243    assert_eq!(
3244        workspace_diagnostic_start_count,
3245        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3246        "The remote client still did not anything to trigger the workspace diagnostics pull"
3247    );
3248    editor_b_lib.update(cx_b, |editor, cx| {
3249        let snapshot = editor.buffer().read(cx).snapshot(cx);
3250        let all_diagnostics = snapshot
3251            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3252            .collect::<Vec<_>>();
3253        let expected_messages = [
3254            expected_pull_diagnostic_lib_message,
3255            expected_push_diagnostic_lib_message,
3256        ];
3257        assert_eq!(
3258            all_diagnostics.len(),
3259            2,
3260            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
3261        );
3262        for diagnostic in all_diagnostics {
3263            assert!(
3264                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3265                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3266                diagnostic.diagnostic.message
3267            );
3268        }
3269    });
3270
3271    if should_stream_workspace_diagnostic {
3272        fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3273            token: expected_workspace_diagnostic_token.clone(),
3274            value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
3275                lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
3276                    items: vec![lsp::WorkspaceDocumentDiagnosticReport::Full(
3277                        lsp::WorkspaceFullDocumentDiagnosticReport {
3278                            uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3279                            version: None,
3280                            full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
3281                                result_id: Some(format!(
3282                                    "workspace_{}",
3283                                    workspace_diagnostics_pulls_made
3284                                        .fetch_add(1, atomic::Ordering::Release)
3285                                        + 1
3286                                )),
3287                                items: vec![lsp::Diagnostic {
3288                                    range: lsp::Range {
3289                                        start: lsp::Position {
3290                                            line: 0,
3291                                            character: 1,
3292                                        },
3293                                        end: lsp::Position {
3294                                            line: 0,
3295                                            character: 2,
3296                                        },
3297                                    },
3298                                    severity: Some(lsp::DiagnosticSeverity::ERROR),
3299                                    message: expected_workspace_pull_diagnostics_lib_message
3300                                        .to_string(),
3301                                    ..lsp::Diagnostic::default()
3302                                }],
3303                            },
3304                        },
3305                    )],
3306                }),
3307            ),
3308        });
3309        workspace_diagnostic_start_count =
3310            workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
3311        workspace_diagnostic_cancel_tx.send(()).await.unwrap();
3312        workspace_diagnostics_pulls_handle.next().await.unwrap();
3313        executor.run_until_parked();
3314        editor_b_lib.update(cx_b, |editor, cx| {
3315            let snapshot = editor.buffer().read(cx).snapshot(cx);
3316            let all_diagnostics = snapshot
3317                .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3318                .collect::<Vec<_>>();
3319            let expected_messages = [
3320                // Despite workspace diagnostics provided,
3321                // the currently open file's diagnostics should be preferred, as LSP suggests.
3322                expected_pull_diagnostic_lib_message,
3323                expected_push_diagnostic_lib_message,
3324            ];
3325            assert_eq!(
3326                all_diagnostics.len(),
3327                2,
3328                "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
3329            );
3330            for diagnostic in all_diagnostics {
3331                assert!(
3332                    expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3333                    "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3334                    diagnostic.diagnostic.message
3335                );
3336            }
3337        });
3338    };
3339
3340    {
3341        assert!(
3342            !diagnostics_pulls_result_ids.lock().await.is_empty(),
3343            "Initial diagnostics pulls should report None at least"
3344        );
3345        assert_eq!(
3346            0,
3347            workspace_diagnostics_pulls_result_ids
3348                .lock()
3349                .await
3350                .deref()
3351                .len(),
3352            "After the initial workspace request, opening files should not reuse any result ids"
3353        );
3354    }
3355
3356    editor_b_lib.update_in(cx_b, |editor, window, cx| {
3357        editor.move_to_end(&MoveToEnd, window, cx);
3358        editor.handle_input(":", window, cx);
3359    });
3360    pull_diagnostics_handle.next().await.unwrap();
3361    // pull_diagnostics_handle.next().await.unwrap();
3362    assert_eq!(
3363        4,
3364        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3365        "Client lib.rs edits should trigger another diagnostics pull for open buffers"
3366    );
3367    workspace_diagnostics_pulls_handle.next().await.unwrap();
3368    assert_eq!(
3369        workspace_diagnostic_start_count + 1,
3370        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3371        "After client lib.rs edits, the workspace diagnostics request should follow"
3372    );
3373    executor.run_until_parked();
3374
3375    editor_b_main.update_in(cx_b, |editor, window, cx| {
3376        editor.move_to_end(&MoveToEnd, window, cx);
3377        editor.handle_input(":", window, cx);
3378    });
3379    pull_diagnostics_handle.next().await.unwrap();
3380    pull_diagnostics_handle.next().await.unwrap();
3381    pull_diagnostics_handle.next().await.unwrap();
3382    assert_eq!(
3383        7,
3384        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3385        "Client main.rs edits should trigger diagnostics pull by both client and host and an extra pull for the client's lib.rs"
3386    );
3387    workspace_diagnostics_pulls_handle.next().await.unwrap();
3388    assert_eq!(
3389        workspace_diagnostic_start_count + 2,
3390        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3391        "After client main.rs edits, the workspace diagnostics pull should follow"
3392    );
3393    executor.run_until_parked();
3394
3395    editor_a_main.update_in(cx_a, |editor, window, cx| {
3396        editor.move_to_end(&MoveToEnd, window, cx);
3397        editor.handle_input(":", window, cx);
3398    });
3399    pull_diagnostics_handle.next().await.unwrap();
3400    pull_diagnostics_handle.next().await.unwrap();
3401    pull_diagnostics_handle.next().await.unwrap();
3402    assert_eq!(
3403        10,
3404        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3405        "Host main.rs edits should trigger another diagnostics pull by both client and host and another pull for the client's lib.rs"
3406    );
3407    workspace_diagnostics_pulls_handle.next().await.unwrap();
3408    assert_eq!(
3409        workspace_diagnostic_start_count + 3,
3410        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3411        "After host main.rs edits, the workspace diagnostics pull should follow"
3412    );
3413    executor.run_until_parked();
3414    let diagnostic_pulls_result_ids = diagnostics_pulls_result_ids.lock().await.len();
3415    let workspace_pulls_result_ids = workspace_diagnostics_pulls_result_ids.lock().await.len();
3416    {
3417        assert!(
3418            diagnostic_pulls_result_ids > 1,
3419            "Should have sent result ids when pulling diagnostics"
3420        );
3421        assert!(
3422            workspace_pulls_result_ids > 1,
3423            "Should have sent result ids when pulling workspace diagnostics"
3424        );
3425    }
3426
3427    fake_language_server
3428        .request::<lsp::request::WorkspaceDiagnosticRefresh>((), DEFAULT_LSP_REQUEST_TIMEOUT)
3429        .await
3430        .into_response()
3431        .expect("workspace diagnostics refresh request failed");
3432    // Workspace refresh now also triggers document diagnostic pulls for all open buffers
3433    pull_diagnostics_handle.next().await.unwrap();
3434    pull_diagnostics_handle.next().await.unwrap();
3435    assert_eq!(
3436        12,
3437        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3438        "Workspace refresh should trigger document pulls for all open buffers (main.rs and lib.rs)"
3439    );
3440    workspace_diagnostics_pulls_handle.next().await.unwrap();
3441    assert_eq!(
3442        workspace_diagnostic_start_count + 4,
3443        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3444        "Another workspace diagnostics pull should happen after the diagnostics refresh server request"
3445    );
3446    {
3447        assert!(
3448            diagnostics_pulls_result_ids.lock().await.len() > diagnostic_pulls_result_ids,
3449            "Document diagnostic pulls should happen after workspace refresh"
3450        );
3451        assert!(
3452            workspace_diagnostics_pulls_result_ids.lock().await.len() > workspace_pulls_result_ids,
3453            "More workspace diagnostics should be pulled"
3454        );
3455    }
3456    editor_b_lib.update(cx_b, |editor, cx| {
3457        let snapshot = editor.buffer().read(cx).snapshot(cx);
3458        let all_diagnostics = snapshot
3459            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3460            .collect::<Vec<_>>();
3461        let expected_messages = [
3462            expected_workspace_pull_diagnostics_lib_message,
3463            expected_pull_diagnostic_lib_message,
3464            expected_push_diagnostic_lib_message,
3465        ];
3466        assert_eq!(all_diagnostics.len(), 2);
3467        for diagnostic in &all_diagnostics {
3468            assert!(
3469                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3470                "Unexpected diagnostics: {all_diagnostics:?}"
3471            );
3472        }
3473    });
3474    editor_b_main.update(cx_b, |editor, cx| {
3475        let snapshot = editor.buffer().read(cx).snapshot(cx);
3476        let all_diagnostics = snapshot
3477            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3478            .collect::<Vec<_>>();
3479        assert_eq!(all_diagnostics.len(), 2);
3480
3481        let expected_messages = [
3482            expected_workspace_pull_diagnostics_main_message,
3483            expected_pull_diagnostic_main_message,
3484            expected_push_diagnostic_main_message,
3485        ];
3486        for diagnostic in &all_diagnostics {
3487            assert!(
3488                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3489                "Unexpected diagnostics: {all_diagnostics:?}"
3490            );
3491        }
3492    });
3493    editor_a_main.update(cx_a, |editor, cx| {
3494        let snapshot = editor.buffer().read(cx).snapshot(cx);
3495        let all_diagnostics = snapshot
3496            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3497            .collect::<Vec<_>>();
3498        assert_eq!(all_diagnostics.len(), 2);
3499        let expected_messages = [
3500            expected_workspace_pull_diagnostics_main_message,
3501            expected_pull_diagnostic_main_message,
3502            expected_push_diagnostic_main_message,
3503        ];
3504        for diagnostic in &all_diagnostics {
3505            assert!(
3506                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3507                "Unexpected diagnostics: {all_diagnostics:?}"
3508            );
3509        }
3510    });
3511}
3512
3513#[gpui::test(iterations = 10)]
3514async fn test_non_streamed_lsp_pull_diagnostics(
3515    cx_a: &mut TestAppContext,
3516    cx_b: &mut TestAppContext,
3517) {
3518    test_lsp_pull_diagnostics(false, cx_a, cx_b).await;
3519}
3520
3521#[gpui::test(iterations = 10)]
3522async fn test_streamed_lsp_pull_diagnostics(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3523    test_lsp_pull_diagnostics(true, cx_a, cx_b).await;
3524}
3525
3526#[gpui::test(iterations = 10)]
3527async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3528    let mut server = TestServer::start(cx_a.executor()).await;
3529    let client_a = server.create_client(cx_a, "user_a").await;
3530    let client_b = server.create_client(cx_b, "user_b").await;
3531    server
3532        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3533        .await;
3534    let active_call_a = cx_a.read(ActiveCall::global);
3535
3536    cx_a.update(editor::init);
3537    cx_b.update(editor::init);
3538    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
3539    let inline_blame_off_settings = Some(InlineBlameSettings {
3540        enabled: Some(false),
3541        ..Default::default()
3542    });
3543    cx_a.update(|cx| {
3544        SettingsStore::update_global(cx, |store, cx| {
3545            store.update_user_settings(cx, |settings| {
3546                settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
3547            });
3548        });
3549    });
3550    cx_b.update(|cx| {
3551        SettingsStore::update_global(cx, |store, cx| {
3552            store.update_user_settings(cx, |settings| {
3553                settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
3554            });
3555        });
3556    });
3557
3558    client_a
3559        .fs()
3560        .insert_tree(
3561            path!("/my-repo"),
3562            json!({
3563                ".git": {},
3564                "file.txt": "line1\nline2\nline3\nline\n",
3565            }),
3566        )
3567        .await;
3568
3569    let blame = git::blame::Blame {
3570        entries: vec![
3571            blame_entry("1b1b1b", 0..1),
3572            blame_entry("0d0d0d", 1..2),
3573            blame_entry("3a3a3a", 2..3),
3574            blame_entry("4c4c4c", 3..4),
3575        ],
3576        messages: [
3577            ("1b1b1b", "message for idx-0"),
3578            ("0d0d0d", "message for idx-1"),
3579            ("3a3a3a", "message for idx-2"),
3580            ("4c4c4c", "message for idx-3"),
3581        ]
3582        .into_iter()
3583        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
3584        .collect(),
3585    };
3586    client_a.fs().set_blame_for_repo(
3587        Path::new(path!("/my-repo/.git")),
3588        vec![(repo_path("file.txt"), blame)],
3589    );
3590
3591    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
3592    let project_id = active_call_a
3593        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3594        .await
3595        .unwrap();
3596
3597    // Create editor_a
3598    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3599    let editor_a = workspace_a
3600        .update_in(cx_a, |workspace, window, cx| {
3601            workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx)
3602        })
3603        .await
3604        .unwrap()
3605        .downcast::<Editor>()
3606        .unwrap();
3607
3608    // Join the project as client B.
3609    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3610    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3611    let editor_b = workspace_b
3612        .update_in(cx_b, |workspace, window, cx| {
3613            workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx)
3614        })
3615        .await
3616        .unwrap()
3617        .downcast::<Editor>()
3618        .unwrap();
3619    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
3620        editor_b
3621            .buffer()
3622            .read(cx)
3623            .as_singleton()
3624            .unwrap()
3625            .read(cx)
3626            .remote_id()
3627    });
3628
3629    // client_b now requests git blame for the open buffer
3630    editor_b.update_in(cx_b, |editor_b, window, cx| {
3631        assert!(editor_b.blame().is_none());
3632        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
3633    });
3634
3635    cx_a.executor().run_until_parked();
3636    cx_b.executor().run_until_parked();
3637
3638    editor_b.update(cx_b, |editor_b, cx| {
3639        let blame = editor_b.blame().expect("editor_b should have blame now");
3640        let entries = blame.update(cx, |blame, cx| {
3641            blame
3642                .blame_for_rows(
3643                    &(0..4)
3644                        .map(|row| RowInfo {
3645                            buffer_row: Some(row),
3646                            buffer_id: Some(buffer_id_b),
3647                            ..Default::default()
3648                        })
3649                        .collect::<Vec<_>>(),
3650                    cx,
3651                )
3652                .collect::<Vec<_>>()
3653        });
3654
3655        assert_eq!(
3656            entries,
3657            vec![
3658                Some((buffer_id_b, blame_entry("1b1b1b", 0..1))),
3659                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3660                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3661                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3662            ]
3663        );
3664
3665        blame.update(cx, |blame, _| {
3666            for (idx, (buffer, entry)) in entries.iter().flatten().enumerate() {
3667                let details = blame.details_for_entry(*buffer, entry).unwrap();
3668                assert_eq!(details.message, format!("message for idx-{}", idx));
3669            }
3670        });
3671    });
3672
3673    // editor_b updates the file, which gets sent to client_a, which updates git blame,
3674    // which gets back to client_b.
3675    editor_b.update_in(cx_b, |editor_b, _, cx| {
3676        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
3677    });
3678
3679    cx_a.executor().run_until_parked();
3680    cx_b.executor().run_until_parked();
3681
3682    editor_b.update(cx_b, |editor_b, cx| {
3683        let blame = editor_b.blame().expect("editor_b should have blame now");
3684        let entries = blame.update(cx, |blame, cx| {
3685            blame
3686                .blame_for_rows(
3687                    &(0..4)
3688                        .map(|row| RowInfo {
3689                            buffer_row: Some(row),
3690                            buffer_id: Some(buffer_id_b),
3691                            ..Default::default()
3692                        })
3693                        .collect::<Vec<_>>(),
3694                    cx,
3695                )
3696                .collect::<Vec<_>>()
3697        });
3698
3699        assert_eq!(
3700            entries,
3701            vec![
3702                None,
3703                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3704                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3705                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3706            ]
3707        );
3708    });
3709
3710    // Now editor_a also updates the file
3711    editor_a.update_in(cx_a, |editor_a, _, cx| {
3712        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
3713    });
3714
3715    cx_a.executor().run_until_parked();
3716    cx_b.executor().run_until_parked();
3717
3718    editor_b.update(cx_b, |editor_b, cx| {
3719        let blame = editor_b.blame().expect("editor_b should have blame now");
3720        let entries = blame.update(cx, |blame, cx| {
3721            blame
3722                .blame_for_rows(
3723                    &(0..4)
3724                        .map(|row| RowInfo {
3725                            buffer_row: Some(row),
3726                            buffer_id: Some(buffer_id_b),
3727                            ..Default::default()
3728                        })
3729                        .collect::<Vec<_>>(),
3730                    cx,
3731                )
3732                .collect::<Vec<_>>()
3733        });
3734
3735        assert_eq!(
3736            entries,
3737            vec![
3738                None,
3739                None,
3740                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3741                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3742            ]
3743        );
3744    });
3745}
3746
3747#[gpui::test(iterations = 30)]
3748async fn test_collaborating_with_editorconfig(
3749    cx_a: &mut TestAppContext,
3750    cx_b: &mut TestAppContext,
3751) {
3752    let mut server = TestServer::start(cx_a.executor()).await;
3753    let client_a = server.create_client(cx_a, "user_a").await;
3754    let client_b = server.create_client(cx_b, "user_b").await;
3755    server
3756        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3757        .await;
3758    let active_call_a = cx_a.read(ActiveCall::global);
3759
3760    cx_b.update(editor::init);
3761
3762    // Set up a fake language server.
3763    client_a.language_registry().add(rust_lang());
3764    client_a
3765        .fs()
3766        .insert_tree(
3767            path!("/a"),
3768            json!({
3769                "src": {
3770                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
3771                    "other_mod": {
3772                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
3773                        ".editorconfig": "",
3774                    },
3775                },
3776                ".editorconfig": "[*]\ntab_width = 2\n",
3777            }),
3778        )
3779        .await;
3780    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
3781    let project_id = active_call_a
3782        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3783        .await
3784        .unwrap();
3785    let main_buffer_a = project_a
3786        .update(cx_a, |p, cx| {
3787            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
3788        })
3789        .await
3790        .unwrap();
3791    let other_buffer_a = project_a
3792        .update(cx_a, |p, cx| {
3793            p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
3794        })
3795        .await
3796        .unwrap();
3797    let cx_a = cx_a.add_empty_window();
3798    let main_editor_a = cx_a.new_window_entity(|window, cx| {
3799        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
3800    });
3801    let other_editor_a = cx_a.new_window_entity(|window, cx| {
3802        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
3803    });
3804    let mut main_editor_cx_a = EditorTestContext {
3805        cx: cx_a.clone(),
3806        window: cx_a.window_handle(),
3807        editor: main_editor_a,
3808        assertion_cx: AssertionContextManager::new(),
3809    };
3810    let mut other_editor_cx_a = EditorTestContext {
3811        cx: cx_a.clone(),
3812        window: cx_a.window_handle(),
3813        editor: other_editor_a,
3814        assertion_cx: AssertionContextManager::new(),
3815    };
3816
3817    // Join the project as client B.
3818    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3819    let main_buffer_b = project_b
3820        .update(cx_b, |p, cx| {
3821            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
3822        })
3823        .await
3824        .unwrap();
3825    let other_buffer_b = project_b
3826        .update(cx_b, |p, cx| {
3827            p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
3828        })
3829        .await
3830        .unwrap();
3831    let cx_b = cx_b.add_empty_window();
3832    let main_editor_b = cx_b.new_window_entity(|window, cx| {
3833        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
3834    });
3835    let other_editor_b = cx_b.new_window_entity(|window, cx| {
3836        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
3837    });
3838    let mut main_editor_cx_b = EditorTestContext {
3839        cx: cx_b.clone(),
3840        window: cx_b.window_handle(),
3841        editor: main_editor_b,
3842        assertion_cx: AssertionContextManager::new(),
3843    };
3844    let mut other_editor_cx_b = EditorTestContext {
3845        cx: cx_b.clone(),
3846        window: cx_b.window_handle(),
3847        editor: other_editor_b,
3848        assertion_cx: AssertionContextManager::new(),
3849    };
3850
3851    let initial_main = indoc! {"
3852ˇmod other;
3853fn main() { let foo = other::foo(); }"};
3854    let initial_other = indoc! {"
3855ˇpub fn foo() -> usize {
3856    4
3857}"};
3858
3859    let first_tabbed_main = indoc! {"
3860  ˇmod other;
3861fn main() { let foo = other::foo(); }"};
3862    tab_undo_assert(
3863        &mut main_editor_cx_a,
3864        &mut main_editor_cx_b,
3865        initial_main,
3866        first_tabbed_main,
3867        true,
3868    );
3869    tab_undo_assert(
3870        &mut main_editor_cx_a,
3871        &mut main_editor_cx_b,
3872        initial_main,
3873        first_tabbed_main,
3874        false,
3875    );
3876
3877    let first_tabbed_other = indoc! {"
3878  ˇpub fn foo() -> usize {
3879    4
3880}"};
3881    tab_undo_assert(
3882        &mut other_editor_cx_a,
3883        &mut other_editor_cx_b,
3884        initial_other,
3885        first_tabbed_other,
3886        true,
3887    );
3888    tab_undo_assert(
3889        &mut other_editor_cx_a,
3890        &mut other_editor_cx_b,
3891        initial_other,
3892        first_tabbed_other,
3893        false,
3894    );
3895
3896    client_a
3897        .fs()
3898        .atomic_write(
3899            PathBuf::from(path!("/a/src/.editorconfig")),
3900            "[*]\ntab_width = 3\n".to_owned(),
3901        )
3902        .await
3903        .unwrap();
3904    cx_a.run_until_parked();
3905    cx_b.run_until_parked();
3906
3907    let second_tabbed_main = indoc! {"
3908   ˇmod other;
3909fn main() { let foo = other::foo(); }"};
3910    tab_undo_assert(
3911        &mut main_editor_cx_a,
3912        &mut main_editor_cx_b,
3913        initial_main,
3914        second_tabbed_main,
3915        true,
3916    );
3917    tab_undo_assert(
3918        &mut main_editor_cx_a,
3919        &mut main_editor_cx_b,
3920        initial_main,
3921        second_tabbed_main,
3922        false,
3923    );
3924
3925    let second_tabbed_other = indoc! {"
3926   ˇpub fn foo() -> usize {
3927    4
3928}"};
3929    tab_undo_assert(
3930        &mut other_editor_cx_a,
3931        &mut other_editor_cx_b,
3932        initial_other,
3933        second_tabbed_other,
3934        true,
3935    );
3936    tab_undo_assert(
3937        &mut other_editor_cx_a,
3938        &mut other_editor_cx_b,
3939        initial_other,
3940        second_tabbed_other,
3941        false,
3942    );
3943
3944    let editorconfig_buffer_b = project_b
3945        .update(cx_b, |p, cx| {
3946            p.open_buffer((worktree_id, rel_path("src/other_mod/.editorconfig")), cx)
3947        })
3948        .await
3949        .unwrap();
3950    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
3951        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
3952    });
3953    project_b
3954        .update(cx_b, |project, cx| {
3955            project.save_buffer(editorconfig_buffer_b.clone(), cx)
3956        })
3957        .await
3958        .unwrap();
3959    cx_a.run_until_parked();
3960    cx_b.run_until_parked();
3961
3962    tab_undo_assert(
3963        &mut main_editor_cx_a,
3964        &mut main_editor_cx_b,
3965        initial_main,
3966        second_tabbed_main,
3967        true,
3968    );
3969    tab_undo_assert(
3970        &mut main_editor_cx_a,
3971        &mut main_editor_cx_b,
3972        initial_main,
3973        second_tabbed_main,
3974        false,
3975    );
3976
3977    let third_tabbed_other = indoc! {"
3978      ˇpub fn foo() -> usize {
3979    4
3980}"};
3981    tab_undo_assert(
3982        &mut other_editor_cx_a,
3983        &mut other_editor_cx_b,
3984        initial_other,
3985        third_tabbed_other,
3986        true,
3987    );
3988
3989    tab_undo_assert(
3990        &mut other_editor_cx_a,
3991        &mut other_editor_cx_b,
3992        initial_other,
3993        third_tabbed_other,
3994        false,
3995    );
3996}
3997
3998#[gpui::test(iterations = 10)]
3999async fn test_collaborating_with_external_editorconfig(
4000    cx_a: &mut TestAppContext,
4001    cx_b: &mut TestAppContext,
4002) {
4003    let mut server = TestServer::start(cx_a.executor()).await;
4004    let client_a = server.create_client(cx_a, "user_a").await;
4005    let client_b = server.create_client(cx_b, "user_b").await;
4006    server
4007        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4008        .await;
4009    let active_call_a = cx_a.read(ActiveCall::global);
4010
4011    client_a.language_registry().add(rust_lang());
4012    client_b.language_registry().add(rust_lang());
4013
4014    // Set up external .editorconfig in parent directory
4015    client_a
4016        .fs()
4017        .insert_tree(
4018            path!("/parent"),
4019            json!({
4020                ".editorconfig": "[*]\nindent_size = 5\n",
4021                "worktree": {
4022                    ".editorconfig": "[*]\n",
4023                    "src": {
4024                        "main.rs": "fn main() {}",
4025                    },
4026                },
4027            }),
4028        )
4029        .await;
4030
4031    let (project_a, worktree_id) = client_a
4032        .build_local_project(path!("/parent/worktree"), cx_a)
4033        .await;
4034    let project_id = active_call_a
4035        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4036        .await
4037        .unwrap();
4038
4039    project_a.update(cx_a, |project, _| project.languages().add(rust_lang()));
4040
4041    // Open buffer on client A
4042    let buffer_a = project_a
4043        .update(cx_a, |p, cx| {
4044            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
4045        })
4046        .await
4047        .unwrap();
4048
4049    cx_a.run_until_parked();
4050
4051    // Verify client A sees external editorconfig settings
4052    cx_a.read(|cx| {
4053        let settings = LanguageSettings::for_buffer(&buffer_a.read(cx), cx);
4054        assert_eq!(Some(settings.tab_size), NonZeroU32::new(5));
4055    });
4056
4057    // Client B joins the project
4058    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4059    project_b.update(cx_b, |project, _| project.languages().add(rust_lang()));
4060    let buffer_b = project_b
4061        .update(cx_b, |p, cx| {
4062            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
4063        })
4064        .await
4065        .unwrap();
4066
4067    cx_b.run_until_parked();
4068
4069    // Verify client B also sees external editorconfig settings
4070    cx_b.read(|cx| {
4071        let settings = LanguageSettings::for_buffer(&buffer_b.read(cx), cx);
4072        assert_eq!(Some(settings.tab_size), NonZeroU32::new(5));
4073    });
4074
4075    // Client A modifies the external .editorconfig
4076    client_a
4077        .fs()
4078        .atomic_write(
4079            PathBuf::from(path!("/parent/.editorconfig")),
4080            "[*]\nindent_size = 9\n".to_owned(),
4081        )
4082        .await
4083        .unwrap();
4084
4085    cx_a.run_until_parked();
4086    cx_b.run_until_parked();
4087
4088    // Verify client A sees updated settings
4089    cx_a.read(|cx| {
4090        let settings = LanguageSettings::for_buffer(&buffer_a.read(cx), cx);
4091        assert_eq!(Some(settings.tab_size), NonZeroU32::new(9));
4092    });
4093
4094    // Verify client B also sees updated settings
4095    cx_b.read(|cx| {
4096        let settings = LanguageSettings::for_buffer(&buffer_b.read(cx), cx);
4097        assert_eq!(Some(settings.tab_size), NonZeroU32::new(9));
4098    });
4099}
4100
4101#[gpui::test]
4102async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4103    let executor = cx_a.executor();
4104    let mut server = TestServer::start(executor.clone()).await;
4105    let client_a = server.create_client(cx_a, "user_a").await;
4106    let client_b = server.create_client(cx_b, "user_b").await;
4107    server
4108        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4109        .await;
4110    let active_call_a = cx_a.read(ActiveCall::global);
4111    let active_call_b = cx_b.read(ActiveCall::global);
4112    cx_a.update(editor::init);
4113    cx_b.update(editor::init);
4114    client_a
4115        .fs()
4116        .insert_tree(
4117            "/a",
4118            json!({
4119                "test.txt": "one\ntwo\nthree\nfour\nfive",
4120            }),
4121        )
4122        .await;
4123    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
4124    let project_path = ProjectPath {
4125        worktree_id,
4126        path: rel_path(&"test.txt").into(),
4127    };
4128    let abs_path = project_a.read_with(cx_a, |project, cx| {
4129        project
4130            .absolute_path(&project_path, cx)
4131            .map(Arc::from)
4132            .unwrap()
4133    });
4134
4135    active_call_a
4136        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
4137        .await
4138        .unwrap();
4139    let project_id = active_call_a
4140        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4141        .await
4142        .unwrap();
4143    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4144    active_call_b
4145        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
4146        .await
4147        .unwrap();
4148    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4149    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4150
4151    // Client A opens an editor.
4152    let editor_a = workspace_a
4153        .update_in(cx_a, |workspace, window, cx| {
4154            workspace.open_path(project_path.clone(), None, true, window, cx)
4155        })
4156        .await
4157        .unwrap()
4158        .downcast::<Editor>()
4159        .unwrap();
4160
4161    // Client B opens same editor as A.
4162    let editor_b = workspace_b
4163        .update_in(cx_b, |workspace, window, cx| {
4164            workspace.open_path(project_path.clone(), None, true, window, cx)
4165        })
4166        .await
4167        .unwrap()
4168        .downcast::<Editor>()
4169        .unwrap();
4170
4171    cx_a.run_until_parked();
4172    cx_b.run_until_parked();
4173
4174    // Client A adds breakpoint on line (1)
4175    editor_a.update_in(cx_a, |editor, window, cx| {
4176        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4177    });
4178
4179    cx_a.run_until_parked();
4180    cx_b.run_until_parked();
4181
4182    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4183        editor
4184            .breakpoint_store()
4185            .unwrap()
4186            .read(cx)
4187            .all_source_breakpoints(cx)
4188    });
4189    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4190        editor
4191            .breakpoint_store()
4192            .unwrap()
4193            .read(cx)
4194            .all_source_breakpoints(cx)
4195    });
4196
4197    assert_eq!(1, breakpoints_a.len());
4198    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
4199    assert_eq!(breakpoints_a, breakpoints_b);
4200
4201    // Client B adds breakpoint on line(2)
4202    editor_b.update_in(cx_b, |editor, window, cx| {
4203        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
4204        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
4205        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4206    });
4207
4208    cx_a.run_until_parked();
4209    cx_b.run_until_parked();
4210
4211    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4212        editor
4213            .breakpoint_store()
4214            .unwrap()
4215            .read(cx)
4216            .all_source_breakpoints(cx)
4217    });
4218    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4219        editor
4220            .breakpoint_store()
4221            .unwrap()
4222            .read(cx)
4223            .all_source_breakpoints(cx)
4224    });
4225
4226    assert_eq!(1, breakpoints_a.len());
4227    assert_eq!(breakpoints_a, breakpoints_b);
4228    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
4229
4230    // Client A removes last added breakpoint from client B
4231    editor_a.update_in(cx_a, |editor, window, cx| {
4232        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
4233        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
4234        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4235    });
4236
4237    cx_a.run_until_parked();
4238    cx_b.run_until_parked();
4239
4240    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4241        editor
4242            .breakpoint_store()
4243            .unwrap()
4244            .read(cx)
4245            .all_source_breakpoints(cx)
4246    });
4247    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4248        editor
4249            .breakpoint_store()
4250            .unwrap()
4251            .read(cx)
4252            .all_source_breakpoints(cx)
4253    });
4254
4255    assert_eq!(1, breakpoints_a.len());
4256    assert_eq!(breakpoints_a, breakpoints_b);
4257    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
4258
4259    // Client B removes first added breakpoint by client A
4260    editor_b.update_in(cx_b, |editor, window, cx| {
4261        editor.move_up(&zed_actions::editor::MoveUp, window, cx);
4262        editor.move_up(&zed_actions::editor::MoveUp, window, cx);
4263        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4264    });
4265
4266    cx_a.run_until_parked();
4267    cx_b.run_until_parked();
4268
4269    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4270        editor
4271            .breakpoint_store()
4272            .unwrap()
4273            .read(cx)
4274            .all_source_breakpoints(cx)
4275    });
4276    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4277        editor
4278            .breakpoint_store()
4279            .unwrap()
4280            .read(cx)
4281            .all_source_breakpoints(cx)
4282    });
4283
4284    assert_eq!(0, breakpoints_a.len());
4285    assert_eq!(breakpoints_a, breakpoints_b);
4286}
4287
4288#[gpui::test]
4289async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4290    let mut server = TestServer::start(cx_a.executor()).await;
4291    let client_a = server.create_client(cx_a, "user_a").await;
4292    let client_b = server.create_client(cx_b, "user_b").await;
4293    server
4294        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4295        .await;
4296    let active_call_a = cx_a.read(ActiveCall::global);
4297    let active_call_b = cx_b.read(ActiveCall::global);
4298
4299    cx_a.update(editor::init);
4300    cx_b.update(editor::init);
4301
4302    client_a.language_registry().add(rust_lang());
4303    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4304        "Rust",
4305        FakeLspAdapter {
4306            name: "rust-analyzer",
4307            ..FakeLspAdapter::default()
4308        },
4309    );
4310    client_b.language_registry().add(rust_lang());
4311    client_b.language_registry().register_fake_lsp_adapter(
4312        "Rust",
4313        FakeLspAdapter {
4314            name: "rust-analyzer",
4315            ..FakeLspAdapter::default()
4316        },
4317    );
4318
4319    client_a
4320        .fs()
4321        .insert_tree(
4322            path!("/a"),
4323            json!({
4324                "main.rs": "fn main() {}",
4325            }),
4326        )
4327        .await;
4328    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4329    active_call_a
4330        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
4331        .await
4332        .unwrap();
4333    let project_id = active_call_a
4334        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4335        .await
4336        .unwrap();
4337
4338    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4339    active_call_b
4340        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
4341        .await
4342        .unwrap();
4343
4344    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4345    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4346
4347    let editor_a = workspace_a
4348        .update_in(cx_a, |workspace, window, cx| {
4349            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
4350        })
4351        .await
4352        .unwrap()
4353        .downcast::<Editor>()
4354        .unwrap();
4355
4356    let editor_b = workspace_b
4357        .update_in(cx_b, |workspace, window, cx| {
4358            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
4359        })
4360        .await
4361        .unwrap()
4362        .downcast::<Editor>()
4363        .unwrap();
4364
4365    let fake_language_server = fake_language_servers.next().await.unwrap();
4366
4367    // host
4368    let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4369        |params, _| async move {
4370            assert_eq!(
4371                params.text_document.uri,
4372                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4373            );
4374            assert_eq!(params.position, lsp::Position::new(0, 0));
4375            Ok(Some(ExpandedMacro {
4376                name: "test_macro_name".to_string(),
4377                expansion: "test_macro_expansion on the host".to_string(),
4378            }))
4379        },
4380    );
4381
4382    editor_a.update_in(cx_a, |editor, window, cx| {
4383        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4384    });
4385    expand_request_a.next().await.unwrap();
4386    cx_a.run_until_parked();
4387
4388    workspace_a.update(cx_a, |workspace, cx| {
4389        workspace.active_pane().update(cx, |pane, cx| {
4390            assert_eq!(
4391                pane.items_len(),
4392                2,
4393                "Should have added a macro expansion to the host's pane"
4394            );
4395            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4396            new_editor.update(cx, |editor, cx| {
4397                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
4398            });
4399        })
4400    });
4401
4402    // client
4403    let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4404        |params, _| async move {
4405            assert_eq!(
4406                params.text_document.uri,
4407                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4408            );
4409            assert_eq!(
4410                params.position,
4411                lsp::Position::new(0, 12),
4412                "editor_b has selected the entire text and should query for a different position"
4413            );
4414            Ok(Some(ExpandedMacro {
4415                name: "test_macro_name".to_string(),
4416                expansion: "test_macro_expansion on the client".to_string(),
4417            }))
4418        },
4419    );
4420
4421    editor_b.update_in(cx_b, |editor, window, cx| {
4422        editor.select_all(&SelectAll, window, cx);
4423        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4424    });
4425    expand_request_b.next().await.unwrap();
4426    cx_b.run_until_parked();
4427
4428    workspace_b.update(cx_b, |workspace, cx| {
4429        workspace.active_pane().update(cx, |pane, cx| {
4430            assert_eq!(
4431                pane.items_len(),
4432                2,
4433                "Should have added a macro expansion to the client's pane"
4434            );
4435            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4436            new_editor.update(cx, |editor, cx| {
4437                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
4438            });
4439        })
4440    });
4441}
4442
4443#[gpui::test]
4444async fn test_copy_file_name_without_extension(
4445    cx_a: &mut TestAppContext,
4446    cx_b: &mut TestAppContext,
4447) {
4448    let mut server = TestServer::start(cx_a.executor()).await;
4449    let client_a = server.create_client(cx_a, "user_a").await;
4450    let client_b = server.create_client(cx_b, "user_b").await;
4451    server
4452        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4453        .await;
4454
4455    cx_b.update(editor::init);
4456
4457    client_a
4458        .fs()
4459        .insert_tree(
4460            path!("/root"),
4461            json!({
4462                "src": {
4463                    "main.rs": indoc! {"
4464                        fn main() {
4465                            println!(\"Hello, world!\");
4466                        }
4467                    "},
4468                }
4469            }),
4470        )
4471        .await;
4472
4473    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
4474    let active_call_a = cx_a.read(ActiveCall::global);
4475    let project_id = active_call_a
4476        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4477        .await
4478        .unwrap();
4479
4480    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4481
4482    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4483    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4484
4485    let editor_a = workspace_a
4486        .update_in(cx_a, |workspace, window, cx| {
4487            workspace.open_path(
4488                (worktree_id, rel_path("src/main.rs")),
4489                None,
4490                true,
4491                window,
4492                cx,
4493            )
4494        })
4495        .await
4496        .unwrap()
4497        .downcast::<Editor>()
4498        .unwrap();
4499
4500    let editor_b = workspace_b
4501        .update_in(cx_b, |workspace, window, cx| {
4502            workspace.open_path(
4503                (worktree_id, rel_path("src/main.rs")),
4504                None,
4505                true,
4506                window,
4507                cx,
4508            )
4509        })
4510        .await
4511        .unwrap()
4512        .downcast::<Editor>()
4513        .unwrap();
4514
4515    cx_a.run_until_parked();
4516    cx_b.run_until_parked();
4517
4518    editor_a.update_in(cx_a, |editor, window, cx| {
4519        editor.copy_file_name_without_extension(&CopyFileNameWithoutExtension, window, cx);
4520    });
4521
4522    assert_eq!(
4523        cx_a.read_from_clipboard().and_then(|item| item.text()),
4524        Some("main".to_string())
4525    );
4526
4527    editor_b.update_in(cx_b, |editor, window, cx| {
4528        editor.copy_file_name_without_extension(&CopyFileNameWithoutExtension, window, cx);
4529    });
4530
4531    assert_eq!(
4532        cx_b.read_from_clipboard().and_then(|item| item.text()),
4533        Some("main".to_string())
4534    );
4535}
4536
4537#[gpui::test]
4538async fn test_copy_file_name(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4539    let mut server = TestServer::start(cx_a.executor()).await;
4540    let client_a = server.create_client(cx_a, "user_a").await;
4541    let client_b = server.create_client(cx_b, "user_b").await;
4542    server
4543        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4544        .await;
4545
4546    cx_b.update(editor::init);
4547
4548    client_a
4549        .fs()
4550        .insert_tree(
4551            path!("/root"),
4552            json!({
4553                "src": {
4554                    "main.rs": indoc! {"
4555                        fn main() {
4556                            println!(\"Hello, world!\");
4557                        }
4558                    "},
4559                }
4560            }),
4561        )
4562        .await;
4563
4564    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
4565    let active_call_a = cx_a.read(ActiveCall::global);
4566    let project_id = active_call_a
4567        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4568        .await
4569        .unwrap();
4570
4571    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4572
4573    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4574    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4575
4576    let editor_a = workspace_a
4577        .update_in(cx_a, |workspace, window, cx| {
4578            workspace.open_path(
4579                (worktree_id, rel_path("src/main.rs")),
4580                None,
4581                true,
4582                window,
4583                cx,
4584            )
4585        })
4586        .await
4587        .unwrap()
4588        .downcast::<Editor>()
4589        .unwrap();
4590
4591    let editor_b = workspace_b
4592        .update_in(cx_b, |workspace, window, cx| {
4593            workspace.open_path(
4594                (worktree_id, rel_path("src/main.rs")),
4595                None,
4596                true,
4597                window,
4598                cx,
4599            )
4600        })
4601        .await
4602        .unwrap()
4603        .downcast::<Editor>()
4604        .unwrap();
4605
4606    cx_a.run_until_parked();
4607    cx_b.run_until_parked();
4608
4609    editor_a.update_in(cx_a, |editor, window, cx| {
4610        editor.copy_file_name(&CopyFileName, window, cx);
4611    });
4612
4613    assert_eq!(
4614        cx_a.read_from_clipboard().and_then(|item| item.text()),
4615        Some("main.rs".to_string())
4616    );
4617
4618    editor_b.update_in(cx_b, |editor, window, cx| {
4619        editor.copy_file_name(&CopyFileName, window, cx);
4620    });
4621
4622    assert_eq!(
4623        cx_b.read_from_clipboard().and_then(|item| item.text()),
4624        Some("main.rs".to_string())
4625    );
4626}
4627
4628#[gpui::test]
4629async fn test_copy_file_location(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4630    let mut server = TestServer::start(cx_a.executor()).await;
4631    let client_a = server.create_client(cx_a, "user_a").await;
4632    let client_b = server.create_client(cx_b, "user_b").await;
4633    server
4634        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4635        .await;
4636
4637    cx_b.update(editor::init);
4638
4639    client_a
4640        .fs()
4641        .insert_tree(
4642            path!("/root"),
4643            json!({
4644                "src": {
4645                    "main.rs": indoc! {"
4646                        fn main() {
4647                            println!(\"Hello, world!\");
4648                        }
4649                    "},
4650                }
4651            }),
4652        )
4653        .await;
4654
4655    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
4656    let active_call_a = cx_a.read(ActiveCall::global);
4657    let project_id = active_call_a
4658        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4659        .await
4660        .unwrap();
4661
4662    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4663
4664    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4665    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4666
4667    let editor_a = workspace_a
4668        .update_in(cx_a, |workspace, window, cx| {
4669            workspace.open_path(
4670                (worktree_id, rel_path("src/main.rs")),
4671                None,
4672                true,
4673                window,
4674                cx,
4675            )
4676        })
4677        .await
4678        .unwrap()
4679        .downcast::<Editor>()
4680        .unwrap();
4681
4682    let editor_b = workspace_b
4683        .update_in(cx_b, |workspace, window, cx| {
4684            workspace.open_path(
4685                (worktree_id, rel_path("src/main.rs")),
4686                None,
4687                true,
4688                window,
4689                cx,
4690            )
4691        })
4692        .await
4693        .unwrap()
4694        .downcast::<Editor>()
4695        .unwrap();
4696
4697    cx_a.run_until_parked();
4698    cx_b.run_until_parked();
4699
4700    editor_a.update_in(cx_a, |editor, window, cx| {
4701        editor.change_selections(Default::default(), window, cx, |s| {
4702            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(16)]);
4703        });
4704        editor.copy_file_location(&CopyFileLocation, window, cx);
4705    });
4706
4707    assert_eq!(
4708        cx_a.read_from_clipboard().and_then(|item| item.text()),
4709        Some(format!("{}:2", path!("src/main.rs")))
4710    );
4711
4712    editor_b.update_in(cx_b, |editor, window, cx| {
4713        editor.change_selections(Default::default(), window, cx, |s| {
4714            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(16)]);
4715        });
4716        editor.copy_file_location(&CopyFileLocation, window, cx);
4717    });
4718
4719    assert_eq!(
4720        cx_b.read_from_clipboard().and_then(|item| item.text()),
4721        Some(format!("{}:2", path!("src/main.rs")))
4722    );
4723
4724    editor_a.update_in(cx_a, |editor, window, cx| {
4725        editor.change_selections(Default::default(), window, cx, |s| {
4726            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(44)]);
4727        });
4728        editor.copy_file_location(&CopyFileLocation, window, cx);
4729    });
4730
4731    assert_eq!(
4732        cx_a.read_from_clipboard().and_then(|item| item.text()),
4733        Some(format!("{}:2-3", path!("src/main.rs")))
4734    );
4735
4736    editor_b.update_in(cx_b, |editor, window, cx| {
4737        editor.change_selections(Default::default(), window, cx, |s| {
4738            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(44)]);
4739        });
4740        editor.copy_file_location(&CopyFileLocation, window, cx);
4741    });
4742
4743    assert_eq!(
4744        cx_b.read_from_clipboard().and_then(|item| item.text()),
4745        Some(format!("{}:2-3", path!("src/main.rs")))
4746    );
4747
4748    editor_a.update_in(cx_a, |editor, window, cx| {
4749        editor.change_selections(Default::default(), window, cx, |s| {
4750            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(43)]);
4751        });
4752        editor.copy_file_location(&CopyFileLocation, window, cx);
4753    });
4754
4755    assert_eq!(
4756        cx_a.read_from_clipboard().and_then(|item| item.text()),
4757        Some(format!("{}:2", path!("src/main.rs")))
4758    );
4759
4760    editor_b.update_in(cx_b, |editor, window, cx| {
4761        editor.change_selections(Default::default(), window, cx, |s| {
4762            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(43)]);
4763        });
4764        editor.copy_file_location(&CopyFileLocation, window, cx);
4765    });
4766
4767    assert_eq!(
4768        cx_b.read_from_clipboard().and_then(|item| item.text()),
4769        Some(format!("{}:2", path!("src/main.rs")))
4770    );
4771}
4772
4773#[track_caller]
4774fn tab_undo_assert(
4775    cx_a: &mut EditorTestContext,
4776    cx_b: &mut EditorTestContext,
4777    expected_initial: &str,
4778    expected_tabbed: &str,
4779    a_tabs: bool,
4780) {
4781    cx_a.assert_editor_state(expected_initial);
4782    cx_b.assert_editor_state(expected_initial);
4783
4784    if a_tabs {
4785        cx_a.update_editor(|editor, window, cx| {
4786            editor.tab(&editor::actions::Tab, window, cx);
4787        });
4788    } else {
4789        cx_b.update_editor(|editor, window, cx| {
4790            editor.tab(&editor::actions::Tab, window, cx);
4791        });
4792    }
4793
4794    cx_a.run_until_parked();
4795    cx_b.run_until_parked();
4796
4797    cx_a.assert_editor_state(expected_tabbed);
4798    cx_b.assert_editor_state(expected_tabbed);
4799
4800    if a_tabs {
4801        cx_a.update_editor(|editor, window, cx| {
4802            editor.undo(&editor::actions::Undo, window, cx);
4803        });
4804    } else {
4805        cx_b.update_editor(|editor, window, cx| {
4806            editor.undo(&editor::actions::Undo, window, cx);
4807        });
4808    }
4809    cx_a.run_until_parked();
4810    cx_b.run_until_parked();
4811    cx_a.assert_editor_state(expected_initial);
4812    cx_b.assert_editor_state(expected_initial);
4813}
4814
4815fn extract_hint_labels(editor: &Editor, cx: &mut App) -> Vec<String> {
4816    let lsp_store = editor.project().unwrap().read(cx).lsp_store();
4817
4818    let mut all_cached_labels = Vec::new();
4819    let mut all_fetched_hints = Vec::new();
4820    for buffer in editor.buffer().read(cx).all_buffers() {
4821        lsp_store.update(cx, |lsp_store, cx| {
4822            let hints = &lsp_store.latest_lsp_data(&buffer, cx).inlay_hints();
4823            all_cached_labels.extend(hints.all_cached_hints().into_iter().map(|hint| {
4824                let mut label = hint.text().to_string();
4825                if hint.padding_left {
4826                    label.insert(0, ' ');
4827                }
4828                if hint.padding_right {
4829                    label.push_str(" ");
4830                }
4831                label
4832            }));
4833            all_fetched_hints.extend(hints.all_fetched_hints());
4834        });
4835    }
4836
4837    assert!(
4838        all_fetched_hints.is_empty(),
4839        "Did not expect background hints fetch tasks, but got {} of them",
4840        all_fetched_hints.len()
4841    );
4842
4843    all_cached_labels
4844}
4845
4846#[track_caller]
4847fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
4848    editor
4849        .all_inlays(cx)
4850        .into_iter()
4851        .filter_map(|inlay| inlay.get_color())
4852        .map(Rgba::from)
4853        .collect()
4854}
4855
4856fn extract_semantic_token_ranges(editor: &Editor, cx: &App) -> Vec<Range<MultiBufferOffset>> {
4857    let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
4858    editor
4859        .display_map
4860        .read(cx)
4861        .semantic_token_highlights
4862        .iter()
4863        .flat_map(|(_, (v, _))| v.iter())
4864        .map(|highlights| highlights.range.to_offset(&multi_buffer_snapshot))
4865        .collect()
4866}
4867
4868#[gpui::test(iterations = 10)]
4869async fn test_mutual_editor_semantic_token_cache_update(
4870    cx_a: &mut TestAppContext,
4871    cx_b: &mut TestAppContext,
4872) {
4873    let mut server = TestServer::start(cx_a.executor()).await;
4874    let executor = cx_a.executor();
4875    let client_a = server.create_client(cx_a, "user_a").await;
4876    let client_b = server.create_client(cx_b, "user_b").await;
4877    server
4878        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4879        .await;
4880    let active_call_a = cx_a.read(ActiveCall::global);
4881    let active_call_b = cx_b.read(ActiveCall::global);
4882
4883    cx_a.update(editor::init);
4884    cx_b.update(editor::init);
4885
4886    cx_a.update(|cx| {
4887        SettingsStore::update_global(cx, |store, cx| {
4888            store.update_user_settings(cx, |settings| {
4889                settings.project.all_languages.defaults.semantic_tokens =
4890                    Some(SemanticTokens::Full);
4891            });
4892        });
4893    });
4894    cx_b.update(|cx| {
4895        SettingsStore::update_global(cx, |store, cx| {
4896            store.update_user_settings(cx, |settings| {
4897                settings.project.all_languages.defaults.semantic_tokens =
4898                    Some(SemanticTokens::Full);
4899            });
4900        });
4901    });
4902
4903    let capabilities = lsp::ServerCapabilities {
4904        semantic_tokens_provider: Some(
4905            lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
4906                lsp::SemanticTokensOptions {
4907                    legend: lsp::SemanticTokensLegend {
4908                        token_types: vec!["function".into()],
4909                        token_modifiers: vec![],
4910                    },
4911                    full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
4912                    ..Default::default()
4913                },
4914            ),
4915        ),
4916        ..lsp::ServerCapabilities::default()
4917    };
4918    client_a.language_registry().add(rust_lang());
4919
4920    let edits_made = Arc::new(AtomicUsize::new(0));
4921    let closure_edits_made = Arc::clone(&edits_made);
4922    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4923        "Rust",
4924        FakeLspAdapter {
4925            capabilities: capabilities.clone(),
4926            initializer: Some(Box::new(move |fake_language_server| {
4927                let closure_edits_made = closure_edits_made.clone();
4928                fake_language_server
4929                    .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
4930                        move |_, _| {
4931                            let edits_made_2 = Arc::clone(&closure_edits_made);
4932                            async move {
4933                                let edits_made =
4934                                    AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire);
4935                                Ok(Some(lsp::SemanticTokensResult::Tokens(
4936                                    lsp::SemanticTokens {
4937                                        data: vec![
4938                                            0,                     // delta_line
4939                                            3,                     // delta_start
4940                                            edits_made as u32 + 4, // length
4941                                            0,                     // token_type
4942                                            0,                     // token_modifiers_bitset
4943                                        ],
4944                                        result_id: None,
4945                                    },
4946                                )))
4947                            }
4948                        },
4949                    );
4950            })),
4951            ..FakeLspAdapter::default()
4952        },
4953    );
4954    client_b.language_registry().add(rust_lang());
4955    client_b.language_registry().register_fake_lsp_adapter(
4956        "Rust",
4957        FakeLspAdapter {
4958            capabilities,
4959            ..FakeLspAdapter::default()
4960        },
4961    );
4962
4963    client_a
4964        .fs()
4965        .insert_tree(
4966            path!("/a"),
4967            json!({
4968                "main.rs": "fn main() { a }",
4969                "other.rs": "// Test file",
4970            }),
4971        )
4972        .await;
4973    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4974    active_call_a
4975        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
4976        .await
4977        .unwrap();
4978    let project_id = active_call_a
4979        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4980        .await
4981        .unwrap();
4982
4983    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4984    active_call_b
4985        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
4986        .await
4987        .unwrap();
4988
4989    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4990
4991    let file_a = workspace_a.update_in(cx_a, |workspace, window, cx| {
4992        workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
4993    });
4994    let _fake_language_server = fake_language_servers.next().await.unwrap();
4995    let editor_a = file_a.await.unwrap().downcast::<Editor>().unwrap();
4996    executor.advance_clock(Duration::from_millis(100));
4997    executor.run_until_parked();
4998
4999    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
5000    editor_a.update(cx_a, |editor, cx| {
5001        let ranges = extract_semantic_token_ranges(editor, cx);
5002        assert_eq!(
5003            ranges,
5004            vec![MultiBufferOffset(3)..MultiBufferOffset(3 + initial_edit + 4)],
5005            "Host should get its first semantic tokens when opening an editor"
5006        );
5007    });
5008
5009    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
5010    let editor_b = workspace_b
5011        .update_in(cx_b, |workspace, window, cx| {
5012            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5013        })
5014        .await
5015        .unwrap()
5016        .downcast::<Editor>()
5017        .unwrap();
5018
5019    executor.advance_clock(Duration::from_millis(100));
5020    executor.run_until_parked();
5021    editor_b.update(cx_b, |editor, cx| {
5022        let ranges = extract_semantic_token_ranges(editor, cx);
5023        assert_eq!(
5024            ranges,
5025            vec![MultiBufferOffset(3)..MultiBufferOffset(3 + initial_edit + 4)],
5026            "Client should get its first semantic tokens when opening an editor"
5027        );
5028    });
5029
5030    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
5031    editor_b.update_in(cx_b, |editor, window, cx| {
5032        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
5033            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)].clone())
5034        });
5035        editor.handle_input(":", window, cx);
5036    });
5037    cx_b.focus(&editor_b);
5038
5039    executor.advance_clock(Duration::from_secs(1));
5040    executor.run_until_parked();
5041    editor_a.update(cx_a, |editor, cx| {
5042        let ranges = extract_semantic_token_ranges(editor, cx);
5043        assert_eq!(
5044            ranges,
5045            vec![MultiBufferOffset(3)..MultiBufferOffset(3 + after_client_edit + 4)],
5046        );
5047    });
5048    editor_b.update(cx_b, |editor, cx| {
5049        let ranges = extract_semantic_token_ranges(editor, cx);
5050        assert_eq!(
5051            ranges,
5052            vec![MultiBufferOffset(3)..MultiBufferOffset(3 + after_client_edit + 4)],
5053        );
5054    });
5055
5056    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
5057    editor_a.update_in(cx_a, |editor, window, cx| {
5058        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
5059            s.select_ranges([MultiBufferOffset(14)..MultiBufferOffset(14)])
5060        });
5061        editor.handle_input("a change", window, cx);
5062    });
5063    cx_a.focus(&editor_a);
5064
5065    executor.advance_clock(Duration::from_secs(1));
5066    executor.run_until_parked();
5067    editor_a.update(cx_a, |editor, cx| {
5068        let ranges = extract_semantic_token_ranges(editor, cx);
5069        assert_eq!(
5070            ranges,
5071            vec![MultiBufferOffset(3)..MultiBufferOffset(3 + after_host_edit + 4)],
5072        );
5073    });
5074    editor_b.update(cx_b, |editor, cx| {
5075        let ranges = extract_semantic_token_ranges(editor, cx);
5076        assert_eq!(
5077            ranges,
5078            vec![MultiBufferOffset(3)..MultiBufferOffset(3 + after_host_edit + 4)],
5079        );
5080    });
5081}
5082
5083#[gpui::test(iterations = 10)]
5084async fn test_semantic_token_refresh_is_forwarded(
5085    cx_a: &mut TestAppContext,
5086    cx_b: &mut TestAppContext,
5087) {
5088    let mut server = TestServer::start(cx_a.executor()).await;
5089    let executor = cx_a.executor();
5090    let client_a = server.create_client(cx_a, "user_a").await;
5091    let client_b = server.create_client(cx_b, "user_b").await;
5092    server
5093        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5094        .await;
5095    let active_call_a = cx_a.read(ActiveCall::global);
5096    let active_call_b = cx_b.read(ActiveCall::global);
5097
5098    cx_a.update(editor::init);
5099    cx_b.update(editor::init);
5100
5101    cx_a.update(|cx| {
5102        SettingsStore::update_global(cx, |store, cx| {
5103            store.update_user_settings(cx, |settings| {
5104                settings.project.all_languages.defaults.semantic_tokens = Some(SemanticTokens::Off);
5105            });
5106        });
5107    });
5108    cx_b.update(|cx| {
5109        SettingsStore::update_global(cx, |store, cx| {
5110            store.update_user_settings(cx, |settings| {
5111                settings.project.all_languages.defaults.semantic_tokens =
5112                    Some(SemanticTokens::Full);
5113            });
5114        });
5115    });
5116
5117    let capabilities = lsp::ServerCapabilities {
5118        semantic_tokens_provider: Some(
5119            lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
5120                lsp::SemanticTokensOptions {
5121                    legend: lsp::SemanticTokensLegend {
5122                        token_types: vec!["function".into()],
5123                        token_modifiers: vec![],
5124                    },
5125                    full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
5126                    ..Default::default()
5127                },
5128            ),
5129        ),
5130        ..lsp::ServerCapabilities::default()
5131    };
5132    client_a.language_registry().add(rust_lang());
5133    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5134        "Rust",
5135        FakeLspAdapter {
5136            capabilities: capabilities.clone(),
5137            ..FakeLspAdapter::default()
5138        },
5139    );
5140    client_b.language_registry().add(rust_lang());
5141    client_b.language_registry().register_fake_lsp_adapter(
5142        "Rust",
5143        FakeLspAdapter {
5144            capabilities,
5145            ..FakeLspAdapter::default()
5146        },
5147    );
5148
5149    client_a
5150        .fs()
5151        .insert_tree(
5152            path!("/a"),
5153            json!({
5154                "main.rs": "fn main() { a }",
5155                "other.rs": "// Test file",
5156            }),
5157        )
5158        .await;
5159    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
5160    active_call_a
5161        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
5162        .await
5163        .unwrap();
5164    let project_id = active_call_a
5165        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5166        .await
5167        .unwrap();
5168
5169    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5170    active_call_b
5171        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5172        .await
5173        .unwrap();
5174
5175    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
5176    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
5177
5178    let editor_a = workspace_a
5179        .update_in(cx_a, |workspace, window, cx| {
5180            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5181        })
5182        .await
5183        .unwrap()
5184        .downcast::<Editor>()
5185        .unwrap();
5186
5187    let editor_b = workspace_b
5188        .update_in(cx_b, |workspace, window, cx| {
5189            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5190        })
5191        .await
5192        .unwrap()
5193        .downcast::<Editor>()
5194        .unwrap();
5195
5196    let other_tokens = Arc::new(AtomicBool::new(false));
5197    let fake_language_server = fake_language_servers.next().await.unwrap();
5198    let closure_other_tokens = Arc::clone(&other_tokens);
5199    fake_language_server
5200        .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(move |params, _| {
5201            let task_other_tokens = Arc::clone(&closure_other_tokens);
5202            async move {
5203                assert_eq!(
5204                    params.text_document.uri,
5205                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
5206                );
5207                let other_tokens = task_other_tokens.load(atomic::Ordering::Acquire);
5208                let (delta_start, length) = if other_tokens { (0, 2) } else { (3, 4) };
5209                Ok(Some(lsp::SemanticTokensResult::Tokens(
5210                    lsp::SemanticTokens {
5211                        data: vec![
5212                            0, // delta_line
5213                            delta_start,
5214                            length,
5215                            0, // token_type
5216                            0, // token_modifiers_bitset
5217                        ],
5218                        result_id: None,
5219                    },
5220                )))
5221            }
5222        })
5223        .next()
5224        .await
5225        .unwrap();
5226
5227    executor.run_until_parked();
5228    editor_a.update(cx_a, |editor, cx| {
5229        assert!(
5230            extract_semantic_token_ranges(editor, cx).is_empty(),
5231            "Host should get no semantic tokens due to them turned off"
5232        );
5233    });
5234
5235    executor.run_until_parked();
5236    editor_b.update(cx_b, |editor, cx| {
5237        assert_eq!(
5238            vec![MultiBufferOffset(3)..MultiBufferOffset(7)],
5239            extract_semantic_token_ranges(editor, cx),
5240            "Client should get its first semantic tokens when opening an editor"
5241        );
5242    });
5243
5244    other_tokens.fetch_or(true, atomic::Ordering::Release);
5245    fake_language_server
5246        .request::<lsp::request::SemanticTokensRefresh>((), DEFAULT_LSP_REQUEST_TIMEOUT)
5247        .await
5248        .into_response()
5249        .expect("semantic tokens refresh request failed");
5250    // wait out the debounce timeout
5251    executor.advance_clock(LSP_REQUEST_DEBOUNCE_TIMEOUT);
5252    executor.run_until_parked();
5253    editor_a.update(cx_a, |editor, cx| {
5254        assert!(
5255            extract_semantic_token_ranges(editor, cx).is_empty(),
5256            "Host should get no semantic tokens due to them turned off, even after the /refresh"
5257        );
5258    });
5259
5260    executor.run_until_parked();
5261    editor_b.update(cx_b, |editor, cx| {
5262        assert_eq!(
5263            vec![MultiBufferOffset(0)..MultiBufferOffset(2)],
5264            extract_semantic_token_ranges(editor, cx),
5265            "Guest should get a /refresh LSP request propagated by host despite host tokens are off"
5266        );
5267    });
5268}
5269
5270#[gpui::test]
5271async fn test_document_folding_ranges(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
5272    let mut server = TestServer::start(cx_a.executor()).await;
5273    let executor = cx_a.executor();
5274    let client_a = server.create_client(cx_a, "user_a").await;
5275    let client_b = server.create_client(cx_b, "user_b").await;
5276    server
5277        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5278        .await;
5279    let active_call_a = cx_a.read(ActiveCall::global);
5280    let active_call_b = cx_b.read(ActiveCall::global);
5281
5282    cx_a.update(editor::init);
5283    cx_b.update(editor::init);
5284
5285    let capabilities = lsp::ServerCapabilities {
5286        folding_range_provider: Some(lsp::FoldingRangeProviderCapability::Simple(true)),
5287        ..lsp::ServerCapabilities::default()
5288    };
5289    client_a.language_registry().add(rust_lang());
5290    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5291        "Rust",
5292        FakeLspAdapter {
5293            capabilities: capabilities.clone(),
5294            ..FakeLspAdapter::default()
5295        },
5296    );
5297    client_b.language_registry().add(rust_lang());
5298    client_b.language_registry().register_fake_lsp_adapter(
5299        "Rust",
5300        FakeLspAdapter {
5301            capabilities,
5302            ..FakeLspAdapter::default()
5303        },
5304    );
5305
5306    client_a
5307        .fs()
5308        .insert_tree(
5309            path!("/a"),
5310            json!({
5311                "main.rs": "fn main() {\n    if true {\n        println!(\"hello\");\n    }\n}\n",
5312            }),
5313        )
5314        .await;
5315    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
5316    active_call_a
5317        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
5318        .await
5319        .unwrap();
5320    let project_id = active_call_a
5321        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5322        .await
5323        .unwrap();
5324
5325    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5326    active_call_b
5327        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5328        .await
5329        .unwrap();
5330
5331    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
5332
5333    let _buffer_a = project_a
5334        .update(cx_a, |project, cx| {
5335            project.open_local_buffer(path!("/a/main.rs"), cx)
5336        })
5337        .await
5338        .unwrap();
5339    let editor_a = workspace_a
5340        .update_in(cx_a, |workspace, window, cx| {
5341            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5342        })
5343        .await
5344        .unwrap()
5345        .downcast::<Editor>()
5346        .unwrap();
5347
5348    let fake_language_server = fake_language_servers.next().await.unwrap();
5349
5350    let folding_request_count = Arc::new(AtomicUsize::new(0));
5351    let closure_count = Arc::clone(&folding_request_count);
5352    let mut folding_request_handle = fake_language_server
5353        .set_request_handler::<lsp::request::FoldingRangeRequest, _, _>(move |_, _| {
5354            let count = Arc::clone(&closure_count);
5355            async move {
5356                count.fetch_add(1, atomic::Ordering::Release);
5357                Ok(Some(vec![lsp::FoldingRange {
5358                    start_line: 0,
5359                    start_character: Some(10),
5360                    end_line: 4,
5361                    end_character: Some(1),
5362                    kind: None,
5363                    collapsed_text: None,
5364                }]))
5365            }
5366        });
5367
5368    executor.run_until_parked();
5369
5370    assert_eq!(
5371        0,
5372        folding_request_count.load(atomic::Ordering::Acquire),
5373        "LSP folding ranges are off by default, no request should have been made"
5374    );
5375    editor_a.update(cx_a, |editor, cx| {
5376        assert!(
5377            !editor.document_folding_ranges_enabled(cx),
5378            "Host should not have LSP folding ranges enabled"
5379        );
5380    });
5381
5382    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
5383    let editor_b = workspace_b
5384        .update_in(cx_b, |workspace, window, cx| {
5385            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5386        })
5387        .await
5388        .unwrap()
5389        .downcast::<Editor>()
5390        .unwrap();
5391    executor.run_until_parked();
5392
5393    editor_b.update(cx_b, |editor, cx| {
5394        assert!(
5395            !editor.document_folding_ranges_enabled(cx),
5396            "Client should not have LSP folding ranges enabled by default"
5397        );
5398    });
5399
5400    cx_b.update(|_, cx| {
5401        SettingsStore::update_global(cx, |store, cx| {
5402            store.update_user_settings(cx, |settings| {
5403                settings
5404                    .project
5405                    .all_languages
5406                    .defaults
5407                    .document_folding_ranges = Some(DocumentFoldingRanges::On);
5408            });
5409        });
5410    });
5411    executor.advance_clock(LSP_REQUEST_DEBOUNCE_TIMEOUT);
5412    folding_request_handle.next().await.unwrap();
5413    executor.run_until_parked();
5414
5415    assert!(
5416        folding_request_count.load(atomic::Ordering::Acquire) > 0,
5417        "After the client enables LSP folding ranges, a request should be made"
5418    );
5419    editor_b.update(cx_b, |editor, cx| {
5420        assert!(
5421            editor.document_folding_ranges_enabled(cx),
5422            "Client should have LSP folding ranges enabled after toggling the setting on"
5423        );
5424    });
5425    editor_a.update(cx_a, |editor, cx| {
5426        assert!(
5427            !editor.document_folding_ranges_enabled(cx),
5428            "Host should remain unaffected by the client's setting change"
5429        );
5430    });
5431
5432    editor_b.update_in(cx_b, |editor, window, cx| {
5433        let snapshot = editor.display_snapshot(cx);
5434        assert!(
5435            !snapshot.is_line_folded(MultiBufferRow(0)),
5436            "Line 0 should not be folded before fold_at"
5437        );
5438        editor.fold_at(MultiBufferRow(0), window, cx);
5439    });
5440    executor.run_until_parked();
5441
5442    editor_b.update(cx_b, |editor, cx| {
5443        let snapshot = editor.display_snapshot(cx);
5444        assert!(
5445            snapshot.is_line_folded(MultiBufferRow(0)),
5446            "Line 0 should be folded after fold_at using LSP folding range"
5447        );
5448    });
5449}
5450
5451#[gpui::test]
5452async fn test_remote_project_worktree_trust(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
5453    let has_restricted_worktrees = |project: &gpui::Entity<project::Project>,
5454                                    cx: &mut VisualTestContext| {
5455        cx.update(|_, cx| {
5456            let worktree_store = project.read(cx).worktree_store();
5457            TrustedWorktrees::try_get_global(cx)
5458                .unwrap()
5459                .read(cx)
5460                .has_restricted_worktrees(&worktree_store, cx)
5461        })
5462    };
5463
5464    cx_a.update(|cx| {
5465        project::trusted_worktrees::init(HashMap::default(), cx);
5466    });
5467    cx_b.update(|cx| {
5468        project::trusted_worktrees::init(HashMap::default(), cx);
5469    });
5470
5471    let mut server = TestServer::start(cx_a.executor()).await;
5472    let client_a = server.create_client(cx_a, "user_a").await;
5473    let client_b = server.create_client(cx_b, "user_b").await;
5474    server
5475        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5476        .await;
5477
5478    client_a
5479        .fs()
5480        .insert_tree(
5481            path!("/a"),
5482            json!({
5483                "file.txt": "contents",
5484            }),
5485        )
5486        .await;
5487
5488    let (project_a, worktree_id) = client_a
5489        .build_local_project_with_trust(path!("/a"), cx_a)
5490        .await;
5491    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
5492    let active_call_a = cx_a.read(ActiveCall::global);
5493    let project_id = active_call_a
5494        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5495        .await
5496        .unwrap();
5497
5498    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5499    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
5500
5501    let _editor_a = workspace_a
5502        .update_in(cx_a, |workspace, window, cx| {
5503            workspace.open_path(
5504                (worktree_id, rel_path("src/main.rs")),
5505                None,
5506                true,
5507                window,
5508                cx,
5509            )
5510        })
5511        .await
5512        .unwrap()
5513        .downcast::<Editor>()
5514        .unwrap();
5515
5516    let _editor_b = workspace_b
5517        .update_in(cx_b, |workspace, window, cx| {
5518            workspace.open_path(
5519                (worktree_id, rel_path("src/main.rs")),
5520                None,
5521                true,
5522                window,
5523                cx,
5524            )
5525        })
5526        .await
5527        .unwrap()
5528        .downcast::<Editor>()
5529        .unwrap();
5530
5531    cx_a.run_until_parked();
5532    cx_b.run_until_parked();
5533
5534    assert!(
5535        has_restricted_worktrees(&project_a, cx_a),
5536        "local client should have restricted worktrees after opening it"
5537    );
5538    assert!(
5539        !has_restricted_worktrees(&project_b, cx_b),
5540        "remote client joined a project should have no restricted worktrees"
5541    );
5542
5543    cx_a.update(|_, cx| {
5544        if let Some(trusted_worktrees) = TrustedWorktrees::try_get_global(cx) {
5545            trusted_worktrees.update(cx, |trusted_worktrees, cx| {
5546                trusted_worktrees.trust(
5547                    &project_a.read(cx).worktree_store(),
5548                    HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
5549                    cx,
5550                );
5551            });
5552        }
5553    });
5554    assert!(
5555        !has_restricted_worktrees(&project_a, cx_a),
5556        "local client should have no worktrees after trusting those"
5557    );
5558    assert!(
5559        !has_restricted_worktrees(&project_b, cx_b),
5560        "remote client should still be trusted"
5561    );
5562}
5563
5564#[gpui::test]
5565async fn test_document_symbols(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
5566    let mut server = TestServer::start(cx_a.executor()).await;
5567    let executor = cx_a.executor();
5568    let client_a = server.create_client(cx_a, "user_a").await;
5569    let client_b = server.create_client(cx_b, "user_b").await;
5570    server
5571        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5572        .await;
5573    let active_call_a = cx_a.read(ActiveCall::global);
5574    let active_call_b = cx_b.read(ActiveCall::global);
5575
5576    cx_a.update(editor::init);
5577    cx_b.update(editor::init);
5578
5579    let capabilities = lsp::ServerCapabilities {
5580        document_symbol_provider: Some(lsp::OneOf::Left(true)),
5581        ..lsp::ServerCapabilities::default()
5582    };
5583    client_a.language_registry().add(rust_lang());
5584    #[allow(deprecated)]
5585    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5586        "Rust",
5587        FakeLspAdapter {
5588            capabilities: capabilities.clone(),
5589            initializer: Some(Box::new(|fake_language_server| {
5590                #[allow(deprecated)]
5591                fake_language_server
5592                    .set_request_handler::<lsp::request::DocumentSymbolRequest, _, _>(
5593                        move |_, _| async move {
5594                            Ok(Some(lsp::DocumentSymbolResponse::Nested(vec![
5595                                lsp::DocumentSymbol {
5596                                    name: "Foo".to_string(),
5597                                    detail: None,
5598                                    kind: lsp::SymbolKind::STRUCT,
5599                                    tags: None,
5600                                    deprecated: None,
5601                                    range: lsp::Range::new(
5602                                        lsp::Position::new(0, 0),
5603                                        lsp::Position::new(2, 1),
5604                                    ),
5605                                    selection_range: lsp::Range::new(
5606                                        lsp::Position::new(0, 7),
5607                                        lsp::Position::new(0, 10),
5608                                    ),
5609                                    children: Some(vec![lsp::DocumentSymbol {
5610                                        name: "bar".to_string(),
5611                                        detail: None,
5612                                        kind: lsp::SymbolKind::FIELD,
5613                                        tags: None,
5614                                        deprecated: None,
5615                                        range: lsp::Range::new(
5616                                            lsp::Position::new(1, 4),
5617                                            lsp::Position::new(1, 13),
5618                                        ),
5619                                        selection_range: lsp::Range::new(
5620                                            lsp::Position::new(1, 4),
5621                                            lsp::Position::new(1, 7),
5622                                        ),
5623                                        children: None,
5624                                    }]),
5625                                },
5626                            ])))
5627                        },
5628                    );
5629            })),
5630            ..FakeLspAdapter::default()
5631        },
5632    );
5633    client_b.language_registry().add(rust_lang());
5634    client_b.language_registry().register_fake_lsp_adapter(
5635        "Rust",
5636        FakeLspAdapter {
5637            capabilities,
5638            ..FakeLspAdapter::default()
5639        },
5640    );
5641
5642    client_a
5643        .fs()
5644        .insert_tree(
5645            path!("/a"),
5646            json!({
5647                "main.rs": "struct Foo {\n    bar: u32,\n}\n",
5648            }),
5649        )
5650        .await;
5651    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
5652    active_call_a
5653        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
5654        .await
5655        .unwrap();
5656    let project_id = active_call_a
5657        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5658        .await
5659        .unwrap();
5660
5661    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5662    active_call_b
5663        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5664        .await
5665        .unwrap();
5666
5667    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
5668
5669    let editor_a = workspace_a
5670        .update_in(cx_a, |workspace, window, cx| {
5671            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5672        })
5673        .await
5674        .unwrap()
5675        .downcast::<Editor>()
5676        .unwrap();
5677
5678    let _fake_language_server = fake_language_servers.next().await.unwrap();
5679    executor.run_until_parked();
5680
5681    cx_a.update(|_, cx| {
5682        SettingsStore::update_global(cx, |store, cx| {
5683            store.update_user_settings(cx, |settings| {
5684                settings.project.all_languages.defaults.document_symbols =
5685                    Some(DocumentSymbols::On);
5686            });
5687        });
5688    });
5689    executor.advance_clock(LSP_REQUEST_DEBOUNCE_TIMEOUT + Duration::from_millis(100));
5690    executor.run_until_parked();
5691
5692    editor_a.update(cx_a, |editor, cx| {
5693        let (breadcrumbs, _) = editor
5694            .breadcrumbs(cx)
5695            .expect("Host should have breadcrumbs");
5696        let texts: Vec<_> = breadcrumbs.iter().map(|b| b.text.as_str()).collect();
5697        assert_eq!(
5698            texts,
5699            vec!["main.rs", "struct Foo"],
5700            "Host should see file path and LSP symbol 'Foo' in breadcrumbs"
5701        );
5702    });
5703
5704    cx_b.update(|cx| {
5705        SettingsStore::update_global(cx, |store, cx| {
5706            store.update_user_settings(cx, |settings| {
5707                settings.project.all_languages.defaults.document_symbols =
5708                    Some(DocumentSymbols::On);
5709            });
5710        });
5711    });
5712    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
5713    let editor_b = workspace_b
5714        .update_in(cx_b, |workspace, window, cx| {
5715            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5716        })
5717        .await
5718        .unwrap()
5719        .downcast::<Editor>()
5720        .unwrap();
5721    executor.advance_clock(LSP_REQUEST_DEBOUNCE_TIMEOUT + Duration::from_millis(100));
5722    executor.run_until_parked();
5723
5724    editor_b.update(cx_b, |editor, cx| {
5725        assert_eq!(
5726            editor
5727                .breadcrumbs(cx)
5728                .expect("Client B should have breadcrumbs")
5729                .0
5730                .iter()
5731                .map(|b| b.text.as_str())
5732                .collect::<Vec<_>>(),
5733            vec!["main.rs", "struct Foo"],
5734            "Client B should see file path and LSP symbol 'Foo' via remote project"
5735        );
5736    });
5737}
5738
5739fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
5740    git::blame::BlameEntry {
5741        sha: sha.parse().unwrap(),
5742        range,
5743        ..Default::default()
5744    }
5745}