editor_tests.rs

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