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    cx_b.update(|cx| {
1207        SettingsStore::update_global(cx, |store, cx| {
1208            store.update_user_settings(cx, |settings| {
1209                settings.editor.code_lens = Some(settings::CodeLens::Menu);
1210            });
1211        });
1212    });
1213
1214    let command_name = "test_command";
1215    let capabilities = lsp::ServerCapabilities {
1216        code_lens_provider: Some(lsp::CodeLensOptions {
1217            resolve_provider: None,
1218        }),
1219        execute_command_provider: Some(lsp::ExecuteCommandOptions {
1220            commands: vec![command_name.to_string()],
1221            ..lsp::ExecuteCommandOptions::default()
1222        }),
1223        ..lsp::ServerCapabilities::default()
1224    };
1225    client_a.language_registry().add(rust_lang());
1226    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1227        "Rust",
1228        FakeLspAdapter {
1229            capabilities: capabilities.clone(),
1230            ..FakeLspAdapter::default()
1231        },
1232    );
1233    client_b.language_registry().add(rust_lang());
1234    client_b.language_registry().register_fake_lsp_adapter(
1235        "Rust",
1236        FakeLspAdapter {
1237            capabilities,
1238            ..FakeLspAdapter::default()
1239        },
1240    );
1241
1242    client_a
1243        .fs()
1244        .insert_tree(
1245            path!("/dir"),
1246            json!({
1247                "one.rs": "const ONE: usize = 1;"
1248            }),
1249        )
1250        .await;
1251    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
1252    let project_id = active_call_a
1253        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1254        .await
1255        .unwrap();
1256    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1257
1258    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
1259    let editor_b = workspace_b
1260        .update_in(cx_b, |workspace, window, cx| {
1261            workspace.open_path((worktree_id, rel_path("one.rs")), None, true, window, cx)
1262        })
1263        .await
1264        .unwrap()
1265        .downcast::<Editor>()
1266        .unwrap();
1267    let (lsp_store_b, buffer_b) = editor_b.update(cx_b, |editor, cx| {
1268        let lsp_store = editor.project().unwrap().read(cx).lsp_store();
1269        let buffer = editor.buffer().read(cx).as_singleton().unwrap();
1270        (lsp_store, buffer)
1271    });
1272    let fake_language_server = fake_language_servers.next().await.unwrap();
1273    cx_a.run_until_parked();
1274    cx_b.run_until_parked();
1275
1276    let long_request_time = DEFAULT_LSP_REQUEST_TIMEOUT / 2;
1277    let (request_started_tx, mut request_started_rx) = mpsc::unbounded();
1278    let requests_started = Arc::new(AtomicUsize::new(0));
1279    let requests_completed = Arc::new(AtomicUsize::new(0));
1280    let _lens_requests = fake_language_server
1281        .set_request_handler::<lsp::request::CodeLensRequest, _, _>({
1282            let request_started_tx = request_started_tx.clone();
1283            let requests_started = requests_started.clone();
1284            let requests_completed = requests_completed.clone();
1285            move |params, cx| {
1286                let mut request_started_tx = request_started_tx.clone();
1287                let requests_started = requests_started.clone();
1288                let requests_completed = requests_completed.clone();
1289                async move {
1290                    assert_eq!(
1291                        params.text_document.uri.as_str(),
1292                        uri!("file:///dir/one.rs")
1293                    );
1294                    requests_started.fetch_add(1, atomic::Ordering::Release);
1295                    request_started_tx.send(()).await.unwrap();
1296                    cx.background_executor().timer(long_request_time).await;
1297                    let i = requests_completed.fetch_add(1, atomic::Ordering::Release) + 1;
1298                    Ok(Some(vec![lsp::CodeLens {
1299                        range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 9)),
1300                        command: Some(lsp::Command {
1301                            title: format!("LSP Command {i}"),
1302                            command: command_name.to_string(),
1303                            arguments: None,
1304                        }),
1305                        data: None,
1306                    }]))
1307                }
1308            }
1309        });
1310
1311    // Move cursor to a location, this should trigger the code lens call.
1312    editor_b.update_in(cx_b, |editor, window, cx| {
1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1314            s.select_ranges([MultiBufferOffset(7)..MultiBufferOffset(7)])
1315        });
1316    });
1317    let () = request_started_rx.next().await.unwrap();
1318    assert_eq!(
1319        requests_started.load(atomic::Ordering::Acquire),
1320        1,
1321        "Selection change should have initiated the first request"
1322    );
1323    assert_eq!(
1324        requests_completed.load(atomic::Ordering::Acquire),
1325        0,
1326        "Slow requests should be running still"
1327    );
1328    let _first_task = lsp_store_b.update(cx_b, |lsp_store, cx| {
1329        lsp_store
1330            .forget_code_lens_task(buffer_b.read(cx).remote_id())
1331            .expect("Should have the fetch task started")
1332    });
1333
1334    editor_b.update_in(cx_b, |editor, window, cx| {
1335        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1336            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
1337        });
1338    });
1339    let () = request_started_rx.next().await.unwrap();
1340    assert_eq!(
1341        requests_started.load(atomic::Ordering::Acquire),
1342        2,
1343        "Selection change should have initiated the second request"
1344    );
1345    assert_eq!(
1346        requests_completed.load(atomic::Ordering::Acquire),
1347        0,
1348        "Slow requests should be running still"
1349    );
1350    let _second_task = lsp_store_b.update(cx_b, |lsp_store, cx| {
1351        lsp_store
1352            .forget_code_lens_task(buffer_b.read(cx).remote_id())
1353            .expect("Should have the fetch task started for the 2nd time")
1354    });
1355
1356    editor_b.update_in(cx_b, |editor, window, cx| {
1357        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1358            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
1359        });
1360    });
1361    let () = request_started_rx.next().await.unwrap();
1362    assert_eq!(
1363        requests_started.load(atomic::Ordering::Acquire),
1364        3,
1365        "Selection change should have initiated the third request"
1366    );
1367    assert_eq!(
1368        requests_completed.load(atomic::Ordering::Acquire),
1369        0,
1370        "Slow requests should be running still"
1371    );
1372
1373    _first_task.await.unwrap();
1374    _second_task.await.unwrap();
1375    cx_b.run_until_parked();
1376    assert_eq!(
1377        requests_started.load(atomic::Ordering::Acquire),
1378        3,
1379        "No selection changes should trigger no more code lens requests"
1380    );
1381    assert_eq!(
1382        requests_completed.load(atomic::Ordering::Acquire),
1383        1,
1384        "After enough time, a single, deduplicated, LSP request should have been served by the language server"
1385    );
1386    let resulting_lens_actions = editor_b
1387        .update(cx_b, |editor, cx| {
1388            let lsp_store = editor.project().unwrap().read(cx).lsp_store();
1389            lsp_store.update(cx, |lsp_store, cx| {
1390                lsp_store.code_lens_actions(&buffer_b, cx)
1391            })
1392        })
1393        .await
1394        .unwrap()
1395        .unwrap();
1396    assert_eq!(
1397        resulting_lens_actions.len(),
1398        1,
1399        "Should have fetched one code lens action, but got: {resulting_lens_actions:?}"
1400    );
1401    assert_eq!(
1402        resulting_lens_actions.first().unwrap().lsp_action.title(),
1403        "LSP Command 1",
1404        "Only the final code lens action should be in the data"
1405    )
1406}
1407
1408#[gpui::test(iterations = 10)]
1409async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1410    let mut server = TestServer::start(cx_a.executor()).await;
1411    let executor = cx_a.executor();
1412    let client_a = server.create_client(cx_a, "user_a").await;
1413    let client_b = server.create_client(cx_b, "user_b").await;
1414    server
1415        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1416        .await;
1417    let active_call_a = cx_a.read(ActiveCall::global);
1418
1419    cx_b.update(editor::init);
1420
1421    client_a.language_registry().add(rust_lang());
1422    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1423        "Rust",
1424        FakeLspAdapter {
1425            name: "the-language-server",
1426            ..Default::default()
1427        },
1428    );
1429
1430    client_a
1431        .fs()
1432        .insert_tree(
1433            path!("/dir"),
1434            json!({
1435                "main.rs": "const ONE: usize = 1;",
1436            }),
1437        )
1438        .await;
1439    let (project_a, _) = client_a.build_local_project(path!("/dir"), cx_a).await;
1440
1441    let _buffer_a = project_a
1442        .update(cx_a, |p, cx| {
1443            p.open_local_buffer_with_lsp(path!("/dir/main.rs"), cx)
1444        })
1445        .await
1446        .unwrap();
1447
1448    let fake_language_server = fake_language_servers.next().await.unwrap();
1449    executor.run_until_parked();
1450    fake_language_server.start_progress("the-token").await;
1451
1452    executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
1453    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1454        token: lsp::NumberOrString::String("the-token".to_string()),
1455        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
1456            lsp::WorkDoneProgressReport {
1457                message: Some("the-message".to_string()),
1458                ..Default::default()
1459            },
1460        )),
1461    });
1462    executor.run_until_parked();
1463
1464    let token = ProgressToken::String(SharedString::from("the-token"));
1465
1466    project_a.read_with(cx_a, |project, cx| {
1467        let status = project.language_server_statuses(cx).next().unwrap().1;
1468        assert_eq!(status.name.0, "the-language-server");
1469        assert_eq!(status.pending_work.len(), 1);
1470        assert_eq!(
1471            status.pending_work[&token].message.as_ref().unwrap(),
1472            "the-message"
1473        );
1474    });
1475
1476    let project_id = active_call_a
1477        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1478        .await
1479        .unwrap();
1480    executor.run_until_parked();
1481    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1482
1483    project_b.read_with(cx_b, |project, cx| {
1484        let status = project.language_server_statuses(cx).next().unwrap().1;
1485        assert_eq!(status.name.0, "the-language-server");
1486    });
1487
1488    executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
1489    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1490        token: lsp::NumberOrString::String("the-token".to_string()),
1491        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
1492            lsp::WorkDoneProgressReport {
1493                message: Some("the-message-2".to_string()),
1494                ..Default::default()
1495            },
1496        )),
1497    });
1498    executor.run_until_parked();
1499
1500    project_a.read_with(cx_a, |project, cx| {
1501        let status = project.language_server_statuses(cx).next().unwrap().1;
1502        assert_eq!(status.name.0, "the-language-server");
1503        assert_eq!(status.pending_work.len(), 1);
1504        assert_eq!(
1505            status.pending_work[&token].message.as_ref().unwrap(),
1506            "the-message-2"
1507        );
1508    });
1509
1510    project_b.read_with(cx_b, |project, cx| {
1511        let status = project.language_server_statuses(cx).next().unwrap().1;
1512        assert_eq!(status.name.0, "the-language-server");
1513        assert_eq!(status.pending_work.len(), 1);
1514        assert_eq!(
1515            status.pending_work[&token].message.as_ref().unwrap(),
1516            "the-message-2"
1517        );
1518    });
1519}
1520
1521#[gpui::test(iterations = 10)]
1522async fn test_share_project(
1523    cx_a: &mut TestAppContext,
1524    cx_b: &mut TestAppContext,
1525    cx_c: &mut TestAppContext,
1526) {
1527    let executor = cx_a.executor();
1528    let cx_b = cx_b.add_empty_window();
1529    let mut server = TestServer::start(executor.clone()).await;
1530    let client_a = server.create_client(cx_a, "user_a").await;
1531    let client_b = server.create_client(cx_b, "user_b").await;
1532    let client_c = server.create_client(cx_c, "user_c").await;
1533    server
1534        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1535        .await;
1536    let active_call_a = cx_a.read(ActiveCall::global);
1537    let active_call_b = cx_b.read(ActiveCall::global);
1538    let active_call_c = cx_c.read(ActiveCall::global);
1539
1540    client_a
1541        .fs()
1542        .insert_tree(
1543            path!("/a"),
1544            json!({
1545                ".gitignore": "ignored-dir",
1546                "a.txt": "a-contents",
1547                "b.txt": "b-contents",
1548                "ignored-dir": {
1549                    "c.txt": "",
1550                    "d.txt": "",
1551                }
1552            }),
1553        )
1554        .await;
1555
1556    // Invite client B to collaborate on a project
1557    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1558    active_call_a
1559        .update(cx_a, |call, cx| {
1560            call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
1561        })
1562        .await
1563        .unwrap();
1564
1565    // Join that project as client B
1566
1567    let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
1568    executor.run_until_parked();
1569    let call = incoming_call_b.borrow().clone().unwrap();
1570    assert_eq!(call.calling_user.github_login, "user_a");
1571    let initial_project = call.initial_project.unwrap();
1572    active_call_b
1573        .update(cx_b, |call, cx| call.accept_incoming(cx))
1574        .await
1575        .unwrap();
1576    let client_b_peer_id = client_b.peer_id().unwrap();
1577    let project_b = client_b.join_remote_project(initial_project.id, cx_b).await;
1578
1579    let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
1580
1581    executor.run_until_parked();
1582
1583    project_a.read_with(cx_a, |project, _| {
1584        let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
1585        assert_eq!(client_b_collaborator.replica_id, replica_id_b);
1586    });
1587
1588    project_b.read_with(cx_b, |project, cx| {
1589        let worktree = project.worktrees(cx).next().unwrap().read(cx);
1590        assert_eq!(
1591            worktree.paths().collect::<Vec<_>>(),
1592            [
1593                rel_path(".gitignore"),
1594                rel_path("a.txt"),
1595                rel_path("b.txt"),
1596                rel_path("ignored-dir"),
1597            ]
1598        );
1599    });
1600
1601    project_b
1602        .update(cx_b, |project, cx| {
1603            let worktree = project.worktrees(cx).next().unwrap();
1604            let entry = worktree
1605                .read(cx)
1606                .entry_for_path(rel_path("ignored-dir"))
1607                .unwrap();
1608            project.expand_entry(worktree_id, entry.id, cx).unwrap()
1609        })
1610        .await
1611        .unwrap();
1612
1613    project_b.read_with(cx_b, |project, cx| {
1614        let worktree = project.worktrees(cx).next().unwrap().read(cx);
1615        assert_eq!(
1616            worktree.paths().collect::<Vec<_>>(),
1617            [
1618                rel_path(".gitignore"),
1619                rel_path("a.txt"),
1620                rel_path("b.txt"),
1621                rel_path("ignored-dir"),
1622                rel_path("ignored-dir/c.txt"),
1623                rel_path("ignored-dir/d.txt"),
1624            ]
1625        );
1626    });
1627
1628    // Open the same file as client B and client A.
1629    let buffer_b = project_b
1630        .update(cx_b, |p, cx| {
1631            p.open_buffer((worktree_id, rel_path("b.txt")), cx)
1632        })
1633        .await
1634        .unwrap();
1635
1636    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
1637
1638    project_a.read_with(cx_a, |project, cx| {
1639        assert!(project.has_open_buffer((worktree_id, rel_path("b.txt")), cx))
1640    });
1641    let buffer_a = project_a
1642        .update(cx_a, |p, cx| {
1643            p.open_buffer((worktree_id, rel_path("b.txt")), cx)
1644        })
1645        .await
1646        .unwrap();
1647
1648    let editor_b =
1649        cx_b.new_window_entity(|window, cx| Editor::for_buffer(buffer_b, None, window, cx));
1650
1651    // Client A sees client B's selection
1652    executor.run_until_parked();
1653
1654    buffer_a.read_with(cx_a, |buffer, _| {
1655        buffer
1656            .snapshot()
1657            .selections_in_range(
1658                text::Anchor::min_max_range_for_buffer(buffer.remote_id()),
1659                false,
1660            )
1661            .count()
1662            == 1
1663    });
1664
1665    // Edit the buffer as client B and see that edit as client A.
1666    editor_b.update_in(cx_b, |editor, window, cx| {
1667        editor.handle_input("ok, ", window, cx)
1668    });
1669    executor.run_until_parked();
1670
1671    buffer_a.read_with(cx_a, |buffer, _| {
1672        assert_eq!(buffer.text(), "ok, b-contents")
1673    });
1674
1675    // Client B can invite client C on a project shared by client A.
1676    active_call_b
1677        .update(cx_b, |call, cx| {
1678            call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
1679        })
1680        .await
1681        .unwrap();
1682
1683    let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
1684    executor.run_until_parked();
1685    let call = incoming_call_c.borrow().clone().unwrap();
1686    assert_eq!(call.calling_user.github_login, "user_b");
1687    let initial_project = call.initial_project.unwrap();
1688    active_call_c
1689        .update(cx_c, |call, cx| call.accept_incoming(cx))
1690        .await
1691        .unwrap();
1692    let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
1693
1694    // Client B closes the editor, and client A sees client B's selections removed.
1695    cx_b.update(move |_, _| drop(editor_b));
1696    executor.run_until_parked();
1697
1698    buffer_a.read_with(cx_a, |buffer, _| {
1699        buffer
1700            .snapshot()
1701            .selections_in_range(
1702                text::Anchor::min_max_range_for_buffer(buffer.remote_id()),
1703                false,
1704            )
1705            .count()
1706            == 0
1707    });
1708}
1709
1710#[gpui::test(iterations = 10)]
1711async fn test_on_input_format_from_host_to_guest(
1712    cx_a: &mut TestAppContext,
1713    cx_b: &mut TestAppContext,
1714) {
1715    let mut server = TestServer::start(cx_a.executor()).await;
1716    let executor = cx_a.executor();
1717    let client_a = server.create_client(cx_a, "user_a").await;
1718    let client_b = server.create_client(cx_b, "user_b").await;
1719    server
1720        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1721        .await;
1722    let active_call_a = cx_a.read(ActiveCall::global);
1723
1724    client_a.language_registry().add(rust_lang());
1725    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1726        "Rust",
1727        FakeLspAdapter {
1728            capabilities: lsp::ServerCapabilities {
1729                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1730                    first_trigger_character: ":".to_string(),
1731                    more_trigger_character: Some(vec![">".to_string()]),
1732                }),
1733                ..Default::default()
1734            },
1735            ..Default::default()
1736        },
1737    );
1738
1739    client_a
1740        .fs()
1741        .insert_tree(
1742            path!("/a"),
1743            json!({
1744                "main.rs": "fn main() { a }",
1745                "other.rs": "// Test file",
1746            }),
1747        )
1748        .await;
1749    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1750    let project_id = active_call_a
1751        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1752        .await
1753        .unwrap();
1754    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1755
1756    // Open a file in an editor as the host.
1757    let buffer_a = project_a
1758        .update(cx_a, |p, cx| {
1759            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1760        })
1761        .await
1762        .unwrap();
1763    let cx_a = cx_a.add_empty_window();
1764    let editor_a = cx_a.new_window_entity(|window, cx| {
1765        Editor::for_buffer(buffer_a, Some(project_a.clone()), window, cx)
1766    });
1767
1768    let fake_language_server = fake_language_servers.next().await.unwrap();
1769    executor.run_until_parked();
1770
1771    // Receive an OnTypeFormatting request as the host's language server.
1772    // Return some formatting from the host's language server.
1773    fake_language_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
1774        |params, _| async move {
1775            assert_eq!(
1776                params.text_document_position.text_document.uri,
1777                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
1778            );
1779            assert_eq!(
1780                params.text_document_position.position,
1781                lsp::Position::new(0, 14),
1782            );
1783
1784            Ok(Some(vec![lsp::TextEdit {
1785                new_text: "~<".to_string(),
1786                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1787            }]))
1788        },
1789    );
1790
1791    // Open the buffer on the guest and see that the formatting worked
1792    let buffer_b = project_b
1793        .update(cx_b, |p, cx| {
1794            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1795        })
1796        .await
1797        .unwrap();
1798
1799    // Type a on type formatting trigger character as the guest.
1800    cx_a.focus(&editor_a);
1801    editor_a.update_in(cx_a, |editor, window, cx| {
1802        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1803            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
1804        });
1805        editor.handle_input(">", window, cx);
1806    });
1807
1808    executor.run_until_parked();
1809
1810    buffer_b.read_with(cx_b, |buffer, _| {
1811        assert_eq!(buffer.text(), "fn main() { a>~< }")
1812    });
1813
1814    // Undo should remove LSP edits first
1815    editor_a.update_in(cx_a, |editor, window, cx| {
1816        assert_eq!(editor.text(cx), "fn main() { a>~< }");
1817        editor.undo(&Undo, window, cx);
1818        assert_eq!(editor.text(cx), "fn main() { a> }");
1819    });
1820    executor.run_until_parked();
1821
1822    buffer_b.read_with(cx_b, |buffer, _| {
1823        assert_eq!(buffer.text(), "fn main() { a> }")
1824    });
1825
1826    editor_a.update_in(cx_a, |editor, window, cx| {
1827        assert_eq!(editor.text(cx), "fn main() { a> }");
1828        editor.undo(&Undo, window, cx);
1829        assert_eq!(editor.text(cx), "fn main() { a }");
1830    });
1831    executor.run_until_parked();
1832
1833    buffer_b.read_with(cx_b, |buffer, _| {
1834        assert_eq!(buffer.text(), "fn main() { a }")
1835    });
1836}
1837
1838#[gpui::test(iterations = 10)]
1839async fn test_on_input_format_from_guest_to_host(
1840    cx_a: &mut TestAppContext,
1841    cx_b: &mut TestAppContext,
1842) {
1843    let mut server = TestServer::start(cx_a.executor()).await;
1844    let executor = cx_a.executor();
1845    let client_a = server.create_client(cx_a, "user_a").await;
1846    let client_b = server.create_client(cx_b, "user_b").await;
1847    server
1848        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1849        .await;
1850    let active_call_a = cx_a.read(ActiveCall::global);
1851
1852    let capabilities = lsp::ServerCapabilities {
1853        document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1854            first_trigger_character: ":".to_string(),
1855            more_trigger_character: Some(vec![">".to_string()]),
1856        }),
1857        ..lsp::ServerCapabilities::default()
1858    };
1859    client_a.language_registry().add(rust_lang());
1860    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1861        "Rust",
1862        FakeLspAdapter {
1863            capabilities: capabilities.clone(),
1864            ..FakeLspAdapter::default()
1865        },
1866    );
1867    client_b.language_registry().add(rust_lang());
1868    client_b.language_registry().register_fake_lsp_adapter(
1869        "Rust",
1870        FakeLspAdapter {
1871            capabilities,
1872            ..FakeLspAdapter::default()
1873        },
1874    );
1875
1876    client_a
1877        .fs()
1878        .insert_tree(
1879            path!("/a"),
1880            json!({
1881                "main.rs": "fn main() { a }",
1882                "other.rs": "// Test file",
1883            }),
1884        )
1885        .await;
1886    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1887    let project_id = active_call_a
1888        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1889        .await
1890        .unwrap();
1891    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1892
1893    // Open a file in an editor as the guest.
1894    let buffer_b = project_b
1895        .update(cx_b, |p, cx| {
1896            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1897        })
1898        .await
1899        .unwrap();
1900    let cx_b = cx_b.add_empty_window();
1901    let editor_b = cx_b.new_window_entity(|window, cx| {
1902        Editor::for_buffer(buffer_b, Some(project_b.clone()), window, cx)
1903    });
1904
1905    let fake_language_server = fake_language_servers.next().await.unwrap();
1906    executor.run_until_parked();
1907
1908    // Type a on type formatting trigger character as the guest.
1909    cx_b.focus(&editor_b);
1910    editor_b.update_in(cx_b, |editor, window, cx| {
1911        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1912            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
1913        });
1914        editor.handle_input(":", window, cx);
1915    });
1916
1917    // Receive an OnTypeFormatting request as the host's language server.
1918    // Return some formatting from the host's language server.
1919    fake_language_server
1920        .set_request_handler::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
1921            assert_eq!(
1922                params.text_document_position.text_document.uri,
1923                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
1924            );
1925            assert_eq!(
1926                params.text_document_position.position,
1927                lsp::Position::new(0, 14),
1928            );
1929
1930            Ok(Some(vec![lsp::TextEdit {
1931                new_text: "~:".to_string(),
1932                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1933            }]))
1934        })
1935        .next()
1936        .await
1937        .unwrap();
1938
1939    // Open the buffer on the host and see that the formatting worked
1940    let buffer_a = project_a
1941        .update(cx_a, |p, cx| {
1942            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1943        })
1944        .await
1945        .unwrap();
1946    executor.run_until_parked();
1947
1948    buffer_a.read_with(cx_a, |buffer, _| {
1949        assert_eq!(buffer.text(), "fn main() { a:~: }")
1950    });
1951
1952    // Undo should remove LSP edits first
1953    editor_b.update_in(cx_b, |editor, window, cx| {
1954        assert_eq!(editor.text(cx), "fn main() { a:~: }");
1955        editor.undo(&Undo, window, cx);
1956        assert_eq!(editor.text(cx), "fn main() { a: }");
1957    });
1958    executor.run_until_parked();
1959
1960    buffer_a.read_with(cx_a, |buffer, _| {
1961        assert_eq!(buffer.text(), "fn main() { a: }")
1962    });
1963
1964    editor_b.update_in(cx_b, |editor, window, cx| {
1965        assert_eq!(editor.text(cx), "fn main() { a: }");
1966        editor.undo(&Undo, window, cx);
1967        assert_eq!(editor.text(cx), "fn main() { a }");
1968    });
1969    executor.run_until_parked();
1970
1971    buffer_a.read_with(cx_a, |buffer, _| {
1972        assert_eq!(buffer.text(), "fn main() { a }")
1973    });
1974}
1975
1976#[gpui::test(iterations = 10)]
1977async fn test_mutual_editor_inlay_hint_cache_update(
1978    cx_a: &mut TestAppContext,
1979    cx_b: &mut TestAppContext,
1980) {
1981    let mut server = TestServer::start(cx_a.executor()).await;
1982    let executor = cx_a.executor();
1983    let client_a = server.create_client(cx_a, "user_a").await;
1984    let client_b = server.create_client(cx_b, "user_b").await;
1985    server
1986        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1987        .await;
1988    let active_call_a = cx_a.read(ActiveCall::global);
1989    let active_call_b = cx_b.read(ActiveCall::global);
1990
1991    cx_a.update(editor::init);
1992    cx_b.update(editor::init);
1993
1994    cx_a.update(|cx| {
1995        SettingsStore::update_global(cx, |store, cx| {
1996            store.update_user_settings(cx, |settings| {
1997                settings.project.all_languages.defaults.inlay_hints =
1998                    Some(InlayHintSettingsContent {
1999                        enabled: Some(true),
2000                        ..InlayHintSettingsContent::default()
2001                    })
2002            });
2003        });
2004    });
2005    cx_b.update(|cx| {
2006        SettingsStore::update_global(cx, |store, cx| {
2007            store.update_user_settings(cx, |settings| {
2008                settings.project.all_languages.defaults.inlay_hints =
2009                    Some(InlayHintSettingsContent {
2010                        enabled: Some(true),
2011                        ..InlayHintSettingsContent::default()
2012                    })
2013            });
2014        });
2015    });
2016
2017    let capabilities = lsp::ServerCapabilities {
2018        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2019        ..lsp::ServerCapabilities::default()
2020    };
2021    client_a.language_registry().add(rust_lang());
2022
2023    // Set up the language server to return an additional inlay hint on each request.
2024    let edits_made = Arc::new(AtomicUsize::new(0));
2025    let closure_edits_made = Arc::clone(&edits_made);
2026    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2027        "Rust",
2028        FakeLspAdapter {
2029            capabilities: capabilities.clone(),
2030            initializer: Some(Box::new(move |fake_language_server| {
2031                let closure_edits_made = closure_edits_made.clone();
2032                fake_language_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
2033                    move |params, _| {
2034                        let edits_made_2 = Arc::clone(&closure_edits_made);
2035                        async move {
2036                            assert_eq!(
2037                                params.text_document.uri,
2038                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2039                            );
2040                            let edits_made =
2041                                AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire);
2042                            Ok(Some(vec![lsp::InlayHint {
2043                                position: lsp::Position::new(0, edits_made as u32),
2044                                label: lsp::InlayHintLabel::String(edits_made.to_string()),
2045                                kind: None,
2046                                text_edits: None,
2047                                tooltip: None,
2048                                padding_left: None,
2049                                padding_right: None,
2050                                data: None,
2051                            }]))
2052                        }
2053                    },
2054                );
2055            })),
2056            ..FakeLspAdapter::default()
2057        },
2058    );
2059    client_b.language_registry().add(rust_lang());
2060    client_b.language_registry().register_fake_lsp_adapter(
2061        "Rust",
2062        FakeLspAdapter {
2063            capabilities,
2064            ..FakeLspAdapter::default()
2065        },
2066    );
2067
2068    // Client A opens a project.
2069    client_a
2070        .fs()
2071        .insert_tree(
2072            path!("/a"),
2073            json!({
2074                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
2075                "other.rs": "// Test file",
2076            }),
2077        )
2078        .await;
2079    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2080    active_call_a
2081        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2082        .await
2083        .unwrap();
2084    let project_id = active_call_a
2085        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2086        .await
2087        .unwrap();
2088
2089    // Client B joins the project
2090    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2091    active_call_b
2092        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2093        .await
2094        .unwrap();
2095
2096    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2097
2098    // The host opens a rust file.
2099    let file_a = workspace_a.update_in(cx_a, |workspace, window, cx| {
2100        workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2101    });
2102    let fake_language_server = fake_language_servers.next().await.unwrap();
2103    let editor_a = file_a.await.unwrap().downcast::<Editor>().unwrap();
2104    executor.advance_clock(Duration::from_millis(100));
2105    executor.run_until_parked();
2106
2107    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
2108    editor_a.update(cx_a, |editor, cx| {
2109        assert_eq!(
2110            vec![initial_edit.to_string()],
2111            extract_hint_labels(editor, cx),
2112            "Host should get its first hints when opens an editor"
2113        );
2114    });
2115    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2116    let editor_b = workspace_b
2117        .update_in(cx_b, |workspace, window, cx| {
2118            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2119        })
2120        .await
2121        .unwrap()
2122        .downcast::<Editor>()
2123        .unwrap();
2124
2125    executor.advance_clock(Duration::from_millis(100));
2126    executor.run_until_parked();
2127    editor_b.update(cx_b, |editor, cx| {
2128        assert_eq!(
2129            vec![initial_edit.to_string()],
2130            extract_hint_labels(editor, cx),
2131            "Client should get its first hints when opens an editor"
2132        );
2133    });
2134
2135    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
2136    editor_b.update_in(cx_b, |editor, window, cx| {
2137        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2138            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)].clone())
2139        });
2140        editor.handle_input(":", window, cx);
2141    });
2142    cx_b.focus(&editor_b);
2143
2144    executor.advance_clock(Duration::from_secs(1));
2145    executor.run_until_parked();
2146    editor_a.update(cx_a, |editor, cx| {
2147        assert_eq!(
2148            vec![after_client_edit.to_string()],
2149            extract_hint_labels(editor, cx),
2150        );
2151    });
2152    editor_b.update(cx_b, |editor, cx| {
2153        assert_eq!(
2154            vec![after_client_edit.to_string()],
2155            extract_hint_labels(editor, cx),
2156        );
2157    });
2158
2159    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
2160    editor_a.update_in(cx_a, |editor, window, cx| {
2161        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2162            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
2163        });
2164        editor.handle_input("a change to increment both buffers' versions", window, cx);
2165    });
2166    cx_a.focus(&editor_a);
2167
2168    executor.advance_clock(Duration::from_secs(1));
2169    executor.run_until_parked();
2170    editor_a.update(cx_a, |editor, cx| {
2171        assert_eq!(
2172            vec![after_host_edit.to_string()],
2173            extract_hint_labels(editor, cx),
2174        );
2175    });
2176    editor_b.update(cx_b, |editor, cx| {
2177        assert_eq!(
2178            vec![after_host_edit.to_string()],
2179            extract_hint_labels(editor, cx),
2180        );
2181    });
2182
2183    let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
2184    fake_language_server
2185        .request::<lsp::request::InlayHintRefreshRequest>((), DEFAULT_LSP_REQUEST_TIMEOUT)
2186        .await
2187        .into_response()
2188        .expect("inlay refresh request failed");
2189
2190    executor.advance_clock(Duration::from_secs(1));
2191    executor.run_until_parked();
2192    editor_a.update(cx_a, |editor, cx| {
2193        assert_eq!(
2194            vec![after_special_edit_for_refresh.to_string()],
2195            extract_hint_labels(editor, cx),
2196            "Host should react to /refresh LSP request"
2197        );
2198    });
2199    editor_b.update(cx_b, |editor, cx| {
2200        assert_eq!(
2201            vec![after_special_edit_for_refresh.to_string()],
2202            extract_hint_labels(editor, cx),
2203            "Guest should get a /refresh LSP request propagated by host"
2204        );
2205    });
2206}
2207
2208#[gpui::test(iterations = 10)]
2209async fn test_inlay_hint_refresh_is_forwarded(
2210    cx_a: &mut TestAppContext,
2211    cx_b: &mut TestAppContext,
2212) {
2213    let mut server = TestServer::start(cx_a.executor()).await;
2214    let executor = cx_a.executor();
2215    let client_a = server.create_client(cx_a, "user_a").await;
2216    let client_b = server.create_client(cx_b, "user_b").await;
2217    server
2218        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2219        .await;
2220    let active_call_a = cx_a.read(ActiveCall::global);
2221    let active_call_b = cx_b.read(ActiveCall::global);
2222
2223    cx_a.update(editor::init);
2224    cx_b.update(editor::init);
2225
2226    cx_a.update(|cx| {
2227        SettingsStore::update_global(cx, |store, cx| {
2228            store.update_user_settings(cx, |settings| {
2229                settings.project.all_languages.defaults.inlay_hints =
2230                    Some(InlayHintSettingsContent {
2231                        show_value_hints: Some(true),
2232                        enabled: Some(false),
2233                        edit_debounce_ms: Some(0),
2234                        scroll_debounce_ms: Some(0),
2235                        show_type_hints: Some(false),
2236                        show_parameter_hints: Some(false),
2237                        show_other_hints: Some(false),
2238                        show_background: Some(false),
2239                        toggle_on_modifiers_press: None,
2240                    })
2241            });
2242        });
2243    });
2244    cx_b.update(|cx| {
2245        SettingsStore::update_global(cx, |store, cx| {
2246            store.update_user_settings(cx, |settings| {
2247                settings.project.all_languages.defaults.inlay_hints =
2248                    Some(InlayHintSettingsContent {
2249                        show_value_hints: Some(true),
2250                        enabled: Some(true),
2251                        edit_debounce_ms: Some(0),
2252                        scroll_debounce_ms: Some(0),
2253                        show_type_hints: Some(true),
2254                        show_parameter_hints: Some(true),
2255                        show_other_hints: Some(true),
2256                        show_background: Some(false),
2257                        toggle_on_modifiers_press: None,
2258                    })
2259            });
2260        });
2261    });
2262
2263    let capabilities = lsp::ServerCapabilities {
2264        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2265        ..lsp::ServerCapabilities::default()
2266    };
2267    client_a.language_registry().add(rust_lang());
2268    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2269        "Rust",
2270        FakeLspAdapter {
2271            capabilities: capabilities.clone(),
2272            ..FakeLspAdapter::default()
2273        },
2274    );
2275    client_b.language_registry().add(rust_lang());
2276    client_b.language_registry().register_fake_lsp_adapter(
2277        "Rust",
2278        FakeLspAdapter {
2279            capabilities,
2280            ..FakeLspAdapter::default()
2281        },
2282    );
2283
2284    client_a
2285        .fs()
2286        .insert_tree(
2287            path!("/a"),
2288            json!({
2289                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
2290                "other.rs": "// Test file",
2291            }),
2292        )
2293        .await;
2294    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2295    active_call_a
2296        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2297        .await
2298        .unwrap();
2299    let project_id = active_call_a
2300        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2301        .await
2302        .unwrap();
2303
2304    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2305    active_call_b
2306        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2307        .await
2308        .unwrap();
2309
2310    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2311    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2312
2313    let editor_a = workspace_a
2314        .update_in(cx_a, |workspace, window, cx| {
2315            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2316        })
2317        .await
2318        .unwrap()
2319        .downcast::<Editor>()
2320        .unwrap();
2321
2322    let editor_b = workspace_b
2323        .update_in(cx_b, |workspace, window, cx| {
2324            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2325        })
2326        .await
2327        .unwrap()
2328        .downcast::<Editor>()
2329        .unwrap();
2330
2331    let other_hints = Arc::new(AtomicBool::new(false));
2332    let fake_language_server = fake_language_servers.next().await.unwrap();
2333    let closure_other_hints = Arc::clone(&other_hints);
2334    fake_language_server
2335        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2336            let task_other_hints = Arc::clone(&closure_other_hints);
2337            async move {
2338                assert_eq!(
2339                    params.text_document.uri,
2340                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2341                );
2342                let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
2343                let character = if other_hints { 0 } else { 2 };
2344                let label = if other_hints {
2345                    "other hint"
2346                } else {
2347                    "initial hint"
2348                };
2349                Ok(Some(vec![
2350                    lsp::InlayHint {
2351                        position: lsp::Position::new(0, character),
2352                        label: lsp::InlayHintLabel::String(label.to_string()),
2353                        kind: None,
2354                        text_edits: None,
2355                        tooltip: None,
2356                        padding_left: None,
2357                        padding_right: None,
2358                        data: None,
2359                    },
2360                    lsp::InlayHint {
2361                        position: lsp::Position::new(1090, 1090),
2362                        label: lsp::InlayHintLabel::String("out-of-bounds hint".to_string()),
2363                        kind: None,
2364                        text_edits: None,
2365                        tooltip: None,
2366                        padding_left: None,
2367                        padding_right: None,
2368                        data: None,
2369                    },
2370                ]))
2371            }
2372        })
2373        .next()
2374        .await
2375        .unwrap();
2376
2377    executor.run_until_parked();
2378    editor_a.update(cx_a, |editor, cx| {
2379        assert!(
2380            extract_hint_labels(editor, cx).is_empty(),
2381            "Host should get no hints due to them turned off"
2382        );
2383    });
2384
2385    executor.run_until_parked();
2386    editor_b.update(cx_b, |editor, cx| {
2387        assert_eq!(
2388            vec!["initial hint".to_string()],
2389            extract_hint_labels(editor, cx),
2390            "Client should get its first hints when opens an editor"
2391        );
2392    });
2393
2394    other_hints.fetch_or(true, atomic::Ordering::Release);
2395    fake_language_server
2396        .request::<lsp::request::InlayHintRefreshRequest>((), DEFAULT_LSP_REQUEST_TIMEOUT)
2397        .await
2398        .into_response()
2399        .expect("inlay refresh request failed");
2400    executor.run_until_parked();
2401    editor_a.update(cx_a, |editor, cx| {
2402        assert!(
2403            extract_hint_labels(editor, cx).is_empty(),
2404            "Host should get no hints due to them turned off, even after the /refresh"
2405        );
2406    });
2407
2408    executor.run_until_parked();
2409    editor_b.update(cx_b, |editor, cx| {
2410        assert_eq!(
2411            vec!["other hint".to_string()],
2412            extract_hint_labels(editor, cx),
2413            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
2414        );
2415    });
2416}
2417
2418#[gpui::test(iterations = 10)]
2419async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2420    let expected_color = Rgba {
2421        r: 0.33,
2422        g: 0.33,
2423        b: 0.33,
2424        a: 0.33,
2425    };
2426    let mut server = TestServer::start(cx_a.executor()).await;
2427    let executor = cx_a.executor();
2428    let client_a = server.create_client(cx_a, "user_a").await;
2429    let client_b = server.create_client(cx_b, "user_b").await;
2430    server
2431        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2432        .await;
2433    let active_call_a = cx_a.read(ActiveCall::global);
2434    let active_call_b = cx_b.read(ActiveCall::global);
2435
2436    cx_a.update(editor::init);
2437    cx_b.update(editor::init);
2438
2439    cx_a.update(|cx| {
2440        SettingsStore::update_global(cx, |store, cx| {
2441            store.update_user_settings(cx, |settings| {
2442                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::None);
2443            });
2444        });
2445    });
2446    cx_b.update(|cx| {
2447        SettingsStore::update_global(cx, |store, cx| {
2448            store.update_user_settings(cx, |settings| {
2449                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2450            });
2451        });
2452    });
2453
2454    let capabilities = lsp::ServerCapabilities {
2455        color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
2456        ..lsp::ServerCapabilities::default()
2457    };
2458    client_a.language_registry().add(rust_lang());
2459    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2460        "Rust",
2461        FakeLspAdapter {
2462            capabilities: capabilities.clone(),
2463            ..FakeLspAdapter::default()
2464        },
2465    );
2466    client_b.language_registry().add(rust_lang());
2467    client_b.language_registry().register_fake_lsp_adapter(
2468        "Rust",
2469        FakeLspAdapter {
2470            capabilities,
2471            ..FakeLspAdapter::default()
2472        },
2473    );
2474
2475    // Client A opens a project.
2476    client_a
2477        .fs()
2478        .insert_tree(
2479            path!("/a"),
2480            json!({
2481                "main.rs": "fn main() { a }",
2482            }),
2483        )
2484        .await;
2485    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2486    active_call_a
2487        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2488        .await
2489        .unwrap();
2490    let project_id = active_call_a
2491        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2492        .await
2493        .unwrap();
2494
2495    // Client B joins the project
2496    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2497    active_call_b
2498        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2499        .await
2500        .unwrap();
2501
2502    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2503
2504    // The host opens a rust file.
2505    let _buffer_a = project_a
2506        .update(cx_a, |project, cx| {
2507            project.open_local_buffer(path!("/a/main.rs"), cx)
2508        })
2509        .await
2510        .unwrap();
2511    let editor_a = workspace_a
2512        .update_in(cx_a, |workspace, window, cx| {
2513            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2514        })
2515        .await
2516        .unwrap()
2517        .downcast::<Editor>()
2518        .unwrap();
2519
2520    let fake_language_server = fake_language_servers.next().await.unwrap();
2521    cx_a.run_until_parked();
2522    cx_b.run_until_parked();
2523
2524    let requests_made = Arc::new(AtomicUsize::new(0));
2525    let closure_requests_made = Arc::clone(&requests_made);
2526    let mut color_request_handle = fake_language_server
2527        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
2528            let requests_made = Arc::clone(&closure_requests_made);
2529            async move {
2530                assert_eq!(
2531                    params.text_document.uri,
2532                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2533                );
2534                requests_made.fetch_add(1, atomic::Ordering::Release);
2535                Ok(vec![lsp::ColorInformation {
2536                    range: lsp::Range {
2537                        start: lsp::Position {
2538                            line: 0,
2539                            character: 0,
2540                        },
2541                        end: lsp::Position {
2542                            line: 0,
2543                            character: 1,
2544                        },
2545                    },
2546                    color: lsp::Color {
2547                        red: 0.33,
2548                        green: 0.33,
2549                        blue: 0.33,
2550                        alpha: 0.33,
2551                    },
2552                }])
2553            }
2554        });
2555    executor.run_until_parked();
2556
2557    assert_eq!(
2558        0,
2559        requests_made.load(atomic::Ordering::Acquire),
2560        "Host did not enable document colors, hence should query for none"
2561    );
2562    editor_a.update(cx_a, |editor, cx| {
2563        assert_eq!(
2564            Vec::<Rgba>::new(),
2565            extract_color_inlays(editor, cx),
2566            "No query colors should result in no hints"
2567        );
2568    });
2569
2570    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2571    let editor_b = workspace_b
2572        .update_in(cx_b, |workspace, window, cx| {
2573            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2574        })
2575        .await
2576        .unwrap()
2577        .downcast::<Editor>()
2578        .unwrap();
2579
2580    color_request_handle.next().await.unwrap();
2581    executor.advance_clock(LSP_REQUEST_DEBOUNCE_TIMEOUT);
2582    executor.run_until_parked();
2583
2584    assert_eq!(
2585        1,
2586        requests_made.load(atomic::Ordering::Acquire),
2587        "The client opened the file and got its first colors back"
2588    );
2589    editor_b.update(cx_b, |editor, cx| {
2590        assert_eq!(
2591            vec![expected_color],
2592            extract_color_inlays(editor, cx),
2593            "With document colors as inlays, color inlays should be pushed"
2594        );
2595    });
2596
2597    editor_a.update_in(cx_a, |editor, window, cx| {
2598        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2599            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)].clone())
2600        });
2601        editor.handle_input(":", window, cx);
2602    });
2603    color_request_handle.next().await.unwrap();
2604    executor.run_until_parked();
2605    assert_eq!(
2606        2,
2607        requests_made.load(atomic::Ordering::Acquire),
2608        "After the host edits his file, the client should request the colors again"
2609    );
2610    editor_a.update(cx_a, |editor, cx| {
2611        assert_eq!(
2612            Vec::<Rgba>::new(),
2613            extract_color_inlays(editor, cx),
2614            "Host has no colors still"
2615        );
2616    });
2617    editor_b.update(cx_b, |editor, cx| {
2618        assert_eq!(vec![expected_color], extract_color_inlays(editor, cx),);
2619    });
2620
2621    cx_b.update(|_, cx| {
2622        SettingsStore::update_global(cx, |store, cx| {
2623            store.update_user_settings(cx, |settings| {
2624                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Background);
2625            });
2626        });
2627    });
2628    executor.run_until_parked();
2629    assert_eq!(
2630        2,
2631        requests_made.load(atomic::Ordering::Acquire),
2632        "After the client have changed the colors settings, no extra queries should happen"
2633    );
2634    editor_a.update(cx_a, |editor, cx| {
2635        assert_eq!(
2636            Vec::<Rgba>::new(),
2637            extract_color_inlays(editor, cx),
2638            "Host is unaffected by the client's settings changes"
2639        );
2640    });
2641    editor_b.update(cx_b, |editor, cx| {
2642        assert_eq!(
2643            Vec::<Rgba>::new(),
2644            extract_color_inlays(editor, cx),
2645            "Client should have no colors hints, as in the settings"
2646        );
2647    });
2648
2649    cx_b.update(|_, cx| {
2650        SettingsStore::update_global(cx, |store, cx| {
2651            store.update_user_settings(cx, |settings| {
2652                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2653            });
2654        });
2655    });
2656    executor.run_until_parked();
2657    assert_eq!(
2658        2,
2659        requests_made.load(atomic::Ordering::Acquire),
2660        "After falling back to colors as inlays, no extra LSP queries are made"
2661    );
2662    editor_a.update(cx_a, |editor, cx| {
2663        assert_eq!(
2664            Vec::<Rgba>::new(),
2665            extract_color_inlays(editor, cx),
2666            "Host is unaffected by the client's settings changes, again"
2667        );
2668    });
2669    editor_b.update(cx_b, |editor, cx| {
2670        assert_eq!(
2671            vec![expected_color],
2672            extract_color_inlays(editor, cx),
2673            "Client should have its color hints back"
2674        );
2675    });
2676
2677    cx_a.update(|_, cx| {
2678        SettingsStore::update_global(cx, |store, cx| {
2679            store.update_user_settings(cx, |settings| {
2680                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Border);
2681            });
2682        });
2683    });
2684    color_request_handle.next().await.unwrap();
2685    executor.run_until_parked();
2686    assert_eq!(
2687        3,
2688        requests_made.load(atomic::Ordering::Acquire),
2689        "After the host enables document colors, another LSP query should be made"
2690    );
2691    editor_a.update(cx_a, |editor, cx| {
2692        assert_eq!(
2693            Vec::<Rgba>::new(),
2694            extract_color_inlays(editor, cx),
2695            "Host did not configure document colors as hints hence gets nothing"
2696        );
2697    });
2698    editor_b.update(cx_b, |editor, cx| {
2699        assert_eq!(
2700            vec![expected_color],
2701            extract_color_inlays(editor, cx),
2702            "Client should be unaffected by the host's settings changes"
2703        );
2704    });
2705}
2706
2707async fn test_lsp_pull_diagnostics(
2708    should_stream_workspace_diagnostic: bool,
2709    cx_a: &mut TestAppContext,
2710    cx_b: &mut TestAppContext,
2711) {
2712    let mut server = TestServer::start(cx_a.executor()).await;
2713    let executor = cx_a.executor();
2714    let client_a = server.create_client(cx_a, "user_a").await;
2715    let client_b = server.create_client(cx_b, "user_b").await;
2716    server
2717        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2718        .await;
2719    let active_call_a = cx_a.read(ActiveCall::global);
2720    let active_call_b = cx_b.read(ActiveCall::global);
2721
2722    cx_a.update(editor::init);
2723    cx_b.update(editor::init);
2724
2725    let expected_push_diagnostic_main_message = "pushed main diagnostic";
2726    let expected_push_diagnostic_lib_message = "pushed lib diagnostic";
2727    let expected_pull_diagnostic_main_message = "pulled main diagnostic";
2728    let expected_pull_diagnostic_lib_message = "pulled lib diagnostic";
2729    let expected_workspace_pull_diagnostics_main_message = "pulled workspace main diagnostic";
2730    let expected_workspace_pull_diagnostics_lib_message = "pulled workspace lib diagnostic";
2731
2732    let diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<Option<String>>::new()));
2733    let workspace_diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<String>::new()));
2734    let diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2735    let closure_diagnostics_pulls_made = diagnostics_pulls_made.clone();
2736    let closure_diagnostics_pulls_result_ids = diagnostics_pulls_result_ids.clone();
2737    let workspace_diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2738    let closure_workspace_diagnostics_pulls_made = workspace_diagnostics_pulls_made.clone();
2739    let closure_workspace_diagnostics_pulls_result_ids =
2740        workspace_diagnostics_pulls_result_ids.clone();
2741    let (workspace_diagnostic_cancel_tx, closure_workspace_diagnostic_cancel_rx) =
2742        smol::channel::bounded::<()>(1);
2743    let (closure_workspace_diagnostic_received_tx, workspace_diagnostic_received_rx) =
2744        smol::channel::bounded::<()>(1);
2745
2746    let capabilities = lsp::ServerCapabilities {
2747        diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
2748            lsp::DiagnosticOptions {
2749                identifier: Some("test-pulls".to_string()),
2750                inter_file_dependencies: true,
2751                workspace_diagnostics: true,
2752                work_done_progress_options: lsp::WorkDoneProgressOptions {
2753                    work_done_progress: None,
2754                },
2755            },
2756        )),
2757        ..lsp::ServerCapabilities::default()
2758    };
2759    client_a.language_registry().add(rust_lang());
2760
2761    let pull_diagnostics_handle = Arc::new(parking_lot::Mutex::new(None));
2762    let workspace_diagnostics_pulls_handle = Arc::new(parking_lot::Mutex::new(None));
2763
2764    let closure_pull_diagnostics_handle = pull_diagnostics_handle.clone();
2765    let closure_workspace_diagnostics_pulls_handle = workspace_diagnostics_pulls_handle.clone();
2766    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2767        "Rust",
2768        FakeLspAdapter {
2769            capabilities: capabilities.clone(),
2770            initializer: Some(Box::new(move |fake_language_server| {
2771                let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!(
2772                    "workspace/diagnostic/{}/1",
2773                    fake_language_server.server.server_id()
2774                ));
2775                let closure_workspace_diagnostics_pulls_result_ids = closure_workspace_diagnostics_pulls_result_ids.clone();
2776                let diagnostics_pulls_made = closure_diagnostics_pulls_made.clone();
2777                let diagnostics_pulls_result_ids = closure_diagnostics_pulls_result_ids.clone();
2778                let closure_pull_diagnostics_handle = closure_pull_diagnostics_handle.clone();
2779                let closure_workspace_diagnostics_pulls_handle = closure_workspace_diagnostics_pulls_handle.clone();
2780                let closure_workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone();
2781                let closure_workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone();
2782                let pull_diagnostics_handle = fake_language_server
2783                    .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(
2784                        move |params, _| {
2785                            let requests_made = diagnostics_pulls_made.clone();
2786                            let diagnostics_pulls_result_ids =
2787                                diagnostics_pulls_result_ids.clone();
2788                            async move {
2789                                let message = if lsp::Uri::from_file_path(path!("/a/main.rs"))
2790                                    .unwrap()
2791                                    == params.text_document.uri
2792                                {
2793                                    expected_pull_diagnostic_main_message.to_string()
2794                                } else if lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap()
2795                                    == params.text_document.uri
2796                                {
2797                                    expected_pull_diagnostic_lib_message.to_string()
2798                                } else {
2799                                    panic!("Unexpected document: {}", params.text_document.uri)
2800                                };
2801                                {
2802                                    diagnostics_pulls_result_ids
2803                                        .lock()
2804                                        .await
2805                                        .insert(params.previous_result_id);
2806                                }
2807                                let new_requests_count =
2808                                    requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2809                                Ok(lsp::DocumentDiagnosticReportResult::Report(
2810                                    lsp::DocumentDiagnosticReport::Full(
2811                                        lsp::RelatedFullDocumentDiagnosticReport {
2812                                            related_documents: None,
2813                                            full_document_diagnostic_report:
2814                                                lsp::FullDocumentDiagnosticReport {
2815                                                    result_id: Some(format!(
2816                                                        "pull-{new_requests_count}"
2817                                                    )),
2818                                                    items: vec![lsp::Diagnostic {
2819                                                        range: lsp::Range {
2820                                                            start: lsp::Position {
2821                                                                line: 0,
2822                                                                character: 0,
2823                                                            },
2824                                                            end: lsp::Position {
2825                                                                line: 0,
2826                                                                character: 2,
2827                                                            },
2828                                                        },
2829                                                        severity: Some(
2830                                                            lsp::DiagnosticSeverity::ERROR,
2831                                                        ),
2832                                                        message,
2833                                                        ..lsp::Diagnostic::default()
2834                                                    }],
2835                                                },
2836                                        },
2837                                    ),
2838                                ))
2839                            }
2840                        },
2841                    );
2842                let _ = closure_pull_diagnostics_handle.lock().insert(pull_diagnostics_handle);
2843
2844                let closure_workspace_diagnostics_pulls_made = closure_workspace_diagnostics_pulls_made.clone();
2845                let workspace_diagnostics_pulls_handle = fake_language_server.set_request_handler::<lsp::request::WorkspaceDiagnosticRequest, _, _>(
2846                    move |params, _| {
2847                        let workspace_requests_made = closure_workspace_diagnostics_pulls_made.clone();
2848                        let workspace_diagnostics_pulls_result_ids =
2849                            closure_workspace_diagnostics_pulls_result_ids.clone();
2850                        let workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone();
2851                        let workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone();
2852                        let expected_workspace_diagnostic_token = expected_workspace_diagnostic_token.clone();
2853                        async move {
2854                            let workspace_request_count =
2855                                workspace_requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2856                            {
2857                                workspace_diagnostics_pulls_result_ids
2858                                    .lock()
2859                                    .await
2860                                    .extend(params.previous_result_ids.into_iter().map(|id| id.value));
2861                            }
2862                            if should_stream_workspace_diagnostic && !workspace_diagnostic_cancel_rx.is_closed()
2863                            {
2864                                assert_eq!(
2865                                    params.partial_result_params.partial_result_token,
2866                                    Some(expected_workspace_diagnostic_token)
2867                                );
2868                                workspace_diagnostic_received_tx.send(()).await.unwrap();
2869                                workspace_diagnostic_cancel_rx.recv().await.unwrap();
2870                                workspace_diagnostic_cancel_rx.close();
2871                                // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#partialResults
2872                                // > The final response has to be empty in terms of result values.
2873                                return Ok(lsp::WorkspaceDiagnosticReportResult::Report(
2874                                    lsp::WorkspaceDiagnosticReport { items: Vec::new() },
2875                                ));
2876                            }
2877                            Ok(lsp::WorkspaceDiagnosticReportResult::Report(
2878                                lsp::WorkspaceDiagnosticReport {
2879                                    items: vec![
2880                                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2881                                            lsp::WorkspaceFullDocumentDiagnosticReport {
2882                                                uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2883                                                version: None,
2884                                                full_document_diagnostic_report:
2885                                                    lsp::FullDocumentDiagnosticReport {
2886                                                        result_id: Some(format!(
2887                                                            "workspace_{workspace_request_count}"
2888                                                        )),
2889                                                        items: vec![lsp::Diagnostic {
2890                                                            range: lsp::Range {
2891                                                                start: lsp::Position {
2892                                                                    line: 0,
2893                                                                    character: 1,
2894                                                                },
2895                                                                end: lsp::Position {
2896                                                                    line: 0,
2897                                                                    character: 3,
2898                                                                },
2899                                                            },
2900                                                            severity: Some(lsp::DiagnosticSeverity::WARNING),
2901                                                            message:
2902                                                                expected_workspace_pull_diagnostics_main_message
2903                                                                    .to_string(),
2904                                                            ..lsp::Diagnostic::default()
2905                                                        }],
2906                                                    },
2907                                            },
2908                                        ),
2909                                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2910                                            lsp::WorkspaceFullDocumentDiagnosticReport {
2911                                                uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
2912                                                version: None,
2913                                                full_document_diagnostic_report:
2914                                                    lsp::FullDocumentDiagnosticReport {
2915                                                        result_id: Some(format!(
2916                                                            "workspace_{workspace_request_count}"
2917                                                        )),
2918                                                        items: vec![lsp::Diagnostic {
2919                                                            range: lsp::Range {
2920                                                                start: lsp::Position {
2921                                                                    line: 0,
2922                                                                    character: 1,
2923                                                                },
2924                                                                end: lsp::Position {
2925                                                                    line: 0,
2926                                                                    character: 3,
2927                                                                },
2928                                                            },
2929                                                            severity: Some(lsp::DiagnosticSeverity::WARNING),
2930                                                            message:
2931                                                                expected_workspace_pull_diagnostics_lib_message
2932                                                                    .to_string(),
2933                                                            ..lsp::Diagnostic::default()
2934                                                        }],
2935                                                    },
2936                                            },
2937                                        ),
2938                                    ],
2939                                },
2940                            ))
2941                        }
2942                    });
2943                let _ = closure_workspace_diagnostics_pulls_handle.lock().insert(workspace_diagnostics_pulls_handle);
2944            })),
2945            ..FakeLspAdapter::default()
2946        },
2947    );
2948
2949    client_b.language_registry().add(rust_lang());
2950    client_b.language_registry().register_fake_lsp_adapter(
2951        "Rust",
2952        FakeLspAdapter {
2953            capabilities,
2954            ..FakeLspAdapter::default()
2955        },
2956    );
2957
2958    // Client A opens a project.
2959    client_a
2960        .fs()
2961        .insert_tree(
2962            path!("/a"),
2963            json!({
2964                "main.rs": "fn main() { a }",
2965                "lib.rs": "fn other() {}",
2966            }),
2967        )
2968        .await;
2969    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2970    active_call_a
2971        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2972        .await
2973        .unwrap();
2974    let project_id = active_call_a
2975        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2976        .await
2977        .unwrap();
2978
2979    // Client B joins the project
2980    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2981    active_call_b
2982        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2983        .await
2984        .unwrap();
2985
2986    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2987
2988    // The host opens a rust file.
2989    let _buffer_a = project_a
2990        .update(cx_a, |project, cx| {
2991            project.open_local_buffer(path!("/a/main.rs"), cx)
2992        })
2993        .await
2994        .unwrap();
2995    let editor_a_main = workspace_a
2996        .update_in(cx_a, |workspace, window, cx| {
2997            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2998        })
2999        .await
3000        .unwrap()
3001        .downcast::<Editor>()
3002        .unwrap();
3003
3004    let fake_language_server = fake_language_servers.next().await.unwrap();
3005    let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!(
3006        "workspace/diagnostic-{}-1",
3007        fake_language_server.server.server_id()
3008    ));
3009    cx_a.run_until_parked();
3010    cx_b.run_until_parked();
3011    let mut pull_diagnostics_handle = pull_diagnostics_handle.lock().take().unwrap();
3012    let mut workspace_diagnostics_pulls_handle =
3013        workspace_diagnostics_pulls_handle.lock().take().unwrap();
3014
3015    if should_stream_workspace_diagnostic {
3016        workspace_diagnostic_received_rx.recv().await.unwrap();
3017    } else {
3018        workspace_diagnostics_pulls_handle.next().await.unwrap();
3019    }
3020    assert_eq!(
3021        1,
3022        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3023        "Workspace diagnostics should be pulled initially on a server startup"
3024    );
3025    pull_diagnostics_handle.next().await.unwrap();
3026    assert_eq!(
3027        1,
3028        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3029        "Host should query pull diagnostics when the editor is opened"
3030    );
3031    executor.run_until_parked();
3032    editor_a_main.update(cx_a, |editor, cx| {
3033        let snapshot = editor.buffer().read(cx).snapshot(cx);
3034        let all_diagnostics = snapshot
3035            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3036            .collect::<Vec<_>>();
3037        assert_eq!(
3038            all_diagnostics.len(),
3039            1,
3040            "Expected single diagnostic, but got: {all_diagnostics:?}"
3041        );
3042        let diagnostic = &all_diagnostics[0];
3043        let mut expected_messages = vec![expected_pull_diagnostic_main_message];
3044        if !should_stream_workspace_diagnostic {
3045            expected_messages.push(expected_workspace_pull_diagnostics_main_message);
3046        }
3047        assert!(
3048            expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3049            "Expected {expected_messages:?} on the host, but got: {}",
3050            diagnostic.diagnostic.message
3051        );
3052    });
3053
3054    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
3055        lsp::PublishDiagnosticsParams {
3056            uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
3057            diagnostics: vec![lsp::Diagnostic {
3058                range: lsp::Range {
3059                    start: lsp::Position {
3060                        line: 0,
3061                        character: 3,
3062                    },
3063                    end: lsp::Position {
3064                        line: 0,
3065                        character: 4,
3066                    },
3067                },
3068                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
3069                message: expected_push_diagnostic_main_message.to_string(),
3070                ..lsp::Diagnostic::default()
3071            }],
3072            version: None,
3073        },
3074    );
3075    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
3076        lsp::PublishDiagnosticsParams {
3077            uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3078            diagnostics: vec![lsp::Diagnostic {
3079                range: lsp::Range {
3080                    start: lsp::Position {
3081                        line: 0,
3082                        character: 3,
3083                    },
3084                    end: lsp::Position {
3085                        line: 0,
3086                        character: 4,
3087                    },
3088                },
3089                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
3090                message: expected_push_diagnostic_lib_message.to_string(),
3091                ..lsp::Diagnostic::default()
3092            }],
3093            version: None,
3094        },
3095    );
3096
3097    if should_stream_workspace_diagnostic {
3098        fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3099            token: expected_workspace_diagnostic_token.clone(),
3100            value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
3101                lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
3102                    items: vec![
3103                        lsp::WorkspaceDocumentDiagnosticReport::Full(
3104                            lsp::WorkspaceFullDocumentDiagnosticReport {
3105                                uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
3106                                version: None,
3107                                full_document_diagnostic_report:
3108                                    lsp::FullDocumentDiagnosticReport {
3109                                        result_id: Some(format!(
3110                                            "workspace_{}",
3111                                            workspace_diagnostics_pulls_made
3112                                                .fetch_add(1, atomic::Ordering::Release)
3113                                                + 1
3114                                        )),
3115                                        items: vec![lsp::Diagnostic {
3116                                            range: lsp::Range {
3117                                                start: lsp::Position {
3118                                                    line: 0,
3119                                                    character: 1,
3120                                                },
3121                                                end: lsp::Position {
3122                                                    line: 0,
3123                                                    character: 2,
3124                                                },
3125                                            },
3126                                            severity: Some(lsp::DiagnosticSeverity::ERROR),
3127                                            message:
3128                                                expected_workspace_pull_diagnostics_main_message
3129                                                    .to_string(),
3130                                            ..lsp::Diagnostic::default()
3131                                        }],
3132                                    },
3133                            },
3134                        ),
3135                        lsp::WorkspaceDocumentDiagnosticReport::Full(
3136                            lsp::WorkspaceFullDocumentDiagnosticReport {
3137                                uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3138                                version: None,
3139                                full_document_diagnostic_report:
3140                                    lsp::FullDocumentDiagnosticReport {
3141                                        result_id: Some(format!(
3142                                            "workspace_{}",
3143                                            workspace_diagnostics_pulls_made
3144                                                .fetch_add(1, atomic::Ordering::Release)
3145                                                + 1
3146                                        )),
3147                                        items: Vec::new(),
3148                                    },
3149                            },
3150                        ),
3151                    ],
3152                }),
3153            ),
3154        });
3155    };
3156
3157    let mut workspace_diagnostic_start_count =
3158        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
3159
3160    executor.run_until_parked();
3161    editor_a_main.update(cx_a, |editor, cx| {
3162        let snapshot = editor.buffer().read(cx).snapshot(cx);
3163        let all_diagnostics = snapshot
3164            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3165            .collect::<Vec<_>>();
3166        assert_eq!(
3167            all_diagnostics.len(),
3168            2,
3169            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
3170        );
3171        let expected_messages = [
3172            expected_workspace_pull_diagnostics_main_message,
3173            expected_pull_diagnostic_main_message,
3174            expected_push_diagnostic_main_message,
3175        ];
3176        for diagnostic in all_diagnostics {
3177            assert!(
3178                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3179                "Expected push and pull messages on the host: {expected_messages:?}, but got: {}",
3180                diagnostic.diagnostic.message
3181            );
3182        }
3183    });
3184
3185    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3186    let editor_b_main = workspace_b
3187        .update_in(cx_b, |workspace, window, cx| {
3188            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
3189        })
3190        .await
3191        .unwrap()
3192        .downcast::<Editor>()
3193        .unwrap();
3194    cx_b.run_until_parked();
3195
3196    pull_diagnostics_handle.next().await.unwrap();
3197    assert_eq!(
3198        2,
3199        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3200        "Client should query pull diagnostics when its editor is opened"
3201    );
3202    executor.run_until_parked();
3203    assert_eq!(
3204        workspace_diagnostic_start_count,
3205        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3206        "Workspace diagnostics should not be changed as the remote client does not initialize the workspace diagnostics pull"
3207    );
3208    editor_b_main.update(cx_b, |editor, cx| {
3209        let snapshot = editor.buffer().read(cx).snapshot(cx);
3210        let all_diagnostics = snapshot
3211            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3212            .collect::<Vec<_>>();
3213        assert_eq!(
3214            all_diagnostics.len(),
3215            2,
3216            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
3217        );
3218
3219        // Despite the workspace diagnostics not re-initialized for the remote client, we can still expect its message synced from the host.
3220        let expected_messages = [
3221            expected_workspace_pull_diagnostics_main_message,
3222            expected_pull_diagnostic_main_message,
3223            expected_push_diagnostic_main_message,
3224        ];
3225        for diagnostic in all_diagnostics {
3226            assert!(
3227                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3228                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3229                diagnostic.diagnostic.message
3230            );
3231        }
3232    });
3233
3234    let editor_b_lib = workspace_b
3235        .update_in(cx_b, |workspace, window, cx| {
3236            workspace.open_path((worktree_id, rel_path("lib.rs")), None, true, window, cx)
3237        })
3238        .await
3239        .unwrap()
3240        .downcast::<Editor>()
3241        .unwrap();
3242
3243    pull_diagnostics_handle.next().await.unwrap();
3244    assert_eq!(
3245        3,
3246        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3247        "Client should query pull diagnostics when its another editor is opened"
3248    );
3249    executor.run_until_parked();
3250    assert_eq!(
3251        workspace_diagnostic_start_count,
3252        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3253        "The remote client still did not anything to trigger the workspace diagnostics pull"
3254    );
3255    editor_b_lib.update(cx_b, |editor, cx| {
3256        let snapshot = editor.buffer().read(cx).snapshot(cx);
3257        let all_diagnostics = snapshot
3258            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3259            .collect::<Vec<_>>();
3260        let expected_messages = [
3261            expected_pull_diagnostic_lib_message,
3262            expected_push_diagnostic_lib_message,
3263        ];
3264        assert_eq!(
3265            all_diagnostics.len(),
3266            2,
3267            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
3268        );
3269        for diagnostic in all_diagnostics {
3270            assert!(
3271                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3272                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3273                diagnostic.diagnostic.message
3274            );
3275        }
3276    });
3277
3278    if should_stream_workspace_diagnostic {
3279        fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3280            token: expected_workspace_diagnostic_token.clone(),
3281            value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
3282                lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
3283                    items: vec![lsp::WorkspaceDocumentDiagnosticReport::Full(
3284                        lsp::WorkspaceFullDocumentDiagnosticReport {
3285                            uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3286                            version: None,
3287                            full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
3288                                result_id: Some(format!(
3289                                    "workspace_{}",
3290                                    workspace_diagnostics_pulls_made
3291                                        .fetch_add(1, atomic::Ordering::Release)
3292                                        + 1
3293                                )),
3294                                items: vec![lsp::Diagnostic {
3295                                    range: lsp::Range {
3296                                        start: lsp::Position {
3297                                            line: 0,
3298                                            character: 1,
3299                                        },
3300                                        end: lsp::Position {
3301                                            line: 0,
3302                                            character: 2,
3303                                        },
3304                                    },
3305                                    severity: Some(lsp::DiagnosticSeverity::ERROR),
3306                                    message: expected_workspace_pull_diagnostics_lib_message
3307                                        .to_string(),
3308                                    ..lsp::Diagnostic::default()
3309                                }],
3310                            },
3311                        },
3312                    )],
3313                }),
3314            ),
3315        });
3316        workspace_diagnostic_start_count =
3317            workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
3318        workspace_diagnostic_cancel_tx.send(()).await.unwrap();
3319        workspace_diagnostics_pulls_handle.next().await.unwrap();
3320        executor.run_until_parked();
3321        editor_b_lib.update(cx_b, |editor, cx| {
3322            let snapshot = editor.buffer().read(cx).snapshot(cx);
3323            let all_diagnostics = snapshot
3324                .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3325                .collect::<Vec<_>>();
3326            let expected_messages = [
3327                // Despite workspace diagnostics provided,
3328                // the currently open file's diagnostics should be preferred, as LSP suggests.
3329                expected_pull_diagnostic_lib_message,
3330                expected_push_diagnostic_lib_message,
3331            ];
3332            assert_eq!(
3333                all_diagnostics.len(),
3334                2,
3335                "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
3336            );
3337            for diagnostic in all_diagnostics {
3338                assert!(
3339                    expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3340                    "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3341                    diagnostic.diagnostic.message
3342                );
3343            }
3344        });
3345    };
3346
3347    {
3348        assert!(
3349            !diagnostics_pulls_result_ids.lock().await.is_empty(),
3350            "Initial diagnostics pulls should report None at least"
3351        );
3352        assert_eq!(
3353            0,
3354            workspace_diagnostics_pulls_result_ids
3355                .lock()
3356                .await
3357                .deref()
3358                .len(),
3359            "After the initial workspace request, opening files should not reuse any result ids"
3360        );
3361    }
3362
3363    editor_b_lib.update_in(cx_b, |editor, window, cx| {
3364        editor.move_to_end(&MoveToEnd, window, cx);
3365        editor.handle_input(":", window, cx);
3366    });
3367    pull_diagnostics_handle.next().await.unwrap();
3368    // pull_diagnostics_handle.next().await.unwrap();
3369    assert_eq!(
3370        4,
3371        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3372        "Client lib.rs edits should trigger another diagnostics pull for open buffers"
3373    );
3374    workspace_diagnostics_pulls_handle.next().await.unwrap();
3375    assert_eq!(
3376        workspace_diagnostic_start_count + 1,
3377        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3378        "After client lib.rs edits, the workspace diagnostics request should follow"
3379    );
3380    executor.run_until_parked();
3381
3382    editor_b_main.update_in(cx_b, |editor, window, cx| {
3383        editor.move_to_end(&MoveToEnd, window, cx);
3384        editor.handle_input(":", window, cx);
3385    });
3386    pull_diagnostics_handle.next().await.unwrap();
3387    pull_diagnostics_handle.next().await.unwrap();
3388    pull_diagnostics_handle.next().await.unwrap();
3389    assert_eq!(
3390        7,
3391        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3392        "Client main.rs edits should trigger diagnostics pull by both client and host and an extra pull for the client's lib.rs"
3393    );
3394    workspace_diagnostics_pulls_handle.next().await.unwrap();
3395    assert_eq!(
3396        workspace_diagnostic_start_count + 2,
3397        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3398        "After client main.rs edits, the workspace diagnostics pull should follow"
3399    );
3400    executor.run_until_parked();
3401
3402    editor_a_main.update_in(cx_a, |editor, window, cx| {
3403        editor.move_to_end(&MoveToEnd, window, cx);
3404        editor.handle_input(":", window, cx);
3405    });
3406    pull_diagnostics_handle.next().await.unwrap();
3407    pull_diagnostics_handle.next().await.unwrap();
3408    pull_diagnostics_handle.next().await.unwrap();
3409    assert_eq!(
3410        10,
3411        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3412        "Host main.rs edits should trigger another diagnostics pull by both client and host and another pull for the client's lib.rs"
3413    );
3414    workspace_diagnostics_pulls_handle.next().await.unwrap();
3415    assert_eq!(
3416        workspace_diagnostic_start_count + 3,
3417        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3418        "After host main.rs edits, the workspace diagnostics pull should follow"
3419    );
3420    executor.run_until_parked();
3421    let diagnostic_pulls_result_ids = diagnostics_pulls_result_ids.lock().await.len();
3422    let workspace_pulls_result_ids = workspace_diagnostics_pulls_result_ids.lock().await.len();
3423    {
3424        assert!(
3425            diagnostic_pulls_result_ids > 1,
3426            "Should have sent result ids when pulling diagnostics"
3427        );
3428        assert!(
3429            workspace_pulls_result_ids > 1,
3430            "Should have sent result ids when pulling workspace diagnostics"
3431        );
3432    }
3433
3434    fake_language_server
3435        .request::<lsp::request::WorkspaceDiagnosticRefresh>((), DEFAULT_LSP_REQUEST_TIMEOUT)
3436        .await
3437        .into_response()
3438        .expect("workspace diagnostics refresh request failed");
3439    // Workspace refresh now also triggers document diagnostic pulls for all open buffers
3440    pull_diagnostics_handle.next().await.unwrap();
3441    pull_diagnostics_handle.next().await.unwrap();
3442    assert_eq!(
3443        12,
3444        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3445        "Workspace refresh should trigger document pulls for all open buffers (main.rs and lib.rs)"
3446    );
3447    workspace_diagnostics_pulls_handle.next().await.unwrap();
3448    assert_eq!(
3449        workspace_diagnostic_start_count + 4,
3450        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3451        "Another workspace diagnostics pull should happen after the diagnostics refresh server request"
3452    );
3453    {
3454        assert!(
3455            diagnostics_pulls_result_ids.lock().await.len() > diagnostic_pulls_result_ids,
3456            "Document diagnostic pulls should happen after workspace refresh"
3457        );
3458        assert!(
3459            workspace_diagnostics_pulls_result_ids.lock().await.len() > workspace_pulls_result_ids,
3460            "More workspace diagnostics should be pulled"
3461        );
3462    }
3463    editor_b_lib.update(cx_b, |editor, cx| {
3464        let snapshot = editor.buffer().read(cx).snapshot(cx);
3465        let all_diagnostics = snapshot
3466            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3467            .collect::<Vec<_>>();
3468        let expected_messages = [
3469            expected_workspace_pull_diagnostics_lib_message,
3470            expected_pull_diagnostic_lib_message,
3471            expected_push_diagnostic_lib_message,
3472        ];
3473        assert_eq!(all_diagnostics.len(), 2);
3474        for diagnostic in &all_diagnostics {
3475            assert!(
3476                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3477                "Unexpected diagnostics: {all_diagnostics:?}"
3478            );
3479        }
3480    });
3481    editor_b_main.update(cx_b, |editor, cx| {
3482        let snapshot = editor.buffer().read(cx).snapshot(cx);
3483        let all_diagnostics = snapshot
3484            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3485            .collect::<Vec<_>>();
3486        assert_eq!(all_diagnostics.len(), 2);
3487
3488        let expected_messages = [
3489            expected_workspace_pull_diagnostics_main_message,
3490            expected_pull_diagnostic_main_message,
3491            expected_push_diagnostic_main_message,
3492        ];
3493        for diagnostic in &all_diagnostics {
3494            assert!(
3495                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3496                "Unexpected diagnostics: {all_diagnostics:?}"
3497            );
3498        }
3499    });
3500    editor_a_main.update(cx_a, |editor, cx| {
3501        let snapshot = editor.buffer().read(cx).snapshot(cx);
3502        let all_diagnostics = snapshot
3503            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3504            .collect::<Vec<_>>();
3505        assert_eq!(all_diagnostics.len(), 2);
3506        let expected_messages = [
3507            expected_workspace_pull_diagnostics_main_message,
3508            expected_pull_diagnostic_main_message,
3509            expected_push_diagnostic_main_message,
3510        ];
3511        for diagnostic in &all_diagnostics {
3512            assert!(
3513                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3514                "Unexpected diagnostics: {all_diagnostics:?}"
3515            );
3516        }
3517    });
3518}
3519
3520#[gpui::test(iterations = 10)]
3521async fn test_non_streamed_lsp_pull_diagnostics(
3522    cx_a: &mut TestAppContext,
3523    cx_b: &mut TestAppContext,
3524) {
3525    test_lsp_pull_diagnostics(false, cx_a, cx_b).await;
3526}
3527
3528#[gpui::test(iterations = 10)]
3529async fn test_streamed_lsp_pull_diagnostics(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3530    test_lsp_pull_diagnostics(true, cx_a, cx_b).await;
3531}
3532
3533#[gpui::test(iterations = 10)]
3534async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3535    let mut server = TestServer::start(cx_a.executor()).await;
3536    let client_a = server.create_client(cx_a, "user_a").await;
3537    let client_b = server.create_client(cx_b, "user_b").await;
3538    server
3539        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3540        .await;
3541    let active_call_a = cx_a.read(ActiveCall::global);
3542
3543    cx_a.update(editor::init);
3544    cx_b.update(editor::init);
3545    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
3546    let inline_blame_off_settings = Some(InlineBlameSettings {
3547        enabled: Some(false),
3548        ..Default::default()
3549    });
3550    cx_a.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    cx_b.update(|cx| {
3558        SettingsStore::update_global(cx, |store, cx| {
3559            store.update_user_settings(cx, |settings| {
3560                settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
3561            });
3562        });
3563    });
3564
3565    client_a
3566        .fs()
3567        .insert_tree(
3568            path!("/my-repo"),
3569            json!({
3570                ".git": {},
3571                "file.txt": "line1\nline2\nline3\nline\n",
3572            }),
3573        )
3574        .await;
3575
3576    let blame = git::blame::Blame {
3577        entries: vec![
3578            blame_entry("1b1b1b", 0..1),
3579            blame_entry("0d0d0d", 1..2),
3580            blame_entry("3a3a3a", 2..3),
3581            blame_entry("4c4c4c", 3..4),
3582        ],
3583        messages: [
3584            ("1b1b1b", "message for idx-0"),
3585            ("0d0d0d", "message for idx-1"),
3586            ("3a3a3a", "message for idx-2"),
3587            ("4c4c4c", "message for idx-3"),
3588        ]
3589        .into_iter()
3590        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
3591        .collect(),
3592    };
3593    client_a.fs().set_blame_for_repo(
3594        Path::new(path!("/my-repo/.git")),
3595        vec![(repo_path("file.txt"), blame)],
3596    );
3597
3598    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
3599    let project_id = active_call_a
3600        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3601        .await
3602        .unwrap();
3603
3604    // Create editor_a
3605    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3606    let editor_a = workspace_a
3607        .update_in(cx_a, |workspace, window, cx| {
3608            workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx)
3609        })
3610        .await
3611        .unwrap()
3612        .downcast::<Editor>()
3613        .unwrap();
3614
3615    // Join the project as client B.
3616    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3617    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3618    let editor_b = workspace_b
3619        .update_in(cx_b, |workspace, window, cx| {
3620            workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx)
3621        })
3622        .await
3623        .unwrap()
3624        .downcast::<Editor>()
3625        .unwrap();
3626    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
3627        editor_b
3628            .buffer()
3629            .read(cx)
3630            .as_singleton()
3631            .unwrap()
3632            .read(cx)
3633            .remote_id()
3634    });
3635
3636    // client_b now requests git blame for the open buffer
3637    editor_b.update_in(cx_b, |editor_b, window, cx| {
3638        assert!(editor_b.blame().is_none());
3639        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
3640    });
3641
3642    cx_a.executor().run_until_parked();
3643    cx_b.executor().run_until_parked();
3644
3645    editor_b.update(cx_b, |editor_b, cx| {
3646        let blame = editor_b.blame().expect("editor_b should have blame now");
3647        let entries = blame.update(cx, |blame, cx| {
3648            blame
3649                .blame_for_rows(
3650                    &(0..4)
3651                        .map(|row| RowInfo {
3652                            buffer_row: Some(row),
3653                            buffer_id: Some(buffer_id_b),
3654                            ..Default::default()
3655                        })
3656                        .collect::<Vec<_>>(),
3657                    cx,
3658                )
3659                .collect::<Vec<_>>()
3660        });
3661
3662        assert_eq!(
3663            entries,
3664            vec![
3665                Some((buffer_id_b, blame_entry("1b1b1b", 0..1))),
3666                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3667                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3668                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3669            ]
3670        );
3671
3672        blame.update(cx, |blame, _| {
3673            for (idx, (buffer, entry)) in entries.iter().flatten().enumerate() {
3674                let details = blame.details_for_entry(*buffer, entry).unwrap();
3675                assert_eq!(details.message, format!("message for idx-{}", idx));
3676            }
3677        });
3678    });
3679
3680    // editor_b updates the file, which gets sent to client_a, which updates git blame,
3681    // which gets back to client_b.
3682    editor_b.update_in(cx_b, |editor_b, _, cx| {
3683        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
3684    });
3685
3686    cx_a.executor().run_until_parked();
3687    cx_b.executor().run_until_parked();
3688
3689    editor_b.update(cx_b, |editor_b, cx| {
3690        let blame = editor_b.blame().expect("editor_b should have blame now");
3691        let entries = blame.update(cx, |blame, cx| {
3692            blame
3693                .blame_for_rows(
3694                    &(0..4)
3695                        .map(|row| RowInfo {
3696                            buffer_row: Some(row),
3697                            buffer_id: Some(buffer_id_b),
3698                            ..Default::default()
3699                        })
3700                        .collect::<Vec<_>>(),
3701                    cx,
3702                )
3703                .collect::<Vec<_>>()
3704        });
3705
3706        assert_eq!(
3707            entries,
3708            vec![
3709                None,
3710                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3711                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3712                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3713            ]
3714        );
3715    });
3716
3717    // Now editor_a also updates the file
3718    editor_a.update_in(cx_a, |editor_a, _, cx| {
3719        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
3720    });
3721
3722    cx_a.executor().run_until_parked();
3723    cx_b.executor().run_until_parked();
3724
3725    editor_b.update(cx_b, |editor_b, cx| {
3726        let blame = editor_b.blame().expect("editor_b should have blame now");
3727        let entries = blame.update(cx, |blame, cx| {
3728            blame
3729                .blame_for_rows(
3730                    &(0..4)
3731                        .map(|row| RowInfo {
3732                            buffer_row: Some(row),
3733                            buffer_id: Some(buffer_id_b),
3734                            ..Default::default()
3735                        })
3736                        .collect::<Vec<_>>(),
3737                    cx,
3738                )
3739                .collect::<Vec<_>>()
3740        });
3741
3742        assert_eq!(
3743            entries,
3744            vec![
3745                None,
3746                None,
3747                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3748                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3749            ]
3750        );
3751    });
3752}
3753
3754#[gpui::test(iterations = 30)]
3755async fn test_collaborating_with_editorconfig(
3756    cx_a: &mut TestAppContext,
3757    cx_b: &mut TestAppContext,
3758) {
3759    let mut server = TestServer::start(cx_a.executor()).await;
3760    let client_a = server.create_client(cx_a, "user_a").await;
3761    let client_b = server.create_client(cx_b, "user_b").await;
3762    server
3763        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3764        .await;
3765    let active_call_a = cx_a.read(ActiveCall::global);
3766
3767    cx_b.update(editor::init);
3768
3769    // Set up a fake language server.
3770    client_a.language_registry().add(rust_lang());
3771    client_a
3772        .fs()
3773        .insert_tree(
3774            path!("/a"),
3775            json!({
3776                "src": {
3777                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
3778                    "other_mod": {
3779                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
3780                        ".editorconfig": "",
3781                    },
3782                },
3783                ".editorconfig": "[*]\ntab_width = 2\n",
3784            }),
3785        )
3786        .await;
3787    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
3788    let project_id = active_call_a
3789        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3790        .await
3791        .unwrap();
3792    let main_buffer_a = project_a
3793        .update(cx_a, |p, cx| {
3794            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
3795        })
3796        .await
3797        .unwrap();
3798    let other_buffer_a = project_a
3799        .update(cx_a, |p, cx| {
3800            p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
3801        })
3802        .await
3803        .unwrap();
3804    let cx_a = cx_a.add_empty_window();
3805    let main_editor_a = cx_a.new_window_entity(|window, cx| {
3806        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
3807    });
3808    let other_editor_a = cx_a.new_window_entity(|window, cx| {
3809        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
3810    });
3811    let mut main_editor_cx_a = EditorTestContext {
3812        cx: cx_a.clone(),
3813        window: cx_a.window_handle(),
3814        editor: main_editor_a,
3815        assertion_cx: AssertionContextManager::new(),
3816    };
3817    let mut other_editor_cx_a = EditorTestContext {
3818        cx: cx_a.clone(),
3819        window: cx_a.window_handle(),
3820        editor: other_editor_a,
3821        assertion_cx: AssertionContextManager::new(),
3822    };
3823
3824    // Join the project as client B.
3825    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3826    let main_buffer_b = project_b
3827        .update(cx_b, |p, cx| {
3828            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
3829        })
3830        .await
3831        .unwrap();
3832    let other_buffer_b = project_b
3833        .update(cx_b, |p, cx| {
3834            p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
3835        })
3836        .await
3837        .unwrap();
3838    let cx_b = cx_b.add_empty_window();
3839    let main_editor_b = cx_b.new_window_entity(|window, cx| {
3840        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
3841    });
3842    let other_editor_b = cx_b.new_window_entity(|window, cx| {
3843        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
3844    });
3845    let mut main_editor_cx_b = EditorTestContext {
3846        cx: cx_b.clone(),
3847        window: cx_b.window_handle(),
3848        editor: main_editor_b,
3849        assertion_cx: AssertionContextManager::new(),
3850    };
3851    let mut other_editor_cx_b = EditorTestContext {
3852        cx: cx_b.clone(),
3853        window: cx_b.window_handle(),
3854        editor: other_editor_b,
3855        assertion_cx: AssertionContextManager::new(),
3856    };
3857
3858    let initial_main = indoc! {"
3859ˇmod other;
3860fn main() { let foo = other::foo(); }"};
3861    let initial_other = indoc! {"
3862ˇpub fn foo() -> usize {
3863    4
3864}"};
3865
3866    let first_tabbed_main = indoc! {"
3867  ˇmod other;
3868fn main() { let foo = other::foo(); }"};
3869    tab_undo_assert(
3870        &mut main_editor_cx_a,
3871        &mut main_editor_cx_b,
3872        initial_main,
3873        first_tabbed_main,
3874        true,
3875    );
3876    tab_undo_assert(
3877        &mut main_editor_cx_a,
3878        &mut main_editor_cx_b,
3879        initial_main,
3880        first_tabbed_main,
3881        false,
3882    );
3883
3884    let first_tabbed_other = indoc! {"
3885  ˇpub fn foo() -> usize {
3886    4
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        true,
3894    );
3895    tab_undo_assert(
3896        &mut other_editor_cx_a,
3897        &mut other_editor_cx_b,
3898        initial_other,
3899        first_tabbed_other,
3900        false,
3901    );
3902
3903    client_a
3904        .fs()
3905        .atomic_write(
3906            PathBuf::from(path!("/a/src/.editorconfig")),
3907            "[*]\ntab_width = 3\n".to_owned(),
3908        )
3909        .await
3910        .unwrap();
3911    cx_a.run_until_parked();
3912    cx_b.run_until_parked();
3913
3914    let second_tabbed_main = indoc! {"
3915   ˇmod other;
3916fn main() { let foo = other::foo(); }"};
3917    tab_undo_assert(
3918        &mut main_editor_cx_a,
3919        &mut main_editor_cx_b,
3920        initial_main,
3921        second_tabbed_main,
3922        true,
3923    );
3924    tab_undo_assert(
3925        &mut main_editor_cx_a,
3926        &mut main_editor_cx_b,
3927        initial_main,
3928        second_tabbed_main,
3929        false,
3930    );
3931
3932    let second_tabbed_other = indoc! {"
3933   ˇpub fn foo() -> usize {
3934    4
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        true,
3942    );
3943    tab_undo_assert(
3944        &mut other_editor_cx_a,
3945        &mut other_editor_cx_b,
3946        initial_other,
3947        second_tabbed_other,
3948        false,
3949    );
3950
3951    let editorconfig_buffer_b = project_b
3952        .update(cx_b, |p, cx| {
3953            p.open_buffer((worktree_id, rel_path("src/other_mod/.editorconfig")), cx)
3954        })
3955        .await
3956        .unwrap();
3957    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
3958        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
3959    });
3960    project_b
3961        .update(cx_b, |project, cx| {
3962            project.save_buffer(editorconfig_buffer_b.clone(), cx)
3963        })
3964        .await
3965        .unwrap();
3966    cx_a.run_until_parked();
3967    cx_b.run_until_parked();
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        true,
3975    );
3976    tab_undo_assert(
3977        &mut main_editor_cx_a,
3978        &mut main_editor_cx_b,
3979        initial_main,
3980        second_tabbed_main,
3981        false,
3982    );
3983
3984    let third_tabbed_other = indoc! {"
3985      ˇpub fn foo() -> usize {
3986    4
3987}"};
3988    tab_undo_assert(
3989        &mut other_editor_cx_a,
3990        &mut other_editor_cx_b,
3991        initial_other,
3992        third_tabbed_other,
3993        true,
3994    );
3995
3996    tab_undo_assert(
3997        &mut other_editor_cx_a,
3998        &mut other_editor_cx_b,
3999        initial_other,
4000        third_tabbed_other,
4001        false,
4002    );
4003}
4004
4005#[gpui::test(iterations = 10)]
4006async fn test_collaborating_with_external_editorconfig(
4007    cx_a: &mut TestAppContext,
4008    cx_b: &mut TestAppContext,
4009) {
4010    let mut server = TestServer::start(cx_a.executor()).await;
4011    let client_a = server.create_client(cx_a, "user_a").await;
4012    let client_b = server.create_client(cx_b, "user_b").await;
4013    server
4014        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4015        .await;
4016    let active_call_a = cx_a.read(ActiveCall::global);
4017
4018    client_a.language_registry().add(rust_lang());
4019    client_b.language_registry().add(rust_lang());
4020
4021    // Set up external .editorconfig in parent directory
4022    client_a
4023        .fs()
4024        .insert_tree(
4025            path!("/parent"),
4026            json!({
4027                ".editorconfig": "[*]\nindent_size = 5\n",
4028                "worktree": {
4029                    ".editorconfig": "[*]\n",
4030                    "src": {
4031                        "main.rs": "fn main() {}",
4032                    },
4033                },
4034            }),
4035        )
4036        .await;
4037
4038    let (project_a, worktree_id) = client_a
4039        .build_local_project(path!("/parent/worktree"), cx_a)
4040        .await;
4041    let project_id = active_call_a
4042        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4043        .await
4044        .unwrap();
4045
4046    project_a.update(cx_a, |project, _| project.languages().add(rust_lang()));
4047
4048    // Open buffer on client A
4049    let buffer_a = project_a
4050        .update(cx_a, |p, cx| {
4051            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
4052        })
4053        .await
4054        .unwrap();
4055
4056    cx_a.run_until_parked();
4057
4058    // Verify client A sees external editorconfig settings
4059    cx_a.read(|cx| {
4060        let settings = LanguageSettings::for_buffer(&buffer_a.read(cx), cx);
4061        assert_eq!(Some(settings.tab_size), NonZeroU32::new(5));
4062    });
4063
4064    // Client B joins the project
4065    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4066    project_b.update(cx_b, |project, _| project.languages().add(rust_lang()));
4067    let buffer_b = project_b
4068        .update(cx_b, |p, cx| {
4069            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
4070        })
4071        .await
4072        .unwrap();
4073
4074    cx_b.run_until_parked();
4075
4076    // Verify client B also sees external editorconfig settings
4077    cx_b.read(|cx| {
4078        let settings = LanguageSettings::for_buffer(&buffer_b.read(cx), cx);
4079        assert_eq!(Some(settings.tab_size), NonZeroU32::new(5));
4080    });
4081
4082    // Client A modifies the external .editorconfig
4083    client_a
4084        .fs()
4085        .atomic_write(
4086            PathBuf::from(path!("/parent/.editorconfig")),
4087            "[*]\nindent_size = 9\n".to_owned(),
4088        )
4089        .await
4090        .unwrap();
4091
4092    cx_a.run_until_parked();
4093    cx_b.run_until_parked();
4094
4095    // Verify client A sees updated settings
4096    cx_a.read(|cx| {
4097        let settings = LanguageSettings::for_buffer(&buffer_a.read(cx), cx);
4098        assert_eq!(Some(settings.tab_size), NonZeroU32::new(9));
4099    });
4100
4101    // Verify client B also sees updated settings
4102    cx_b.read(|cx| {
4103        let settings = LanguageSettings::for_buffer(&buffer_b.read(cx), cx);
4104        assert_eq!(Some(settings.tab_size), NonZeroU32::new(9));
4105    });
4106}
4107
4108#[gpui::test]
4109async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4110    let executor = cx_a.executor();
4111    let mut server = TestServer::start(executor.clone()).await;
4112    let client_a = server.create_client(cx_a, "user_a").await;
4113    let client_b = server.create_client(cx_b, "user_b").await;
4114    server
4115        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4116        .await;
4117    let active_call_a = cx_a.read(ActiveCall::global);
4118    let active_call_b = cx_b.read(ActiveCall::global);
4119    cx_a.update(editor::init);
4120    cx_b.update(editor::init);
4121    client_a
4122        .fs()
4123        .insert_tree(
4124            "/a",
4125            json!({
4126                "test.txt": "one\ntwo\nthree\nfour\nfive",
4127            }),
4128        )
4129        .await;
4130    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
4131    let project_path = ProjectPath {
4132        worktree_id,
4133        path: rel_path(&"test.txt").into(),
4134    };
4135    let abs_path = project_a.read_with(cx_a, |project, cx| {
4136        project
4137            .absolute_path(&project_path, cx)
4138            .map(Arc::from)
4139            .unwrap()
4140    });
4141
4142    active_call_a
4143        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
4144        .await
4145        .unwrap();
4146    let project_id = active_call_a
4147        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4148        .await
4149        .unwrap();
4150    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4151    active_call_b
4152        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
4153        .await
4154        .unwrap();
4155    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4156    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4157
4158    // Client A opens an editor.
4159    let editor_a = workspace_a
4160        .update_in(cx_a, |workspace, window, cx| {
4161            workspace.open_path(project_path.clone(), None, true, window, cx)
4162        })
4163        .await
4164        .unwrap()
4165        .downcast::<Editor>()
4166        .unwrap();
4167
4168    // Client B opens same editor as A.
4169    let editor_b = workspace_b
4170        .update_in(cx_b, |workspace, window, cx| {
4171            workspace.open_path(project_path.clone(), None, true, window, cx)
4172        })
4173        .await
4174        .unwrap()
4175        .downcast::<Editor>()
4176        .unwrap();
4177
4178    cx_a.run_until_parked();
4179    cx_b.run_until_parked();
4180
4181    // Client A adds breakpoint on line (1)
4182    editor_a.update_in(cx_a, |editor, window, cx| {
4183        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4184    });
4185
4186    cx_a.run_until_parked();
4187    cx_b.run_until_parked();
4188
4189    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4190        editor
4191            .breakpoint_store()
4192            .unwrap()
4193            .read(cx)
4194            .all_source_breakpoints(cx)
4195    });
4196    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4197        editor
4198            .breakpoint_store()
4199            .unwrap()
4200            .read(cx)
4201            .all_source_breakpoints(cx)
4202    });
4203
4204    assert_eq!(1, breakpoints_a.len());
4205    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
4206    assert_eq!(breakpoints_a, breakpoints_b);
4207
4208    // Client B adds breakpoint on line(2)
4209    editor_b.update_in(cx_b, |editor, window, cx| {
4210        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
4211        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
4212        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4213    });
4214
4215    cx_a.run_until_parked();
4216    cx_b.run_until_parked();
4217
4218    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4219        editor
4220            .breakpoint_store()
4221            .unwrap()
4222            .read(cx)
4223            .all_source_breakpoints(cx)
4224    });
4225    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4226        editor
4227            .breakpoint_store()
4228            .unwrap()
4229            .read(cx)
4230            .all_source_breakpoints(cx)
4231    });
4232
4233    assert_eq!(1, breakpoints_a.len());
4234    assert_eq!(breakpoints_a, breakpoints_b);
4235    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
4236
4237    // Client A removes last added breakpoint from client B
4238    editor_a.update_in(cx_a, |editor, window, cx| {
4239        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
4240        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
4241        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4242    });
4243
4244    cx_a.run_until_parked();
4245    cx_b.run_until_parked();
4246
4247    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4248        editor
4249            .breakpoint_store()
4250            .unwrap()
4251            .read(cx)
4252            .all_source_breakpoints(cx)
4253    });
4254    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4255        editor
4256            .breakpoint_store()
4257            .unwrap()
4258            .read(cx)
4259            .all_source_breakpoints(cx)
4260    });
4261
4262    assert_eq!(1, breakpoints_a.len());
4263    assert_eq!(breakpoints_a, breakpoints_b);
4264    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
4265
4266    // Client B removes first added breakpoint by client A
4267    editor_b.update_in(cx_b, |editor, window, cx| {
4268        editor.move_up(&zed_actions::editor::MoveUp, window, cx);
4269        editor.move_up(&zed_actions::editor::MoveUp, window, cx);
4270        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4271    });
4272
4273    cx_a.run_until_parked();
4274    cx_b.run_until_parked();
4275
4276    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4277        editor
4278            .breakpoint_store()
4279            .unwrap()
4280            .read(cx)
4281            .all_source_breakpoints(cx)
4282    });
4283    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4284        editor
4285            .breakpoint_store()
4286            .unwrap()
4287            .read(cx)
4288            .all_source_breakpoints(cx)
4289    });
4290
4291    assert_eq!(0, breakpoints_a.len());
4292    assert_eq!(breakpoints_a, breakpoints_b);
4293}
4294
4295#[gpui::test]
4296async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4297    let mut server = TestServer::start(cx_a.executor()).await;
4298    let client_a = server.create_client(cx_a, "user_a").await;
4299    let client_b = server.create_client(cx_b, "user_b").await;
4300    server
4301        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4302        .await;
4303    let active_call_a = cx_a.read(ActiveCall::global);
4304    let active_call_b = cx_b.read(ActiveCall::global);
4305
4306    cx_a.update(editor::init);
4307    cx_b.update(editor::init);
4308
4309    client_a.language_registry().add(rust_lang());
4310    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4311        "Rust",
4312        FakeLspAdapter {
4313            name: "rust-analyzer",
4314            ..FakeLspAdapter::default()
4315        },
4316    );
4317    client_b.language_registry().add(rust_lang());
4318    client_b.language_registry().register_fake_lsp_adapter(
4319        "Rust",
4320        FakeLspAdapter {
4321            name: "rust-analyzer",
4322            ..FakeLspAdapter::default()
4323        },
4324    );
4325
4326    client_a
4327        .fs()
4328        .insert_tree(
4329            path!("/a"),
4330            json!({
4331                "main.rs": "fn main() {}",
4332            }),
4333        )
4334        .await;
4335    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4336    active_call_a
4337        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
4338        .await
4339        .unwrap();
4340    let project_id = active_call_a
4341        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4342        .await
4343        .unwrap();
4344
4345    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4346    active_call_b
4347        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
4348        .await
4349        .unwrap();
4350
4351    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4352    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4353
4354    let editor_a = workspace_a
4355        .update_in(cx_a, |workspace, window, cx| {
4356            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
4357        })
4358        .await
4359        .unwrap()
4360        .downcast::<Editor>()
4361        .unwrap();
4362
4363    let editor_b = workspace_b
4364        .update_in(cx_b, |workspace, window, cx| {
4365            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
4366        })
4367        .await
4368        .unwrap()
4369        .downcast::<Editor>()
4370        .unwrap();
4371
4372    let fake_language_server = fake_language_servers.next().await.unwrap();
4373
4374    // host
4375    let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4376        |params, _| async move {
4377            assert_eq!(
4378                params.text_document.uri,
4379                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4380            );
4381            assert_eq!(params.position, lsp::Position::new(0, 0));
4382            Ok(Some(ExpandedMacro {
4383                name: "test_macro_name".to_string(),
4384                expansion: "test_macro_expansion on the host".to_string(),
4385            }))
4386        },
4387    );
4388
4389    editor_a.update_in(cx_a, |editor, window, cx| {
4390        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4391    });
4392    expand_request_a.next().await.unwrap();
4393    cx_a.run_until_parked();
4394
4395    workspace_a.update(cx_a, |workspace, cx| {
4396        workspace.active_pane().update(cx, |pane, cx| {
4397            assert_eq!(
4398                pane.items_len(),
4399                2,
4400                "Should have added a macro expansion to the host's pane"
4401            );
4402            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4403            new_editor.update(cx, |editor, cx| {
4404                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
4405            });
4406        })
4407    });
4408
4409    // client
4410    let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4411        |params, _| async move {
4412            assert_eq!(
4413                params.text_document.uri,
4414                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4415            );
4416            assert_eq!(
4417                params.position,
4418                lsp::Position::new(0, 12),
4419                "editor_b has selected the entire text and should query for a different position"
4420            );
4421            Ok(Some(ExpandedMacro {
4422                name: "test_macro_name".to_string(),
4423                expansion: "test_macro_expansion on the client".to_string(),
4424            }))
4425        },
4426    );
4427
4428    editor_b.update_in(cx_b, |editor, window, cx| {
4429        editor.select_all(&SelectAll, window, cx);
4430        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4431    });
4432    expand_request_b.next().await.unwrap();
4433    cx_b.run_until_parked();
4434
4435    workspace_b.update(cx_b, |workspace, cx| {
4436        workspace.active_pane().update(cx, |pane, cx| {
4437            assert_eq!(
4438                pane.items_len(),
4439                2,
4440                "Should have added a macro expansion to the client's pane"
4441            );
4442            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4443            new_editor.update(cx, |editor, cx| {
4444                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
4445            });
4446        })
4447    });
4448}
4449
4450#[gpui::test]
4451async fn test_copy_file_name_without_extension(
4452    cx_a: &mut TestAppContext,
4453    cx_b: &mut TestAppContext,
4454) {
4455    let mut server = TestServer::start(cx_a.executor()).await;
4456    let client_a = server.create_client(cx_a, "user_a").await;
4457    let client_b = server.create_client(cx_b, "user_b").await;
4458    server
4459        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4460        .await;
4461
4462    cx_b.update(editor::init);
4463
4464    client_a
4465        .fs()
4466        .insert_tree(
4467            path!("/root"),
4468            json!({
4469                "src": {
4470                    "main.rs": indoc! {"
4471                        fn main() {
4472                            println!(\"Hello, world!\");
4473                        }
4474                    "},
4475                }
4476            }),
4477        )
4478        .await;
4479
4480    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
4481    let active_call_a = cx_a.read(ActiveCall::global);
4482    let project_id = active_call_a
4483        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4484        .await
4485        .unwrap();
4486
4487    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4488
4489    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4490    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4491
4492    let editor_a = workspace_a
4493        .update_in(cx_a, |workspace, window, cx| {
4494            workspace.open_path(
4495                (worktree_id, rel_path("src/main.rs")),
4496                None,
4497                true,
4498                window,
4499                cx,
4500            )
4501        })
4502        .await
4503        .unwrap()
4504        .downcast::<Editor>()
4505        .unwrap();
4506
4507    let editor_b = workspace_b
4508        .update_in(cx_b, |workspace, window, cx| {
4509            workspace.open_path(
4510                (worktree_id, rel_path("src/main.rs")),
4511                None,
4512                true,
4513                window,
4514                cx,
4515            )
4516        })
4517        .await
4518        .unwrap()
4519        .downcast::<Editor>()
4520        .unwrap();
4521
4522    cx_a.run_until_parked();
4523    cx_b.run_until_parked();
4524
4525    editor_a.update_in(cx_a, |editor, window, cx| {
4526        editor.copy_file_name_without_extension(&CopyFileNameWithoutExtension, window, cx);
4527    });
4528
4529    assert_eq!(
4530        cx_a.read_from_clipboard().and_then(|item| item.text()),
4531        Some("main".to_string())
4532    );
4533
4534    editor_b.update_in(cx_b, |editor, window, cx| {
4535        editor.copy_file_name_without_extension(&CopyFileNameWithoutExtension, window, cx);
4536    });
4537
4538    assert_eq!(
4539        cx_b.read_from_clipboard().and_then(|item| item.text()),
4540        Some("main".to_string())
4541    );
4542}
4543
4544#[gpui::test]
4545async fn test_copy_file_name(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4546    let mut server = TestServer::start(cx_a.executor()).await;
4547    let client_a = server.create_client(cx_a, "user_a").await;
4548    let client_b = server.create_client(cx_b, "user_b").await;
4549    server
4550        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4551        .await;
4552
4553    cx_b.update(editor::init);
4554
4555    client_a
4556        .fs()
4557        .insert_tree(
4558            path!("/root"),
4559            json!({
4560                "src": {
4561                    "main.rs": indoc! {"
4562                        fn main() {
4563                            println!(\"Hello, world!\");
4564                        }
4565                    "},
4566                }
4567            }),
4568        )
4569        .await;
4570
4571    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
4572    let active_call_a = cx_a.read(ActiveCall::global);
4573    let project_id = active_call_a
4574        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4575        .await
4576        .unwrap();
4577
4578    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4579
4580    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4581    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4582
4583    let editor_a = workspace_a
4584        .update_in(cx_a, |workspace, window, cx| {
4585            workspace.open_path(
4586                (worktree_id, rel_path("src/main.rs")),
4587                None,
4588                true,
4589                window,
4590                cx,
4591            )
4592        })
4593        .await
4594        .unwrap()
4595        .downcast::<Editor>()
4596        .unwrap();
4597
4598    let editor_b = workspace_b
4599        .update_in(cx_b, |workspace, window, cx| {
4600            workspace.open_path(
4601                (worktree_id, rel_path("src/main.rs")),
4602                None,
4603                true,
4604                window,
4605                cx,
4606            )
4607        })
4608        .await
4609        .unwrap()
4610        .downcast::<Editor>()
4611        .unwrap();
4612
4613    cx_a.run_until_parked();
4614    cx_b.run_until_parked();
4615
4616    editor_a.update_in(cx_a, |editor, window, cx| {
4617        editor.copy_file_name(&CopyFileName, window, cx);
4618    });
4619
4620    assert_eq!(
4621        cx_a.read_from_clipboard().and_then(|item| item.text()),
4622        Some("main.rs".to_string())
4623    );
4624
4625    editor_b.update_in(cx_b, |editor, window, cx| {
4626        editor.copy_file_name(&CopyFileName, window, cx);
4627    });
4628
4629    assert_eq!(
4630        cx_b.read_from_clipboard().and_then(|item| item.text()),
4631        Some("main.rs".to_string())
4632    );
4633}
4634
4635#[gpui::test]
4636async fn test_copy_file_location(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4637    let mut server = TestServer::start(cx_a.executor()).await;
4638    let client_a = server.create_client(cx_a, "user_a").await;
4639    let client_b = server.create_client(cx_b, "user_b").await;
4640    server
4641        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4642        .await;
4643
4644    cx_b.update(editor::init);
4645
4646    client_a
4647        .fs()
4648        .insert_tree(
4649            path!("/root"),
4650            json!({
4651                "src": {
4652                    "main.rs": indoc! {"
4653                        fn main() {
4654                            println!(\"Hello, world!\");
4655                        }
4656                    "},
4657                }
4658            }),
4659        )
4660        .await;
4661
4662    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
4663    let active_call_a = cx_a.read(ActiveCall::global);
4664    let project_id = active_call_a
4665        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4666        .await
4667        .unwrap();
4668
4669    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4670
4671    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4672    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4673
4674    let editor_a = workspace_a
4675        .update_in(cx_a, |workspace, window, cx| {
4676            workspace.open_path(
4677                (worktree_id, rel_path("src/main.rs")),
4678                None,
4679                true,
4680                window,
4681                cx,
4682            )
4683        })
4684        .await
4685        .unwrap()
4686        .downcast::<Editor>()
4687        .unwrap();
4688
4689    let editor_b = workspace_b
4690        .update_in(cx_b, |workspace, window, cx| {
4691            workspace.open_path(
4692                (worktree_id, rel_path("src/main.rs")),
4693                None,
4694                true,
4695                window,
4696                cx,
4697            )
4698        })
4699        .await
4700        .unwrap()
4701        .downcast::<Editor>()
4702        .unwrap();
4703
4704    cx_a.run_until_parked();
4705    cx_b.run_until_parked();
4706
4707    editor_a.update_in(cx_a, |editor, window, cx| {
4708        editor.change_selections(Default::default(), window, cx, |s| {
4709            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(16)]);
4710        });
4711        editor.copy_file_location(&CopyFileLocation, window, cx);
4712    });
4713
4714    assert_eq!(
4715        cx_a.read_from_clipboard().and_then(|item| item.text()),
4716        Some(format!("{}:2", path!("src/main.rs")))
4717    );
4718
4719    editor_b.update_in(cx_b, |editor, window, cx| {
4720        editor.change_selections(Default::default(), window, cx, |s| {
4721            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(16)]);
4722        });
4723        editor.copy_file_location(&CopyFileLocation, window, cx);
4724    });
4725
4726    assert_eq!(
4727        cx_b.read_from_clipboard().and_then(|item| item.text()),
4728        Some(format!("{}:2", path!("src/main.rs")))
4729    );
4730
4731    editor_a.update_in(cx_a, |editor, window, cx| {
4732        editor.change_selections(Default::default(), window, cx, |s| {
4733            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(44)]);
4734        });
4735        editor.copy_file_location(&CopyFileLocation, window, cx);
4736    });
4737
4738    assert_eq!(
4739        cx_a.read_from_clipboard().and_then(|item| item.text()),
4740        Some(format!("{}:2-3", path!("src/main.rs")))
4741    );
4742
4743    editor_b.update_in(cx_b, |editor, window, cx| {
4744        editor.change_selections(Default::default(), window, cx, |s| {
4745            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(44)]);
4746        });
4747        editor.copy_file_location(&CopyFileLocation, window, cx);
4748    });
4749
4750    assert_eq!(
4751        cx_b.read_from_clipboard().and_then(|item| item.text()),
4752        Some(format!("{}:2-3", path!("src/main.rs")))
4753    );
4754
4755    editor_a.update_in(cx_a, |editor, window, cx| {
4756        editor.change_selections(Default::default(), window, cx, |s| {
4757            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(43)]);
4758        });
4759        editor.copy_file_location(&CopyFileLocation, window, cx);
4760    });
4761
4762    assert_eq!(
4763        cx_a.read_from_clipboard().and_then(|item| item.text()),
4764        Some(format!("{}:2", path!("src/main.rs")))
4765    );
4766
4767    editor_b.update_in(cx_b, |editor, window, cx| {
4768        editor.change_selections(Default::default(), window, cx, |s| {
4769            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(43)]);
4770        });
4771        editor.copy_file_location(&CopyFileLocation, window, cx);
4772    });
4773
4774    assert_eq!(
4775        cx_b.read_from_clipboard().and_then(|item| item.text()),
4776        Some(format!("{}:2", path!("src/main.rs")))
4777    );
4778}
4779
4780#[track_caller]
4781fn tab_undo_assert(
4782    cx_a: &mut EditorTestContext,
4783    cx_b: &mut EditorTestContext,
4784    expected_initial: &str,
4785    expected_tabbed: &str,
4786    a_tabs: bool,
4787) {
4788    cx_a.assert_editor_state(expected_initial);
4789    cx_b.assert_editor_state(expected_initial);
4790
4791    if a_tabs {
4792        cx_a.update_editor(|editor, window, cx| {
4793            editor.tab(&editor::actions::Tab, window, cx);
4794        });
4795    } else {
4796        cx_b.update_editor(|editor, window, cx| {
4797            editor.tab(&editor::actions::Tab, window, cx);
4798        });
4799    }
4800
4801    cx_a.run_until_parked();
4802    cx_b.run_until_parked();
4803
4804    cx_a.assert_editor_state(expected_tabbed);
4805    cx_b.assert_editor_state(expected_tabbed);
4806
4807    if a_tabs {
4808        cx_a.update_editor(|editor, window, cx| {
4809            editor.undo(&editor::actions::Undo, window, cx);
4810        });
4811    } else {
4812        cx_b.update_editor(|editor, window, cx| {
4813            editor.undo(&editor::actions::Undo, window, cx);
4814        });
4815    }
4816    cx_a.run_until_parked();
4817    cx_b.run_until_parked();
4818    cx_a.assert_editor_state(expected_initial);
4819    cx_b.assert_editor_state(expected_initial);
4820}
4821
4822fn extract_hint_labels(editor: &Editor, cx: &mut App) -> Vec<String> {
4823    let lsp_store = editor.project().unwrap().read(cx).lsp_store();
4824
4825    let mut all_cached_labels = Vec::new();
4826    let mut all_fetched_hints = Vec::new();
4827    for buffer in editor.buffer().read(cx).all_buffers() {
4828        lsp_store.update(cx, |lsp_store, cx| {
4829            let hints = &lsp_store.latest_lsp_data(&buffer, cx).inlay_hints();
4830            all_cached_labels.extend(hints.all_cached_hints().into_iter().map(|hint| {
4831                let mut label = hint.text().to_string();
4832                if hint.padding_left {
4833                    label.insert(0, ' ');
4834                }
4835                if hint.padding_right {
4836                    label.push_str(" ");
4837                }
4838                label
4839            }));
4840            all_fetched_hints.extend(hints.all_fetched_hints());
4841        });
4842    }
4843
4844    assert!(
4845        all_fetched_hints.is_empty(),
4846        "Did not expect background hints fetch tasks, but got {} of them",
4847        all_fetched_hints.len()
4848    );
4849
4850    all_cached_labels
4851}
4852
4853#[track_caller]
4854fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
4855    editor
4856        .all_inlays(cx)
4857        .into_iter()
4858        .filter_map(|inlay| inlay.get_color())
4859        .map(Rgba::from)
4860        .collect()
4861}
4862
4863fn extract_semantic_token_ranges(editor: &Editor, cx: &App) -> Vec<Range<MultiBufferOffset>> {
4864    let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
4865    editor
4866        .display_map
4867        .read(cx)
4868        .semantic_token_highlights
4869        .iter()
4870        .flat_map(|(_, (v, _))| v.iter())
4871        .map(|highlights| highlights.range.to_offset(&multi_buffer_snapshot))
4872        .collect()
4873}
4874
4875#[gpui::test(iterations = 10)]
4876async fn test_mutual_editor_semantic_token_cache_update(
4877    cx_a: &mut TestAppContext,
4878    cx_b: &mut TestAppContext,
4879) {
4880    let mut server = TestServer::start(cx_a.executor()).await;
4881    let executor = cx_a.executor();
4882    let client_a = server.create_client(cx_a, "user_a").await;
4883    let client_b = server.create_client(cx_b, "user_b").await;
4884    server
4885        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4886        .await;
4887    let active_call_a = cx_a.read(ActiveCall::global);
4888    let active_call_b = cx_b.read(ActiveCall::global);
4889
4890    cx_a.update(editor::init);
4891    cx_b.update(editor::init);
4892
4893    cx_a.update(|cx| {
4894        SettingsStore::update_global(cx, |store, cx| {
4895            store.update_user_settings(cx, |settings| {
4896                settings.project.all_languages.defaults.semantic_tokens =
4897                    Some(SemanticTokens::Full);
4898            });
4899        });
4900    });
4901    cx_b.update(|cx| {
4902        SettingsStore::update_global(cx, |store, cx| {
4903            store.update_user_settings(cx, |settings| {
4904                settings.project.all_languages.defaults.semantic_tokens =
4905                    Some(SemanticTokens::Full);
4906            });
4907        });
4908    });
4909
4910    let capabilities = lsp::ServerCapabilities {
4911        semantic_tokens_provider: Some(
4912            lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
4913                lsp::SemanticTokensOptions {
4914                    legend: lsp::SemanticTokensLegend {
4915                        token_types: vec!["function".into()],
4916                        token_modifiers: vec![],
4917                    },
4918                    full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
4919                    ..Default::default()
4920                },
4921            ),
4922        ),
4923        ..lsp::ServerCapabilities::default()
4924    };
4925    client_a.language_registry().add(rust_lang());
4926
4927    let edits_made = Arc::new(AtomicUsize::new(0));
4928    let closure_edits_made = Arc::clone(&edits_made);
4929    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4930        "Rust",
4931        FakeLspAdapter {
4932            capabilities: capabilities.clone(),
4933            initializer: Some(Box::new(move |fake_language_server| {
4934                let closure_edits_made = closure_edits_made.clone();
4935                fake_language_server
4936                    .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
4937                        move |_, _| {
4938                            let edits_made_2 = Arc::clone(&closure_edits_made);
4939                            async move {
4940                                let edits_made =
4941                                    AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire);
4942                                Ok(Some(lsp::SemanticTokensResult::Tokens(
4943                                    lsp::SemanticTokens {
4944                                        data: vec![
4945                                            0,                     // delta_line
4946                                            3,                     // delta_start
4947                                            edits_made as u32 + 4, // length
4948                                            0,                     // token_type
4949                                            0,                     // token_modifiers_bitset
4950                                        ],
4951                                        result_id: None,
4952                                    },
4953                                )))
4954                            }
4955                        },
4956                    );
4957            })),
4958            ..FakeLspAdapter::default()
4959        },
4960    );
4961    client_b.language_registry().add(rust_lang());
4962    client_b.language_registry().register_fake_lsp_adapter(
4963        "Rust",
4964        FakeLspAdapter {
4965            capabilities,
4966            ..FakeLspAdapter::default()
4967        },
4968    );
4969
4970    client_a
4971        .fs()
4972        .insert_tree(
4973            path!("/a"),
4974            json!({
4975                "main.rs": "fn main() { a }",
4976                "other.rs": "// Test file",
4977            }),
4978        )
4979        .await;
4980    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4981    active_call_a
4982        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
4983        .await
4984        .unwrap();
4985    let project_id = active_call_a
4986        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4987        .await
4988        .unwrap();
4989
4990    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4991    active_call_b
4992        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
4993        .await
4994        .unwrap();
4995
4996    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4997
4998    let file_a = workspace_a.update_in(cx_a, |workspace, window, cx| {
4999        workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5000    });
5001    let _fake_language_server = fake_language_servers.next().await.unwrap();
5002    let editor_a = file_a.await.unwrap().downcast::<Editor>().unwrap();
5003    executor.advance_clock(Duration::from_millis(100));
5004    executor.run_until_parked();
5005
5006    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
5007    editor_a.update(cx_a, |editor, cx| {
5008        let ranges = extract_semantic_token_ranges(editor, cx);
5009        assert_eq!(
5010            ranges,
5011            vec![MultiBufferOffset(3)..MultiBufferOffset(3 + initial_edit + 4)],
5012            "Host should get its first semantic tokens when opening an editor"
5013        );
5014    });
5015
5016    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
5017    let editor_b = workspace_b
5018        .update_in(cx_b, |workspace, window, cx| {
5019            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5020        })
5021        .await
5022        .unwrap()
5023        .downcast::<Editor>()
5024        .unwrap();
5025
5026    executor.advance_clock(Duration::from_millis(100));
5027    executor.run_until_parked();
5028    editor_b.update(cx_b, |editor, cx| {
5029        let ranges = extract_semantic_token_ranges(editor, cx);
5030        assert_eq!(
5031            ranges,
5032            vec![MultiBufferOffset(3)..MultiBufferOffset(3 + initial_edit + 4)],
5033            "Client should get its first semantic tokens when opening an editor"
5034        );
5035    });
5036
5037    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
5038    editor_b.update_in(cx_b, |editor, window, cx| {
5039        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
5040            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)].clone())
5041        });
5042        editor.handle_input(":", window, cx);
5043    });
5044    cx_b.focus(&editor_b);
5045
5046    executor.advance_clock(Duration::from_secs(1));
5047    executor.run_until_parked();
5048    editor_a.update(cx_a, |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    editor_b.update(cx_b, |editor, cx| {
5056        let ranges = extract_semantic_token_ranges(editor, cx);
5057        assert_eq!(
5058            ranges,
5059            vec![MultiBufferOffset(3)..MultiBufferOffset(3 + after_client_edit + 4)],
5060        );
5061    });
5062
5063    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
5064    editor_a.update_in(cx_a, |editor, window, cx| {
5065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
5066            s.select_ranges([MultiBufferOffset(14)..MultiBufferOffset(14)])
5067        });
5068        editor.handle_input("a change", window, cx);
5069    });
5070    cx_a.focus(&editor_a);
5071
5072    executor.advance_clock(Duration::from_secs(1));
5073    executor.run_until_parked();
5074    editor_a.update(cx_a, |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    editor_b.update(cx_b, |editor, cx| {
5082        let ranges = extract_semantic_token_ranges(editor, cx);
5083        assert_eq!(
5084            ranges,
5085            vec![MultiBufferOffset(3)..MultiBufferOffset(3 + after_host_edit + 4)],
5086        );
5087    });
5088}
5089
5090#[gpui::test(iterations = 10)]
5091async fn test_semantic_token_refresh_is_forwarded(
5092    cx_a: &mut TestAppContext,
5093    cx_b: &mut TestAppContext,
5094) {
5095    let mut server = TestServer::start(cx_a.executor()).await;
5096    let executor = cx_a.executor();
5097    let client_a = server.create_client(cx_a, "user_a").await;
5098    let client_b = server.create_client(cx_b, "user_b").await;
5099    server
5100        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5101        .await;
5102    let active_call_a = cx_a.read(ActiveCall::global);
5103    let active_call_b = cx_b.read(ActiveCall::global);
5104
5105    cx_a.update(editor::init);
5106    cx_b.update(editor::init);
5107
5108    cx_a.update(|cx| {
5109        SettingsStore::update_global(cx, |store, cx| {
5110            store.update_user_settings(cx, |settings| {
5111                settings.project.all_languages.defaults.semantic_tokens = Some(SemanticTokens::Off);
5112            });
5113        });
5114    });
5115    cx_b.update(|cx| {
5116        SettingsStore::update_global(cx, |store, cx| {
5117            store.update_user_settings(cx, |settings| {
5118                settings.project.all_languages.defaults.semantic_tokens =
5119                    Some(SemanticTokens::Full);
5120            });
5121        });
5122    });
5123
5124    let capabilities = lsp::ServerCapabilities {
5125        semantic_tokens_provider: Some(
5126            lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
5127                lsp::SemanticTokensOptions {
5128                    legend: lsp::SemanticTokensLegend {
5129                        token_types: vec!["function".into()],
5130                        token_modifiers: vec![],
5131                    },
5132                    full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
5133                    ..Default::default()
5134                },
5135            ),
5136        ),
5137        ..lsp::ServerCapabilities::default()
5138    };
5139    client_a.language_registry().add(rust_lang());
5140    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5141        "Rust",
5142        FakeLspAdapter {
5143            capabilities: capabilities.clone(),
5144            ..FakeLspAdapter::default()
5145        },
5146    );
5147    client_b.language_registry().add(rust_lang());
5148    client_b.language_registry().register_fake_lsp_adapter(
5149        "Rust",
5150        FakeLspAdapter {
5151            capabilities,
5152            ..FakeLspAdapter::default()
5153        },
5154    );
5155
5156    client_a
5157        .fs()
5158        .insert_tree(
5159            path!("/a"),
5160            json!({
5161                "main.rs": "fn main() { a }",
5162                "other.rs": "// Test file",
5163            }),
5164        )
5165        .await;
5166    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
5167    active_call_a
5168        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
5169        .await
5170        .unwrap();
5171    let project_id = active_call_a
5172        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5173        .await
5174        .unwrap();
5175
5176    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5177    active_call_b
5178        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5179        .await
5180        .unwrap();
5181
5182    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
5183    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
5184
5185    let editor_a = workspace_a
5186        .update_in(cx_a, |workspace, window, cx| {
5187            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5188        })
5189        .await
5190        .unwrap()
5191        .downcast::<Editor>()
5192        .unwrap();
5193
5194    let editor_b = workspace_b
5195        .update_in(cx_b, |workspace, window, cx| {
5196            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5197        })
5198        .await
5199        .unwrap()
5200        .downcast::<Editor>()
5201        .unwrap();
5202
5203    let other_tokens = Arc::new(AtomicBool::new(false));
5204    let fake_language_server = fake_language_servers.next().await.unwrap();
5205    let closure_other_tokens = Arc::clone(&other_tokens);
5206    fake_language_server
5207        .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(move |params, _| {
5208            let task_other_tokens = Arc::clone(&closure_other_tokens);
5209            async move {
5210                assert_eq!(
5211                    params.text_document.uri,
5212                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
5213                );
5214                let other_tokens = task_other_tokens.load(atomic::Ordering::Acquire);
5215                let (delta_start, length) = if other_tokens { (0, 2) } else { (3, 4) };
5216                Ok(Some(lsp::SemanticTokensResult::Tokens(
5217                    lsp::SemanticTokens {
5218                        data: vec![
5219                            0, // delta_line
5220                            delta_start,
5221                            length,
5222                            0, // token_type
5223                            0, // token_modifiers_bitset
5224                        ],
5225                        result_id: None,
5226                    },
5227                )))
5228            }
5229        })
5230        .next()
5231        .await
5232        .unwrap();
5233
5234    executor.run_until_parked();
5235    editor_a.update(cx_a, |editor, cx| {
5236        assert!(
5237            extract_semantic_token_ranges(editor, cx).is_empty(),
5238            "Host should get no semantic tokens due to them turned off"
5239        );
5240    });
5241
5242    executor.run_until_parked();
5243    editor_b.update(cx_b, |editor, cx| {
5244        assert_eq!(
5245            vec![MultiBufferOffset(3)..MultiBufferOffset(7)],
5246            extract_semantic_token_ranges(editor, cx),
5247            "Client should get its first semantic tokens when opening an editor"
5248        );
5249    });
5250
5251    other_tokens.fetch_or(true, atomic::Ordering::Release);
5252    fake_language_server
5253        .request::<lsp::request::SemanticTokensRefresh>((), DEFAULT_LSP_REQUEST_TIMEOUT)
5254        .await
5255        .into_response()
5256        .expect("semantic tokens refresh request failed");
5257    // wait out the debounce timeout
5258    executor.advance_clock(LSP_REQUEST_DEBOUNCE_TIMEOUT);
5259    executor.run_until_parked();
5260    editor_a.update(cx_a, |editor, cx| {
5261        assert!(
5262            extract_semantic_token_ranges(editor, cx).is_empty(),
5263            "Host should get no semantic tokens due to them turned off, even after the /refresh"
5264        );
5265    });
5266
5267    executor.run_until_parked();
5268    editor_b.update(cx_b, |editor, cx| {
5269        assert_eq!(
5270            vec![MultiBufferOffset(0)..MultiBufferOffset(2)],
5271            extract_semantic_token_ranges(editor, cx),
5272            "Guest should get a /refresh LSP request propagated by host despite host tokens are off"
5273        );
5274    });
5275}
5276
5277#[gpui::test]
5278async fn test_document_folding_ranges(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
5279    let mut server = TestServer::start(cx_a.executor()).await;
5280    let executor = cx_a.executor();
5281    let client_a = server.create_client(cx_a, "user_a").await;
5282    let client_b = server.create_client(cx_b, "user_b").await;
5283    server
5284        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5285        .await;
5286    let active_call_a = cx_a.read(ActiveCall::global);
5287    let active_call_b = cx_b.read(ActiveCall::global);
5288
5289    cx_a.update(editor::init);
5290    cx_b.update(editor::init);
5291
5292    let capabilities = lsp::ServerCapabilities {
5293        folding_range_provider: Some(lsp::FoldingRangeProviderCapability::Simple(true)),
5294        ..lsp::ServerCapabilities::default()
5295    };
5296    client_a.language_registry().add(rust_lang());
5297    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5298        "Rust",
5299        FakeLspAdapter {
5300            capabilities: capabilities.clone(),
5301            ..FakeLspAdapter::default()
5302        },
5303    );
5304    client_b.language_registry().add(rust_lang());
5305    client_b.language_registry().register_fake_lsp_adapter(
5306        "Rust",
5307        FakeLspAdapter {
5308            capabilities,
5309            ..FakeLspAdapter::default()
5310        },
5311    );
5312
5313    client_a
5314        .fs()
5315        .insert_tree(
5316            path!("/a"),
5317            json!({
5318                "main.rs": "fn main() {\n    if true {\n        println!(\"hello\");\n    }\n}\n",
5319            }),
5320        )
5321        .await;
5322    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
5323    active_call_a
5324        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
5325        .await
5326        .unwrap();
5327    let project_id = active_call_a
5328        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5329        .await
5330        .unwrap();
5331
5332    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5333    active_call_b
5334        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5335        .await
5336        .unwrap();
5337
5338    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
5339
5340    let _buffer_a = project_a
5341        .update(cx_a, |project, cx| {
5342            project.open_local_buffer(path!("/a/main.rs"), cx)
5343        })
5344        .await
5345        .unwrap();
5346    let editor_a = workspace_a
5347        .update_in(cx_a, |workspace, window, cx| {
5348            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5349        })
5350        .await
5351        .unwrap()
5352        .downcast::<Editor>()
5353        .unwrap();
5354
5355    let fake_language_server = fake_language_servers.next().await.unwrap();
5356
5357    let folding_request_count = Arc::new(AtomicUsize::new(0));
5358    let closure_count = Arc::clone(&folding_request_count);
5359    let mut folding_request_handle = fake_language_server
5360        .set_request_handler::<lsp::request::FoldingRangeRequest, _, _>(move |_, _| {
5361            let count = Arc::clone(&closure_count);
5362            async move {
5363                count.fetch_add(1, atomic::Ordering::Release);
5364                Ok(Some(vec![lsp::FoldingRange {
5365                    start_line: 0,
5366                    start_character: Some(10),
5367                    end_line: 4,
5368                    end_character: Some(1),
5369                    kind: None,
5370                    collapsed_text: None,
5371                }]))
5372            }
5373        });
5374
5375    executor.run_until_parked();
5376
5377    assert_eq!(
5378        0,
5379        folding_request_count.load(atomic::Ordering::Acquire),
5380        "LSP folding ranges are off by default, no request should have been made"
5381    );
5382    editor_a.update(cx_a, |editor, cx| {
5383        assert!(
5384            !editor.document_folding_ranges_enabled(cx),
5385            "Host should not have LSP folding ranges enabled"
5386        );
5387    });
5388
5389    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
5390    let editor_b = workspace_b
5391        .update_in(cx_b, |workspace, window, cx| {
5392            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5393        })
5394        .await
5395        .unwrap()
5396        .downcast::<Editor>()
5397        .unwrap();
5398    executor.run_until_parked();
5399
5400    editor_b.update(cx_b, |editor, cx| {
5401        assert!(
5402            !editor.document_folding_ranges_enabled(cx),
5403            "Client should not have LSP folding ranges enabled by default"
5404        );
5405    });
5406
5407    cx_b.update(|_, cx| {
5408        SettingsStore::update_global(cx, |store, cx| {
5409            store.update_user_settings(cx, |settings| {
5410                settings
5411                    .project
5412                    .all_languages
5413                    .defaults
5414                    .document_folding_ranges = Some(DocumentFoldingRanges::On);
5415            });
5416        });
5417    });
5418    executor.advance_clock(LSP_REQUEST_DEBOUNCE_TIMEOUT);
5419    folding_request_handle.next().await.unwrap();
5420    executor.run_until_parked();
5421
5422    assert!(
5423        folding_request_count.load(atomic::Ordering::Acquire) > 0,
5424        "After the client enables LSP folding ranges, a request should be made"
5425    );
5426    editor_b.update(cx_b, |editor, cx| {
5427        assert!(
5428            editor.document_folding_ranges_enabled(cx),
5429            "Client should have LSP folding ranges enabled after toggling the setting on"
5430        );
5431    });
5432    editor_a.update(cx_a, |editor, cx| {
5433        assert!(
5434            !editor.document_folding_ranges_enabled(cx),
5435            "Host should remain unaffected by the client's setting change"
5436        );
5437    });
5438
5439    editor_b.update_in(cx_b, |editor, window, cx| {
5440        let snapshot = editor.display_snapshot(cx);
5441        assert!(
5442            !snapshot.is_line_folded(MultiBufferRow(0)),
5443            "Line 0 should not be folded before fold_at"
5444        );
5445        editor.fold_at(MultiBufferRow(0), window, cx);
5446    });
5447    executor.run_until_parked();
5448
5449    editor_b.update(cx_b, |editor, cx| {
5450        let snapshot = editor.display_snapshot(cx);
5451        assert!(
5452            snapshot.is_line_folded(MultiBufferRow(0)),
5453            "Line 0 should be folded after fold_at using LSP folding range"
5454        );
5455    });
5456}
5457
5458#[gpui::test]
5459async fn test_remote_project_worktree_trust(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
5460    let has_restricted_worktrees = |project: &gpui::Entity<project::Project>,
5461                                    cx: &mut VisualTestContext| {
5462        cx.update(|_, cx| {
5463            let worktree_store = project.read(cx).worktree_store();
5464            TrustedWorktrees::try_get_global(cx)
5465                .unwrap()
5466                .read(cx)
5467                .has_restricted_worktrees(&worktree_store, cx)
5468        })
5469    };
5470
5471    cx_a.update(|cx| {
5472        project::trusted_worktrees::init(HashMap::default(), cx);
5473    });
5474    cx_b.update(|cx| {
5475        project::trusted_worktrees::init(HashMap::default(), cx);
5476    });
5477
5478    let mut server = TestServer::start(cx_a.executor()).await;
5479    let client_a = server.create_client(cx_a, "user_a").await;
5480    let client_b = server.create_client(cx_b, "user_b").await;
5481    server
5482        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5483        .await;
5484
5485    client_a
5486        .fs()
5487        .insert_tree(
5488            path!("/a"),
5489            json!({
5490                "file.txt": "contents",
5491            }),
5492        )
5493        .await;
5494
5495    let (project_a, worktree_id) = client_a
5496        .build_local_project_with_trust(path!("/a"), cx_a)
5497        .await;
5498    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
5499    let active_call_a = cx_a.read(ActiveCall::global);
5500    let project_id = active_call_a
5501        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5502        .await
5503        .unwrap();
5504
5505    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5506    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
5507
5508    let _editor_a = workspace_a
5509        .update_in(cx_a, |workspace, window, cx| {
5510            workspace.open_path(
5511                (worktree_id, rel_path("src/main.rs")),
5512                None,
5513                true,
5514                window,
5515                cx,
5516            )
5517        })
5518        .await
5519        .unwrap()
5520        .downcast::<Editor>()
5521        .unwrap();
5522
5523    let _editor_b = workspace_b
5524        .update_in(cx_b, |workspace, window, cx| {
5525            workspace.open_path(
5526                (worktree_id, rel_path("src/main.rs")),
5527                None,
5528                true,
5529                window,
5530                cx,
5531            )
5532        })
5533        .await
5534        .unwrap()
5535        .downcast::<Editor>()
5536        .unwrap();
5537
5538    cx_a.run_until_parked();
5539    cx_b.run_until_parked();
5540
5541    assert!(
5542        has_restricted_worktrees(&project_a, cx_a),
5543        "local client should have restricted worktrees after opening it"
5544    );
5545    assert!(
5546        !has_restricted_worktrees(&project_b, cx_b),
5547        "remote client joined a project should have no restricted worktrees"
5548    );
5549
5550    cx_a.update(|_, cx| {
5551        if let Some(trusted_worktrees) = TrustedWorktrees::try_get_global(cx) {
5552            trusted_worktrees.update(cx, |trusted_worktrees, cx| {
5553                trusted_worktrees.trust(
5554                    &project_a.read(cx).worktree_store(),
5555                    HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
5556                    cx,
5557                );
5558            });
5559        }
5560    });
5561    assert!(
5562        !has_restricted_worktrees(&project_a, cx_a),
5563        "local client should have no worktrees after trusting those"
5564    );
5565    assert!(
5566        !has_restricted_worktrees(&project_b, cx_b),
5567        "remote client should still be trusted"
5568    );
5569}
5570
5571#[gpui::test]
5572async fn test_document_symbols(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
5573    let mut server = TestServer::start(cx_a.executor()).await;
5574    let executor = cx_a.executor();
5575    let client_a = server.create_client(cx_a, "user_a").await;
5576    let client_b = server.create_client(cx_b, "user_b").await;
5577    server
5578        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5579        .await;
5580    let active_call_a = cx_a.read(ActiveCall::global);
5581    let active_call_b = cx_b.read(ActiveCall::global);
5582
5583    cx_a.update(editor::init);
5584    cx_b.update(editor::init);
5585
5586    let capabilities = lsp::ServerCapabilities {
5587        document_symbol_provider: Some(lsp::OneOf::Left(true)),
5588        ..lsp::ServerCapabilities::default()
5589    };
5590    client_a.language_registry().add(rust_lang());
5591    #[allow(deprecated)]
5592    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5593        "Rust",
5594        FakeLspAdapter {
5595            capabilities: capabilities.clone(),
5596            initializer: Some(Box::new(|fake_language_server| {
5597                #[allow(deprecated)]
5598                fake_language_server
5599                    .set_request_handler::<lsp::request::DocumentSymbolRequest, _, _>(
5600                        move |_, _| async move {
5601                            Ok(Some(lsp::DocumentSymbolResponse::Nested(vec![
5602                                lsp::DocumentSymbol {
5603                                    name: "Foo".to_string(),
5604                                    detail: None,
5605                                    kind: lsp::SymbolKind::STRUCT,
5606                                    tags: None,
5607                                    deprecated: None,
5608                                    range: lsp::Range::new(
5609                                        lsp::Position::new(0, 0),
5610                                        lsp::Position::new(2, 1),
5611                                    ),
5612                                    selection_range: lsp::Range::new(
5613                                        lsp::Position::new(0, 7),
5614                                        lsp::Position::new(0, 10),
5615                                    ),
5616                                    children: Some(vec![lsp::DocumentSymbol {
5617                                        name: "bar".to_string(),
5618                                        detail: None,
5619                                        kind: lsp::SymbolKind::FIELD,
5620                                        tags: None,
5621                                        deprecated: None,
5622                                        range: lsp::Range::new(
5623                                            lsp::Position::new(1, 4),
5624                                            lsp::Position::new(1, 13),
5625                                        ),
5626                                        selection_range: lsp::Range::new(
5627                                            lsp::Position::new(1, 4),
5628                                            lsp::Position::new(1, 7),
5629                                        ),
5630                                        children: None,
5631                                    }]),
5632                                },
5633                            ])))
5634                        },
5635                    );
5636            })),
5637            ..FakeLspAdapter::default()
5638        },
5639    );
5640    client_b.language_registry().add(rust_lang());
5641    client_b.language_registry().register_fake_lsp_adapter(
5642        "Rust",
5643        FakeLspAdapter {
5644            capabilities,
5645            ..FakeLspAdapter::default()
5646        },
5647    );
5648
5649    client_a
5650        .fs()
5651        .insert_tree(
5652            path!("/a"),
5653            json!({
5654                "main.rs": "struct Foo {\n    bar: u32,\n}\n",
5655            }),
5656        )
5657        .await;
5658    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
5659    active_call_a
5660        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
5661        .await
5662        .unwrap();
5663    let project_id = active_call_a
5664        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5665        .await
5666        .unwrap();
5667
5668    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5669    active_call_b
5670        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5671        .await
5672        .unwrap();
5673
5674    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
5675
5676    let editor_a = workspace_a
5677        .update_in(cx_a, |workspace, window, cx| {
5678            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5679        })
5680        .await
5681        .unwrap()
5682        .downcast::<Editor>()
5683        .unwrap();
5684
5685    let _fake_language_server = fake_language_servers.next().await.unwrap();
5686    executor.run_until_parked();
5687
5688    cx_a.update(|_, cx| {
5689        SettingsStore::update_global(cx, |store, cx| {
5690            store.update_user_settings(cx, |settings| {
5691                settings.project.all_languages.defaults.document_symbols =
5692                    Some(DocumentSymbols::On);
5693            });
5694        });
5695    });
5696    executor.advance_clock(LSP_REQUEST_DEBOUNCE_TIMEOUT + Duration::from_millis(100));
5697    executor.run_until_parked();
5698
5699    editor_a.update(cx_a, |editor, cx| {
5700        let (breadcrumbs, _) = editor
5701            .breadcrumbs(cx)
5702            .expect("Host should have breadcrumbs");
5703        let texts: Vec<_> = breadcrumbs.iter().map(|b| b.text.as_str()).collect();
5704        assert_eq!(
5705            texts,
5706            vec!["main.rs", "struct Foo"],
5707            "Host should see file path and LSP symbol 'Foo' in breadcrumbs"
5708        );
5709    });
5710
5711    cx_b.update(|cx| {
5712        SettingsStore::update_global(cx, |store, cx| {
5713            store.update_user_settings(cx, |settings| {
5714                settings.project.all_languages.defaults.document_symbols =
5715                    Some(DocumentSymbols::On);
5716            });
5717        });
5718    });
5719    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
5720    let editor_b = workspace_b
5721        .update_in(cx_b, |workspace, window, cx| {
5722            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
5723        })
5724        .await
5725        .unwrap()
5726        .downcast::<Editor>()
5727        .unwrap();
5728    executor.advance_clock(LSP_REQUEST_DEBOUNCE_TIMEOUT + Duration::from_millis(100));
5729    executor.run_until_parked();
5730
5731    editor_b.update(cx_b, |editor, cx| {
5732        assert_eq!(
5733            editor
5734                .breadcrumbs(cx)
5735                .expect("Client B should have breadcrumbs")
5736                .0
5737                .iter()
5738                .map(|b| b.text.as_str())
5739                .collect::<Vec<_>>(),
5740            vec!["main.rs", "struct Foo"],
5741            "Client B should see file path and LSP symbol 'Foo' via remote project"
5742        );
5743    });
5744}
5745
5746fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
5747    git::blame::BlameEntry {
5748        sha: sha.parse().unwrap(),
5749        range,
5750        ..Default::default()
5751    }
5752}