editor_tests.rs

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