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