editor_tests.rs

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