editor_tests.rs

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