editor_tests.rs

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