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