editor_tests.rs

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