editor_tests.rs

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