editor_tests.rs

   1use crate::{
   2    rpc::RECONNECT_TIMEOUT,
   3    tests::{TestServer, rust_lang},
   4};
   5use call::ActiveCall;
   6use editor::{
   7    DocumentColorsRenderMode, Editor, FETCH_COLORS_DEBOUNCE_TIMEOUT, RowInfo, SelectionEffects,
   8    actions::{
   9        ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst,
  10        ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo,
  11    },
  12    test::{
  13        editor_test_context::{AssertionContextManager, EditorTestContext},
  14        expand_macro_recursively,
  15    },
  16};
  17use fs::Fs;
  18use futures::{SinkExt, StreamExt, channel::mpsc, lock::Mutex};
  19use git::repository::repo_path;
  20use gpui::{
  21    App, Rgba, SharedString, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext,
  22};
  23use indoc::indoc;
  24use language::FakeLspAdapter;
  25use lsp::LSP_REQUEST_TIMEOUT;
  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([13..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([46..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([68..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([7..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            6..9
 981        );
 982        rename.editor.update(cx, |rename_editor, cx| {
 983            let rename_selection = rename_editor.selections.newest::<usize>(&rename_editor.display_snapshot(cx));
 984            assert_eq!(
 985                rename_selection.range(),
 986                0..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([(0..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([7..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, 6..9);
1029        rename.editor.update(cx, |rename_editor, cx| {
1030            let rename_selection = rename_editor.selections.newest::<usize>(&rename_editor.display_snapshot(cx));
1031            assert_eq!(
1032                rename_selection.range(),
1033                1..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([(0..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([7..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([1..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([2..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(text::Anchor::MIN..text::Anchor::MAX, false)
1583            .count()
1584            == 1
1585    });
1586
1587    // Edit the buffer as client B and see that edit as client A.
1588    editor_b.update_in(cx_b, |editor, window, cx| {
1589        editor.handle_input("ok, ", window, cx)
1590    });
1591    executor.run_until_parked();
1592
1593    buffer_a.read_with(cx_a, |buffer, _| {
1594        assert_eq!(buffer.text(), "ok, b-contents")
1595    });
1596
1597    // Client B can invite client C on a project shared by client A.
1598    active_call_b
1599        .update(cx_b, |call, cx| {
1600            call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
1601        })
1602        .await
1603        .unwrap();
1604
1605    let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
1606    executor.run_until_parked();
1607    let call = incoming_call_c.borrow().clone().unwrap();
1608    assert_eq!(call.calling_user.github_login, "user_b");
1609    let initial_project = call.initial_project.unwrap();
1610    active_call_c
1611        .update(cx_c, |call, cx| call.accept_incoming(cx))
1612        .await
1613        .unwrap();
1614    let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
1615
1616    // Client B closes the editor, and client A sees client B's selections removed.
1617    cx_b.update(move |_, _| drop(editor_b));
1618    executor.run_until_parked();
1619
1620    buffer_a.read_with(cx_a, |buffer, _| {
1621        buffer
1622            .snapshot()
1623            .selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
1624            .count()
1625            == 0
1626    });
1627}
1628
1629#[gpui::test(iterations = 10)]
1630async fn test_on_input_format_from_host_to_guest(
1631    cx_a: &mut TestAppContext,
1632    cx_b: &mut TestAppContext,
1633) {
1634    let mut server = TestServer::start(cx_a.executor()).await;
1635    let executor = cx_a.executor();
1636    let client_a = server.create_client(cx_a, "user_a").await;
1637    let client_b = server.create_client(cx_b, "user_b").await;
1638    server
1639        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1640        .await;
1641    let active_call_a = cx_a.read(ActiveCall::global);
1642
1643    client_a.language_registry().add(rust_lang());
1644    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1645        "Rust",
1646        FakeLspAdapter {
1647            capabilities: lsp::ServerCapabilities {
1648                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1649                    first_trigger_character: ":".to_string(),
1650                    more_trigger_character: Some(vec![">".to_string()]),
1651                }),
1652                ..Default::default()
1653            },
1654            ..Default::default()
1655        },
1656    );
1657
1658    client_a
1659        .fs()
1660        .insert_tree(
1661            path!("/a"),
1662            json!({
1663                "main.rs": "fn main() { a }",
1664                "other.rs": "// Test file",
1665            }),
1666        )
1667        .await;
1668    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1669    let project_id = active_call_a
1670        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1671        .await
1672        .unwrap();
1673    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1674
1675    // Open a file in an editor as the host.
1676    let buffer_a = project_a
1677        .update(cx_a, |p, cx| {
1678            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1679        })
1680        .await
1681        .unwrap();
1682    let cx_a = cx_a.add_empty_window();
1683    let editor_a = cx_a.new_window_entity(|window, cx| {
1684        Editor::for_buffer(buffer_a, Some(project_a.clone()), window, cx)
1685    });
1686
1687    let fake_language_server = fake_language_servers.next().await.unwrap();
1688    executor.run_until_parked();
1689
1690    // Receive an OnTypeFormatting request as the host's language server.
1691    // Return some formatting from the host's language server.
1692    fake_language_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
1693        |params, _| async move {
1694            assert_eq!(
1695                params.text_document_position.text_document.uri,
1696                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
1697            );
1698            assert_eq!(
1699                params.text_document_position.position,
1700                lsp::Position::new(0, 14),
1701            );
1702
1703            Ok(Some(vec![lsp::TextEdit {
1704                new_text: "~<".to_string(),
1705                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1706            }]))
1707        },
1708    );
1709
1710    // Open the buffer on the guest and see that the formatting worked
1711    let buffer_b = project_b
1712        .update(cx_b, |p, cx| {
1713            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1714        })
1715        .await
1716        .unwrap();
1717
1718    // Type a on type formatting trigger character as the guest.
1719    cx_a.focus(&editor_a);
1720    editor_a.update_in(cx_a, |editor, window, cx| {
1721        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1722            s.select_ranges([13..13])
1723        });
1724        editor.handle_input(">", window, cx);
1725    });
1726
1727    executor.run_until_parked();
1728
1729    buffer_b.read_with(cx_b, |buffer, _| {
1730        assert_eq!(buffer.text(), "fn main() { a>~< }")
1731    });
1732
1733    // Undo should remove LSP edits first
1734    editor_a.update_in(cx_a, |editor, window, cx| {
1735        assert_eq!(editor.text(cx), "fn main() { a>~< }");
1736        editor.undo(&Undo, window, cx);
1737        assert_eq!(editor.text(cx), "fn main() { a> }");
1738    });
1739    executor.run_until_parked();
1740
1741    buffer_b.read_with(cx_b, |buffer, _| {
1742        assert_eq!(buffer.text(), "fn main() { a> }")
1743    });
1744
1745    editor_a.update_in(cx_a, |editor, window, cx| {
1746        assert_eq!(editor.text(cx), "fn main() { a> }");
1747        editor.undo(&Undo, window, cx);
1748        assert_eq!(editor.text(cx), "fn main() { a }");
1749    });
1750    executor.run_until_parked();
1751
1752    buffer_b.read_with(cx_b, |buffer, _| {
1753        assert_eq!(buffer.text(), "fn main() { a }")
1754    });
1755}
1756
1757#[gpui::test(iterations = 10)]
1758async fn test_on_input_format_from_guest_to_host(
1759    cx_a: &mut TestAppContext,
1760    cx_b: &mut TestAppContext,
1761) {
1762    let mut server = TestServer::start(cx_a.executor()).await;
1763    let executor = cx_a.executor();
1764    let client_a = server.create_client(cx_a, "user_a").await;
1765    let client_b = server.create_client(cx_b, "user_b").await;
1766    server
1767        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1768        .await;
1769    let active_call_a = cx_a.read(ActiveCall::global);
1770
1771    let capabilities = lsp::ServerCapabilities {
1772        document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1773            first_trigger_character: ":".to_string(),
1774            more_trigger_character: Some(vec![">".to_string()]),
1775        }),
1776        ..lsp::ServerCapabilities::default()
1777    };
1778    client_a.language_registry().add(rust_lang());
1779    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1780        "Rust",
1781        FakeLspAdapter {
1782            capabilities: capabilities.clone(),
1783            ..FakeLspAdapter::default()
1784        },
1785    );
1786    client_b.language_registry().add(rust_lang());
1787    client_b.language_registry().register_fake_lsp_adapter(
1788        "Rust",
1789        FakeLspAdapter {
1790            capabilities,
1791            ..FakeLspAdapter::default()
1792        },
1793    );
1794
1795    client_a
1796        .fs()
1797        .insert_tree(
1798            path!("/a"),
1799            json!({
1800                "main.rs": "fn main() { a }",
1801                "other.rs": "// Test file",
1802            }),
1803        )
1804        .await;
1805    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1806    let project_id = active_call_a
1807        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1808        .await
1809        .unwrap();
1810    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1811
1812    // Open a file in an editor as the guest.
1813    let buffer_b = project_b
1814        .update(cx_b, |p, cx| {
1815            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1816        })
1817        .await
1818        .unwrap();
1819    let cx_b = cx_b.add_empty_window();
1820    let editor_b = cx_b.new_window_entity(|window, cx| {
1821        Editor::for_buffer(buffer_b, Some(project_b.clone()), window, cx)
1822    });
1823
1824    let fake_language_server = fake_language_servers.next().await.unwrap();
1825    executor.run_until_parked();
1826
1827    // Type a on type formatting trigger character as the guest.
1828    cx_b.focus(&editor_b);
1829    editor_b.update_in(cx_b, |editor, window, cx| {
1830        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1831            s.select_ranges([13..13])
1832        });
1833        editor.handle_input(":", window, cx);
1834    });
1835
1836    // Receive an OnTypeFormatting request as the host's language server.
1837    // Return some formatting from the host's language server.
1838    executor.start_waiting();
1839    fake_language_server
1840        .set_request_handler::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
1841            assert_eq!(
1842                params.text_document_position.text_document.uri,
1843                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
1844            );
1845            assert_eq!(
1846                params.text_document_position.position,
1847                lsp::Position::new(0, 14),
1848            );
1849
1850            Ok(Some(vec![lsp::TextEdit {
1851                new_text: "~:".to_string(),
1852                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1853            }]))
1854        })
1855        .next()
1856        .await
1857        .unwrap();
1858    executor.finish_waiting();
1859
1860    // Open the buffer on the host and see that the formatting worked
1861    let buffer_a = project_a
1862        .update(cx_a, |p, cx| {
1863            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1864        })
1865        .await
1866        .unwrap();
1867    executor.run_until_parked();
1868
1869    buffer_a.read_with(cx_a, |buffer, _| {
1870        assert_eq!(buffer.text(), "fn main() { a:~: }")
1871    });
1872
1873    // Undo should remove LSP edits first
1874    editor_b.update_in(cx_b, |editor, window, cx| {
1875        assert_eq!(editor.text(cx), "fn main() { a:~: }");
1876        editor.undo(&Undo, window, cx);
1877        assert_eq!(editor.text(cx), "fn main() { a: }");
1878    });
1879    executor.run_until_parked();
1880
1881    buffer_a.read_with(cx_a, |buffer, _| {
1882        assert_eq!(buffer.text(), "fn main() { a: }")
1883    });
1884
1885    editor_b.update_in(cx_b, |editor, window, cx| {
1886        assert_eq!(editor.text(cx), "fn main() { a: }");
1887        editor.undo(&Undo, window, cx);
1888        assert_eq!(editor.text(cx), "fn main() { a }");
1889    });
1890    executor.run_until_parked();
1891
1892    buffer_a.read_with(cx_a, |buffer, _| {
1893        assert_eq!(buffer.text(), "fn main() { a }")
1894    });
1895}
1896
1897#[gpui::test(iterations = 10)]
1898async fn test_mutual_editor_inlay_hint_cache_update(
1899    cx_a: &mut TestAppContext,
1900    cx_b: &mut TestAppContext,
1901) {
1902    let mut server = TestServer::start(cx_a.executor()).await;
1903    let executor = cx_a.executor();
1904    let client_a = server.create_client(cx_a, "user_a").await;
1905    let client_b = server.create_client(cx_b, "user_b").await;
1906    server
1907        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1908        .await;
1909    let active_call_a = cx_a.read(ActiveCall::global);
1910    let active_call_b = cx_b.read(ActiveCall::global);
1911
1912    cx_a.update(editor::init);
1913    cx_b.update(editor::init);
1914
1915    cx_a.update(|cx| {
1916        SettingsStore::update_global(cx, |store, cx| {
1917            store.update_user_settings(cx, |settings| {
1918                settings.project.all_languages.defaults.inlay_hints =
1919                    Some(InlayHintSettingsContent {
1920                        enabled: Some(true),
1921                        ..InlayHintSettingsContent::default()
1922                    })
1923            });
1924        });
1925    });
1926    cx_b.update(|cx| {
1927        SettingsStore::update_global(cx, |store, cx| {
1928            store.update_user_settings(cx, |settings| {
1929                settings.project.all_languages.defaults.inlay_hints =
1930                    Some(InlayHintSettingsContent {
1931                        enabled: Some(true),
1932                        ..InlayHintSettingsContent::default()
1933                    })
1934            });
1935        });
1936    });
1937
1938    let capabilities = lsp::ServerCapabilities {
1939        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1940        ..lsp::ServerCapabilities::default()
1941    };
1942    client_a.language_registry().add(rust_lang());
1943
1944    // Set up the language server to return an additional inlay hint on each request.
1945    let edits_made = Arc::new(AtomicUsize::new(0));
1946    let closure_edits_made = Arc::clone(&edits_made);
1947    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1948        "Rust",
1949        FakeLspAdapter {
1950            capabilities: capabilities.clone(),
1951            initializer: Some(Box::new(move |fake_language_server| {
1952                let closure_edits_made = closure_edits_made.clone();
1953                fake_language_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
1954                    move |params, _| {
1955                        let edits_made_2 = Arc::clone(&closure_edits_made);
1956                        async move {
1957                            assert_eq!(
1958                                params.text_document.uri,
1959                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
1960                            );
1961                            let edits_made =
1962                                AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire);
1963                            Ok(Some(vec![lsp::InlayHint {
1964                                position: lsp::Position::new(0, edits_made as u32),
1965                                label: lsp::InlayHintLabel::String(edits_made.to_string()),
1966                                kind: None,
1967                                text_edits: None,
1968                                tooltip: None,
1969                                padding_left: None,
1970                                padding_right: None,
1971                                data: None,
1972                            }]))
1973                        }
1974                    },
1975                );
1976            })),
1977            ..FakeLspAdapter::default()
1978        },
1979    );
1980    client_b.language_registry().add(rust_lang());
1981    client_b.language_registry().register_fake_lsp_adapter(
1982        "Rust",
1983        FakeLspAdapter {
1984            capabilities,
1985            ..FakeLspAdapter::default()
1986        },
1987    );
1988
1989    // Client A opens a project.
1990    client_a
1991        .fs()
1992        .insert_tree(
1993            path!("/a"),
1994            json!({
1995                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1996                "other.rs": "// Test file",
1997            }),
1998        )
1999        .await;
2000    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2001    active_call_a
2002        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2003        .await
2004        .unwrap();
2005    let project_id = active_call_a
2006        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2007        .await
2008        .unwrap();
2009
2010    // Client B joins the project
2011    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2012    active_call_b
2013        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2014        .await
2015        .unwrap();
2016
2017    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2018
2019    // The host opens a rust file.
2020    let file_a = workspace_a.update_in(cx_a, |workspace, window, cx| {
2021        workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2022    });
2023    let fake_language_server = fake_language_servers.next().await.unwrap();
2024    let editor_a = file_a.await.unwrap().downcast::<Editor>().unwrap();
2025    executor.advance_clock(Duration::from_millis(100));
2026    executor.run_until_parked();
2027
2028    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
2029    editor_a.update(cx_a, |editor, cx| {
2030        assert_eq!(
2031            vec![initial_edit.to_string()],
2032            extract_hint_labels(editor, cx),
2033            "Host should get its first hints when opens an editor"
2034        );
2035    });
2036    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2037    let editor_b = workspace_b
2038        .update_in(cx_b, |workspace, window, cx| {
2039            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2040        })
2041        .await
2042        .unwrap()
2043        .downcast::<Editor>()
2044        .unwrap();
2045
2046    executor.advance_clock(Duration::from_millis(100));
2047    executor.run_until_parked();
2048    editor_b.update(cx_b, |editor, cx| {
2049        assert_eq!(
2050            vec![initial_edit.to_string()],
2051            extract_hint_labels(editor, cx),
2052            "Client should get its first hints when opens an editor"
2053        );
2054    });
2055
2056    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
2057    editor_b.update_in(cx_b, |editor, window, cx| {
2058        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2059            s.select_ranges([13..13].clone())
2060        });
2061        editor.handle_input(":", window, cx);
2062    });
2063    cx_b.focus(&editor_b);
2064
2065    executor.advance_clock(Duration::from_secs(1));
2066    executor.run_until_parked();
2067    editor_a.update(cx_a, |editor, cx| {
2068        assert_eq!(
2069            vec![after_client_edit.to_string()],
2070            extract_hint_labels(editor, cx),
2071        );
2072    });
2073    editor_b.update(cx_b, |editor, cx| {
2074        assert_eq!(
2075            vec![after_client_edit.to_string()],
2076            extract_hint_labels(editor, cx),
2077        );
2078    });
2079
2080    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
2081    editor_a.update_in(cx_a, |editor, window, cx| {
2082        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2083            s.select_ranges([13..13])
2084        });
2085        editor.handle_input("a change to increment both buffers' versions", window, cx);
2086    });
2087    cx_a.focus(&editor_a);
2088
2089    executor.advance_clock(Duration::from_secs(1));
2090    executor.run_until_parked();
2091    editor_a.update(cx_a, |editor, cx| {
2092        assert_eq!(
2093            vec![after_host_edit.to_string()],
2094            extract_hint_labels(editor, cx),
2095        );
2096    });
2097    editor_b.update(cx_b, |editor, cx| {
2098        assert_eq!(
2099            vec![after_host_edit.to_string()],
2100            extract_hint_labels(editor, cx),
2101        );
2102    });
2103
2104    let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
2105    fake_language_server
2106        .request::<lsp::request::InlayHintRefreshRequest>(())
2107        .await
2108        .into_response()
2109        .expect("inlay refresh request failed");
2110
2111    executor.advance_clock(Duration::from_secs(1));
2112    executor.run_until_parked();
2113    editor_a.update(cx_a, |editor, cx| {
2114        assert_eq!(
2115            vec![after_special_edit_for_refresh.to_string()],
2116            extract_hint_labels(editor, cx),
2117            "Host should react to /refresh LSP request"
2118        );
2119    });
2120    editor_b.update(cx_b, |editor, cx| {
2121        assert_eq!(
2122            vec![after_special_edit_for_refresh.to_string()],
2123            extract_hint_labels(editor, cx),
2124            "Guest should get a /refresh LSP request propagated by host"
2125        );
2126    });
2127}
2128
2129#[gpui::test(iterations = 10)]
2130async fn test_inlay_hint_refresh_is_forwarded(
2131    cx_a: &mut TestAppContext,
2132    cx_b: &mut TestAppContext,
2133) {
2134    let mut server = TestServer::start(cx_a.executor()).await;
2135    let executor = cx_a.executor();
2136    let client_a = server.create_client(cx_a, "user_a").await;
2137    let client_b = server.create_client(cx_b, "user_b").await;
2138    server
2139        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2140        .await;
2141    let active_call_a = cx_a.read(ActiveCall::global);
2142    let active_call_b = cx_b.read(ActiveCall::global);
2143
2144    cx_a.update(editor::init);
2145    cx_b.update(editor::init);
2146
2147    cx_a.update(|cx| {
2148        SettingsStore::update_global(cx, |store, cx| {
2149            store.update_user_settings(cx, |settings| {
2150                settings.project.all_languages.defaults.inlay_hints =
2151                    Some(InlayHintSettingsContent {
2152                        show_value_hints: Some(true),
2153                        enabled: Some(false),
2154                        edit_debounce_ms: Some(0),
2155                        scroll_debounce_ms: Some(0),
2156                        show_type_hints: Some(false),
2157                        show_parameter_hints: Some(false),
2158                        show_other_hints: Some(false),
2159                        show_background: Some(false),
2160                        toggle_on_modifiers_press: None,
2161                    })
2162            });
2163        });
2164    });
2165    cx_b.update(|cx| {
2166        SettingsStore::update_global(cx, |store, cx| {
2167            store.update_user_settings(cx, |settings| {
2168                settings.project.all_languages.defaults.inlay_hints =
2169                    Some(InlayHintSettingsContent {
2170                        show_value_hints: Some(true),
2171                        enabled: Some(true),
2172                        edit_debounce_ms: Some(0),
2173                        scroll_debounce_ms: Some(0),
2174                        show_type_hints: Some(true),
2175                        show_parameter_hints: Some(true),
2176                        show_other_hints: Some(true),
2177                        show_background: Some(false),
2178                        toggle_on_modifiers_press: None,
2179                    })
2180            });
2181        });
2182    });
2183
2184    let capabilities = lsp::ServerCapabilities {
2185        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2186        ..lsp::ServerCapabilities::default()
2187    };
2188    client_a.language_registry().add(rust_lang());
2189    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2190        "Rust",
2191        FakeLspAdapter {
2192            capabilities: capabilities.clone(),
2193            ..FakeLspAdapter::default()
2194        },
2195    );
2196    client_b.language_registry().add(rust_lang());
2197    client_b.language_registry().register_fake_lsp_adapter(
2198        "Rust",
2199        FakeLspAdapter {
2200            capabilities,
2201            ..FakeLspAdapter::default()
2202        },
2203    );
2204
2205    client_a
2206        .fs()
2207        .insert_tree(
2208            path!("/a"),
2209            json!({
2210                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
2211                "other.rs": "// Test file",
2212            }),
2213        )
2214        .await;
2215    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2216    active_call_a
2217        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2218        .await
2219        .unwrap();
2220    let project_id = active_call_a
2221        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2222        .await
2223        .unwrap();
2224
2225    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2226    active_call_b
2227        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2228        .await
2229        .unwrap();
2230
2231    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2232    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2233
2234    cx_a.background_executor.start_waiting();
2235
2236    let editor_a = workspace_a
2237        .update_in(cx_a, |workspace, window, cx| {
2238            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2239        })
2240        .await
2241        .unwrap()
2242        .downcast::<Editor>()
2243        .unwrap();
2244
2245    let editor_b = workspace_b
2246        .update_in(cx_b, |workspace, window, cx| {
2247            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2248        })
2249        .await
2250        .unwrap()
2251        .downcast::<Editor>()
2252        .unwrap();
2253
2254    let other_hints = Arc::new(AtomicBool::new(false));
2255    let fake_language_server = fake_language_servers.next().await.unwrap();
2256    let closure_other_hints = Arc::clone(&other_hints);
2257    fake_language_server
2258        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2259            let task_other_hints = Arc::clone(&closure_other_hints);
2260            async move {
2261                assert_eq!(
2262                    params.text_document.uri,
2263                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2264                );
2265                let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
2266                let character = if other_hints { 0 } else { 2 };
2267                let label = if other_hints {
2268                    "other hint"
2269                } else {
2270                    "initial hint"
2271                };
2272                Ok(Some(vec![
2273                    lsp::InlayHint {
2274                        position: lsp::Position::new(0, character),
2275                        label: lsp::InlayHintLabel::String(label.to_string()),
2276                        kind: None,
2277                        text_edits: None,
2278                        tooltip: None,
2279                        padding_left: None,
2280                        padding_right: None,
2281                        data: None,
2282                    },
2283                    lsp::InlayHint {
2284                        position: lsp::Position::new(1090, 1090),
2285                        label: lsp::InlayHintLabel::String("out-of-bounds hint".to_string()),
2286                        kind: None,
2287                        text_edits: None,
2288                        tooltip: None,
2289                        padding_left: None,
2290                        padding_right: None,
2291                        data: None,
2292                    },
2293                ]))
2294            }
2295        })
2296        .next()
2297        .await
2298        .unwrap();
2299    executor.finish_waiting();
2300
2301    executor.run_until_parked();
2302    editor_a.update(cx_a, |editor, cx| {
2303        assert!(
2304            extract_hint_labels(editor, cx).is_empty(),
2305            "Host should get no hints due to them turned off"
2306        );
2307    });
2308
2309    executor.run_until_parked();
2310    editor_b.update(cx_b, |editor, cx| {
2311        assert_eq!(
2312            vec!["initial hint".to_string()],
2313            extract_hint_labels(editor, cx),
2314            "Client should get its first hints when opens an editor"
2315        );
2316    });
2317
2318    other_hints.fetch_or(true, atomic::Ordering::Release);
2319    fake_language_server
2320        .request::<lsp::request::InlayHintRefreshRequest>(())
2321        .await
2322        .into_response()
2323        .expect("inlay refresh request failed");
2324    executor.run_until_parked();
2325    editor_a.update(cx_a, |editor, cx| {
2326        assert!(
2327            extract_hint_labels(editor, cx).is_empty(),
2328            "Host should get no hints due to them turned off, even after the /refresh"
2329        );
2330    });
2331
2332    executor.run_until_parked();
2333    editor_b.update(cx_b, |editor, cx| {
2334        assert_eq!(
2335            vec!["other hint".to_string()],
2336            extract_hint_labels(editor, cx),
2337            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
2338        );
2339    });
2340}
2341
2342#[gpui::test(iterations = 10)]
2343async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2344    let expected_color = Rgba {
2345        r: 0.33,
2346        g: 0.33,
2347        b: 0.33,
2348        a: 0.33,
2349    };
2350    let mut server = TestServer::start(cx_a.executor()).await;
2351    let executor = cx_a.executor();
2352    let client_a = server.create_client(cx_a, "user_a").await;
2353    let client_b = server.create_client(cx_b, "user_b").await;
2354    server
2355        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2356        .await;
2357    let active_call_a = cx_a.read(ActiveCall::global);
2358    let active_call_b = cx_b.read(ActiveCall::global);
2359
2360    cx_a.update(editor::init);
2361    cx_b.update(editor::init);
2362
2363    cx_a.update(|cx| {
2364        SettingsStore::update_global(cx, |store, cx| {
2365            store.update_user_settings(cx, |settings| {
2366                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::None);
2367            });
2368        });
2369    });
2370    cx_b.update(|cx| {
2371        SettingsStore::update_global(cx, |store, cx| {
2372            store.update_user_settings(cx, |settings| {
2373                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2374            });
2375        });
2376    });
2377
2378    let capabilities = lsp::ServerCapabilities {
2379        color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
2380        ..lsp::ServerCapabilities::default()
2381    };
2382    client_a.language_registry().add(rust_lang());
2383    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2384        "Rust",
2385        FakeLspAdapter {
2386            capabilities: capabilities.clone(),
2387            ..FakeLspAdapter::default()
2388        },
2389    );
2390    client_b.language_registry().add(rust_lang());
2391    client_b.language_registry().register_fake_lsp_adapter(
2392        "Rust",
2393        FakeLspAdapter {
2394            capabilities,
2395            ..FakeLspAdapter::default()
2396        },
2397    );
2398
2399    // Client A opens a project.
2400    client_a
2401        .fs()
2402        .insert_tree(
2403            path!("/a"),
2404            json!({
2405                "main.rs": "fn main() { a }",
2406            }),
2407        )
2408        .await;
2409    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2410    active_call_a
2411        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2412        .await
2413        .unwrap();
2414    let project_id = active_call_a
2415        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2416        .await
2417        .unwrap();
2418
2419    // Client B joins the project
2420    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2421    active_call_b
2422        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2423        .await
2424        .unwrap();
2425
2426    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2427
2428    // The host opens a rust file.
2429    let _buffer_a = project_a
2430        .update(cx_a, |project, cx| {
2431            project.open_local_buffer(path!("/a/main.rs"), cx)
2432        })
2433        .await
2434        .unwrap();
2435    let editor_a = workspace_a
2436        .update_in(cx_a, |workspace, window, cx| {
2437            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2438        })
2439        .await
2440        .unwrap()
2441        .downcast::<Editor>()
2442        .unwrap();
2443
2444    let fake_language_server = fake_language_servers.next().await.unwrap();
2445    cx_a.run_until_parked();
2446    cx_b.run_until_parked();
2447
2448    let requests_made = Arc::new(AtomicUsize::new(0));
2449    let closure_requests_made = Arc::clone(&requests_made);
2450    let mut color_request_handle = fake_language_server
2451        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
2452            let requests_made = Arc::clone(&closure_requests_made);
2453            async move {
2454                assert_eq!(
2455                    params.text_document.uri,
2456                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2457                );
2458                requests_made.fetch_add(1, atomic::Ordering::Release);
2459                Ok(vec![lsp::ColorInformation {
2460                    range: lsp::Range {
2461                        start: lsp::Position {
2462                            line: 0,
2463                            character: 0,
2464                        },
2465                        end: lsp::Position {
2466                            line: 0,
2467                            character: 1,
2468                        },
2469                    },
2470                    color: lsp::Color {
2471                        red: 0.33,
2472                        green: 0.33,
2473                        blue: 0.33,
2474                        alpha: 0.33,
2475                    },
2476                }])
2477            }
2478        });
2479    executor.run_until_parked();
2480
2481    assert_eq!(
2482        0,
2483        requests_made.load(atomic::Ordering::Acquire),
2484        "Host did not enable document colors, hence should query for none"
2485    );
2486    editor_a.update(cx_a, |editor, cx| {
2487        assert_eq!(
2488            Vec::<Rgba>::new(),
2489            extract_color_inlays(editor, cx),
2490            "No query colors should result in no hints"
2491        );
2492    });
2493
2494    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2495    let editor_b = workspace_b
2496        .update_in(cx_b, |workspace, window, cx| {
2497            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2498        })
2499        .await
2500        .unwrap()
2501        .downcast::<Editor>()
2502        .unwrap();
2503
2504    color_request_handle.next().await.unwrap();
2505    executor.advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
2506    executor.run_until_parked();
2507
2508    assert_eq!(
2509        1,
2510        requests_made.load(atomic::Ordering::Acquire),
2511        "The client opened the file and got its first colors back"
2512    );
2513    editor_b.update(cx_b, |editor, cx| {
2514        assert_eq!(
2515            vec![expected_color],
2516            extract_color_inlays(editor, cx),
2517            "With document colors as inlays, color inlays should be pushed"
2518        );
2519    });
2520
2521    editor_a.update_in(cx_a, |editor, window, cx| {
2522        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2523            s.select_ranges([13..13].clone())
2524        });
2525        editor.handle_input(":", window, cx);
2526    });
2527    color_request_handle.next().await.unwrap();
2528    executor.run_until_parked();
2529    assert_eq!(
2530        2,
2531        requests_made.load(atomic::Ordering::Acquire),
2532        "After the host edits his file, the client should request the colors again"
2533    );
2534    editor_a.update(cx_a, |editor, cx| {
2535        assert_eq!(
2536            Vec::<Rgba>::new(),
2537            extract_color_inlays(editor, cx),
2538            "Host has no colors still"
2539        );
2540    });
2541    editor_b.update(cx_b, |editor, cx| {
2542        assert_eq!(vec![expected_color], extract_color_inlays(editor, cx),);
2543    });
2544
2545    cx_b.update(|_, cx| {
2546        SettingsStore::update_global(cx, |store, cx| {
2547            store.update_user_settings(cx, |settings| {
2548                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Background);
2549            });
2550        });
2551    });
2552    executor.run_until_parked();
2553    assert_eq!(
2554        2,
2555        requests_made.load(atomic::Ordering::Acquire),
2556        "After the client have changed the colors settings, no extra queries should happen"
2557    );
2558    editor_a.update(cx_a, |editor, cx| {
2559        assert_eq!(
2560            Vec::<Rgba>::new(),
2561            extract_color_inlays(editor, cx),
2562            "Host is unaffected by the client's settings changes"
2563        );
2564    });
2565    editor_b.update(cx_b, |editor, cx| {
2566        assert_eq!(
2567            Vec::<Rgba>::new(),
2568            extract_color_inlays(editor, cx),
2569            "Client should have no colors hints, as in the settings"
2570        );
2571    });
2572
2573    cx_b.update(|_, cx| {
2574        SettingsStore::update_global(cx, |store, cx| {
2575            store.update_user_settings(cx, |settings| {
2576                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2577            });
2578        });
2579    });
2580    executor.run_until_parked();
2581    assert_eq!(
2582        2,
2583        requests_made.load(atomic::Ordering::Acquire),
2584        "After falling back to colors as inlays, no extra LSP queries are made"
2585    );
2586    editor_a.update(cx_a, |editor, cx| {
2587        assert_eq!(
2588            Vec::<Rgba>::new(),
2589            extract_color_inlays(editor, cx),
2590            "Host is unaffected by the client's settings changes, again"
2591        );
2592    });
2593    editor_b.update(cx_b, |editor, cx| {
2594        assert_eq!(
2595            vec![expected_color],
2596            extract_color_inlays(editor, cx),
2597            "Client should have its color hints back"
2598        );
2599    });
2600
2601    cx_a.update(|_, cx| {
2602        SettingsStore::update_global(cx, |store, cx| {
2603            store.update_user_settings(cx, |settings| {
2604                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Border);
2605            });
2606        });
2607    });
2608    color_request_handle.next().await.unwrap();
2609    executor.run_until_parked();
2610    assert_eq!(
2611        3,
2612        requests_made.load(atomic::Ordering::Acquire),
2613        "After the host enables document colors, another LSP query should be made"
2614    );
2615    editor_a.update(cx_a, |editor, cx| {
2616        assert_eq!(
2617            Vec::<Rgba>::new(),
2618            extract_color_inlays(editor, cx),
2619            "Host did not configure document colors as hints hence gets nothing"
2620        );
2621    });
2622    editor_b.update(cx_b, |editor, cx| {
2623        assert_eq!(
2624            vec![expected_color],
2625            extract_color_inlays(editor, cx),
2626            "Client should be unaffected by the host's settings changes"
2627        );
2628    });
2629}
2630
2631async fn test_lsp_pull_diagnostics(
2632    should_stream_workspace_diagnostic: bool,
2633    cx_a: &mut TestAppContext,
2634    cx_b: &mut TestAppContext,
2635) {
2636    let mut server = TestServer::start(cx_a.executor()).await;
2637    let executor = cx_a.executor();
2638    let client_a = server.create_client(cx_a, "user_a").await;
2639    let client_b = server.create_client(cx_b, "user_b").await;
2640    server
2641        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2642        .await;
2643    let active_call_a = cx_a.read(ActiveCall::global);
2644    let active_call_b = cx_b.read(ActiveCall::global);
2645
2646    cx_a.update(editor::init);
2647    cx_b.update(editor::init);
2648
2649    let expected_push_diagnostic_main_message = "pushed main diagnostic";
2650    let expected_push_diagnostic_lib_message = "pushed lib diagnostic";
2651    let expected_pull_diagnostic_main_message = "pulled main diagnostic";
2652    let expected_pull_diagnostic_lib_message = "pulled lib diagnostic";
2653    let expected_workspace_pull_diagnostics_main_message = "pulled workspace main diagnostic";
2654    let expected_workspace_pull_diagnostics_lib_message = "pulled workspace lib diagnostic";
2655
2656    let diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<Option<String>>::new()));
2657    let workspace_diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<String>::new()));
2658    let diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2659    let closure_diagnostics_pulls_made = diagnostics_pulls_made.clone();
2660    let closure_diagnostics_pulls_result_ids = diagnostics_pulls_result_ids.clone();
2661    let workspace_diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2662    let closure_workspace_diagnostics_pulls_made = workspace_diagnostics_pulls_made.clone();
2663    let closure_workspace_diagnostics_pulls_result_ids =
2664        workspace_diagnostics_pulls_result_ids.clone();
2665    let (workspace_diagnostic_cancel_tx, closure_workspace_diagnostic_cancel_rx) =
2666        smol::channel::bounded::<()>(1);
2667    let (closure_workspace_diagnostic_received_tx, workspace_diagnostic_received_rx) =
2668        smol::channel::bounded::<()>(1);
2669
2670    let capabilities = lsp::ServerCapabilities {
2671        diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
2672            lsp::DiagnosticOptions {
2673                identifier: Some("test-pulls".to_string()),
2674                inter_file_dependencies: true,
2675                workspace_diagnostics: true,
2676                work_done_progress_options: lsp::WorkDoneProgressOptions {
2677                    work_done_progress: None,
2678                },
2679            },
2680        )),
2681        ..lsp::ServerCapabilities::default()
2682    };
2683    client_a.language_registry().add(rust_lang());
2684
2685    let pull_diagnostics_handle = Arc::new(parking_lot::Mutex::new(None));
2686    let workspace_diagnostics_pulls_handle = Arc::new(parking_lot::Mutex::new(None));
2687
2688    let closure_pull_diagnostics_handle = pull_diagnostics_handle.clone();
2689    let closure_workspace_diagnostics_pulls_handle = workspace_diagnostics_pulls_handle.clone();
2690    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2691        "Rust",
2692        FakeLspAdapter {
2693            capabilities: capabilities.clone(),
2694            initializer: Some(Box::new(move |fake_language_server| {
2695                let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!(
2696                    "workspace/diagnostic/{}/1",
2697                    fake_language_server.server.server_id()
2698                ));
2699                let closure_workspace_diagnostics_pulls_result_ids = closure_workspace_diagnostics_pulls_result_ids.clone();
2700                let diagnostics_pulls_made = closure_diagnostics_pulls_made.clone();
2701                let diagnostics_pulls_result_ids = closure_diagnostics_pulls_result_ids.clone();
2702                let closure_pull_diagnostics_handle = closure_pull_diagnostics_handle.clone();
2703                let closure_workspace_diagnostics_pulls_handle = closure_workspace_diagnostics_pulls_handle.clone();
2704                let closure_workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone();
2705                let closure_workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone();
2706                let pull_diagnostics_handle = fake_language_server
2707                    .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(
2708                        move |params, _| {
2709                            let requests_made = diagnostics_pulls_made.clone();
2710                            let diagnostics_pulls_result_ids =
2711                                diagnostics_pulls_result_ids.clone();
2712                            async move {
2713                                let message = if lsp::Uri::from_file_path(path!("/a/main.rs"))
2714                                    .unwrap()
2715                                    == params.text_document.uri
2716                                {
2717                                    expected_pull_diagnostic_main_message.to_string()
2718                                } else if lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap()
2719                                    == params.text_document.uri
2720                                {
2721                                    expected_pull_diagnostic_lib_message.to_string()
2722                                } else {
2723                                    panic!("Unexpected document: {}", params.text_document.uri)
2724                                };
2725                                {
2726                                    diagnostics_pulls_result_ids
2727                                        .lock()
2728                                        .await
2729                                        .insert(params.previous_result_id);
2730                                }
2731                                let new_requests_count =
2732                                    requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2733                                Ok(lsp::DocumentDiagnosticReportResult::Report(
2734                                    lsp::DocumentDiagnosticReport::Full(
2735                                        lsp::RelatedFullDocumentDiagnosticReport {
2736                                            related_documents: None,
2737                                            full_document_diagnostic_report:
2738                                                lsp::FullDocumentDiagnosticReport {
2739                                                    result_id: Some(format!(
2740                                                        "pull-{new_requests_count}"
2741                                                    )),
2742                                                    items: vec![lsp::Diagnostic {
2743                                                        range: lsp::Range {
2744                                                            start: lsp::Position {
2745                                                                line: 0,
2746                                                                character: 0,
2747                                                            },
2748                                                            end: lsp::Position {
2749                                                                line: 0,
2750                                                                character: 2,
2751                                                            },
2752                                                        },
2753                                                        severity: Some(
2754                                                            lsp::DiagnosticSeverity::ERROR,
2755                                                        ),
2756                                                        message,
2757                                                        ..lsp::Diagnostic::default()
2758                                                    }],
2759                                                },
2760                                        },
2761                                    ),
2762                                ))
2763                            }
2764                        },
2765                    );
2766                let _ = closure_pull_diagnostics_handle.lock().insert(pull_diagnostics_handle);
2767
2768                let closure_workspace_diagnostics_pulls_made = closure_workspace_diagnostics_pulls_made.clone();
2769                let workspace_diagnostics_pulls_handle = fake_language_server.set_request_handler::<lsp::request::WorkspaceDiagnosticRequest, _, _>(
2770                    move |params, _| {
2771                        let workspace_requests_made = closure_workspace_diagnostics_pulls_made.clone();
2772                        let workspace_diagnostics_pulls_result_ids =
2773                            closure_workspace_diagnostics_pulls_result_ids.clone();
2774                        let workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone();
2775                        let workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone();
2776                        let expected_workspace_diagnostic_token = expected_workspace_diagnostic_token.clone();
2777                        async move {
2778                            let workspace_request_count =
2779                                workspace_requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2780                            {
2781                                workspace_diagnostics_pulls_result_ids
2782                                    .lock()
2783                                    .await
2784                                    .extend(params.previous_result_ids.into_iter().map(|id| id.value));
2785                            }
2786                            if should_stream_workspace_diagnostic && !workspace_diagnostic_cancel_rx.is_closed()
2787                            {
2788                                assert_eq!(
2789                                    params.partial_result_params.partial_result_token,
2790                                    Some(expected_workspace_diagnostic_token)
2791                                );
2792                                workspace_diagnostic_received_tx.send(()).await.unwrap();
2793                                workspace_diagnostic_cancel_rx.recv().await.unwrap();
2794                                workspace_diagnostic_cancel_rx.close();
2795                                // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#partialResults
2796                                // > The final response has to be empty in terms of result values.
2797                                return Ok(lsp::WorkspaceDiagnosticReportResult::Report(
2798                                    lsp::WorkspaceDiagnosticReport { items: Vec::new() },
2799                                ));
2800                            }
2801                            Ok(lsp::WorkspaceDiagnosticReportResult::Report(
2802                                lsp::WorkspaceDiagnosticReport {
2803                                    items: vec![
2804                                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2805                                            lsp::WorkspaceFullDocumentDiagnosticReport {
2806                                                uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2807                                                version: None,
2808                                                full_document_diagnostic_report:
2809                                                    lsp::FullDocumentDiagnosticReport {
2810                                                        result_id: Some(format!(
2811                                                            "workspace_{workspace_request_count}"
2812                                                        )),
2813                                                        items: vec![lsp::Diagnostic {
2814                                                            range: lsp::Range {
2815                                                                start: lsp::Position {
2816                                                                    line: 0,
2817                                                                    character: 1,
2818                                                                },
2819                                                                end: lsp::Position {
2820                                                                    line: 0,
2821                                                                    character: 3,
2822                                                                },
2823                                                            },
2824                                                            severity: Some(lsp::DiagnosticSeverity::WARNING),
2825                                                            message:
2826                                                                expected_workspace_pull_diagnostics_main_message
2827                                                                    .to_string(),
2828                                                            ..lsp::Diagnostic::default()
2829                                                        }],
2830                                                    },
2831                                            },
2832                                        ),
2833                                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2834                                            lsp::WorkspaceFullDocumentDiagnosticReport {
2835                                                uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
2836                                                version: None,
2837                                                full_document_diagnostic_report:
2838                                                    lsp::FullDocumentDiagnosticReport {
2839                                                        result_id: Some(format!(
2840                                                            "workspace_{workspace_request_count}"
2841                                                        )),
2842                                                        items: vec![lsp::Diagnostic {
2843                                                            range: lsp::Range {
2844                                                                start: lsp::Position {
2845                                                                    line: 0,
2846                                                                    character: 1,
2847                                                                },
2848                                                                end: lsp::Position {
2849                                                                    line: 0,
2850                                                                    character: 3,
2851                                                                },
2852                                                            },
2853                                                            severity: Some(lsp::DiagnosticSeverity::WARNING),
2854                                                            message:
2855                                                                expected_workspace_pull_diagnostics_lib_message
2856                                                                    .to_string(),
2857                                                            ..lsp::Diagnostic::default()
2858                                                        }],
2859                                                    },
2860                                            },
2861                                        ),
2862                                    ],
2863                                },
2864                            ))
2865                        }
2866                    });
2867                let _ = closure_workspace_diagnostics_pulls_handle.lock().insert(workspace_diagnostics_pulls_handle);
2868            })),
2869            ..FakeLspAdapter::default()
2870        },
2871    );
2872
2873    client_b.language_registry().add(rust_lang());
2874    client_b.language_registry().register_fake_lsp_adapter(
2875        "Rust",
2876        FakeLspAdapter {
2877            capabilities,
2878            ..FakeLspAdapter::default()
2879        },
2880    );
2881
2882    // Client A opens a project.
2883    client_a
2884        .fs()
2885        .insert_tree(
2886            path!("/a"),
2887            json!({
2888                "main.rs": "fn main() { a }",
2889                "lib.rs": "fn other() {}",
2890            }),
2891        )
2892        .await;
2893    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2894    active_call_a
2895        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2896        .await
2897        .unwrap();
2898    let project_id = active_call_a
2899        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2900        .await
2901        .unwrap();
2902
2903    // Client B joins the project
2904    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2905    active_call_b
2906        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2907        .await
2908        .unwrap();
2909
2910    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2911    executor.start_waiting();
2912
2913    // The host opens a rust file.
2914    let _buffer_a = project_a
2915        .update(cx_a, |project, cx| {
2916            project.open_local_buffer(path!("/a/main.rs"), cx)
2917        })
2918        .await
2919        .unwrap();
2920    let editor_a_main = workspace_a
2921        .update_in(cx_a, |workspace, window, cx| {
2922            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2923        })
2924        .await
2925        .unwrap()
2926        .downcast::<Editor>()
2927        .unwrap();
2928
2929    let fake_language_server = fake_language_servers.next().await.unwrap();
2930    let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!(
2931        "workspace/diagnostic-{}-1",
2932        fake_language_server.server.server_id()
2933    ));
2934    cx_a.run_until_parked();
2935    cx_b.run_until_parked();
2936    let mut pull_diagnostics_handle = pull_diagnostics_handle.lock().take().unwrap();
2937    let mut workspace_diagnostics_pulls_handle =
2938        workspace_diagnostics_pulls_handle.lock().take().unwrap();
2939
2940    if should_stream_workspace_diagnostic {
2941        workspace_diagnostic_received_rx.recv().await.unwrap();
2942    } else {
2943        workspace_diagnostics_pulls_handle.next().await.unwrap();
2944    }
2945    assert_eq!(
2946        1,
2947        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2948        "Workspace diagnostics should be pulled initially on a server startup"
2949    );
2950    pull_diagnostics_handle.next().await.unwrap();
2951    assert_eq!(
2952        1,
2953        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2954        "Host should query pull diagnostics when the editor is opened"
2955    );
2956    executor.run_until_parked();
2957    editor_a_main.update(cx_a, |editor, cx| {
2958        let snapshot = editor.buffer().read(cx).snapshot(cx);
2959        let all_diagnostics = snapshot
2960            .diagnostics_in_range(0..snapshot.len())
2961            .collect::<Vec<_>>();
2962        assert_eq!(
2963            all_diagnostics.len(),
2964            1,
2965            "Expected single diagnostic, but got: {all_diagnostics:?}"
2966        );
2967        let diagnostic = &all_diagnostics[0];
2968        let mut expected_messages = vec![expected_pull_diagnostic_main_message];
2969        if !should_stream_workspace_diagnostic {
2970            expected_messages.push(expected_workspace_pull_diagnostics_main_message);
2971        }
2972        assert!(
2973            expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2974            "Expected {expected_messages:?} on the host, but got: {}",
2975            diagnostic.diagnostic.message
2976        );
2977    });
2978
2979    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2980        lsp::PublishDiagnosticsParams {
2981            uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2982            diagnostics: vec![lsp::Diagnostic {
2983                range: lsp::Range {
2984                    start: lsp::Position {
2985                        line: 0,
2986                        character: 3,
2987                    },
2988                    end: lsp::Position {
2989                        line: 0,
2990                        character: 4,
2991                    },
2992                },
2993                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
2994                message: expected_push_diagnostic_main_message.to_string(),
2995                ..lsp::Diagnostic::default()
2996            }],
2997            version: None,
2998        },
2999    );
3000    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
3001        lsp::PublishDiagnosticsParams {
3002            uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3003            diagnostics: vec![lsp::Diagnostic {
3004                range: lsp::Range {
3005                    start: lsp::Position {
3006                        line: 0,
3007                        character: 3,
3008                    },
3009                    end: lsp::Position {
3010                        line: 0,
3011                        character: 4,
3012                    },
3013                },
3014                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
3015                message: expected_push_diagnostic_lib_message.to_string(),
3016                ..lsp::Diagnostic::default()
3017            }],
3018            version: None,
3019        },
3020    );
3021
3022    if should_stream_workspace_diagnostic {
3023        fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3024            token: expected_workspace_diagnostic_token.clone(),
3025            value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
3026                lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
3027                    items: vec![
3028                        lsp::WorkspaceDocumentDiagnosticReport::Full(
3029                            lsp::WorkspaceFullDocumentDiagnosticReport {
3030                                uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
3031                                version: None,
3032                                full_document_diagnostic_report:
3033                                    lsp::FullDocumentDiagnosticReport {
3034                                        result_id: Some(format!(
3035                                            "workspace_{}",
3036                                            workspace_diagnostics_pulls_made
3037                                                .fetch_add(1, atomic::Ordering::Release)
3038                                                + 1
3039                                        )),
3040                                        items: vec![lsp::Diagnostic {
3041                                            range: lsp::Range {
3042                                                start: lsp::Position {
3043                                                    line: 0,
3044                                                    character: 1,
3045                                                },
3046                                                end: lsp::Position {
3047                                                    line: 0,
3048                                                    character: 2,
3049                                                },
3050                                            },
3051                                            severity: Some(lsp::DiagnosticSeverity::ERROR),
3052                                            message:
3053                                                expected_workspace_pull_diagnostics_main_message
3054                                                    .to_string(),
3055                                            ..lsp::Diagnostic::default()
3056                                        }],
3057                                    },
3058                            },
3059                        ),
3060                        lsp::WorkspaceDocumentDiagnosticReport::Full(
3061                            lsp::WorkspaceFullDocumentDiagnosticReport {
3062                                uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3063                                version: None,
3064                                full_document_diagnostic_report:
3065                                    lsp::FullDocumentDiagnosticReport {
3066                                        result_id: Some(format!(
3067                                            "workspace_{}",
3068                                            workspace_diagnostics_pulls_made
3069                                                .fetch_add(1, atomic::Ordering::Release)
3070                                                + 1
3071                                        )),
3072                                        items: Vec::new(),
3073                                    },
3074                            },
3075                        ),
3076                    ],
3077                }),
3078            ),
3079        });
3080    };
3081
3082    let mut workspace_diagnostic_start_count =
3083        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
3084
3085    executor.run_until_parked();
3086    editor_a_main.update(cx_a, |editor, cx| {
3087        let snapshot = editor.buffer().read(cx).snapshot(cx);
3088        let all_diagnostics = snapshot
3089            .diagnostics_in_range(0..snapshot.len())
3090            .collect::<Vec<_>>();
3091        assert_eq!(
3092            all_diagnostics.len(),
3093            2,
3094            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
3095        );
3096        let expected_messages = [
3097            expected_workspace_pull_diagnostics_main_message,
3098            expected_pull_diagnostic_main_message,
3099            expected_push_diagnostic_main_message,
3100        ];
3101        for diagnostic in all_diagnostics {
3102            assert!(
3103                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3104                "Expected push and pull messages on the host: {expected_messages:?}, but got: {}",
3105                diagnostic.diagnostic.message
3106            );
3107        }
3108    });
3109
3110    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3111    let editor_b_main = workspace_b
3112        .update_in(cx_b, |workspace, window, cx| {
3113            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
3114        })
3115        .await
3116        .unwrap()
3117        .downcast::<Editor>()
3118        .unwrap();
3119    cx_b.run_until_parked();
3120
3121    pull_diagnostics_handle.next().await.unwrap();
3122    assert_eq!(
3123        2,
3124        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3125        "Client should query pull diagnostics when its editor is opened"
3126    );
3127    executor.run_until_parked();
3128    assert_eq!(
3129        workspace_diagnostic_start_count,
3130        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3131        "Workspace diagnostics should not be changed as the remote client does not initialize the workspace diagnostics pull"
3132    );
3133    editor_b_main.update(cx_b, |editor, cx| {
3134        let snapshot = editor.buffer().read(cx).snapshot(cx);
3135        let all_diagnostics = snapshot
3136            .diagnostics_in_range(0..snapshot.len())
3137            .collect::<Vec<_>>();
3138        assert_eq!(
3139            all_diagnostics.len(),
3140            2,
3141            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
3142        );
3143
3144        // Despite the workspace diagnostics not re-initialized for the remote client, we can still expect its message synced from the host.
3145        let expected_messages = [
3146            expected_workspace_pull_diagnostics_main_message,
3147            expected_pull_diagnostic_main_message,
3148            expected_push_diagnostic_main_message,
3149        ];
3150        for diagnostic in all_diagnostics {
3151            assert!(
3152                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3153                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3154                diagnostic.diagnostic.message
3155            );
3156        }
3157    });
3158
3159    let editor_b_lib = workspace_b
3160        .update_in(cx_b, |workspace, window, cx| {
3161            workspace.open_path((worktree_id, rel_path("lib.rs")), None, true, window, cx)
3162        })
3163        .await
3164        .unwrap()
3165        .downcast::<Editor>()
3166        .unwrap();
3167
3168    pull_diagnostics_handle.next().await.unwrap();
3169    assert_eq!(
3170        3,
3171        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3172        "Client should query pull diagnostics when its another editor is opened"
3173    );
3174    executor.run_until_parked();
3175    assert_eq!(
3176        workspace_diagnostic_start_count,
3177        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3178        "The remote client still did not anything to trigger the workspace diagnostics pull"
3179    );
3180    editor_b_lib.update(cx_b, |editor, cx| {
3181        let snapshot = editor.buffer().read(cx).snapshot(cx);
3182        let all_diagnostics = snapshot
3183            .diagnostics_in_range(0..snapshot.len())
3184            .collect::<Vec<_>>();
3185        let expected_messages = [
3186            expected_pull_diagnostic_lib_message,
3187            // TODO bug: the pushed diagnostics are not being sent to the client when they open the corresponding buffer.
3188            // expected_push_diagnostic_lib_message,
3189        ];
3190        assert_eq!(
3191            all_diagnostics.len(),
3192            1,
3193            "Expected pull diagnostics, but got: {all_diagnostics:?}"
3194        );
3195        for diagnostic in all_diagnostics {
3196            assert!(
3197                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3198                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3199                diagnostic.diagnostic.message
3200            );
3201        }
3202    });
3203
3204    if should_stream_workspace_diagnostic {
3205        fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3206            token: expected_workspace_diagnostic_token.clone(),
3207            value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
3208                lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
3209                    items: vec![lsp::WorkspaceDocumentDiagnosticReport::Full(
3210                        lsp::WorkspaceFullDocumentDiagnosticReport {
3211                            uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3212                            version: None,
3213                            full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
3214                                result_id: Some(format!(
3215                                    "workspace_{}",
3216                                    workspace_diagnostics_pulls_made
3217                                        .fetch_add(1, atomic::Ordering::Release)
3218                                        + 1
3219                                )),
3220                                items: vec![lsp::Diagnostic {
3221                                    range: lsp::Range {
3222                                        start: lsp::Position {
3223                                            line: 0,
3224                                            character: 1,
3225                                        },
3226                                        end: lsp::Position {
3227                                            line: 0,
3228                                            character: 2,
3229                                        },
3230                                    },
3231                                    severity: Some(lsp::DiagnosticSeverity::ERROR),
3232                                    message: expected_workspace_pull_diagnostics_lib_message
3233                                        .to_string(),
3234                                    ..lsp::Diagnostic::default()
3235                                }],
3236                            },
3237                        },
3238                    )],
3239                }),
3240            ),
3241        });
3242        workspace_diagnostic_start_count =
3243            workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
3244        workspace_diagnostic_cancel_tx.send(()).await.unwrap();
3245        workspace_diagnostics_pulls_handle.next().await.unwrap();
3246        executor.run_until_parked();
3247        editor_b_lib.update(cx_b, |editor, cx| {
3248            let snapshot = editor.buffer().read(cx).snapshot(cx);
3249            let all_diagnostics = snapshot
3250                .diagnostics_in_range(0..snapshot.len())
3251                .collect::<Vec<_>>();
3252            let expected_messages = [
3253                expected_workspace_pull_diagnostics_lib_message,
3254                // TODO bug: the pushed diagnostics are not being sent to the client when they open the corresponding buffer.
3255                // expected_push_diagnostic_lib_message,
3256            ];
3257            assert_eq!(
3258                all_diagnostics.len(),
3259                1,
3260                "Expected pull diagnostics, but got: {all_diagnostics:?}"
3261            );
3262            for diagnostic in all_diagnostics {
3263                assert!(
3264                    expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3265                    "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3266                    diagnostic.diagnostic.message
3267                );
3268            }
3269        });
3270    };
3271
3272    {
3273        assert!(
3274            !diagnostics_pulls_result_ids.lock().await.is_empty(),
3275            "Initial diagnostics pulls should report None at least"
3276        );
3277        assert_eq!(
3278            0,
3279            workspace_diagnostics_pulls_result_ids
3280                .lock()
3281                .await
3282                .deref()
3283                .len(),
3284            "After the initial workspace request, opening files should not reuse any result ids"
3285        );
3286    }
3287
3288    editor_b_lib.update_in(cx_b, |editor, window, cx| {
3289        editor.move_to_end(&MoveToEnd, window, cx);
3290        editor.handle_input(":", window, cx);
3291    });
3292    pull_diagnostics_handle.next().await.unwrap();
3293    assert_eq!(
3294        4,
3295        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3296        "Client lib.rs edits should trigger another diagnostics pull for a buffer"
3297    );
3298    workspace_diagnostics_pulls_handle.next().await.unwrap();
3299    assert_eq!(
3300        workspace_diagnostic_start_count + 1,
3301        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3302        "After client lib.rs edits, the workspace diagnostics request should follow"
3303    );
3304    executor.run_until_parked();
3305
3306    editor_b_main.update_in(cx_b, |editor, window, cx| {
3307        editor.move_to_end(&MoveToEnd, window, cx);
3308        editor.handle_input(":", window, cx);
3309    });
3310    pull_diagnostics_handle.next().await.unwrap();
3311    pull_diagnostics_handle.next().await.unwrap();
3312    assert_eq!(
3313        6,
3314        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3315        "Client main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
3316    );
3317    workspace_diagnostics_pulls_handle.next().await.unwrap();
3318    assert_eq!(
3319        workspace_diagnostic_start_count + 2,
3320        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3321        "After client main.rs edits, the workspace diagnostics pull should follow"
3322    );
3323    executor.run_until_parked();
3324
3325    editor_a_main.update_in(cx_a, |editor, window, cx| {
3326        editor.move_to_end(&MoveToEnd, window, cx);
3327        editor.handle_input(":", window, cx);
3328    });
3329    pull_diagnostics_handle.next().await.unwrap();
3330    pull_diagnostics_handle.next().await.unwrap();
3331    assert_eq!(
3332        8,
3333        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3334        "Host main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
3335    );
3336    workspace_diagnostics_pulls_handle.next().await.unwrap();
3337    assert_eq!(
3338        workspace_diagnostic_start_count + 3,
3339        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3340        "After host main.rs edits, the workspace diagnostics pull should follow"
3341    );
3342    executor.run_until_parked();
3343    let diagnostic_pulls_result_ids = diagnostics_pulls_result_ids.lock().await.len();
3344    let workspace_pulls_result_ids = workspace_diagnostics_pulls_result_ids.lock().await.len();
3345    {
3346        assert!(
3347            diagnostic_pulls_result_ids > 1,
3348            "Should have sent result ids when pulling diagnostics"
3349        );
3350        assert!(
3351            workspace_pulls_result_ids > 1,
3352            "Should have sent result ids when pulling workspace diagnostics"
3353        );
3354    }
3355
3356    fake_language_server
3357        .request::<lsp::request::WorkspaceDiagnosticRefresh>(())
3358        .await
3359        .into_response()
3360        .expect("workspace diagnostics refresh request failed");
3361    assert_eq!(
3362        8,
3363        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3364        "No single file pulls should happen after the diagnostics refresh server request"
3365    );
3366    workspace_diagnostics_pulls_handle.next().await.unwrap();
3367    assert_eq!(
3368        workspace_diagnostic_start_count + 4,
3369        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3370        "Another workspace diagnostics pull should happen after the diagnostics refresh server request"
3371    );
3372    {
3373        assert!(
3374            diagnostics_pulls_result_ids.lock().await.len() == diagnostic_pulls_result_ids,
3375            "Pulls should not happen hence no extra ids should appear"
3376        );
3377        assert!(
3378            workspace_diagnostics_pulls_result_ids.lock().await.len() > workspace_pulls_result_ids,
3379            "More workspace diagnostics should be pulled"
3380        );
3381    }
3382    editor_b_lib.update(cx_b, |editor, cx| {
3383        let snapshot = editor.buffer().read(cx).snapshot(cx);
3384        let all_diagnostics = snapshot
3385            .diagnostics_in_range(0..snapshot.len())
3386            .collect::<Vec<_>>();
3387        let expected_messages = [
3388            expected_workspace_pull_diagnostics_lib_message,
3389            expected_pull_diagnostic_lib_message,
3390            expected_push_diagnostic_lib_message,
3391        ];
3392        assert_eq!(all_diagnostics.len(), 1);
3393        for diagnostic in &all_diagnostics {
3394            assert!(
3395                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3396                "Unexpected diagnostics: {all_diagnostics:?}"
3397            );
3398        }
3399    });
3400    editor_b_main.update(cx_b, |editor, cx| {
3401        let snapshot = editor.buffer().read(cx).snapshot(cx);
3402        let all_diagnostics = snapshot
3403            .diagnostics_in_range(0..snapshot.len())
3404            .collect::<Vec<_>>();
3405        assert_eq!(all_diagnostics.len(), 2);
3406
3407        let expected_messages = [
3408            expected_workspace_pull_diagnostics_main_message,
3409            expected_pull_diagnostic_main_message,
3410            expected_push_diagnostic_main_message,
3411        ];
3412        for diagnostic in &all_diagnostics {
3413            assert!(
3414                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3415                "Unexpected diagnostics: {all_diagnostics:?}"
3416            );
3417        }
3418    });
3419    editor_a_main.update(cx_a, |editor, cx| {
3420        let snapshot = editor.buffer().read(cx).snapshot(cx);
3421        let all_diagnostics = snapshot
3422            .diagnostics_in_range(0..snapshot.len())
3423            .collect::<Vec<_>>();
3424        assert_eq!(all_diagnostics.len(), 2);
3425        let expected_messages = [
3426            expected_workspace_pull_diagnostics_main_message,
3427            expected_pull_diagnostic_main_message,
3428            expected_push_diagnostic_main_message,
3429        ];
3430        for diagnostic in &all_diagnostics {
3431            assert!(
3432                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3433                "Unexpected diagnostics: {all_diagnostics:?}"
3434            );
3435        }
3436    });
3437}
3438
3439#[gpui::test(iterations = 10)]
3440async fn test_non_streamed_lsp_pull_diagnostics(
3441    cx_a: &mut TestAppContext,
3442    cx_b: &mut TestAppContext,
3443) {
3444    test_lsp_pull_diagnostics(false, cx_a, cx_b).await;
3445}
3446
3447#[gpui::test(iterations = 10)]
3448async fn test_streamed_lsp_pull_diagnostics(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3449    test_lsp_pull_diagnostics(true, cx_a, cx_b).await;
3450}
3451
3452#[gpui::test(iterations = 10)]
3453async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3454    let mut server = TestServer::start(cx_a.executor()).await;
3455    let client_a = server.create_client(cx_a, "user_a").await;
3456    let client_b = server.create_client(cx_b, "user_b").await;
3457    server
3458        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3459        .await;
3460    let active_call_a = cx_a.read(ActiveCall::global);
3461
3462    cx_a.update(editor::init);
3463    cx_b.update(editor::init);
3464    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
3465    let inline_blame_off_settings = Some(InlineBlameSettings {
3466        enabled: Some(false),
3467        ..Default::default()
3468    });
3469    cx_a.update(|cx| {
3470        SettingsStore::update_global(cx, |store, cx| {
3471            store.update_user_settings(cx, |settings| {
3472                settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
3473            });
3474        });
3475    });
3476    cx_b.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
3484    client_a
3485        .fs()
3486        .insert_tree(
3487            path!("/my-repo"),
3488            json!({
3489                ".git": {},
3490                "file.txt": "line1\nline2\nline3\nline\n",
3491            }),
3492        )
3493        .await;
3494
3495    let blame = git::blame::Blame {
3496        entries: vec![
3497            blame_entry("1b1b1b", 0..1),
3498            blame_entry("0d0d0d", 1..2),
3499            blame_entry("3a3a3a", 2..3),
3500            blame_entry("4c4c4c", 3..4),
3501        ],
3502        messages: [
3503            ("1b1b1b", "message for idx-0"),
3504            ("0d0d0d", "message for idx-1"),
3505            ("3a3a3a", "message for idx-2"),
3506            ("4c4c4c", "message for idx-3"),
3507        ]
3508        .into_iter()
3509        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
3510        .collect(),
3511        remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
3512    };
3513    client_a.fs().set_blame_for_repo(
3514        Path::new(path!("/my-repo/.git")),
3515        vec![(repo_path("file.txt"), blame)],
3516    );
3517
3518    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
3519    let project_id = active_call_a
3520        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3521        .await
3522        .unwrap();
3523
3524    // Create editor_a
3525    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3526    let editor_a = workspace_a
3527        .update_in(cx_a, |workspace, window, cx| {
3528            workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx)
3529        })
3530        .await
3531        .unwrap()
3532        .downcast::<Editor>()
3533        .unwrap();
3534
3535    // Join the project as client B.
3536    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3537    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3538    let editor_b = workspace_b
3539        .update_in(cx_b, |workspace, window, cx| {
3540            workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx)
3541        })
3542        .await
3543        .unwrap()
3544        .downcast::<Editor>()
3545        .unwrap();
3546    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
3547        editor_b
3548            .buffer()
3549            .read(cx)
3550            .as_singleton()
3551            .unwrap()
3552            .read(cx)
3553            .remote_id()
3554    });
3555
3556    // client_b now requests git blame for the open buffer
3557    editor_b.update_in(cx_b, |editor_b, window, cx| {
3558        assert!(editor_b.blame().is_none());
3559        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
3560    });
3561
3562    cx_a.executor().run_until_parked();
3563    cx_b.executor().run_until_parked();
3564
3565    editor_b.update(cx_b, |editor_b, cx| {
3566        let blame = editor_b.blame().expect("editor_b should have blame now");
3567        let entries = blame.update(cx, |blame, cx| {
3568            blame
3569                .blame_for_rows(
3570                    &(0..4)
3571                        .map(|row| RowInfo {
3572                            buffer_row: Some(row),
3573                            buffer_id: Some(buffer_id_b),
3574                            ..Default::default()
3575                        })
3576                        .collect::<Vec<_>>(),
3577                    cx,
3578                )
3579                .collect::<Vec<_>>()
3580        });
3581
3582        assert_eq!(
3583            entries,
3584            vec![
3585                Some((buffer_id_b, blame_entry("1b1b1b", 0..1))),
3586                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3587                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3588                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3589            ]
3590        );
3591
3592        blame.update(cx, |blame, _| {
3593            for (idx, (buffer, entry)) in entries.iter().flatten().enumerate() {
3594                let details = blame.details_for_entry(*buffer, entry).unwrap();
3595                assert_eq!(details.message, format!("message for idx-{}", idx));
3596                assert_eq!(
3597                    details.permalink.unwrap().to_string(),
3598                    format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
3599                );
3600            }
3601        });
3602    });
3603
3604    // editor_b updates the file, which gets sent to client_a, which updates git blame,
3605    // which gets back to client_b.
3606    editor_b.update_in(cx_b, |editor_b, _, cx| {
3607        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
3608    });
3609
3610    cx_a.executor().run_until_parked();
3611    cx_b.executor().run_until_parked();
3612
3613    editor_b.update(cx_b, |editor_b, cx| {
3614        let blame = editor_b.blame().expect("editor_b should have blame now");
3615        let entries = blame.update(cx, |blame, cx| {
3616            blame
3617                .blame_for_rows(
3618                    &(0..4)
3619                        .map(|row| RowInfo {
3620                            buffer_row: Some(row),
3621                            buffer_id: Some(buffer_id_b),
3622                            ..Default::default()
3623                        })
3624                        .collect::<Vec<_>>(),
3625                    cx,
3626                )
3627                .collect::<Vec<_>>()
3628        });
3629
3630        assert_eq!(
3631            entries,
3632            vec![
3633                None,
3634                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3635                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3636                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3637            ]
3638        );
3639    });
3640
3641    // Now editor_a also updates the file
3642    editor_a.update_in(cx_a, |editor_a, _, cx| {
3643        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
3644    });
3645
3646    cx_a.executor().run_until_parked();
3647    cx_b.executor().run_until_parked();
3648
3649    editor_b.update(cx_b, |editor_b, cx| {
3650        let blame = editor_b.blame().expect("editor_b should have blame now");
3651        let entries = blame.update(cx, |blame, cx| {
3652            blame
3653                .blame_for_rows(
3654                    &(0..4)
3655                        .map(|row| RowInfo {
3656                            buffer_row: Some(row),
3657                            buffer_id: Some(buffer_id_b),
3658                            ..Default::default()
3659                        })
3660                        .collect::<Vec<_>>(),
3661                    cx,
3662                )
3663                .collect::<Vec<_>>()
3664        });
3665
3666        assert_eq!(
3667            entries,
3668            vec![
3669                None,
3670                None,
3671                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3672                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3673            ]
3674        );
3675    });
3676}
3677
3678#[gpui::test(iterations = 30)]
3679async fn test_collaborating_with_editorconfig(
3680    cx_a: &mut TestAppContext,
3681    cx_b: &mut TestAppContext,
3682) {
3683    let mut server = TestServer::start(cx_a.executor()).await;
3684    let client_a = server.create_client(cx_a, "user_a").await;
3685    let client_b = server.create_client(cx_b, "user_b").await;
3686    server
3687        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3688        .await;
3689    let active_call_a = cx_a.read(ActiveCall::global);
3690
3691    cx_b.update(editor::init);
3692
3693    // Set up a fake language server.
3694    client_a.language_registry().add(rust_lang());
3695    client_a
3696        .fs()
3697        .insert_tree(
3698            path!("/a"),
3699            json!({
3700                "src": {
3701                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
3702                    "other_mod": {
3703                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
3704                        ".editorconfig": "",
3705                    },
3706                },
3707                ".editorconfig": "[*]\ntab_width = 2\n",
3708            }),
3709        )
3710        .await;
3711    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
3712    let project_id = active_call_a
3713        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3714        .await
3715        .unwrap();
3716    let main_buffer_a = project_a
3717        .update(cx_a, |p, cx| {
3718            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
3719        })
3720        .await
3721        .unwrap();
3722    let other_buffer_a = project_a
3723        .update(cx_a, |p, cx| {
3724            p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
3725        })
3726        .await
3727        .unwrap();
3728    let cx_a = cx_a.add_empty_window();
3729    let main_editor_a = cx_a.new_window_entity(|window, cx| {
3730        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
3731    });
3732    let other_editor_a = cx_a.new_window_entity(|window, cx| {
3733        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
3734    });
3735    let mut main_editor_cx_a = EditorTestContext {
3736        cx: cx_a.clone(),
3737        window: cx_a.window_handle(),
3738        editor: main_editor_a,
3739        assertion_cx: AssertionContextManager::new(),
3740    };
3741    let mut other_editor_cx_a = EditorTestContext {
3742        cx: cx_a.clone(),
3743        window: cx_a.window_handle(),
3744        editor: other_editor_a,
3745        assertion_cx: AssertionContextManager::new(),
3746    };
3747
3748    // Join the project as client B.
3749    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3750    let main_buffer_b = project_b
3751        .update(cx_b, |p, cx| {
3752            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
3753        })
3754        .await
3755        .unwrap();
3756    let other_buffer_b = project_b
3757        .update(cx_b, |p, cx| {
3758            p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
3759        })
3760        .await
3761        .unwrap();
3762    let cx_b = cx_b.add_empty_window();
3763    let main_editor_b = cx_b.new_window_entity(|window, cx| {
3764        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
3765    });
3766    let other_editor_b = cx_b.new_window_entity(|window, cx| {
3767        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
3768    });
3769    let mut main_editor_cx_b = EditorTestContext {
3770        cx: cx_b.clone(),
3771        window: cx_b.window_handle(),
3772        editor: main_editor_b,
3773        assertion_cx: AssertionContextManager::new(),
3774    };
3775    let mut other_editor_cx_b = EditorTestContext {
3776        cx: cx_b.clone(),
3777        window: cx_b.window_handle(),
3778        editor: other_editor_b,
3779        assertion_cx: AssertionContextManager::new(),
3780    };
3781
3782    let initial_main = indoc! {"
3783ˇmod other;
3784fn main() { let foo = other::foo(); }"};
3785    let initial_other = indoc! {"
3786ˇpub fn foo() -> usize {
3787    4
3788}"};
3789
3790    let first_tabbed_main = indoc! {"
3791  ˇmod other;
3792fn main() { let foo = other::foo(); }"};
3793    tab_undo_assert(
3794        &mut main_editor_cx_a,
3795        &mut main_editor_cx_b,
3796        initial_main,
3797        first_tabbed_main,
3798        true,
3799    );
3800    tab_undo_assert(
3801        &mut main_editor_cx_a,
3802        &mut main_editor_cx_b,
3803        initial_main,
3804        first_tabbed_main,
3805        false,
3806    );
3807
3808    let first_tabbed_other = indoc! {"
3809  ˇpub fn foo() -> usize {
3810    4
3811}"};
3812    tab_undo_assert(
3813        &mut other_editor_cx_a,
3814        &mut other_editor_cx_b,
3815        initial_other,
3816        first_tabbed_other,
3817        true,
3818    );
3819    tab_undo_assert(
3820        &mut other_editor_cx_a,
3821        &mut other_editor_cx_b,
3822        initial_other,
3823        first_tabbed_other,
3824        false,
3825    );
3826
3827    client_a
3828        .fs()
3829        .atomic_write(
3830            PathBuf::from(path!("/a/src/.editorconfig")),
3831            "[*]\ntab_width = 3\n".to_owned(),
3832        )
3833        .await
3834        .unwrap();
3835    cx_a.run_until_parked();
3836    cx_b.run_until_parked();
3837
3838    let second_tabbed_main = indoc! {"
3839   ˇmod other;
3840fn main() { let foo = other::foo(); }"};
3841    tab_undo_assert(
3842        &mut main_editor_cx_a,
3843        &mut main_editor_cx_b,
3844        initial_main,
3845        second_tabbed_main,
3846        true,
3847    );
3848    tab_undo_assert(
3849        &mut main_editor_cx_a,
3850        &mut main_editor_cx_b,
3851        initial_main,
3852        second_tabbed_main,
3853        false,
3854    );
3855
3856    let second_tabbed_other = indoc! {"
3857   ˇpub fn foo() -> usize {
3858    4
3859}"};
3860    tab_undo_assert(
3861        &mut other_editor_cx_a,
3862        &mut other_editor_cx_b,
3863        initial_other,
3864        second_tabbed_other,
3865        true,
3866    );
3867    tab_undo_assert(
3868        &mut other_editor_cx_a,
3869        &mut other_editor_cx_b,
3870        initial_other,
3871        second_tabbed_other,
3872        false,
3873    );
3874
3875    let editorconfig_buffer_b = project_b
3876        .update(cx_b, |p, cx| {
3877            p.open_buffer((worktree_id, rel_path("src/other_mod/.editorconfig")), cx)
3878        })
3879        .await
3880        .unwrap();
3881    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
3882        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
3883    });
3884    project_b
3885        .update(cx_b, |project, cx| {
3886            project.save_buffer(editorconfig_buffer_b.clone(), cx)
3887        })
3888        .await
3889        .unwrap();
3890    cx_a.run_until_parked();
3891    cx_b.run_until_parked();
3892
3893    tab_undo_assert(
3894        &mut main_editor_cx_a,
3895        &mut main_editor_cx_b,
3896        initial_main,
3897        second_tabbed_main,
3898        true,
3899    );
3900    tab_undo_assert(
3901        &mut main_editor_cx_a,
3902        &mut main_editor_cx_b,
3903        initial_main,
3904        second_tabbed_main,
3905        false,
3906    );
3907
3908    let third_tabbed_other = indoc! {"
3909      ˇpub fn foo() -> usize {
3910    4
3911}"};
3912    tab_undo_assert(
3913        &mut other_editor_cx_a,
3914        &mut other_editor_cx_b,
3915        initial_other,
3916        third_tabbed_other,
3917        true,
3918    );
3919
3920    tab_undo_assert(
3921        &mut other_editor_cx_a,
3922        &mut other_editor_cx_b,
3923        initial_other,
3924        third_tabbed_other,
3925        false,
3926    );
3927}
3928
3929#[gpui::test]
3930async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3931    let executor = cx_a.executor();
3932    let mut server = TestServer::start(executor.clone()).await;
3933    let client_a = server.create_client(cx_a, "user_a").await;
3934    let client_b = server.create_client(cx_b, "user_b").await;
3935    server
3936        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3937        .await;
3938    let active_call_a = cx_a.read(ActiveCall::global);
3939    let active_call_b = cx_b.read(ActiveCall::global);
3940    cx_a.update(editor::init);
3941    cx_b.update(editor::init);
3942    client_a
3943        .fs()
3944        .insert_tree(
3945            "/a",
3946            json!({
3947                "test.txt": "one\ntwo\nthree\nfour\nfive",
3948            }),
3949        )
3950        .await;
3951    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3952    let project_path = ProjectPath {
3953        worktree_id,
3954        path: rel_path(&"test.txt").into(),
3955    };
3956    let abs_path = project_a.read_with(cx_a, |project, cx| {
3957        project
3958            .absolute_path(&project_path, cx)
3959            .map(Arc::from)
3960            .unwrap()
3961    });
3962
3963    active_call_a
3964        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
3965        .await
3966        .unwrap();
3967    let project_id = active_call_a
3968        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3969        .await
3970        .unwrap();
3971    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3972    active_call_b
3973        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
3974        .await
3975        .unwrap();
3976    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3977    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3978
3979    // Client A opens an editor.
3980    let editor_a = workspace_a
3981        .update_in(cx_a, |workspace, window, cx| {
3982            workspace.open_path(project_path.clone(), None, true, window, cx)
3983        })
3984        .await
3985        .unwrap()
3986        .downcast::<Editor>()
3987        .unwrap();
3988
3989    // Client B opens same editor as A.
3990    let editor_b = workspace_b
3991        .update_in(cx_b, |workspace, window, cx| {
3992            workspace.open_path(project_path.clone(), None, true, window, cx)
3993        })
3994        .await
3995        .unwrap()
3996        .downcast::<Editor>()
3997        .unwrap();
3998
3999    cx_a.run_until_parked();
4000    cx_b.run_until_parked();
4001
4002    // Client A adds breakpoint on line (1)
4003    editor_a.update_in(cx_a, |editor, window, cx| {
4004        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4005    });
4006
4007    cx_a.run_until_parked();
4008    cx_b.run_until_parked();
4009
4010    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4011        editor
4012            .breakpoint_store()
4013            .unwrap()
4014            .read(cx)
4015            .all_source_breakpoints(cx)
4016    });
4017    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4018        editor
4019            .breakpoint_store()
4020            .unwrap()
4021            .read(cx)
4022            .all_source_breakpoints(cx)
4023    });
4024
4025    assert_eq!(1, breakpoints_a.len());
4026    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
4027    assert_eq!(breakpoints_a, breakpoints_b);
4028
4029    // Client B adds breakpoint on line(2)
4030    editor_b.update_in(cx_b, |editor, window, cx| {
4031        editor.move_down(&editor::actions::MoveDown, window, cx);
4032        editor.move_down(&editor::actions::MoveDown, window, cx);
4033        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4034    });
4035
4036    cx_a.run_until_parked();
4037    cx_b.run_until_parked();
4038
4039    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4040        editor
4041            .breakpoint_store()
4042            .unwrap()
4043            .read(cx)
4044            .all_source_breakpoints(cx)
4045    });
4046    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4047        editor
4048            .breakpoint_store()
4049            .unwrap()
4050            .read(cx)
4051            .all_source_breakpoints(cx)
4052    });
4053
4054    assert_eq!(1, breakpoints_a.len());
4055    assert_eq!(breakpoints_a, breakpoints_b);
4056    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
4057
4058    // Client A removes last added breakpoint from client B
4059    editor_a.update_in(cx_a, |editor, window, cx| {
4060        editor.move_down(&editor::actions::MoveDown, window, cx);
4061        editor.move_down(&editor::actions::MoveDown, window, cx);
4062        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4063    });
4064
4065    cx_a.run_until_parked();
4066    cx_b.run_until_parked();
4067
4068    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4069        editor
4070            .breakpoint_store()
4071            .unwrap()
4072            .read(cx)
4073            .all_source_breakpoints(cx)
4074    });
4075    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4076        editor
4077            .breakpoint_store()
4078            .unwrap()
4079            .read(cx)
4080            .all_source_breakpoints(cx)
4081    });
4082
4083    assert_eq!(1, breakpoints_a.len());
4084    assert_eq!(breakpoints_a, breakpoints_b);
4085    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
4086
4087    // Client B removes first added breakpoint by client A
4088    editor_b.update_in(cx_b, |editor, window, cx| {
4089        editor.move_up(&editor::actions::MoveUp, window, cx);
4090        editor.move_up(&editor::actions::MoveUp, window, cx);
4091        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4092    });
4093
4094    cx_a.run_until_parked();
4095    cx_b.run_until_parked();
4096
4097    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4098        editor
4099            .breakpoint_store()
4100            .unwrap()
4101            .read(cx)
4102            .all_source_breakpoints(cx)
4103    });
4104    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4105        editor
4106            .breakpoint_store()
4107            .unwrap()
4108            .read(cx)
4109            .all_source_breakpoints(cx)
4110    });
4111
4112    assert_eq!(0, breakpoints_a.len());
4113    assert_eq!(breakpoints_a, breakpoints_b);
4114}
4115
4116#[gpui::test]
4117async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4118    let mut server = TestServer::start(cx_a.executor()).await;
4119    let client_a = server.create_client(cx_a, "user_a").await;
4120    let client_b = server.create_client(cx_b, "user_b").await;
4121    server
4122        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4123        .await;
4124    let active_call_a = cx_a.read(ActiveCall::global);
4125    let active_call_b = cx_b.read(ActiveCall::global);
4126
4127    cx_a.update(editor::init);
4128    cx_b.update(editor::init);
4129
4130    client_a.language_registry().add(rust_lang());
4131    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4132        "Rust",
4133        FakeLspAdapter {
4134            name: "rust-analyzer",
4135            ..FakeLspAdapter::default()
4136        },
4137    );
4138    client_b.language_registry().add(rust_lang());
4139    client_b.language_registry().register_fake_lsp_adapter(
4140        "Rust",
4141        FakeLspAdapter {
4142            name: "rust-analyzer",
4143            ..FakeLspAdapter::default()
4144        },
4145    );
4146
4147    client_a
4148        .fs()
4149        .insert_tree(
4150            path!("/a"),
4151            json!({
4152                "main.rs": "fn main() {}",
4153            }),
4154        )
4155        .await;
4156    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4157    active_call_a
4158        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
4159        .await
4160        .unwrap();
4161    let project_id = active_call_a
4162        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4163        .await
4164        .unwrap();
4165
4166    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4167    active_call_b
4168        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
4169        .await
4170        .unwrap();
4171
4172    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4173    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4174
4175    let editor_a = workspace_a
4176        .update_in(cx_a, |workspace, window, cx| {
4177            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
4178        })
4179        .await
4180        .unwrap()
4181        .downcast::<Editor>()
4182        .unwrap();
4183
4184    let editor_b = workspace_b
4185        .update_in(cx_b, |workspace, window, cx| {
4186            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
4187        })
4188        .await
4189        .unwrap()
4190        .downcast::<Editor>()
4191        .unwrap();
4192
4193    let fake_language_server = fake_language_servers.next().await.unwrap();
4194
4195    // host
4196    let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4197        |params, _| async move {
4198            assert_eq!(
4199                params.text_document.uri,
4200                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4201            );
4202            assert_eq!(params.position, lsp::Position::new(0, 0));
4203            Ok(Some(ExpandedMacro {
4204                name: "test_macro_name".to_string(),
4205                expansion: "test_macro_expansion on the host".to_string(),
4206            }))
4207        },
4208    );
4209
4210    editor_a.update_in(cx_a, |editor, window, cx| {
4211        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4212    });
4213    expand_request_a.next().await.unwrap();
4214    cx_a.run_until_parked();
4215
4216    workspace_a.update(cx_a, |workspace, cx| {
4217        workspace.active_pane().update(cx, |pane, cx| {
4218            assert_eq!(
4219                pane.items_len(),
4220                2,
4221                "Should have added a macro expansion to the host's pane"
4222            );
4223            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4224            new_editor.update(cx, |editor, cx| {
4225                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
4226            });
4227        })
4228    });
4229
4230    // client
4231    let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4232        |params, _| async move {
4233            assert_eq!(
4234                params.text_document.uri,
4235                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4236            );
4237            assert_eq!(
4238                params.position,
4239                lsp::Position::new(0, 12),
4240                "editor_b has selected the entire text and should query for a different position"
4241            );
4242            Ok(Some(ExpandedMacro {
4243                name: "test_macro_name".to_string(),
4244                expansion: "test_macro_expansion on the client".to_string(),
4245            }))
4246        },
4247    );
4248
4249    editor_b.update_in(cx_b, |editor, window, cx| {
4250        editor.select_all(&SelectAll, window, cx);
4251        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4252    });
4253    expand_request_b.next().await.unwrap();
4254    cx_b.run_until_parked();
4255
4256    workspace_b.update(cx_b, |workspace, cx| {
4257        workspace.active_pane().update(cx, |pane, cx| {
4258            assert_eq!(
4259                pane.items_len(),
4260                2,
4261                "Should have added a macro expansion to the client's pane"
4262            );
4263            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4264            new_editor.update(cx, |editor, cx| {
4265                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
4266            });
4267        })
4268    });
4269}
4270
4271#[track_caller]
4272fn tab_undo_assert(
4273    cx_a: &mut EditorTestContext,
4274    cx_b: &mut EditorTestContext,
4275    expected_initial: &str,
4276    expected_tabbed: &str,
4277    a_tabs: bool,
4278) {
4279    cx_a.assert_editor_state(expected_initial);
4280    cx_b.assert_editor_state(expected_initial);
4281
4282    if a_tabs {
4283        cx_a.update_editor(|editor, window, cx| {
4284            editor.tab(&editor::actions::Tab, window, cx);
4285        });
4286    } else {
4287        cx_b.update_editor(|editor, window, cx| {
4288            editor.tab(&editor::actions::Tab, window, cx);
4289        });
4290    }
4291
4292    cx_a.run_until_parked();
4293    cx_b.run_until_parked();
4294
4295    cx_a.assert_editor_state(expected_tabbed);
4296    cx_b.assert_editor_state(expected_tabbed);
4297
4298    if a_tabs {
4299        cx_a.update_editor(|editor, window, cx| {
4300            editor.undo(&editor::actions::Undo, window, cx);
4301        });
4302    } else {
4303        cx_b.update_editor(|editor, window, cx| {
4304            editor.undo(&editor::actions::Undo, window, cx);
4305        });
4306    }
4307    cx_a.run_until_parked();
4308    cx_b.run_until_parked();
4309    cx_a.assert_editor_state(expected_initial);
4310    cx_b.assert_editor_state(expected_initial);
4311}
4312
4313fn extract_hint_labels(editor: &Editor, cx: &mut App) -> Vec<String> {
4314    let lsp_store = editor.project().unwrap().read(cx).lsp_store();
4315
4316    let mut all_cached_labels = Vec::new();
4317    let mut all_fetched_hints = Vec::new();
4318    for buffer in editor.buffer().read(cx).all_buffers() {
4319        lsp_store.update(cx, |lsp_store, cx| {
4320            let hints = &lsp_store.latest_lsp_data(&buffer, cx).inlay_hints();
4321            all_cached_labels.extend(hints.all_cached_hints().into_iter().map(|hint| {
4322                let mut label = hint.text().to_string();
4323                if hint.padding_left {
4324                    label.insert(0, ' ');
4325                }
4326                if hint.padding_right {
4327                    label.push_str(" ");
4328                }
4329                label
4330            }));
4331            all_fetched_hints.extend(hints.all_fetched_hints());
4332        });
4333    }
4334
4335    assert!(
4336        all_fetched_hints.is_empty(),
4337        "Did not expect background hints fetch tasks, but got {} of them",
4338        all_fetched_hints.len()
4339    );
4340
4341    all_cached_labels
4342}
4343
4344#[track_caller]
4345fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
4346    editor
4347        .all_inlays(cx)
4348        .into_iter()
4349        .filter_map(|inlay| inlay.get_color())
4350        .map(Rgba::from)
4351        .collect()
4352}
4353
4354fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
4355    git::blame::BlameEntry {
4356        sha: sha.parse().unwrap(),
4357        range,
4358        ..Default::default()
4359    }
4360}