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