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