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