editor_tests.rs

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