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