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    // Workspace refresh now also triggers document diagnostic pulls for all open buffers
3372    pull_diagnostics_handle.next().await.unwrap();
3373    pull_diagnostics_handle.next().await.unwrap();
3374    assert_eq!(
3375        13,
3376        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3377        "Workspace refresh should trigger document pulls for all open buffers (main.rs and lib.rs)"
3378    );
3379    workspace_diagnostics_pulls_handle.next().await.unwrap();
3380    assert_eq!(
3381        workspace_diagnostic_start_count + 4,
3382        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3383        "Another workspace diagnostics pull should happen after the diagnostics refresh server request"
3384    );
3385    {
3386        assert!(
3387            diagnostics_pulls_result_ids.lock().await.len() > diagnostic_pulls_result_ids,
3388            "Document diagnostic pulls should happen after workspace refresh"
3389        );
3390        assert!(
3391            workspace_diagnostics_pulls_result_ids.lock().await.len() > workspace_pulls_result_ids,
3392            "More workspace diagnostics should be pulled"
3393        );
3394    }
3395    editor_b_lib.update(cx_b, |editor, cx| {
3396        let snapshot = editor.buffer().read(cx).snapshot(cx);
3397        let all_diagnostics = snapshot
3398            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3399            .collect::<Vec<_>>();
3400        let expected_messages = [
3401            expected_workspace_pull_diagnostics_lib_message,
3402            expected_pull_diagnostic_lib_message,
3403            expected_push_diagnostic_lib_message,
3404        ];
3405        assert_eq!(all_diagnostics.len(), 2);
3406        for diagnostic in &all_diagnostics {
3407            assert!(
3408                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3409                "Unexpected diagnostics: {all_diagnostics:?}"
3410            );
3411        }
3412    });
3413    editor_b_main.update(cx_b, |editor, cx| {
3414        let snapshot = editor.buffer().read(cx).snapshot(cx);
3415        let all_diagnostics = snapshot
3416            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3417            .collect::<Vec<_>>();
3418        assert_eq!(all_diagnostics.len(), 2);
3419
3420        let expected_messages = [
3421            expected_workspace_pull_diagnostics_main_message,
3422            expected_pull_diagnostic_main_message,
3423            expected_push_diagnostic_main_message,
3424        ];
3425        for diagnostic in &all_diagnostics {
3426            assert!(
3427                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3428                "Unexpected diagnostics: {all_diagnostics:?}"
3429            );
3430        }
3431    });
3432    editor_a_main.update(cx_a, |editor, cx| {
3433        let snapshot = editor.buffer().read(cx).snapshot(cx);
3434        let all_diagnostics = snapshot
3435            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3436            .collect::<Vec<_>>();
3437        assert_eq!(all_diagnostics.len(), 2);
3438        let expected_messages = [
3439            expected_workspace_pull_diagnostics_main_message,
3440            expected_pull_diagnostic_main_message,
3441            expected_push_diagnostic_main_message,
3442        ];
3443        for diagnostic in &all_diagnostics {
3444            assert!(
3445                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3446                "Unexpected diagnostics: {all_diagnostics:?}"
3447            );
3448        }
3449    });
3450}
3451
3452#[gpui::test(iterations = 10)]
3453async fn test_non_streamed_lsp_pull_diagnostics(
3454    cx_a: &mut TestAppContext,
3455    cx_b: &mut TestAppContext,
3456) {
3457    test_lsp_pull_diagnostics(false, cx_a, cx_b).await;
3458}
3459
3460#[gpui::test(iterations = 10)]
3461async fn test_streamed_lsp_pull_diagnostics(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3462    test_lsp_pull_diagnostics(true, cx_a, cx_b).await;
3463}
3464
3465#[gpui::test(iterations = 10)]
3466async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3467    let mut server = TestServer::start(cx_a.executor()).await;
3468    let client_a = server.create_client(cx_a, "user_a").await;
3469    let client_b = server.create_client(cx_b, "user_b").await;
3470    server
3471        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3472        .await;
3473    let active_call_a = cx_a.read(ActiveCall::global);
3474
3475    cx_a.update(editor::init);
3476    cx_b.update(editor::init);
3477    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
3478    let inline_blame_off_settings = Some(InlineBlameSettings {
3479        enabled: Some(false),
3480        ..Default::default()
3481    });
3482    cx_a.update(|cx| {
3483        SettingsStore::update_global(cx, |store, cx| {
3484            store.update_user_settings(cx, |settings| {
3485                settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
3486            });
3487        });
3488    });
3489    cx_b.update(|cx| {
3490        SettingsStore::update_global(cx, |store, cx| {
3491            store.update_user_settings(cx, |settings| {
3492                settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
3493            });
3494        });
3495    });
3496
3497    client_a
3498        .fs()
3499        .insert_tree(
3500            path!("/my-repo"),
3501            json!({
3502                ".git": {},
3503                "file.txt": "line1\nline2\nline3\nline\n",
3504            }),
3505        )
3506        .await;
3507
3508    let blame = git::blame::Blame {
3509        entries: vec![
3510            blame_entry("1b1b1b", 0..1),
3511            blame_entry("0d0d0d", 1..2),
3512            blame_entry("3a3a3a", 2..3),
3513            blame_entry("4c4c4c", 3..4),
3514        ],
3515        messages: [
3516            ("1b1b1b", "message for idx-0"),
3517            ("0d0d0d", "message for idx-1"),
3518            ("3a3a3a", "message for idx-2"),
3519            ("4c4c4c", "message for idx-3"),
3520        ]
3521        .into_iter()
3522        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
3523        .collect(),
3524    };
3525    client_a.fs().set_blame_for_repo(
3526        Path::new(path!("/my-repo/.git")),
3527        vec![(repo_path("file.txt"), blame)],
3528    );
3529
3530    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
3531    let project_id = active_call_a
3532        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3533        .await
3534        .unwrap();
3535
3536    // Create editor_a
3537    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3538    let editor_a = workspace_a
3539        .update_in(cx_a, |workspace, window, cx| {
3540            workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx)
3541        })
3542        .await
3543        .unwrap()
3544        .downcast::<Editor>()
3545        .unwrap();
3546
3547    // Join the project as client B.
3548    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3549    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3550    let editor_b = workspace_b
3551        .update_in(cx_b, |workspace, window, cx| {
3552            workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx)
3553        })
3554        .await
3555        .unwrap()
3556        .downcast::<Editor>()
3557        .unwrap();
3558    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
3559        editor_b
3560            .buffer()
3561            .read(cx)
3562            .as_singleton()
3563            .unwrap()
3564            .read(cx)
3565            .remote_id()
3566    });
3567
3568    // client_b now requests git blame for the open buffer
3569    editor_b.update_in(cx_b, |editor_b, window, cx| {
3570        assert!(editor_b.blame().is_none());
3571        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
3572    });
3573
3574    cx_a.executor().run_until_parked();
3575    cx_b.executor().run_until_parked();
3576
3577    editor_b.update(cx_b, |editor_b, cx| {
3578        let blame = editor_b.blame().expect("editor_b should have blame now");
3579        let entries = blame.update(cx, |blame, cx| {
3580            blame
3581                .blame_for_rows(
3582                    &(0..4)
3583                        .map(|row| RowInfo {
3584                            buffer_row: Some(row),
3585                            buffer_id: Some(buffer_id_b),
3586                            ..Default::default()
3587                        })
3588                        .collect::<Vec<_>>(),
3589                    cx,
3590                )
3591                .collect::<Vec<_>>()
3592        });
3593
3594        assert_eq!(
3595            entries,
3596            vec![
3597                Some((buffer_id_b, blame_entry("1b1b1b", 0..1))),
3598                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3599                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3600                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3601            ]
3602        );
3603
3604        blame.update(cx, |blame, _| {
3605            for (idx, (buffer, entry)) in entries.iter().flatten().enumerate() {
3606                let details = blame.details_for_entry(*buffer, entry).unwrap();
3607                assert_eq!(details.message, format!("message for idx-{}", idx));
3608            }
3609        });
3610    });
3611
3612    // editor_b updates the file, which gets sent to client_a, which updates git blame,
3613    // which gets back to client_b.
3614    editor_b.update_in(cx_b, |editor_b, _, cx| {
3615        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
3616    });
3617
3618    cx_a.executor().run_until_parked();
3619    cx_b.executor().run_until_parked();
3620
3621    editor_b.update(cx_b, |editor_b, cx| {
3622        let blame = editor_b.blame().expect("editor_b should have blame now");
3623        let entries = blame.update(cx, |blame, cx| {
3624            blame
3625                .blame_for_rows(
3626                    &(0..4)
3627                        .map(|row| RowInfo {
3628                            buffer_row: Some(row),
3629                            buffer_id: Some(buffer_id_b),
3630                            ..Default::default()
3631                        })
3632                        .collect::<Vec<_>>(),
3633                    cx,
3634                )
3635                .collect::<Vec<_>>()
3636        });
3637
3638        assert_eq!(
3639            entries,
3640            vec![
3641                None,
3642                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3643                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3644                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3645            ]
3646        );
3647    });
3648
3649    // Now editor_a also updates the file
3650    editor_a.update_in(cx_a, |editor_a, _, cx| {
3651        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
3652    });
3653
3654    cx_a.executor().run_until_parked();
3655    cx_b.executor().run_until_parked();
3656
3657    editor_b.update(cx_b, |editor_b, cx| {
3658        let blame = editor_b.blame().expect("editor_b should have blame now");
3659        let entries = blame.update(cx, |blame, cx| {
3660            blame
3661                .blame_for_rows(
3662                    &(0..4)
3663                        .map(|row| RowInfo {
3664                            buffer_row: Some(row),
3665                            buffer_id: Some(buffer_id_b),
3666                            ..Default::default()
3667                        })
3668                        .collect::<Vec<_>>(),
3669                    cx,
3670                )
3671                .collect::<Vec<_>>()
3672        });
3673
3674        assert_eq!(
3675            entries,
3676            vec![
3677                None,
3678                None,
3679                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3680                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3681            ]
3682        );
3683    });
3684}
3685
3686#[gpui::test(iterations = 30)]
3687async fn test_collaborating_with_editorconfig(
3688    cx_a: &mut TestAppContext,
3689    cx_b: &mut TestAppContext,
3690) {
3691    let mut server = TestServer::start(cx_a.executor()).await;
3692    let client_a = server.create_client(cx_a, "user_a").await;
3693    let client_b = server.create_client(cx_b, "user_b").await;
3694    server
3695        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3696        .await;
3697    let active_call_a = cx_a.read(ActiveCall::global);
3698
3699    cx_b.update(editor::init);
3700
3701    // Set up a fake language server.
3702    client_a.language_registry().add(rust_lang());
3703    client_a
3704        .fs()
3705        .insert_tree(
3706            path!("/a"),
3707            json!({
3708                "src": {
3709                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
3710                    "other_mod": {
3711                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
3712                        ".editorconfig": "",
3713                    },
3714                },
3715                ".editorconfig": "[*]\ntab_width = 2\n",
3716            }),
3717        )
3718        .await;
3719    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
3720    let project_id = active_call_a
3721        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3722        .await
3723        .unwrap();
3724    let main_buffer_a = project_a
3725        .update(cx_a, |p, cx| {
3726            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
3727        })
3728        .await
3729        .unwrap();
3730    let other_buffer_a = project_a
3731        .update(cx_a, |p, cx| {
3732            p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
3733        })
3734        .await
3735        .unwrap();
3736    let cx_a = cx_a.add_empty_window();
3737    let main_editor_a = cx_a.new_window_entity(|window, cx| {
3738        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
3739    });
3740    let other_editor_a = cx_a.new_window_entity(|window, cx| {
3741        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
3742    });
3743    let mut main_editor_cx_a = EditorTestContext {
3744        cx: cx_a.clone(),
3745        window: cx_a.window_handle(),
3746        editor: main_editor_a,
3747        assertion_cx: AssertionContextManager::new(),
3748    };
3749    let mut other_editor_cx_a = EditorTestContext {
3750        cx: cx_a.clone(),
3751        window: cx_a.window_handle(),
3752        editor: other_editor_a,
3753        assertion_cx: AssertionContextManager::new(),
3754    };
3755
3756    // Join the project as client B.
3757    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3758    let main_buffer_b = project_b
3759        .update(cx_b, |p, cx| {
3760            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
3761        })
3762        .await
3763        .unwrap();
3764    let other_buffer_b = project_b
3765        .update(cx_b, |p, cx| {
3766            p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
3767        })
3768        .await
3769        .unwrap();
3770    let cx_b = cx_b.add_empty_window();
3771    let main_editor_b = cx_b.new_window_entity(|window, cx| {
3772        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
3773    });
3774    let other_editor_b = cx_b.new_window_entity(|window, cx| {
3775        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
3776    });
3777    let mut main_editor_cx_b = EditorTestContext {
3778        cx: cx_b.clone(),
3779        window: cx_b.window_handle(),
3780        editor: main_editor_b,
3781        assertion_cx: AssertionContextManager::new(),
3782    };
3783    let mut other_editor_cx_b = EditorTestContext {
3784        cx: cx_b.clone(),
3785        window: cx_b.window_handle(),
3786        editor: other_editor_b,
3787        assertion_cx: AssertionContextManager::new(),
3788    };
3789
3790    let initial_main = indoc! {"
3791ˇmod other;
3792fn main() { let foo = other::foo(); }"};
3793    let initial_other = indoc! {"
3794ˇpub fn foo() -> usize {
3795    4
3796}"};
3797
3798    let first_tabbed_main = indoc! {"
3799  ˇmod other;
3800fn main() { let foo = other::foo(); }"};
3801    tab_undo_assert(
3802        &mut main_editor_cx_a,
3803        &mut main_editor_cx_b,
3804        initial_main,
3805        first_tabbed_main,
3806        true,
3807    );
3808    tab_undo_assert(
3809        &mut main_editor_cx_a,
3810        &mut main_editor_cx_b,
3811        initial_main,
3812        first_tabbed_main,
3813        false,
3814    );
3815
3816    let first_tabbed_other = indoc! {"
3817  ˇpub fn foo() -> usize {
3818    4
3819}"};
3820    tab_undo_assert(
3821        &mut other_editor_cx_a,
3822        &mut other_editor_cx_b,
3823        initial_other,
3824        first_tabbed_other,
3825        true,
3826    );
3827    tab_undo_assert(
3828        &mut other_editor_cx_a,
3829        &mut other_editor_cx_b,
3830        initial_other,
3831        first_tabbed_other,
3832        false,
3833    );
3834
3835    client_a
3836        .fs()
3837        .atomic_write(
3838            PathBuf::from(path!("/a/src/.editorconfig")),
3839            "[*]\ntab_width = 3\n".to_owned(),
3840        )
3841        .await
3842        .unwrap();
3843    cx_a.run_until_parked();
3844    cx_b.run_until_parked();
3845
3846    let second_tabbed_main = indoc! {"
3847   ˇmod other;
3848fn main() { let foo = other::foo(); }"};
3849    tab_undo_assert(
3850        &mut main_editor_cx_a,
3851        &mut main_editor_cx_b,
3852        initial_main,
3853        second_tabbed_main,
3854        true,
3855    );
3856    tab_undo_assert(
3857        &mut main_editor_cx_a,
3858        &mut main_editor_cx_b,
3859        initial_main,
3860        second_tabbed_main,
3861        false,
3862    );
3863
3864    let second_tabbed_other = indoc! {"
3865   ˇpub fn foo() -> usize {
3866    4
3867}"};
3868    tab_undo_assert(
3869        &mut other_editor_cx_a,
3870        &mut other_editor_cx_b,
3871        initial_other,
3872        second_tabbed_other,
3873        true,
3874    );
3875    tab_undo_assert(
3876        &mut other_editor_cx_a,
3877        &mut other_editor_cx_b,
3878        initial_other,
3879        second_tabbed_other,
3880        false,
3881    );
3882
3883    let editorconfig_buffer_b = project_b
3884        .update(cx_b, |p, cx| {
3885            p.open_buffer((worktree_id, rel_path("src/other_mod/.editorconfig")), cx)
3886        })
3887        .await
3888        .unwrap();
3889    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
3890        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
3891    });
3892    project_b
3893        .update(cx_b, |project, cx| {
3894            project.save_buffer(editorconfig_buffer_b.clone(), cx)
3895        })
3896        .await
3897        .unwrap();
3898    cx_a.run_until_parked();
3899    cx_b.run_until_parked();
3900
3901    tab_undo_assert(
3902        &mut main_editor_cx_a,
3903        &mut main_editor_cx_b,
3904        initial_main,
3905        second_tabbed_main,
3906        true,
3907    );
3908    tab_undo_assert(
3909        &mut main_editor_cx_a,
3910        &mut main_editor_cx_b,
3911        initial_main,
3912        second_tabbed_main,
3913        false,
3914    );
3915
3916    let third_tabbed_other = indoc! {"
3917      ˇpub fn foo() -> usize {
3918    4
3919}"};
3920    tab_undo_assert(
3921        &mut other_editor_cx_a,
3922        &mut other_editor_cx_b,
3923        initial_other,
3924        third_tabbed_other,
3925        true,
3926    );
3927
3928    tab_undo_assert(
3929        &mut other_editor_cx_a,
3930        &mut other_editor_cx_b,
3931        initial_other,
3932        third_tabbed_other,
3933        false,
3934    );
3935}
3936
3937#[gpui::test]
3938async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3939    let executor = cx_a.executor();
3940    let mut server = TestServer::start(executor.clone()).await;
3941    let client_a = server.create_client(cx_a, "user_a").await;
3942    let client_b = server.create_client(cx_b, "user_b").await;
3943    server
3944        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3945        .await;
3946    let active_call_a = cx_a.read(ActiveCall::global);
3947    let active_call_b = cx_b.read(ActiveCall::global);
3948    cx_a.update(editor::init);
3949    cx_b.update(editor::init);
3950    client_a
3951        .fs()
3952        .insert_tree(
3953            "/a",
3954            json!({
3955                "test.txt": "one\ntwo\nthree\nfour\nfive",
3956            }),
3957        )
3958        .await;
3959    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3960    let project_path = ProjectPath {
3961        worktree_id,
3962        path: rel_path(&"test.txt").into(),
3963    };
3964    let abs_path = project_a.read_with(cx_a, |project, cx| {
3965        project
3966            .absolute_path(&project_path, cx)
3967            .map(Arc::from)
3968            .unwrap()
3969    });
3970
3971    active_call_a
3972        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
3973        .await
3974        .unwrap();
3975    let project_id = active_call_a
3976        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3977        .await
3978        .unwrap();
3979    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3980    active_call_b
3981        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
3982        .await
3983        .unwrap();
3984    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3985    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3986
3987    // Client A opens an editor.
3988    let editor_a = workspace_a
3989        .update_in(cx_a, |workspace, window, cx| {
3990            workspace.open_path(project_path.clone(), None, true, window, cx)
3991        })
3992        .await
3993        .unwrap()
3994        .downcast::<Editor>()
3995        .unwrap();
3996
3997    // Client B opens same editor as A.
3998    let editor_b = workspace_b
3999        .update_in(cx_b, |workspace, window, cx| {
4000            workspace.open_path(project_path.clone(), None, true, window, cx)
4001        })
4002        .await
4003        .unwrap()
4004        .downcast::<Editor>()
4005        .unwrap();
4006
4007    cx_a.run_until_parked();
4008    cx_b.run_until_parked();
4009
4010    // Client A adds breakpoint on line (1)
4011    editor_a.update_in(cx_a, |editor, window, cx| {
4012        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4013    });
4014
4015    cx_a.run_until_parked();
4016    cx_b.run_until_parked();
4017
4018    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4019        editor
4020            .breakpoint_store()
4021            .unwrap()
4022            .read(cx)
4023            .all_source_breakpoints(cx)
4024    });
4025    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4026        editor
4027            .breakpoint_store()
4028            .unwrap()
4029            .read(cx)
4030            .all_source_breakpoints(cx)
4031    });
4032
4033    assert_eq!(1, breakpoints_a.len());
4034    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
4035    assert_eq!(breakpoints_a, breakpoints_b);
4036
4037    // Client B adds breakpoint on line(2)
4038    editor_b.update_in(cx_b, |editor, window, cx| {
4039        editor.move_down(&editor::actions::MoveDown, window, cx);
4040        editor.move_down(&editor::actions::MoveDown, window, cx);
4041        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4042    });
4043
4044    cx_a.run_until_parked();
4045    cx_b.run_until_parked();
4046
4047    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4048        editor
4049            .breakpoint_store()
4050            .unwrap()
4051            .read(cx)
4052            .all_source_breakpoints(cx)
4053    });
4054    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4055        editor
4056            .breakpoint_store()
4057            .unwrap()
4058            .read(cx)
4059            .all_source_breakpoints(cx)
4060    });
4061
4062    assert_eq!(1, breakpoints_a.len());
4063    assert_eq!(breakpoints_a, breakpoints_b);
4064    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
4065
4066    // Client A removes last added breakpoint from client B
4067    editor_a.update_in(cx_a, |editor, window, cx| {
4068        editor.move_down(&editor::actions::MoveDown, window, cx);
4069        editor.move_down(&editor::actions::MoveDown, window, cx);
4070        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4071    });
4072
4073    cx_a.run_until_parked();
4074    cx_b.run_until_parked();
4075
4076    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4077        editor
4078            .breakpoint_store()
4079            .unwrap()
4080            .read(cx)
4081            .all_source_breakpoints(cx)
4082    });
4083    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4084        editor
4085            .breakpoint_store()
4086            .unwrap()
4087            .read(cx)
4088            .all_source_breakpoints(cx)
4089    });
4090
4091    assert_eq!(1, breakpoints_a.len());
4092    assert_eq!(breakpoints_a, breakpoints_b);
4093    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
4094
4095    // Client B removes first added breakpoint by client A
4096    editor_b.update_in(cx_b, |editor, window, cx| {
4097        editor.move_up(&editor::actions::MoveUp, window, cx);
4098        editor.move_up(&editor::actions::MoveUp, window, cx);
4099        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4100    });
4101
4102    cx_a.run_until_parked();
4103    cx_b.run_until_parked();
4104
4105    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4106        editor
4107            .breakpoint_store()
4108            .unwrap()
4109            .read(cx)
4110            .all_source_breakpoints(cx)
4111    });
4112    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4113        editor
4114            .breakpoint_store()
4115            .unwrap()
4116            .read(cx)
4117            .all_source_breakpoints(cx)
4118    });
4119
4120    assert_eq!(0, breakpoints_a.len());
4121    assert_eq!(breakpoints_a, breakpoints_b);
4122}
4123
4124#[gpui::test]
4125async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4126    let mut server = TestServer::start(cx_a.executor()).await;
4127    let client_a = server.create_client(cx_a, "user_a").await;
4128    let client_b = server.create_client(cx_b, "user_b").await;
4129    server
4130        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4131        .await;
4132    let active_call_a = cx_a.read(ActiveCall::global);
4133    let active_call_b = cx_b.read(ActiveCall::global);
4134
4135    cx_a.update(editor::init);
4136    cx_b.update(editor::init);
4137
4138    client_a.language_registry().add(rust_lang());
4139    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4140        "Rust",
4141        FakeLspAdapter {
4142            name: "rust-analyzer",
4143            ..FakeLspAdapter::default()
4144        },
4145    );
4146    client_b.language_registry().add(rust_lang());
4147    client_b.language_registry().register_fake_lsp_adapter(
4148        "Rust",
4149        FakeLspAdapter {
4150            name: "rust-analyzer",
4151            ..FakeLspAdapter::default()
4152        },
4153    );
4154
4155    client_a
4156        .fs()
4157        .insert_tree(
4158            path!("/a"),
4159            json!({
4160                "main.rs": "fn main() {}",
4161            }),
4162        )
4163        .await;
4164    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4165    active_call_a
4166        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
4167        .await
4168        .unwrap();
4169    let project_id = active_call_a
4170        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4171        .await
4172        .unwrap();
4173
4174    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4175    active_call_b
4176        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
4177        .await
4178        .unwrap();
4179
4180    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4181    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4182
4183    let editor_a = workspace_a
4184        .update_in(cx_a, |workspace, window, cx| {
4185            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
4186        })
4187        .await
4188        .unwrap()
4189        .downcast::<Editor>()
4190        .unwrap();
4191
4192    let editor_b = workspace_b
4193        .update_in(cx_b, |workspace, window, cx| {
4194            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
4195        })
4196        .await
4197        .unwrap()
4198        .downcast::<Editor>()
4199        .unwrap();
4200
4201    let fake_language_server = fake_language_servers.next().await.unwrap();
4202
4203    // host
4204    let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4205        |params, _| async move {
4206            assert_eq!(
4207                params.text_document.uri,
4208                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4209            );
4210            assert_eq!(params.position, lsp::Position::new(0, 0));
4211            Ok(Some(ExpandedMacro {
4212                name: "test_macro_name".to_string(),
4213                expansion: "test_macro_expansion on the host".to_string(),
4214            }))
4215        },
4216    );
4217
4218    editor_a.update_in(cx_a, |editor, window, cx| {
4219        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4220    });
4221    expand_request_a.next().await.unwrap();
4222    cx_a.run_until_parked();
4223
4224    workspace_a.update(cx_a, |workspace, cx| {
4225        workspace.active_pane().update(cx, |pane, cx| {
4226            assert_eq!(
4227                pane.items_len(),
4228                2,
4229                "Should have added a macro expansion to the host's pane"
4230            );
4231            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4232            new_editor.update(cx, |editor, cx| {
4233                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
4234            });
4235        })
4236    });
4237
4238    // client
4239    let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4240        |params, _| async move {
4241            assert_eq!(
4242                params.text_document.uri,
4243                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4244            );
4245            assert_eq!(
4246                params.position,
4247                lsp::Position::new(0, 12),
4248                "editor_b has selected the entire text and should query for a different position"
4249            );
4250            Ok(Some(ExpandedMacro {
4251                name: "test_macro_name".to_string(),
4252                expansion: "test_macro_expansion on the client".to_string(),
4253            }))
4254        },
4255    );
4256
4257    editor_b.update_in(cx_b, |editor, window, cx| {
4258        editor.select_all(&SelectAll, window, cx);
4259        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4260    });
4261    expand_request_b.next().await.unwrap();
4262    cx_b.run_until_parked();
4263
4264    workspace_b.update(cx_b, |workspace, cx| {
4265        workspace.active_pane().update(cx, |pane, cx| {
4266            assert_eq!(
4267                pane.items_len(),
4268                2,
4269                "Should have added a macro expansion to the client's pane"
4270            );
4271            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4272            new_editor.update(cx, |editor, cx| {
4273                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
4274            });
4275        })
4276    });
4277}
4278
4279#[gpui::test]
4280async fn test_copy_file_name_without_extension(
4281    cx_a: &mut TestAppContext,
4282    cx_b: &mut TestAppContext,
4283) {
4284    let mut server = TestServer::start(cx_a.executor()).await;
4285    let client_a = server.create_client(cx_a, "user_a").await;
4286    let client_b = server.create_client(cx_b, "user_b").await;
4287    server
4288        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4289        .await;
4290
4291    cx_b.update(editor::init);
4292
4293    client_a
4294        .fs()
4295        .insert_tree(
4296            path!("/root"),
4297            json!({
4298                "src": {
4299                    "main.rs": indoc! {"
4300                        fn main() {
4301                            println!(\"Hello, world!\");
4302                        }
4303                    "},
4304                }
4305            }),
4306        )
4307        .await;
4308
4309    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
4310    let active_call_a = cx_a.read(ActiveCall::global);
4311    let project_id = active_call_a
4312        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4313        .await
4314        .unwrap();
4315
4316    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4317
4318    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4319    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4320
4321    let editor_a = workspace_a
4322        .update_in(cx_a, |workspace, window, cx| {
4323            workspace.open_path(
4324                (worktree_id, rel_path("src/main.rs")),
4325                None,
4326                true,
4327                window,
4328                cx,
4329            )
4330        })
4331        .await
4332        .unwrap()
4333        .downcast::<Editor>()
4334        .unwrap();
4335
4336    let editor_b = workspace_b
4337        .update_in(cx_b, |workspace, window, cx| {
4338            workspace.open_path(
4339                (worktree_id, rel_path("src/main.rs")),
4340                None,
4341                true,
4342                window,
4343                cx,
4344            )
4345        })
4346        .await
4347        .unwrap()
4348        .downcast::<Editor>()
4349        .unwrap();
4350
4351    cx_a.run_until_parked();
4352    cx_b.run_until_parked();
4353
4354    editor_a.update_in(cx_a, |editor, window, cx| {
4355        editor.copy_file_name_without_extension(&CopyFileNameWithoutExtension, window, cx);
4356    });
4357
4358    assert_eq!(
4359        cx_a.read_from_clipboard().and_then(|item| item.text()),
4360        Some("main".to_string())
4361    );
4362
4363    editor_b.update_in(cx_b, |editor, window, cx| {
4364        editor.copy_file_name_without_extension(&CopyFileNameWithoutExtension, window, cx);
4365    });
4366
4367    assert_eq!(
4368        cx_b.read_from_clipboard().and_then(|item| item.text()),
4369        Some("main".to_string())
4370    );
4371}
4372
4373#[gpui::test]
4374async fn test_copy_file_name(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4375    let mut server = TestServer::start(cx_a.executor()).await;
4376    let client_a = server.create_client(cx_a, "user_a").await;
4377    let client_b = server.create_client(cx_b, "user_b").await;
4378    server
4379        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4380        .await;
4381
4382    cx_b.update(editor::init);
4383
4384    client_a
4385        .fs()
4386        .insert_tree(
4387            path!("/root"),
4388            json!({
4389                "src": {
4390                    "main.rs": indoc! {"
4391                        fn main() {
4392                            println!(\"Hello, world!\");
4393                        }
4394                    "},
4395                }
4396            }),
4397        )
4398        .await;
4399
4400    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
4401    let active_call_a = cx_a.read(ActiveCall::global);
4402    let project_id = active_call_a
4403        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4404        .await
4405        .unwrap();
4406
4407    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4408
4409    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4410    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4411
4412    let editor_a = workspace_a
4413        .update_in(cx_a, |workspace, window, cx| {
4414            workspace.open_path(
4415                (worktree_id, rel_path("src/main.rs")),
4416                None,
4417                true,
4418                window,
4419                cx,
4420            )
4421        })
4422        .await
4423        .unwrap()
4424        .downcast::<Editor>()
4425        .unwrap();
4426
4427    let editor_b = workspace_b
4428        .update_in(cx_b, |workspace, window, cx| {
4429            workspace.open_path(
4430                (worktree_id, rel_path("src/main.rs")),
4431                None,
4432                true,
4433                window,
4434                cx,
4435            )
4436        })
4437        .await
4438        .unwrap()
4439        .downcast::<Editor>()
4440        .unwrap();
4441
4442    cx_a.run_until_parked();
4443    cx_b.run_until_parked();
4444
4445    editor_a.update_in(cx_a, |editor, window, cx| {
4446        editor.copy_file_name(&CopyFileName, window, cx);
4447    });
4448
4449    assert_eq!(
4450        cx_a.read_from_clipboard().and_then(|item| item.text()),
4451        Some("main.rs".to_string())
4452    );
4453
4454    editor_b.update_in(cx_b, |editor, window, cx| {
4455        editor.copy_file_name(&CopyFileName, window, cx);
4456    });
4457
4458    assert_eq!(
4459        cx_b.read_from_clipboard().and_then(|item| item.text()),
4460        Some("main.rs".to_string())
4461    );
4462}
4463
4464#[gpui::test]
4465async fn test_copy_file_location(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4466    let mut server = TestServer::start(cx_a.executor()).await;
4467    let client_a = server.create_client(cx_a, "user_a").await;
4468    let client_b = server.create_client(cx_b, "user_b").await;
4469    server
4470        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4471        .await;
4472
4473    cx_b.update(editor::init);
4474
4475    client_a
4476        .fs()
4477        .insert_tree(
4478            path!("/root"),
4479            json!({
4480                "src": {
4481                    "main.rs": indoc! {"
4482                        fn main() {
4483                            println!(\"Hello, world!\");
4484                        }
4485                    "},
4486                }
4487            }),
4488        )
4489        .await;
4490
4491    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
4492    let active_call_a = cx_a.read(ActiveCall::global);
4493    let project_id = active_call_a
4494        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4495        .await
4496        .unwrap();
4497
4498    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4499
4500    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4501    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4502
4503    let editor_a = workspace_a
4504        .update_in(cx_a, |workspace, window, cx| {
4505            workspace.open_path(
4506                (worktree_id, rel_path("src/main.rs")),
4507                None,
4508                true,
4509                window,
4510                cx,
4511            )
4512        })
4513        .await
4514        .unwrap()
4515        .downcast::<Editor>()
4516        .unwrap();
4517
4518    let editor_b = workspace_b
4519        .update_in(cx_b, |workspace, window, cx| {
4520            workspace.open_path(
4521                (worktree_id, rel_path("src/main.rs")),
4522                None,
4523                true,
4524                window,
4525                cx,
4526            )
4527        })
4528        .await
4529        .unwrap()
4530        .downcast::<Editor>()
4531        .unwrap();
4532
4533    cx_a.run_until_parked();
4534    cx_b.run_until_parked();
4535
4536    editor_a.update_in(cx_a, |editor, window, cx| {
4537        editor.change_selections(Default::default(), window, cx, |s| {
4538            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(16)]);
4539        });
4540        editor.copy_file_location(&CopyFileLocation, window, cx);
4541    });
4542
4543    assert_eq!(
4544        cx_a.read_from_clipboard().and_then(|item| item.text()),
4545        Some(format!("{}:2", path!("src/main.rs")))
4546    );
4547
4548    editor_b.update_in(cx_b, |editor, window, cx| {
4549        editor.change_selections(Default::default(), window, cx, |s| {
4550            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(16)]);
4551        });
4552        editor.copy_file_location(&CopyFileLocation, window, cx);
4553    });
4554
4555    assert_eq!(
4556        cx_b.read_from_clipboard().and_then(|item| item.text()),
4557        Some(format!("{}:2", path!("src/main.rs")))
4558    );
4559}
4560
4561#[track_caller]
4562fn tab_undo_assert(
4563    cx_a: &mut EditorTestContext,
4564    cx_b: &mut EditorTestContext,
4565    expected_initial: &str,
4566    expected_tabbed: &str,
4567    a_tabs: bool,
4568) {
4569    cx_a.assert_editor_state(expected_initial);
4570    cx_b.assert_editor_state(expected_initial);
4571
4572    if a_tabs {
4573        cx_a.update_editor(|editor, window, cx| {
4574            editor.tab(&editor::actions::Tab, window, cx);
4575        });
4576    } else {
4577        cx_b.update_editor(|editor, window, cx| {
4578            editor.tab(&editor::actions::Tab, window, cx);
4579        });
4580    }
4581
4582    cx_a.run_until_parked();
4583    cx_b.run_until_parked();
4584
4585    cx_a.assert_editor_state(expected_tabbed);
4586    cx_b.assert_editor_state(expected_tabbed);
4587
4588    if a_tabs {
4589        cx_a.update_editor(|editor, window, cx| {
4590            editor.undo(&editor::actions::Undo, window, cx);
4591        });
4592    } else {
4593        cx_b.update_editor(|editor, window, cx| {
4594            editor.undo(&editor::actions::Undo, window, cx);
4595        });
4596    }
4597    cx_a.run_until_parked();
4598    cx_b.run_until_parked();
4599    cx_a.assert_editor_state(expected_initial);
4600    cx_b.assert_editor_state(expected_initial);
4601}
4602
4603fn extract_hint_labels(editor: &Editor, cx: &mut App) -> Vec<String> {
4604    let lsp_store = editor.project().unwrap().read(cx).lsp_store();
4605
4606    let mut all_cached_labels = Vec::new();
4607    let mut all_fetched_hints = Vec::new();
4608    for buffer in editor.buffer().read(cx).all_buffers() {
4609        lsp_store.update(cx, |lsp_store, cx| {
4610            let hints = &lsp_store.latest_lsp_data(&buffer, cx).inlay_hints();
4611            all_cached_labels.extend(hints.all_cached_hints().into_iter().map(|hint| {
4612                let mut label = hint.text().to_string();
4613                if hint.padding_left {
4614                    label.insert(0, ' ');
4615                }
4616                if hint.padding_right {
4617                    label.push_str(" ");
4618                }
4619                label
4620            }));
4621            all_fetched_hints.extend(hints.all_fetched_hints());
4622        });
4623    }
4624
4625    assert!(
4626        all_fetched_hints.is_empty(),
4627        "Did not expect background hints fetch tasks, but got {} of them",
4628        all_fetched_hints.len()
4629    );
4630
4631    all_cached_labels
4632}
4633
4634#[track_caller]
4635fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
4636    editor
4637        .all_inlays(cx)
4638        .into_iter()
4639        .filter_map(|inlay| inlay.get_color())
4640        .map(Rgba::from)
4641        .collect()
4642}
4643
4644#[gpui::test]
4645async fn test_remote_project_worktree_trust(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4646    let has_restricted_worktrees = |project: &gpui::Entity<project::Project>,
4647                                    cx: &mut VisualTestContext| {
4648        cx.update(|_, cx| {
4649            let worktree_store = project.read(cx).worktree_store();
4650            TrustedWorktrees::try_get_global(cx)
4651                .unwrap()
4652                .read(cx)
4653                .has_restricted_worktrees(&worktree_store, cx)
4654        })
4655    };
4656
4657    cx_a.update(|cx| {
4658        project::trusted_worktrees::init(HashMap::default(), None, None, cx);
4659    });
4660    cx_b.update(|cx| {
4661        project::trusted_worktrees::init(HashMap::default(), None, None, cx);
4662    });
4663
4664    let mut server = TestServer::start(cx_a.executor()).await;
4665    let client_a = server.create_client(cx_a, "user_a").await;
4666    let client_b = server.create_client(cx_b, "user_b").await;
4667    server
4668        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4669        .await;
4670
4671    client_a
4672        .fs()
4673        .insert_tree(
4674            path!("/a"),
4675            json!({
4676                "file.txt": "contents",
4677            }),
4678        )
4679        .await;
4680
4681    let (project_a, worktree_id) = client_a
4682        .build_local_project_with_trust(path!("/a"), cx_a)
4683        .await;
4684    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4685    let active_call_a = cx_a.read(ActiveCall::global);
4686    let project_id = active_call_a
4687        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4688        .await
4689        .unwrap();
4690
4691    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4692    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4693
4694    let _editor_a = workspace_a
4695        .update_in(cx_a, |workspace, window, cx| {
4696            workspace.open_path(
4697                (worktree_id, rel_path("src/main.rs")),
4698                None,
4699                true,
4700                window,
4701                cx,
4702            )
4703        })
4704        .await
4705        .unwrap()
4706        .downcast::<Editor>()
4707        .unwrap();
4708
4709    let _editor_b = workspace_b
4710        .update_in(cx_b, |workspace, window, cx| {
4711            workspace.open_path(
4712                (worktree_id, rel_path("src/main.rs")),
4713                None,
4714                true,
4715                window,
4716                cx,
4717            )
4718        })
4719        .await
4720        .unwrap()
4721        .downcast::<Editor>()
4722        .unwrap();
4723
4724    cx_a.run_until_parked();
4725    cx_b.run_until_parked();
4726
4727    assert!(
4728        has_restricted_worktrees(&project_a, cx_a),
4729        "local client should have restricted worktrees after opening it"
4730    );
4731    assert!(
4732        !has_restricted_worktrees(&project_b, cx_b),
4733        "remote client joined a project should have no restricted worktrees"
4734    );
4735
4736    cx_a.update(|_, cx| {
4737        if let Some(trusted_worktrees) = TrustedWorktrees::try_get_global(cx) {
4738            trusted_worktrees.update(cx, |trusted_worktrees, cx| {
4739                trusted_worktrees.trust(
4740                    &project_a.read(cx).worktree_store(),
4741                    HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
4742                    cx,
4743                );
4744            });
4745        }
4746    });
4747    assert!(
4748        !has_restricted_worktrees(&project_a, cx_a),
4749        "local client should have no worktrees after trusting those"
4750    );
4751    assert!(
4752        !has_restricted_worktrees(&project_b, cx_b),
4753        "remote client should still be trusted"
4754    );
4755}
4756
4757fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
4758    git::blame::BlameEntry {
4759        sha: sha.parse().unwrap(),
4760        range,
4761        ..Default::default()
4762    }
4763}