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