editor_tests.rs

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