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(
1585                text::Anchor::min_max_range_for_buffer(buffer.remote_id()),
1586                false,
1587            )
1588            .count()
1589            == 1
1590    });
1591
1592    // Edit the buffer as client B and see that edit as client A.
1593    editor_b.update_in(cx_b, |editor, window, cx| {
1594        editor.handle_input("ok, ", window, cx)
1595    });
1596    executor.run_until_parked();
1597
1598    buffer_a.read_with(cx_a, |buffer, _| {
1599        assert_eq!(buffer.text(), "ok, b-contents")
1600    });
1601
1602    // Client B can invite client C on a project shared by client A.
1603    active_call_b
1604        .update(cx_b, |call, cx| {
1605            call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
1606        })
1607        .await
1608        .unwrap();
1609
1610    let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
1611    executor.run_until_parked();
1612    let call = incoming_call_c.borrow().clone().unwrap();
1613    assert_eq!(call.calling_user.github_login, "user_b");
1614    let initial_project = call.initial_project.unwrap();
1615    active_call_c
1616        .update(cx_c, |call, cx| call.accept_incoming(cx))
1617        .await
1618        .unwrap();
1619    let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
1620
1621    // Client B closes the editor, and client A sees client B's selections removed.
1622    cx_b.update(move |_, _| drop(editor_b));
1623    executor.run_until_parked();
1624
1625    buffer_a.read_with(cx_a, |buffer, _| {
1626        buffer
1627            .snapshot()
1628            .selections_in_range(
1629                text::Anchor::min_max_range_for_buffer(buffer.remote_id()),
1630                false,
1631            )
1632            .count()
1633            == 0
1634    });
1635}
1636
1637#[gpui::test(iterations = 10)]
1638async fn test_on_input_format_from_host_to_guest(
1639    cx_a: &mut TestAppContext,
1640    cx_b: &mut TestAppContext,
1641) {
1642    let mut server = TestServer::start(cx_a.executor()).await;
1643    let executor = cx_a.executor();
1644    let client_a = server.create_client(cx_a, "user_a").await;
1645    let client_b = server.create_client(cx_b, "user_b").await;
1646    server
1647        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1648        .await;
1649    let active_call_a = cx_a.read(ActiveCall::global);
1650
1651    client_a.language_registry().add(rust_lang());
1652    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1653        "Rust",
1654        FakeLspAdapter {
1655            capabilities: lsp::ServerCapabilities {
1656                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1657                    first_trigger_character: ":".to_string(),
1658                    more_trigger_character: Some(vec![">".to_string()]),
1659                }),
1660                ..Default::default()
1661            },
1662            ..Default::default()
1663        },
1664    );
1665
1666    client_a
1667        .fs()
1668        .insert_tree(
1669            path!("/a"),
1670            json!({
1671                "main.rs": "fn main() { a }",
1672                "other.rs": "// Test file",
1673            }),
1674        )
1675        .await;
1676    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1677    let project_id = active_call_a
1678        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1679        .await
1680        .unwrap();
1681    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1682
1683    // Open a file in an editor as the host.
1684    let buffer_a = project_a
1685        .update(cx_a, |p, cx| {
1686            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1687        })
1688        .await
1689        .unwrap();
1690    let cx_a = cx_a.add_empty_window();
1691    let editor_a = cx_a.new_window_entity(|window, cx| {
1692        Editor::for_buffer(buffer_a, Some(project_a.clone()), window, cx)
1693    });
1694
1695    let fake_language_server = fake_language_servers.next().await.unwrap();
1696    executor.run_until_parked();
1697
1698    // Receive an OnTypeFormatting request as the host's language server.
1699    // Return some formatting from the host's language server.
1700    fake_language_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
1701        |params, _| async move {
1702            assert_eq!(
1703                params.text_document_position.text_document.uri,
1704                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
1705            );
1706            assert_eq!(
1707                params.text_document_position.position,
1708                lsp::Position::new(0, 14),
1709            );
1710
1711            Ok(Some(vec![lsp::TextEdit {
1712                new_text: "~<".to_string(),
1713                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1714            }]))
1715        },
1716    );
1717
1718    // Open the buffer on the guest and see that the formatting worked
1719    let buffer_b = project_b
1720        .update(cx_b, |p, cx| {
1721            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1722        })
1723        .await
1724        .unwrap();
1725
1726    // Type a on type formatting trigger character as the guest.
1727    cx_a.focus(&editor_a);
1728    editor_a.update_in(cx_a, |editor, window, cx| {
1729        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1730            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
1731        });
1732        editor.handle_input(">", window, cx);
1733    });
1734
1735    executor.run_until_parked();
1736
1737    buffer_b.read_with(cx_b, |buffer, _| {
1738        assert_eq!(buffer.text(), "fn main() { a>~< }")
1739    });
1740
1741    // Undo should remove LSP edits first
1742    editor_a.update_in(cx_a, |editor, window, cx| {
1743        assert_eq!(editor.text(cx), "fn main() { a>~< }");
1744        editor.undo(&Undo, window, cx);
1745        assert_eq!(editor.text(cx), "fn main() { a> }");
1746    });
1747    executor.run_until_parked();
1748
1749    buffer_b.read_with(cx_b, |buffer, _| {
1750        assert_eq!(buffer.text(), "fn main() { a> }")
1751    });
1752
1753    editor_a.update_in(cx_a, |editor, window, cx| {
1754        assert_eq!(editor.text(cx), "fn main() { a> }");
1755        editor.undo(&Undo, window, cx);
1756        assert_eq!(editor.text(cx), "fn main() { a }");
1757    });
1758    executor.run_until_parked();
1759
1760    buffer_b.read_with(cx_b, |buffer, _| {
1761        assert_eq!(buffer.text(), "fn main() { a }")
1762    });
1763}
1764
1765#[gpui::test(iterations = 10)]
1766async fn test_on_input_format_from_guest_to_host(
1767    cx_a: &mut TestAppContext,
1768    cx_b: &mut TestAppContext,
1769) {
1770    let mut server = TestServer::start(cx_a.executor()).await;
1771    let executor = cx_a.executor();
1772    let client_a = server.create_client(cx_a, "user_a").await;
1773    let client_b = server.create_client(cx_b, "user_b").await;
1774    server
1775        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1776        .await;
1777    let active_call_a = cx_a.read(ActiveCall::global);
1778
1779    let capabilities = lsp::ServerCapabilities {
1780        document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1781            first_trigger_character: ":".to_string(),
1782            more_trigger_character: Some(vec![">".to_string()]),
1783        }),
1784        ..lsp::ServerCapabilities::default()
1785    };
1786    client_a.language_registry().add(rust_lang());
1787    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1788        "Rust",
1789        FakeLspAdapter {
1790            capabilities: capabilities.clone(),
1791            ..FakeLspAdapter::default()
1792        },
1793    );
1794    client_b.language_registry().add(rust_lang());
1795    client_b.language_registry().register_fake_lsp_adapter(
1796        "Rust",
1797        FakeLspAdapter {
1798            capabilities,
1799            ..FakeLspAdapter::default()
1800        },
1801    );
1802
1803    client_a
1804        .fs()
1805        .insert_tree(
1806            path!("/a"),
1807            json!({
1808                "main.rs": "fn main() { a }",
1809                "other.rs": "// Test file",
1810            }),
1811        )
1812        .await;
1813    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
1814    let project_id = active_call_a
1815        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1816        .await
1817        .unwrap();
1818    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1819
1820    // Open a file in an editor as the guest.
1821    let buffer_b = project_b
1822        .update(cx_b, |p, cx| {
1823            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1824        })
1825        .await
1826        .unwrap();
1827    let cx_b = cx_b.add_empty_window();
1828    let editor_b = cx_b.new_window_entity(|window, cx| {
1829        Editor::for_buffer(buffer_b, Some(project_b.clone()), window, cx)
1830    });
1831
1832    let fake_language_server = fake_language_servers.next().await.unwrap();
1833    executor.run_until_parked();
1834
1835    // Type a on type formatting trigger character as the guest.
1836    cx_b.focus(&editor_b);
1837    editor_b.update_in(cx_b, |editor, window, cx| {
1838        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1839            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
1840        });
1841        editor.handle_input(":", window, cx);
1842    });
1843
1844    // Receive an OnTypeFormatting request as the host's language server.
1845    // Return some formatting from the host's language server.
1846    executor.start_waiting();
1847    fake_language_server
1848        .set_request_handler::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
1849            assert_eq!(
1850                params.text_document_position.text_document.uri,
1851                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
1852            );
1853            assert_eq!(
1854                params.text_document_position.position,
1855                lsp::Position::new(0, 14),
1856            );
1857
1858            Ok(Some(vec![lsp::TextEdit {
1859                new_text: "~:".to_string(),
1860                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1861            }]))
1862        })
1863        .next()
1864        .await
1865        .unwrap();
1866    executor.finish_waiting();
1867
1868    // Open the buffer on the host and see that the formatting worked
1869    let buffer_a = project_a
1870        .update(cx_a, |p, cx| {
1871            p.open_buffer((worktree_id, rel_path("main.rs")), cx)
1872        })
1873        .await
1874        .unwrap();
1875    executor.run_until_parked();
1876
1877    buffer_a.read_with(cx_a, |buffer, _| {
1878        assert_eq!(buffer.text(), "fn main() { a:~: }")
1879    });
1880
1881    // Undo should remove LSP edits first
1882    editor_b.update_in(cx_b, |editor, window, cx| {
1883        assert_eq!(editor.text(cx), "fn main() { a:~: }");
1884        editor.undo(&Undo, window, cx);
1885        assert_eq!(editor.text(cx), "fn main() { a: }");
1886    });
1887    executor.run_until_parked();
1888
1889    buffer_a.read_with(cx_a, |buffer, _| {
1890        assert_eq!(buffer.text(), "fn main() { a: }")
1891    });
1892
1893    editor_b.update_in(cx_b, |editor, window, cx| {
1894        assert_eq!(editor.text(cx), "fn main() { a: }");
1895        editor.undo(&Undo, window, cx);
1896        assert_eq!(editor.text(cx), "fn main() { a }");
1897    });
1898    executor.run_until_parked();
1899
1900    buffer_a.read_with(cx_a, |buffer, _| {
1901        assert_eq!(buffer.text(), "fn main() { a }")
1902    });
1903}
1904
1905#[gpui::test(iterations = 10)]
1906async fn test_mutual_editor_inlay_hint_cache_update(
1907    cx_a: &mut TestAppContext,
1908    cx_b: &mut TestAppContext,
1909) {
1910    let mut server = TestServer::start(cx_a.executor()).await;
1911    let executor = cx_a.executor();
1912    let client_a = server.create_client(cx_a, "user_a").await;
1913    let client_b = server.create_client(cx_b, "user_b").await;
1914    server
1915        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1916        .await;
1917    let active_call_a = cx_a.read(ActiveCall::global);
1918    let active_call_b = cx_b.read(ActiveCall::global);
1919
1920    cx_a.update(editor::init);
1921    cx_b.update(editor::init);
1922
1923    cx_a.update(|cx| {
1924        SettingsStore::update_global(cx, |store, cx| {
1925            store.update_user_settings(cx, |settings| {
1926                settings.project.all_languages.defaults.inlay_hints =
1927                    Some(InlayHintSettingsContent {
1928                        enabled: Some(true),
1929                        ..InlayHintSettingsContent::default()
1930                    })
1931            });
1932        });
1933    });
1934    cx_b.update(|cx| {
1935        SettingsStore::update_global(cx, |store, cx| {
1936            store.update_user_settings(cx, |settings| {
1937                settings.project.all_languages.defaults.inlay_hints =
1938                    Some(InlayHintSettingsContent {
1939                        enabled: Some(true),
1940                        ..InlayHintSettingsContent::default()
1941                    })
1942            });
1943        });
1944    });
1945
1946    let capabilities = lsp::ServerCapabilities {
1947        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1948        ..lsp::ServerCapabilities::default()
1949    };
1950    client_a.language_registry().add(rust_lang());
1951
1952    // Set up the language server to return an additional inlay hint on each request.
1953    let edits_made = Arc::new(AtomicUsize::new(0));
1954    let closure_edits_made = Arc::clone(&edits_made);
1955    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
1956        "Rust",
1957        FakeLspAdapter {
1958            capabilities: capabilities.clone(),
1959            initializer: Some(Box::new(move |fake_language_server| {
1960                let closure_edits_made = closure_edits_made.clone();
1961                fake_language_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
1962                    move |params, _| {
1963                        let edits_made_2 = Arc::clone(&closure_edits_made);
1964                        async move {
1965                            assert_eq!(
1966                                params.text_document.uri,
1967                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
1968                            );
1969                            let edits_made =
1970                                AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire);
1971                            Ok(Some(vec![lsp::InlayHint {
1972                                position: lsp::Position::new(0, edits_made as u32),
1973                                label: lsp::InlayHintLabel::String(edits_made.to_string()),
1974                                kind: None,
1975                                text_edits: None,
1976                                tooltip: None,
1977                                padding_left: None,
1978                                padding_right: None,
1979                                data: None,
1980                            }]))
1981                        }
1982                    },
1983                );
1984            })),
1985            ..FakeLspAdapter::default()
1986        },
1987    );
1988    client_b.language_registry().add(rust_lang());
1989    client_b.language_registry().register_fake_lsp_adapter(
1990        "Rust",
1991        FakeLspAdapter {
1992            capabilities,
1993            ..FakeLspAdapter::default()
1994        },
1995    );
1996
1997    // Client A opens a project.
1998    client_a
1999        .fs()
2000        .insert_tree(
2001            path!("/a"),
2002            json!({
2003                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
2004                "other.rs": "// Test file",
2005            }),
2006        )
2007        .await;
2008    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2009    active_call_a
2010        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2011        .await
2012        .unwrap();
2013    let project_id = active_call_a
2014        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2015        .await
2016        .unwrap();
2017
2018    // Client B joins the project
2019    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2020    active_call_b
2021        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2022        .await
2023        .unwrap();
2024
2025    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2026
2027    // The host opens a rust file.
2028    let file_a = workspace_a.update_in(cx_a, |workspace, window, cx| {
2029        workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2030    });
2031    let fake_language_server = fake_language_servers.next().await.unwrap();
2032    let editor_a = file_a.await.unwrap().downcast::<Editor>().unwrap();
2033    executor.advance_clock(Duration::from_millis(100));
2034    executor.run_until_parked();
2035
2036    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
2037    editor_a.update(cx_a, |editor, cx| {
2038        assert_eq!(
2039            vec![initial_edit.to_string()],
2040            extract_hint_labels(editor, cx),
2041            "Host should get its first hints when opens an editor"
2042        );
2043    });
2044    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2045    let editor_b = workspace_b
2046        .update_in(cx_b, |workspace, window, cx| {
2047            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2048        })
2049        .await
2050        .unwrap()
2051        .downcast::<Editor>()
2052        .unwrap();
2053
2054    executor.advance_clock(Duration::from_millis(100));
2055    executor.run_until_parked();
2056    editor_b.update(cx_b, |editor, cx| {
2057        assert_eq!(
2058            vec![initial_edit.to_string()],
2059            extract_hint_labels(editor, cx),
2060            "Client should get its first hints when opens an editor"
2061        );
2062    });
2063
2064    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
2065    editor_b.update_in(cx_b, |editor, window, cx| {
2066        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2067            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)].clone())
2068        });
2069        editor.handle_input(":", window, cx);
2070    });
2071    cx_b.focus(&editor_b);
2072
2073    executor.advance_clock(Duration::from_secs(1));
2074    executor.run_until_parked();
2075    editor_a.update(cx_a, |editor, cx| {
2076        assert_eq!(
2077            vec![after_client_edit.to_string()],
2078            extract_hint_labels(editor, cx),
2079        );
2080    });
2081    editor_b.update(cx_b, |editor, cx| {
2082        assert_eq!(
2083            vec![after_client_edit.to_string()],
2084            extract_hint_labels(editor, cx),
2085        );
2086    });
2087
2088    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
2089    editor_a.update_in(cx_a, |editor, window, cx| {
2090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2091            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
2092        });
2093        editor.handle_input("a change to increment both buffers' versions", window, cx);
2094    });
2095    cx_a.focus(&editor_a);
2096
2097    executor.advance_clock(Duration::from_secs(1));
2098    executor.run_until_parked();
2099    editor_a.update(cx_a, |editor, cx| {
2100        assert_eq!(
2101            vec![after_host_edit.to_string()],
2102            extract_hint_labels(editor, cx),
2103        );
2104    });
2105    editor_b.update(cx_b, |editor, cx| {
2106        assert_eq!(
2107            vec![after_host_edit.to_string()],
2108            extract_hint_labels(editor, cx),
2109        );
2110    });
2111
2112    let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
2113    fake_language_server
2114        .request::<lsp::request::InlayHintRefreshRequest>(())
2115        .await
2116        .into_response()
2117        .expect("inlay refresh request failed");
2118
2119    executor.advance_clock(Duration::from_secs(1));
2120    executor.run_until_parked();
2121    editor_a.update(cx_a, |editor, cx| {
2122        assert_eq!(
2123            vec![after_special_edit_for_refresh.to_string()],
2124            extract_hint_labels(editor, cx),
2125            "Host should react to /refresh LSP request"
2126        );
2127    });
2128    editor_b.update(cx_b, |editor, cx| {
2129        assert_eq!(
2130            vec![after_special_edit_for_refresh.to_string()],
2131            extract_hint_labels(editor, cx),
2132            "Guest should get a /refresh LSP request propagated by host"
2133        );
2134    });
2135}
2136
2137#[gpui::test(iterations = 10)]
2138async fn test_inlay_hint_refresh_is_forwarded(
2139    cx_a: &mut TestAppContext,
2140    cx_b: &mut TestAppContext,
2141) {
2142    let mut server = TestServer::start(cx_a.executor()).await;
2143    let executor = cx_a.executor();
2144    let client_a = server.create_client(cx_a, "user_a").await;
2145    let client_b = server.create_client(cx_b, "user_b").await;
2146    server
2147        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2148        .await;
2149    let active_call_a = cx_a.read(ActiveCall::global);
2150    let active_call_b = cx_b.read(ActiveCall::global);
2151
2152    cx_a.update(editor::init);
2153    cx_b.update(editor::init);
2154
2155    cx_a.update(|cx| {
2156        SettingsStore::update_global(cx, |store, cx| {
2157            store.update_user_settings(cx, |settings| {
2158                settings.project.all_languages.defaults.inlay_hints =
2159                    Some(InlayHintSettingsContent {
2160                        show_value_hints: Some(true),
2161                        enabled: Some(false),
2162                        edit_debounce_ms: Some(0),
2163                        scroll_debounce_ms: Some(0),
2164                        show_type_hints: Some(false),
2165                        show_parameter_hints: Some(false),
2166                        show_other_hints: Some(false),
2167                        show_background: Some(false),
2168                        toggle_on_modifiers_press: None,
2169                    })
2170            });
2171        });
2172    });
2173    cx_b.update(|cx| {
2174        SettingsStore::update_global(cx, |store, cx| {
2175            store.update_user_settings(cx, |settings| {
2176                settings.project.all_languages.defaults.inlay_hints =
2177                    Some(InlayHintSettingsContent {
2178                        show_value_hints: Some(true),
2179                        enabled: Some(true),
2180                        edit_debounce_ms: Some(0),
2181                        scroll_debounce_ms: Some(0),
2182                        show_type_hints: Some(true),
2183                        show_parameter_hints: Some(true),
2184                        show_other_hints: Some(true),
2185                        show_background: Some(false),
2186                        toggle_on_modifiers_press: None,
2187                    })
2188            });
2189        });
2190    });
2191
2192    let capabilities = lsp::ServerCapabilities {
2193        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2194        ..lsp::ServerCapabilities::default()
2195    };
2196    client_a.language_registry().add(rust_lang());
2197    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2198        "Rust",
2199        FakeLspAdapter {
2200            capabilities: capabilities.clone(),
2201            ..FakeLspAdapter::default()
2202        },
2203    );
2204    client_b.language_registry().add(rust_lang());
2205    client_b.language_registry().register_fake_lsp_adapter(
2206        "Rust",
2207        FakeLspAdapter {
2208            capabilities,
2209            ..FakeLspAdapter::default()
2210        },
2211    );
2212
2213    client_a
2214        .fs()
2215        .insert_tree(
2216            path!("/a"),
2217            json!({
2218                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
2219                "other.rs": "// Test file",
2220            }),
2221        )
2222        .await;
2223    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2224    active_call_a
2225        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2226        .await
2227        .unwrap();
2228    let project_id = active_call_a
2229        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2230        .await
2231        .unwrap();
2232
2233    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2234    active_call_b
2235        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2236        .await
2237        .unwrap();
2238
2239    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2240    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2241
2242    cx_a.background_executor.start_waiting();
2243
2244    let editor_a = workspace_a
2245        .update_in(cx_a, |workspace, window, cx| {
2246            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2247        })
2248        .await
2249        .unwrap()
2250        .downcast::<Editor>()
2251        .unwrap();
2252
2253    let editor_b = workspace_b
2254        .update_in(cx_b, |workspace, window, cx| {
2255            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2256        })
2257        .await
2258        .unwrap()
2259        .downcast::<Editor>()
2260        .unwrap();
2261
2262    let other_hints = Arc::new(AtomicBool::new(false));
2263    let fake_language_server = fake_language_servers.next().await.unwrap();
2264    let closure_other_hints = Arc::clone(&other_hints);
2265    fake_language_server
2266        .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2267            let task_other_hints = Arc::clone(&closure_other_hints);
2268            async move {
2269                assert_eq!(
2270                    params.text_document.uri,
2271                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2272                );
2273                let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
2274                let character = if other_hints { 0 } else { 2 };
2275                let label = if other_hints {
2276                    "other hint"
2277                } else {
2278                    "initial hint"
2279                };
2280                Ok(Some(vec![
2281                    lsp::InlayHint {
2282                        position: lsp::Position::new(0, character),
2283                        label: lsp::InlayHintLabel::String(label.to_string()),
2284                        kind: None,
2285                        text_edits: None,
2286                        tooltip: None,
2287                        padding_left: None,
2288                        padding_right: None,
2289                        data: None,
2290                    },
2291                    lsp::InlayHint {
2292                        position: lsp::Position::new(1090, 1090),
2293                        label: lsp::InlayHintLabel::String("out-of-bounds hint".to_string()),
2294                        kind: None,
2295                        text_edits: None,
2296                        tooltip: None,
2297                        padding_left: None,
2298                        padding_right: None,
2299                        data: None,
2300                    },
2301                ]))
2302            }
2303        })
2304        .next()
2305        .await
2306        .unwrap();
2307    executor.finish_waiting();
2308
2309    executor.run_until_parked();
2310    editor_a.update(cx_a, |editor, cx| {
2311        assert!(
2312            extract_hint_labels(editor, cx).is_empty(),
2313            "Host should get no hints due to them turned off"
2314        );
2315    });
2316
2317    executor.run_until_parked();
2318    editor_b.update(cx_b, |editor, cx| {
2319        assert_eq!(
2320            vec!["initial hint".to_string()],
2321            extract_hint_labels(editor, cx),
2322            "Client should get its first hints when opens an editor"
2323        );
2324    });
2325
2326    other_hints.fetch_or(true, atomic::Ordering::Release);
2327    fake_language_server
2328        .request::<lsp::request::InlayHintRefreshRequest>(())
2329        .await
2330        .into_response()
2331        .expect("inlay refresh request failed");
2332    executor.run_until_parked();
2333    editor_a.update(cx_a, |editor, cx| {
2334        assert!(
2335            extract_hint_labels(editor, cx).is_empty(),
2336            "Host should get no hints due to them turned off, even after the /refresh"
2337        );
2338    });
2339
2340    executor.run_until_parked();
2341    editor_b.update(cx_b, |editor, cx| {
2342        assert_eq!(
2343            vec!["other hint".to_string()],
2344            extract_hint_labels(editor, cx),
2345            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
2346        );
2347    });
2348}
2349
2350#[gpui::test(iterations = 10)]
2351async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2352    let expected_color = Rgba {
2353        r: 0.33,
2354        g: 0.33,
2355        b: 0.33,
2356        a: 0.33,
2357    };
2358    let mut server = TestServer::start(cx_a.executor()).await;
2359    let executor = cx_a.executor();
2360    let client_a = server.create_client(cx_a, "user_a").await;
2361    let client_b = server.create_client(cx_b, "user_b").await;
2362    server
2363        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2364        .await;
2365    let active_call_a = cx_a.read(ActiveCall::global);
2366    let active_call_b = cx_b.read(ActiveCall::global);
2367
2368    cx_a.update(editor::init);
2369    cx_b.update(editor::init);
2370
2371    cx_a.update(|cx| {
2372        SettingsStore::update_global(cx, |store, cx| {
2373            store.update_user_settings(cx, |settings| {
2374                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::None);
2375            });
2376        });
2377    });
2378    cx_b.update(|cx| {
2379        SettingsStore::update_global(cx, |store, cx| {
2380            store.update_user_settings(cx, |settings| {
2381                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2382            });
2383        });
2384    });
2385
2386    let capabilities = lsp::ServerCapabilities {
2387        color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
2388        ..lsp::ServerCapabilities::default()
2389    };
2390    client_a.language_registry().add(rust_lang());
2391    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2392        "Rust",
2393        FakeLspAdapter {
2394            capabilities: capabilities.clone(),
2395            ..FakeLspAdapter::default()
2396        },
2397    );
2398    client_b.language_registry().add(rust_lang());
2399    client_b.language_registry().register_fake_lsp_adapter(
2400        "Rust",
2401        FakeLspAdapter {
2402            capabilities,
2403            ..FakeLspAdapter::default()
2404        },
2405    );
2406
2407    // Client A opens a project.
2408    client_a
2409        .fs()
2410        .insert_tree(
2411            path!("/a"),
2412            json!({
2413                "main.rs": "fn main() { a }",
2414            }),
2415        )
2416        .await;
2417    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2418    active_call_a
2419        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2420        .await
2421        .unwrap();
2422    let project_id = active_call_a
2423        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2424        .await
2425        .unwrap();
2426
2427    // Client B joins the project
2428    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2429    active_call_b
2430        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2431        .await
2432        .unwrap();
2433
2434    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2435
2436    // The host opens a rust file.
2437    let _buffer_a = project_a
2438        .update(cx_a, |project, cx| {
2439            project.open_local_buffer(path!("/a/main.rs"), cx)
2440        })
2441        .await
2442        .unwrap();
2443    let editor_a = workspace_a
2444        .update_in(cx_a, |workspace, window, cx| {
2445            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2446        })
2447        .await
2448        .unwrap()
2449        .downcast::<Editor>()
2450        .unwrap();
2451
2452    let fake_language_server = fake_language_servers.next().await.unwrap();
2453    cx_a.run_until_parked();
2454    cx_b.run_until_parked();
2455
2456    let requests_made = Arc::new(AtomicUsize::new(0));
2457    let closure_requests_made = Arc::clone(&requests_made);
2458    let mut color_request_handle = fake_language_server
2459        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
2460            let requests_made = Arc::clone(&closure_requests_made);
2461            async move {
2462                assert_eq!(
2463                    params.text_document.uri,
2464                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2465                );
2466                requests_made.fetch_add(1, atomic::Ordering::Release);
2467                Ok(vec![lsp::ColorInformation {
2468                    range: lsp::Range {
2469                        start: lsp::Position {
2470                            line: 0,
2471                            character: 0,
2472                        },
2473                        end: lsp::Position {
2474                            line: 0,
2475                            character: 1,
2476                        },
2477                    },
2478                    color: lsp::Color {
2479                        red: 0.33,
2480                        green: 0.33,
2481                        blue: 0.33,
2482                        alpha: 0.33,
2483                    },
2484                }])
2485            }
2486        });
2487    executor.run_until_parked();
2488
2489    assert_eq!(
2490        0,
2491        requests_made.load(atomic::Ordering::Acquire),
2492        "Host did not enable document colors, hence should query for none"
2493    );
2494    editor_a.update(cx_a, |editor, cx| {
2495        assert_eq!(
2496            Vec::<Rgba>::new(),
2497            extract_color_inlays(editor, cx),
2498            "No query colors should result in no hints"
2499        );
2500    });
2501
2502    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
2503    let editor_b = workspace_b
2504        .update_in(cx_b, |workspace, window, cx| {
2505            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2506        })
2507        .await
2508        .unwrap()
2509        .downcast::<Editor>()
2510        .unwrap();
2511
2512    color_request_handle.next().await.unwrap();
2513    executor.advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
2514    executor.run_until_parked();
2515
2516    assert_eq!(
2517        1,
2518        requests_made.load(atomic::Ordering::Acquire),
2519        "The client opened the file and got its first colors back"
2520    );
2521    editor_b.update(cx_b, |editor, cx| {
2522        assert_eq!(
2523            vec![expected_color],
2524            extract_color_inlays(editor, cx),
2525            "With document colors as inlays, color inlays should be pushed"
2526        );
2527    });
2528
2529    editor_a.update_in(cx_a, |editor, window, cx| {
2530        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2531            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)].clone())
2532        });
2533        editor.handle_input(":", window, cx);
2534    });
2535    color_request_handle.next().await.unwrap();
2536    executor.run_until_parked();
2537    assert_eq!(
2538        2,
2539        requests_made.load(atomic::Ordering::Acquire),
2540        "After the host edits his file, the client should request the colors again"
2541    );
2542    editor_a.update(cx_a, |editor, cx| {
2543        assert_eq!(
2544            Vec::<Rgba>::new(),
2545            extract_color_inlays(editor, cx),
2546            "Host has no colors still"
2547        );
2548    });
2549    editor_b.update(cx_b, |editor, cx| {
2550        assert_eq!(vec![expected_color], extract_color_inlays(editor, cx),);
2551    });
2552
2553    cx_b.update(|_, cx| {
2554        SettingsStore::update_global(cx, |store, cx| {
2555            store.update_user_settings(cx, |settings| {
2556                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Background);
2557            });
2558        });
2559    });
2560    executor.run_until_parked();
2561    assert_eq!(
2562        2,
2563        requests_made.load(atomic::Ordering::Acquire),
2564        "After the client have changed the colors settings, no extra queries should happen"
2565    );
2566    editor_a.update(cx_a, |editor, cx| {
2567        assert_eq!(
2568            Vec::<Rgba>::new(),
2569            extract_color_inlays(editor, cx),
2570            "Host is unaffected by the client's settings changes"
2571        );
2572    });
2573    editor_b.update(cx_b, |editor, cx| {
2574        assert_eq!(
2575            Vec::<Rgba>::new(),
2576            extract_color_inlays(editor, cx),
2577            "Client should have no colors hints, as in the settings"
2578        );
2579    });
2580
2581    cx_b.update(|_, cx| {
2582        SettingsStore::update_global(cx, |store, cx| {
2583            store.update_user_settings(cx, |settings| {
2584                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
2585            });
2586        });
2587    });
2588    executor.run_until_parked();
2589    assert_eq!(
2590        2,
2591        requests_made.load(atomic::Ordering::Acquire),
2592        "After falling back to colors as inlays, no extra LSP queries are made"
2593    );
2594    editor_a.update(cx_a, |editor, cx| {
2595        assert_eq!(
2596            Vec::<Rgba>::new(),
2597            extract_color_inlays(editor, cx),
2598            "Host is unaffected by the client's settings changes, again"
2599        );
2600    });
2601    editor_b.update(cx_b, |editor, cx| {
2602        assert_eq!(
2603            vec![expected_color],
2604            extract_color_inlays(editor, cx),
2605            "Client should have its color hints back"
2606        );
2607    });
2608
2609    cx_a.update(|_, cx| {
2610        SettingsStore::update_global(cx, |store, cx| {
2611            store.update_user_settings(cx, |settings| {
2612                settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Border);
2613            });
2614        });
2615    });
2616    color_request_handle.next().await.unwrap();
2617    executor.run_until_parked();
2618    assert_eq!(
2619        3,
2620        requests_made.load(atomic::Ordering::Acquire),
2621        "After the host enables document colors, another LSP query should be made"
2622    );
2623    editor_a.update(cx_a, |editor, cx| {
2624        assert_eq!(
2625            Vec::<Rgba>::new(),
2626            extract_color_inlays(editor, cx),
2627            "Host did not configure document colors as hints hence gets nothing"
2628        );
2629    });
2630    editor_b.update(cx_b, |editor, cx| {
2631        assert_eq!(
2632            vec![expected_color],
2633            extract_color_inlays(editor, cx),
2634            "Client should be unaffected by the host's settings changes"
2635        );
2636    });
2637}
2638
2639async fn test_lsp_pull_diagnostics(
2640    should_stream_workspace_diagnostic: bool,
2641    cx_a: &mut TestAppContext,
2642    cx_b: &mut TestAppContext,
2643) {
2644    let mut server = TestServer::start(cx_a.executor()).await;
2645    let executor = cx_a.executor();
2646    let client_a = server.create_client(cx_a, "user_a").await;
2647    let client_b = server.create_client(cx_b, "user_b").await;
2648    server
2649        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2650        .await;
2651    let active_call_a = cx_a.read(ActiveCall::global);
2652    let active_call_b = cx_b.read(ActiveCall::global);
2653
2654    cx_a.update(editor::init);
2655    cx_b.update(editor::init);
2656
2657    let expected_push_diagnostic_main_message = "pushed main diagnostic";
2658    let expected_push_diagnostic_lib_message = "pushed lib diagnostic";
2659    let expected_pull_diagnostic_main_message = "pulled main diagnostic";
2660    let expected_pull_diagnostic_lib_message = "pulled lib diagnostic";
2661    let expected_workspace_pull_diagnostics_main_message = "pulled workspace main diagnostic";
2662    let expected_workspace_pull_diagnostics_lib_message = "pulled workspace lib diagnostic";
2663
2664    let diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<Option<String>>::new()));
2665    let workspace_diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::<String>::new()));
2666    let diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2667    let closure_diagnostics_pulls_made = diagnostics_pulls_made.clone();
2668    let closure_diagnostics_pulls_result_ids = diagnostics_pulls_result_ids.clone();
2669    let workspace_diagnostics_pulls_made = Arc::new(AtomicUsize::new(0));
2670    let closure_workspace_diagnostics_pulls_made = workspace_diagnostics_pulls_made.clone();
2671    let closure_workspace_diagnostics_pulls_result_ids =
2672        workspace_diagnostics_pulls_result_ids.clone();
2673    let (workspace_diagnostic_cancel_tx, closure_workspace_diagnostic_cancel_rx) =
2674        smol::channel::bounded::<()>(1);
2675    let (closure_workspace_diagnostic_received_tx, workspace_diagnostic_received_rx) =
2676        smol::channel::bounded::<()>(1);
2677
2678    let capabilities = lsp::ServerCapabilities {
2679        diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
2680            lsp::DiagnosticOptions {
2681                identifier: Some("test-pulls".to_string()),
2682                inter_file_dependencies: true,
2683                workspace_diagnostics: true,
2684                work_done_progress_options: lsp::WorkDoneProgressOptions {
2685                    work_done_progress: None,
2686                },
2687            },
2688        )),
2689        ..lsp::ServerCapabilities::default()
2690    };
2691    client_a.language_registry().add(rust_lang());
2692
2693    let pull_diagnostics_handle = Arc::new(parking_lot::Mutex::new(None));
2694    let workspace_diagnostics_pulls_handle = Arc::new(parking_lot::Mutex::new(None));
2695
2696    let closure_pull_diagnostics_handle = pull_diagnostics_handle.clone();
2697    let closure_workspace_diagnostics_pulls_handle = workspace_diagnostics_pulls_handle.clone();
2698    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
2699        "Rust",
2700        FakeLspAdapter {
2701            capabilities: capabilities.clone(),
2702            initializer: Some(Box::new(move |fake_language_server| {
2703                let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!(
2704                    "workspace/diagnostic/{}/1",
2705                    fake_language_server.server.server_id()
2706                ));
2707                let closure_workspace_diagnostics_pulls_result_ids = closure_workspace_diagnostics_pulls_result_ids.clone();
2708                let diagnostics_pulls_made = closure_diagnostics_pulls_made.clone();
2709                let diagnostics_pulls_result_ids = closure_diagnostics_pulls_result_ids.clone();
2710                let closure_pull_diagnostics_handle = closure_pull_diagnostics_handle.clone();
2711                let closure_workspace_diagnostics_pulls_handle = closure_workspace_diagnostics_pulls_handle.clone();
2712                let closure_workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone();
2713                let closure_workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone();
2714                let pull_diagnostics_handle = fake_language_server
2715                    .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(
2716                        move |params, _| {
2717                            let requests_made = diagnostics_pulls_made.clone();
2718                            let diagnostics_pulls_result_ids =
2719                                diagnostics_pulls_result_ids.clone();
2720                            async move {
2721                                let message = if lsp::Uri::from_file_path(path!("/a/main.rs"))
2722                                    .unwrap()
2723                                    == params.text_document.uri
2724                                {
2725                                    expected_pull_diagnostic_main_message.to_string()
2726                                } else if lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap()
2727                                    == params.text_document.uri
2728                                {
2729                                    expected_pull_diagnostic_lib_message.to_string()
2730                                } else {
2731                                    panic!("Unexpected document: {}", params.text_document.uri)
2732                                };
2733                                {
2734                                    diagnostics_pulls_result_ids
2735                                        .lock()
2736                                        .await
2737                                        .insert(params.previous_result_id);
2738                                }
2739                                let new_requests_count =
2740                                    requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2741                                Ok(lsp::DocumentDiagnosticReportResult::Report(
2742                                    lsp::DocumentDiagnosticReport::Full(
2743                                        lsp::RelatedFullDocumentDiagnosticReport {
2744                                            related_documents: None,
2745                                            full_document_diagnostic_report:
2746                                                lsp::FullDocumentDiagnosticReport {
2747                                                    result_id: Some(format!(
2748                                                        "pull-{new_requests_count}"
2749                                                    )),
2750                                                    items: vec![lsp::Diagnostic {
2751                                                        range: lsp::Range {
2752                                                            start: lsp::Position {
2753                                                                line: 0,
2754                                                                character: 0,
2755                                                            },
2756                                                            end: lsp::Position {
2757                                                                line: 0,
2758                                                                character: 2,
2759                                                            },
2760                                                        },
2761                                                        severity: Some(
2762                                                            lsp::DiagnosticSeverity::ERROR,
2763                                                        ),
2764                                                        message,
2765                                                        ..lsp::Diagnostic::default()
2766                                                    }],
2767                                                },
2768                                        },
2769                                    ),
2770                                ))
2771                            }
2772                        },
2773                    );
2774                let _ = closure_pull_diagnostics_handle.lock().insert(pull_diagnostics_handle);
2775
2776                let closure_workspace_diagnostics_pulls_made = closure_workspace_diagnostics_pulls_made.clone();
2777                let workspace_diagnostics_pulls_handle = fake_language_server.set_request_handler::<lsp::request::WorkspaceDiagnosticRequest, _, _>(
2778                    move |params, _| {
2779                        let workspace_requests_made = closure_workspace_diagnostics_pulls_made.clone();
2780                        let workspace_diagnostics_pulls_result_ids =
2781                            closure_workspace_diagnostics_pulls_result_ids.clone();
2782                        let workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone();
2783                        let workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone();
2784                        let expected_workspace_diagnostic_token = expected_workspace_diagnostic_token.clone();
2785                        async move {
2786                            let workspace_request_count =
2787                                workspace_requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
2788                            {
2789                                workspace_diagnostics_pulls_result_ids
2790                                    .lock()
2791                                    .await
2792                                    .extend(params.previous_result_ids.into_iter().map(|id| id.value));
2793                            }
2794                            if should_stream_workspace_diagnostic && !workspace_diagnostic_cancel_rx.is_closed()
2795                            {
2796                                assert_eq!(
2797                                    params.partial_result_params.partial_result_token,
2798                                    Some(expected_workspace_diagnostic_token)
2799                                );
2800                                workspace_diagnostic_received_tx.send(()).await.unwrap();
2801                                workspace_diagnostic_cancel_rx.recv().await.unwrap();
2802                                workspace_diagnostic_cancel_rx.close();
2803                                // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#partialResults
2804                                // > The final response has to be empty in terms of result values.
2805                                return Ok(lsp::WorkspaceDiagnosticReportResult::Report(
2806                                    lsp::WorkspaceDiagnosticReport { items: Vec::new() },
2807                                ));
2808                            }
2809                            Ok(lsp::WorkspaceDiagnosticReportResult::Report(
2810                                lsp::WorkspaceDiagnosticReport {
2811                                    items: vec![
2812                                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2813                                            lsp::WorkspaceFullDocumentDiagnosticReport {
2814                                                uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2815                                                version: None,
2816                                                full_document_diagnostic_report:
2817                                                    lsp::FullDocumentDiagnosticReport {
2818                                                        result_id: Some(format!(
2819                                                            "workspace_{workspace_request_count}"
2820                                                        )),
2821                                                        items: vec![lsp::Diagnostic {
2822                                                            range: lsp::Range {
2823                                                                start: lsp::Position {
2824                                                                    line: 0,
2825                                                                    character: 1,
2826                                                                },
2827                                                                end: lsp::Position {
2828                                                                    line: 0,
2829                                                                    character: 3,
2830                                                                },
2831                                                            },
2832                                                            severity: Some(lsp::DiagnosticSeverity::WARNING),
2833                                                            message:
2834                                                                expected_workspace_pull_diagnostics_main_message
2835                                                                    .to_string(),
2836                                                            ..lsp::Diagnostic::default()
2837                                                        }],
2838                                                    },
2839                                            },
2840                                        ),
2841                                        lsp::WorkspaceDocumentDiagnosticReport::Full(
2842                                            lsp::WorkspaceFullDocumentDiagnosticReport {
2843                                                uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
2844                                                version: None,
2845                                                full_document_diagnostic_report:
2846                                                    lsp::FullDocumentDiagnosticReport {
2847                                                        result_id: Some(format!(
2848                                                            "workspace_{workspace_request_count}"
2849                                                        )),
2850                                                        items: vec![lsp::Diagnostic {
2851                                                            range: lsp::Range {
2852                                                                start: lsp::Position {
2853                                                                    line: 0,
2854                                                                    character: 1,
2855                                                                },
2856                                                                end: lsp::Position {
2857                                                                    line: 0,
2858                                                                    character: 3,
2859                                                                },
2860                                                            },
2861                                                            severity: Some(lsp::DiagnosticSeverity::WARNING),
2862                                                            message:
2863                                                                expected_workspace_pull_diagnostics_lib_message
2864                                                                    .to_string(),
2865                                                            ..lsp::Diagnostic::default()
2866                                                        }],
2867                                                    },
2868                                            },
2869                                        ),
2870                                    ],
2871                                },
2872                            ))
2873                        }
2874                    });
2875                let _ = closure_workspace_diagnostics_pulls_handle.lock().insert(workspace_diagnostics_pulls_handle);
2876            })),
2877            ..FakeLspAdapter::default()
2878        },
2879    );
2880
2881    client_b.language_registry().add(rust_lang());
2882    client_b.language_registry().register_fake_lsp_adapter(
2883        "Rust",
2884        FakeLspAdapter {
2885            capabilities,
2886            ..FakeLspAdapter::default()
2887        },
2888    );
2889
2890    // Client A opens a project.
2891    client_a
2892        .fs()
2893        .insert_tree(
2894            path!("/a"),
2895            json!({
2896                "main.rs": "fn main() { a }",
2897                "lib.rs": "fn other() {}",
2898            }),
2899        )
2900        .await;
2901    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2902    active_call_a
2903        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2904        .await
2905        .unwrap();
2906    let project_id = active_call_a
2907        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2908        .await
2909        .unwrap();
2910
2911    // Client B joins the project
2912    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2913    active_call_b
2914        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2915        .await
2916        .unwrap();
2917
2918    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
2919    executor.start_waiting();
2920
2921    // The host opens a rust file.
2922    let _buffer_a = project_a
2923        .update(cx_a, |project, cx| {
2924            project.open_local_buffer(path!("/a/main.rs"), cx)
2925        })
2926        .await
2927        .unwrap();
2928    let editor_a_main = workspace_a
2929        .update_in(cx_a, |workspace, window, cx| {
2930            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
2931        })
2932        .await
2933        .unwrap()
2934        .downcast::<Editor>()
2935        .unwrap();
2936
2937    let fake_language_server = fake_language_servers.next().await.unwrap();
2938    let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!(
2939        "workspace/diagnostic-{}-1",
2940        fake_language_server.server.server_id()
2941    ));
2942    cx_a.run_until_parked();
2943    cx_b.run_until_parked();
2944    let mut pull_diagnostics_handle = pull_diagnostics_handle.lock().take().unwrap();
2945    let mut workspace_diagnostics_pulls_handle =
2946        workspace_diagnostics_pulls_handle.lock().take().unwrap();
2947
2948    if should_stream_workspace_diagnostic {
2949        workspace_diagnostic_received_rx.recv().await.unwrap();
2950    } else {
2951        workspace_diagnostics_pulls_handle.next().await.unwrap();
2952    }
2953    assert_eq!(
2954        1,
2955        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2956        "Workspace diagnostics should be pulled initially on a server startup"
2957    );
2958    pull_diagnostics_handle.next().await.unwrap();
2959    assert_eq!(
2960        1,
2961        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
2962        "Host should query pull diagnostics when the editor is opened"
2963    );
2964    executor.run_until_parked();
2965    editor_a_main.update(cx_a, |editor, cx| {
2966        let snapshot = editor.buffer().read(cx).snapshot(cx);
2967        let all_diagnostics = snapshot
2968            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
2969            .collect::<Vec<_>>();
2970        assert_eq!(
2971            all_diagnostics.len(),
2972            1,
2973            "Expected single diagnostic, but got: {all_diagnostics:?}"
2974        );
2975        let diagnostic = &all_diagnostics[0];
2976        let mut expected_messages = vec![expected_pull_diagnostic_main_message];
2977        if !should_stream_workspace_diagnostic {
2978            expected_messages.push(expected_workspace_pull_diagnostics_main_message);
2979        }
2980        assert!(
2981            expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
2982            "Expected {expected_messages:?} on the host, but got: {}",
2983            diagnostic.diagnostic.message
2984        );
2985    });
2986
2987    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2988        lsp::PublishDiagnosticsParams {
2989            uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
2990            diagnostics: vec![lsp::Diagnostic {
2991                range: lsp::Range {
2992                    start: lsp::Position {
2993                        line: 0,
2994                        character: 3,
2995                    },
2996                    end: lsp::Position {
2997                        line: 0,
2998                        character: 4,
2999                    },
3000                },
3001                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
3002                message: expected_push_diagnostic_main_message.to_string(),
3003                ..lsp::Diagnostic::default()
3004            }],
3005            version: None,
3006        },
3007    );
3008    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
3009        lsp::PublishDiagnosticsParams {
3010            uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3011            diagnostics: vec![lsp::Diagnostic {
3012                range: lsp::Range {
3013                    start: lsp::Position {
3014                        line: 0,
3015                        character: 3,
3016                    },
3017                    end: lsp::Position {
3018                        line: 0,
3019                        character: 4,
3020                    },
3021                },
3022                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
3023                message: expected_push_diagnostic_lib_message.to_string(),
3024                ..lsp::Diagnostic::default()
3025            }],
3026            version: None,
3027        },
3028    );
3029
3030    if should_stream_workspace_diagnostic {
3031        fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3032            token: expected_workspace_diagnostic_token.clone(),
3033            value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
3034                lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
3035                    items: vec![
3036                        lsp::WorkspaceDocumentDiagnosticReport::Full(
3037                            lsp::WorkspaceFullDocumentDiagnosticReport {
3038                                uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
3039                                version: None,
3040                                full_document_diagnostic_report:
3041                                    lsp::FullDocumentDiagnosticReport {
3042                                        result_id: Some(format!(
3043                                            "workspace_{}",
3044                                            workspace_diagnostics_pulls_made
3045                                                .fetch_add(1, atomic::Ordering::Release)
3046                                                + 1
3047                                        )),
3048                                        items: vec![lsp::Diagnostic {
3049                                            range: lsp::Range {
3050                                                start: lsp::Position {
3051                                                    line: 0,
3052                                                    character: 1,
3053                                                },
3054                                                end: lsp::Position {
3055                                                    line: 0,
3056                                                    character: 2,
3057                                                },
3058                                            },
3059                                            severity: Some(lsp::DiagnosticSeverity::ERROR),
3060                                            message:
3061                                                expected_workspace_pull_diagnostics_main_message
3062                                                    .to_string(),
3063                                            ..lsp::Diagnostic::default()
3064                                        }],
3065                                    },
3066                            },
3067                        ),
3068                        lsp::WorkspaceDocumentDiagnosticReport::Full(
3069                            lsp::WorkspaceFullDocumentDiagnosticReport {
3070                                uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3071                                version: None,
3072                                full_document_diagnostic_report:
3073                                    lsp::FullDocumentDiagnosticReport {
3074                                        result_id: Some(format!(
3075                                            "workspace_{}",
3076                                            workspace_diagnostics_pulls_made
3077                                                .fetch_add(1, atomic::Ordering::Release)
3078                                                + 1
3079                                        )),
3080                                        items: Vec::new(),
3081                                    },
3082                            },
3083                        ),
3084                    ],
3085                }),
3086            ),
3087        });
3088    };
3089
3090    let mut workspace_diagnostic_start_count =
3091        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
3092
3093    executor.run_until_parked();
3094    editor_a_main.update(cx_a, |editor, cx| {
3095        let snapshot = editor.buffer().read(cx).snapshot(cx);
3096        let all_diagnostics = snapshot
3097            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3098            .collect::<Vec<_>>();
3099        assert_eq!(
3100            all_diagnostics.len(),
3101            2,
3102            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
3103        );
3104        let expected_messages = [
3105            expected_workspace_pull_diagnostics_main_message,
3106            expected_pull_diagnostic_main_message,
3107            expected_push_diagnostic_main_message,
3108        ];
3109        for diagnostic in all_diagnostics {
3110            assert!(
3111                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3112                "Expected push and pull messages on the host: {expected_messages:?}, but got: {}",
3113                diagnostic.diagnostic.message
3114            );
3115        }
3116    });
3117
3118    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3119    let editor_b_main = workspace_b
3120        .update_in(cx_b, |workspace, window, cx| {
3121            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
3122        })
3123        .await
3124        .unwrap()
3125        .downcast::<Editor>()
3126        .unwrap();
3127    cx_b.run_until_parked();
3128
3129    pull_diagnostics_handle.next().await.unwrap();
3130    assert_eq!(
3131        2,
3132        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3133        "Client should query pull diagnostics when its editor is opened"
3134    );
3135    executor.run_until_parked();
3136    assert_eq!(
3137        workspace_diagnostic_start_count,
3138        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3139        "Workspace diagnostics should not be changed as the remote client does not initialize the workspace diagnostics pull"
3140    );
3141    editor_b_main.update(cx_b, |editor, cx| {
3142        let snapshot = editor.buffer().read(cx).snapshot(cx);
3143        let all_diagnostics = snapshot
3144            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3145            .collect::<Vec<_>>();
3146        assert_eq!(
3147            all_diagnostics.len(),
3148            2,
3149            "Expected pull and push diagnostics, but got: {all_diagnostics:?}"
3150        );
3151
3152        // Despite the workspace diagnostics not re-initialized for the remote client, we can still expect its message synced from the host.
3153        let expected_messages = [
3154            expected_workspace_pull_diagnostics_main_message,
3155            expected_pull_diagnostic_main_message,
3156            expected_push_diagnostic_main_message,
3157        ];
3158        for diagnostic in all_diagnostics {
3159            assert!(
3160                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3161                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3162                diagnostic.diagnostic.message
3163            );
3164        }
3165    });
3166
3167    let editor_b_lib = workspace_b
3168        .update_in(cx_b, |workspace, window, cx| {
3169            workspace.open_path((worktree_id, rel_path("lib.rs")), None, true, window, cx)
3170        })
3171        .await
3172        .unwrap()
3173        .downcast::<Editor>()
3174        .unwrap();
3175
3176    pull_diagnostics_handle.next().await.unwrap();
3177    assert_eq!(
3178        3,
3179        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3180        "Client should query pull diagnostics when its another editor is opened"
3181    );
3182    executor.run_until_parked();
3183    assert_eq!(
3184        workspace_diagnostic_start_count,
3185        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3186        "The remote client still did not anything to trigger the workspace diagnostics pull"
3187    );
3188    editor_b_lib.update(cx_b, |editor, cx| {
3189        let snapshot = editor.buffer().read(cx).snapshot(cx);
3190        let all_diagnostics = snapshot
3191            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3192            .collect::<Vec<_>>();
3193        let expected_messages = [
3194            expected_pull_diagnostic_lib_message,
3195            // TODO bug: the pushed diagnostics are not being sent to the client when they open the corresponding buffer.
3196            // expected_push_diagnostic_lib_message,
3197        ];
3198        assert_eq!(
3199            all_diagnostics.len(),
3200            1,
3201            "Expected pull diagnostics, but got: {all_diagnostics:?}"
3202        );
3203        for diagnostic in all_diagnostics {
3204            assert!(
3205                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3206                "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3207                diagnostic.diagnostic.message
3208            );
3209        }
3210    });
3211
3212    if should_stream_workspace_diagnostic {
3213        fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3214            token: expected_workspace_diagnostic_token.clone(),
3215            value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
3216                lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
3217                    items: vec![lsp::WorkspaceDocumentDiagnosticReport::Full(
3218                        lsp::WorkspaceFullDocumentDiagnosticReport {
3219                            uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
3220                            version: None,
3221                            full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
3222                                result_id: Some(format!(
3223                                    "workspace_{}",
3224                                    workspace_diagnostics_pulls_made
3225                                        .fetch_add(1, atomic::Ordering::Release)
3226                                        + 1
3227                                )),
3228                                items: vec![lsp::Diagnostic {
3229                                    range: lsp::Range {
3230                                        start: lsp::Position {
3231                                            line: 0,
3232                                            character: 1,
3233                                        },
3234                                        end: lsp::Position {
3235                                            line: 0,
3236                                            character: 2,
3237                                        },
3238                                    },
3239                                    severity: Some(lsp::DiagnosticSeverity::ERROR),
3240                                    message: expected_workspace_pull_diagnostics_lib_message
3241                                        .to_string(),
3242                                    ..lsp::Diagnostic::default()
3243                                }],
3244                            },
3245                        },
3246                    )],
3247                }),
3248            ),
3249        });
3250        workspace_diagnostic_start_count =
3251            workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
3252        workspace_diagnostic_cancel_tx.send(()).await.unwrap();
3253        workspace_diagnostics_pulls_handle.next().await.unwrap();
3254        executor.run_until_parked();
3255        editor_b_lib.update(cx_b, |editor, cx| {
3256            let snapshot = editor.buffer().read(cx).snapshot(cx);
3257            let all_diagnostics = snapshot
3258                .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3259                .collect::<Vec<_>>();
3260            let expected_messages = [
3261                expected_workspace_pull_diagnostics_lib_message,
3262                // TODO bug: the pushed diagnostics are not being sent to the client when they open the corresponding buffer.
3263                // expected_push_diagnostic_lib_message,
3264            ];
3265            assert_eq!(
3266                all_diagnostics.len(),
3267                1,
3268                "Expected pull diagnostics, but got: {all_diagnostics:?}"
3269            );
3270            for diagnostic in all_diagnostics {
3271                assert!(
3272                    expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3273                    "The client should get both push and pull messages: {expected_messages:?}, but got: {}",
3274                    diagnostic.diagnostic.message
3275                );
3276            }
3277        });
3278    };
3279
3280    {
3281        assert!(
3282            !diagnostics_pulls_result_ids.lock().await.is_empty(),
3283            "Initial diagnostics pulls should report None at least"
3284        );
3285        assert_eq!(
3286            0,
3287            workspace_diagnostics_pulls_result_ids
3288                .lock()
3289                .await
3290                .deref()
3291                .len(),
3292            "After the initial workspace request, opening files should not reuse any result ids"
3293        );
3294    }
3295
3296    editor_b_lib.update_in(cx_b, |editor, window, cx| {
3297        editor.move_to_end(&MoveToEnd, window, cx);
3298        editor.handle_input(":", window, cx);
3299    });
3300    pull_diagnostics_handle.next().await.unwrap();
3301    assert_eq!(
3302        4,
3303        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3304        "Client lib.rs edits should trigger another diagnostics pull for a buffer"
3305    );
3306    workspace_diagnostics_pulls_handle.next().await.unwrap();
3307    assert_eq!(
3308        workspace_diagnostic_start_count + 1,
3309        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3310        "After client lib.rs edits, the workspace diagnostics request should follow"
3311    );
3312    executor.run_until_parked();
3313
3314    editor_b_main.update_in(cx_b, |editor, window, cx| {
3315        editor.move_to_end(&MoveToEnd, window, cx);
3316        editor.handle_input(":", window, cx);
3317    });
3318    pull_diagnostics_handle.next().await.unwrap();
3319    pull_diagnostics_handle.next().await.unwrap();
3320    assert_eq!(
3321        6,
3322        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3323        "Client main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
3324    );
3325    workspace_diagnostics_pulls_handle.next().await.unwrap();
3326    assert_eq!(
3327        workspace_diagnostic_start_count + 2,
3328        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3329        "After client main.rs edits, the workspace diagnostics pull should follow"
3330    );
3331    executor.run_until_parked();
3332
3333    editor_a_main.update_in(cx_a, |editor, window, cx| {
3334        editor.move_to_end(&MoveToEnd, window, cx);
3335        editor.handle_input(":", window, cx);
3336    });
3337    pull_diagnostics_handle.next().await.unwrap();
3338    pull_diagnostics_handle.next().await.unwrap();
3339    assert_eq!(
3340        8,
3341        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3342        "Host main.rs edits should trigger another diagnostics pull by both client and host as they share the buffer"
3343    );
3344    workspace_diagnostics_pulls_handle.next().await.unwrap();
3345    assert_eq!(
3346        workspace_diagnostic_start_count + 3,
3347        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3348        "After host main.rs edits, the workspace diagnostics pull should follow"
3349    );
3350    executor.run_until_parked();
3351    let diagnostic_pulls_result_ids = diagnostics_pulls_result_ids.lock().await.len();
3352    let workspace_pulls_result_ids = workspace_diagnostics_pulls_result_ids.lock().await.len();
3353    {
3354        assert!(
3355            diagnostic_pulls_result_ids > 1,
3356            "Should have sent result ids when pulling diagnostics"
3357        );
3358        assert!(
3359            workspace_pulls_result_ids > 1,
3360            "Should have sent result ids when pulling workspace diagnostics"
3361        );
3362    }
3363
3364    fake_language_server
3365        .request::<lsp::request::WorkspaceDiagnosticRefresh>(())
3366        .await
3367        .into_response()
3368        .expect("workspace diagnostics refresh request failed");
3369    assert_eq!(
3370        8,
3371        diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3372        "No single file pulls should happen after the diagnostics refresh server request"
3373    );
3374    workspace_diagnostics_pulls_handle.next().await.unwrap();
3375    assert_eq!(
3376        workspace_diagnostic_start_count + 4,
3377        workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
3378        "Another workspace diagnostics pull should happen after the diagnostics refresh server request"
3379    );
3380    {
3381        assert!(
3382            diagnostics_pulls_result_ids.lock().await.len() == diagnostic_pulls_result_ids,
3383            "Pulls should not happen hence no extra ids should appear"
3384        );
3385        assert!(
3386            workspace_diagnostics_pulls_result_ids.lock().await.len() > workspace_pulls_result_ids,
3387            "More workspace diagnostics should be pulled"
3388        );
3389    }
3390    editor_b_lib.update(cx_b, |editor, cx| {
3391        let snapshot = editor.buffer().read(cx).snapshot(cx);
3392        let all_diagnostics = snapshot
3393            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3394            .collect::<Vec<_>>();
3395        let expected_messages = [
3396            expected_workspace_pull_diagnostics_lib_message,
3397            expected_pull_diagnostic_lib_message,
3398            expected_push_diagnostic_lib_message,
3399        ];
3400        assert_eq!(all_diagnostics.len(), 1);
3401        for diagnostic in &all_diagnostics {
3402            assert!(
3403                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3404                "Unexpected diagnostics: {all_diagnostics:?}"
3405            );
3406        }
3407    });
3408    editor_b_main.update(cx_b, |editor, cx| {
3409        let snapshot = editor.buffer().read(cx).snapshot(cx);
3410        let all_diagnostics = snapshot
3411            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3412            .collect::<Vec<_>>();
3413        assert_eq!(all_diagnostics.len(), 2);
3414
3415        let expected_messages = [
3416            expected_workspace_pull_diagnostics_main_message,
3417            expected_pull_diagnostic_main_message,
3418            expected_push_diagnostic_main_message,
3419        ];
3420        for diagnostic in &all_diagnostics {
3421            assert!(
3422                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3423                "Unexpected diagnostics: {all_diagnostics:?}"
3424            );
3425        }
3426    });
3427    editor_a_main.update(cx_a, |editor, cx| {
3428        let snapshot = editor.buffer().read(cx).snapshot(cx);
3429        let all_diagnostics = snapshot
3430            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
3431            .collect::<Vec<_>>();
3432        assert_eq!(all_diagnostics.len(), 2);
3433        let expected_messages = [
3434            expected_workspace_pull_diagnostics_main_message,
3435            expected_pull_diagnostic_main_message,
3436            expected_push_diagnostic_main_message,
3437        ];
3438        for diagnostic in &all_diagnostics {
3439            assert!(
3440                expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
3441                "Unexpected diagnostics: {all_diagnostics:?}"
3442            );
3443        }
3444    });
3445}
3446
3447#[gpui::test(iterations = 10)]
3448async fn test_non_streamed_lsp_pull_diagnostics(
3449    cx_a: &mut TestAppContext,
3450    cx_b: &mut TestAppContext,
3451) {
3452    test_lsp_pull_diagnostics(false, cx_a, cx_b).await;
3453}
3454
3455#[gpui::test(iterations = 10)]
3456async fn test_streamed_lsp_pull_diagnostics(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3457    test_lsp_pull_diagnostics(true, cx_a, cx_b).await;
3458}
3459
3460#[gpui::test(iterations = 10)]
3461async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3462    let mut server = TestServer::start(cx_a.executor()).await;
3463    let client_a = server.create_client(cx_a, "user_a").await;
3464    let client_b = server.create_client(cx_b, "user_b").await;
3465    server
3466        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3467        .await;
3468    let active_call_a = cx_a.read(ActiveCall::global);
3469
3470    cx_a.update(editor::init);
3471    cx_b.update(editor::init);
3472    // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
3473    let inline_blame_off_settings = Some(InlineBlameSettings {
3474        enabled: Some(false),
3475        ..Default::default()
3476    });
3477    cx_a.update(|cx| {
3478        SettingsStore::update_global(cx, |store, cx| {
3479            store.update_user_settings(cx, |settings| {
3480                settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
3481            });
3482        });
3483    });
3484    cx_b.update(|cx| {
3485        SettingsStore::update_global(cx, |store, cx| {
3486            store.update_user_settings(cx, |settings| {
3487                settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
3488            });
3489        });
3490    });
3491
3492    client_a
3493        .fs()
3494        .insert_tree(
3495            path!("/my-repo"),
3496            json!({
3497                ".git": {},
3498                "file.txt": "line1\nline2\nline3\nline\n",
3499            }),
3500        )
3501        .await;
3502
3503    let blame = git::blame::Blame {
3504        entries: vec![
3505            blame_entry("1b1b1b", 0..1),
3506            blame_entry("0d0d0d", 1..2),
3507            blame_entry("3a3a3a", 2..3),
3508            blame_entry("4c4c4c", 3..4),
3509        ],
3510        messages: [
3511            ("1b1b1b", "message for idx-0"),
3512            ("0d0d0d", "message for idx-1"),
3513            ("3a3a3a", "message for idx-2"),
3514            ("4c4c4c", "message for idx-3"),
3515        ]
3516        .into_iter()
3517        .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
3518        .collect(),
3519        remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
3520    };
3521    client_a.fs().set_blame_for_repo(
3522        Path::new(path!("/my-repo/.git")),
3523        vec![(repo_path("file.txt"), blame)],
3524    );
3525
3526    let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
3527    let project_id = active_call_a
3528        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3529        .await
3530        .unwrap();
3531
3532    // Create editor_a
3533    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3534    let editor_a = workspace_a
3535        .update_in(cx_a, |workspace, window, cx| {
3536            workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx)
3537        })
3538        .await
3539        .unwrap()
3540        .downcast::<Editor>()
3541        .unwrap();
3542
3543    // Join the project as client B.
3544    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3545    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3546    let editor_b = workspace_b
3547        .update_in(cx_b, |workspace, window, cx| {
3548            workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx)
3549        })
3550        .await
3551        .unwrap()
3552        .downcast::<Editor>()
3553        .unwrap();
3554    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
3555        editor_b
3556            .buffer()
3557            .read(cx)
3558            .as_singleton()
3559            .unwrap()
3560            .read(cx)
3561            .remote_id()
3562    });
3563
3564    // client_b now requests git blame for the open buffer
3565    editor_b.update_in(cx_b, |editor_b, window, cx| {
3566        assert!(editor_b.blame().is_none());
3567        editor_b.toggle_git_blame(&git::Blame {}, window, cx);
3568    });
3569
3570    cx_a.executor().run_until_parked();
3571    cx_b.executor().run_until_parked();
3572
3573    editor_b.update(cx_b, |editor_b, cx| {
3574        let blame = editor_b.blame().expect("editor_b should have blame now");
3575        let entries = blame.update(cx, |blame, cx| {
3576            blame
3577                .blame_for_rows(
3578                    &(0..4)
3579                        .map(|row| RowInfo {
3580                            buffer_row: Some(row),
3581                            buffer_id: Some(buffer_id_b),
3582                            ..Default::default()
3583                        })
3584                        .collect::<Vec<_>>(),
3585                    cx,
3586                )
3587                .collect::<Vec<_>>()
3588        });
3589
3590        assert_eq!(
3591            entries,
3592            vec![
3593                Some((buffer_id_b, blame_entry("1b1b1b", 0..1))),
3594                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3595                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3596                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3597            ]
3598        );
3599
3600        blame.update(cx, |blame, _| {
3601            for (idx, (buffer, entry)) in entries.iter().flatten().enumerate() {
3602                let details = blame.details_for_entry(*buffer, entry).unwrap();
3603                assert_eq!(details.message, format!("message for idx-{}", idx));
3604                assert_eq!(
3605                    details.permalink.unwrap().to_string(),
3606                    format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
3607                );
3608            }
3609        });
3610    });
3611
3612    // editor_b updates the file, which gets sent to client_a, which updates git blame,
3613    // which gets back to client_b.
3614    editor_b.update_in(cx_b, |editor_b, _, cx| {
3615        editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
3616    });
3617
3618    cx_a.executor().run_until_parked();
3619    cx_b.executor().run_until_parked();
3620
3621    editor_b.update(cx_b, |editor_b, cx| {
3622        let blame = editor_b.blame().expect("editor_b should have blame now");
3623        let entries = blame.update(cx, |blame, cx| {
3624            blame
3625                .blame_for_rows(
3626                    &(0..4)
3627                        .map(|row| RowInfo {
3628                            buffer_row: Some(row),
3629                            buffer_id: Some(buffer_id_b),
3630                            ..Default::default()
3631                        })
3632                        .collect::<Vec<_>>(),
3633                    cx,
3634                )
3635                .collect::<Vec<_>>()
3636        });
3637
3638        assert_eq!(
3639            entries,
3640            vec![
3641                None,
3642                Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
3643                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3644                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3645            ]
3646        );
3647    });
3648
3649    // Now editor_a also updates the file
3650    editor_a.update_in(cx_a, |editor_a, _, cx| {
3651        editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
3652    });
3653
3654    cx_a.executor().run_until_parked();
3655    cx_b.executor().run_until_parked();
3656
3657    editor_b.update(cx_b, |editor_b, cx| {
3658        let blame = editor_b.blame().expect("editor_b should have blame now");
3659        let entries = blame.update(cx, |blame, cx| {
3660            blame
3661                .blame_for_rows(
3662                    &(0..4)
3663                        .map(|row| RowInfo {
3664                            buffer_row: Some(row),
3665                            buffer_id: Some(buffer_id_b),
3666                            ..Default::default()
3667                        })
3668                        .collect::<Vec<_>>(),
3669                    cx,
3670                )
3671                .collect::<Vec<_>>()
3672        });
3673
3674        assert_eq!(
3675            entries,
3676            vec![
3677                None,
3678                None,
3679                Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
3680                Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
3681            ]
3682        );
3683    });
3684}
3685
3686#[gpui::test(iterations = 30)]
3687async fn test_collaborating_with_editorconfig(
3688    cx_a: &mut TestAppContext,
3689    cx_b: &mut TestAppContext,
3690) {
3691    let mut server = TestServer::start(cx_a.executor()).await;
3692    let client_a = server.create_client(cx_a, "user_a").await;
3693    let client_b = server.create_client(cx_b, "user_b").await;
3694    server
3695        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3696        .await;
3697    let active_call_a = cx_a.read(ActiveCall::global);
3698
3699    cx_b.update(editor::init);
3700
3701    // Set up a fake language server.
3702    client_a.language_registry().add(rust_lang());
3703    client_a
3704        .fs()
3705        .insert_tree(
3706            path!("/a"),
3707            json!({
3708                "src": {
3709                    "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
3710                    "other_mod": {
3711                        "other.rs": "pub fn foo() -> usize {\n    4\n}",
3712                        ".editorconfig": "",
3713                    },
3714                },
3715                ".editorconfig": "[*]\ntab_width = 2\n",
3716            }),
3717        )
3718        .await;
3719    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
3720    let project_id = active_call_a
3721        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3722        .await
3723        .unwrap();
3724    let main_buffer_a = project_a
3725        .update(cx_a, |p, cx| {
3726            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
3727        })
3728        .await
3729        .unwrap();
3730    let other_buffer_a = project_a
3731        .update(cx_a, |p, cx| {
3732            p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
3733        })
3734        .await
3735        .unwrap();
3736    let cx_a = cx_a.add_empty_window();
3737    let main_editor_a = cx_a.new_window_entity(|window, cx| {
3738        Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
3739    });
3740    let other_editor_a = cx_a.new_window_entity(|window, cx| {
3741        Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
3742    });
3743    let mut main_editor_cx_a = EditorTestContext {
3744        cx: cx_a.clone(),
3745        window: cx_a.window_handle(),
3746        editor: main_editor_a,
3747        assertion_cx: AssertionContextManager::new(),
3748    };
3749    let mut other_editor_cx_a = EditorTestContext {
3750        cx: cx_a.clone(),
3751        window: cx_a.window_handle(),
3752        editor: other_editor_a,
3753        assertion_cx: AssertionContextManager::new(),
3754    };
3755
3756    // Join the project as client B.
3757    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3758    let main_buffer_b = project_b
3759        .update(cx_b, |p, cx| {
3760            p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
3761        })
3762        .await
3763        .unwrap();
3764    let other_buffer_b = project_b
3765        .update(cx_b, |p, cx| {
3766            p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
3767        })
3768        .await
3769        .unwrap();
3770    let cx_b = cx_b.add_empty_window();
3771    let main_editor_b = cx_b.new_window_entity(|window, cx| {
3772        Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
3773    });
3774    let other_editor_b = cx_b.new_window_entity(|window, cx| {
3775        Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
3776    });
3777    let mut main_editor_cx_b = EditorTestContext {
3778        cx: cx_b.clone(),
3779        window: cx_b.window_handle(),
3780        editor: main_editor_b,
3781        assertion_cx: AssertionContextManager::new(),
3782    };
3783    let mut other_editor_cx_b = EditorTestContext {
3784        cx: cx_b.clone(),
3785        window: cx_b.window_handle(),
3786        editor: other_editor_b,
3787        assertion_cx: AssertionContextManager::new(),
3788    };
3789
3790    let initial_main = indoc! {"
3791ˇmod other;
3792fn main() { let foo = other::foo(); }"};
3793    let initial_other = indoc! {"
3794ˇpub fn foo() -> usize {
3795    4
3796}"};
3797
3798    let first_tabbed_main = indoc! {"
3799  ˇmod other;
3800fn main() { let foo = other::foo(); }"};
3801    tab_undo_assert(
3802        &mut main_editor_cx_a,
3803        &mut main_editor_cx_b,
3804        initial_main,
3805        first_tabbed_main,
3806        true,
3807    );
3808    tab_undo_assert(
3809        &mut main_editor_cx_a,
3810        &mut main_editor_cx_b,
3811        initial_main,
3812        first_tabbed_main,
3813        false,
3814    );
3815
3816    let first_tabbed_other = indoc! {"
3817  ˇpub fn foo() -> usize {
3818    4
3819}"};
3820    tab_undo_assert(
3821        &mut other_editor_cx_a,
3822        &mut other_editor_cx_b,
3823        initial_other,
3824        first_tabbed_other,
3825        true,
3826    );
3827    tab_undo_assert(
3828        &mut other_editor_cx_a,
3829        &mut other_editor_cx_b,
3830        initial_other,
3831        first_tabbed_other,
3832        false,
3833    );
3834
3835    client_a
3836        .fs()
3837        .atomic_write(
3838            PathBuf::from(path!("/a/src/.editorconfig")),
3839            "[*]\ntab_width = 3\n".to_owned(),
3840        )
3841        .await
3842        .unwrap();
3843    cx_a.run_until_parked();
3844    cx_b.run_until_parked();
3845
3846    let second_tabbed_main = indoc! {"
3847   ˇmod other;
3848fn main() { let foo = other::foo(); }"};
3849    tab_undo_assert(
3850        &mut main_editor_cx_a,
3851        &mut main_editor_cx_b,
3852        initial_main,
3853        second_tabbed_main,
3854        true,
3855    );
3856    tab_undo_assert(
3857        &mut main_editor_cx_a,
3858        &mut main_editor_cx_b,
3859        initial_main,
3860        second_tabbed_main,
3861        false,
3862    );
3863
3864    let second_tabbed_other = indoc! {"
3865   ˇpub fn foo() -> usize {
3866    4
3867}"};
3868    tab_undo_assert(
3869        &mut other_editor_cx_a,
3870        &mut other_editor_cx_b,
3871        initial_other,
3872        second_tabbed_other,
3873        true,
3874    );
3875    tab_undo_assert(
3876        &mut other_editor_cx_a,
3877        &mut other_editor_cx_b,
3878        initial_other,
3879        second_tabbed_other,
3880        false,
3881    );
3882
3883    let editorconfig_buffer_b = project_b
3884        .update(cx_b, |p, cx| {
3885            p.open_buffer((worktree_id, rel_path("src/other_mod/.editorconfig")), cx)
3886        })
3887        .await
3888        .unwrap();
3889    editorconfig_buffer_b.update(cx_b, |buffer, cx| {
3890        buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
3891    });
3892    project_b
3893        .update(cx_b, |project, cx| {
3894            project.save_buffer(editorconfig_buffer_b.clone(), cx)
3895        })
3896        .await
3897        .unwrap();
3898    cx_a.run_until_parked();
3899    cx_b.run_until_parked();
3900
3901    tab_undo_assert(
3902        &mut main_editor_cx_a,
3903        &mut main_editor_cx_b,
3904        initial_main,
3905        second_tabbed_main,
3906        true,
3907    );
3908    tab_undo_assert(
3909        &mut main_editor_cx_a,
3910        &mut main_editor_cx_b,
3911        initial_main,
3912        second_tabbed_main,
3913        false,
3914    );
3915
3916    let third_tabbed_other = indoc! {"
3917      ˇpub fn foo() -> usize {
3918    4
3919}"};
3920    tab_undo_assert(
3921        &mut other_editor_cx_a,
3922        &mut other_editor_cx_b,
3923        initial_other,
3924        third_tabbed_other,
3925        true,
3926    );
3927
3928    tab_undo_assert(
3929        &mut other_editor_cx_a,
3930        &mut other_editor_cx_b,
3931        initial_other,
3932        third_tabbed_other,
3933        false,
3934    );
3935}
3936
3937#[gpui::test]
3938async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3939    let executor = cx_a.executor();
3940    let mut server = TestServer::start(executor.clone()).await;
3941    let client_a = server.create_client(cx_a, "user_a").await;
3942    let client_b = server.create_client(cx_b, "user_b").await;
3943    server
3944        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3945        .await;
3946    let active_call_a = cx_a.read(ActiveCall::global);
3947    let active_call_b = cx_b.read(ActiveCall::global);
3948    cx_a.update(editor::init);
3949    cx_b.update(editor::init);
3950    client_a
3951        .fs()
3952        .insert_tree(
3953            "/a",
3954            json!({
3955                "test.txt": "one\ntwo\nthree\nfour\nfive",
3956            }),
3957        )
3958        .await;
3959    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3960    let project_path = ProjectPath {
3961        worktree_id,
3962        path: rel_path(&"test.txt").into(),
3963    };
3964    let abs_path = project_a.read_with(cx_a, |project, cx| {
3965        project
3966            .absolute_path(&project_path, cx)
3967            .map(Arc::from)
3968            .unwrap()
3969    });
3970
3971    active_call_a
3972        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
3973        .await
3974        .unwrap();
3975    let project_id = active_call_a
3976        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3977        .await
3978        .unwrap();
3979    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3980    active_call_b
3981        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
3982        .await
3983        .unwrap();
3984    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
3985    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
3986
3987    // Client A opens an editor.
3988    let editor_a = workspace_a
3989        .update_in(cx_a, |workspace, window, cx| {
3990            workspace.open_path(project_path.clone(), None, true, window, cx)
3991        })
3992        .await
3993        .unwrap()
3994        .downcast::<Editor>()
3995        .unwrap();
3996
3997    // Client B opens same editor as A.
3998    let editor_b = workspace_b
3999        .update_in(cx_b, |workspace, window, cx| {
4000            workspace.open_path(project_path.clone(), None, true, window, cx)
4001        })
4002        .await
4003        .unwrap()
4004        .downcast::<Editor>()
4005        .unwrap();
4006
4007    cx_a.run_until_parked();
4008    cx_b.run_until_parked();
4009
4010    // Client A adds breakpoint on line (1)
4011    editor_a.update_in(cx_a, |editor, window, cx| {
4012        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4013    });
4014
4015    cx_a.run_until_parked();
4016    cx_b.run_until_parked();
4017
4018    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4019        editor
4020            .breakpoint_store()
4021            .unwrap()
4022            .read(cx)
4023            .all_source_breakpoints(cx)
4024    });
4025    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4026        editor
4027            .breakpoint_store()
4028            .unwrap()
4029            .read(cx)
4030            .all_source_breakpoints(cx)
4031    });
4032
4033    assert_eq!(1, breakpoints_a.len());
4034    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
4035    assert_eq!(breakpoints_a, breakpoints_b);
4036
4037    // Client B adds breakpoint on line(2)
4038    editor_b.update_in(cx_b, |editor, window, cx| {
4039        editor.move_down(&editor::actions::MoveDown, window, cx);
4040        editor.move_down(&editor::actions::MoveDown, window, cx);
4041        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4042    });
4043
4044    cx_a.run_until_parked();
4045    cx_b.run_until_parked();
4046
4047    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4048        editor
4049            .breakpoint_store()
4050            .unwrap()
4051            .read(cx)
4052            .all_source_breakpoints(cx)
4053    });
4054    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4055        editor
4056            .breakpoint_store()
4057            .unwrap()
4058            .read(cx)
4059            .all_source_breakpoints(cx)
4060    });
4061
4062    assert_eq!(1, breakpoints_a.len());
4063    assert_eq!(breakpoints_a, breakpoints_b);
4064    assert_eq!(2, breakpoints_a.get(&abs_path).unwrap().len());
4065
4066    // Client A removes last added breakpoint from client B
4067    editor_a.update_in(cx_a, |editor, window, cx| {
4068        editor.move_down(&editor::actions::MoveDown, window, cx);
4069        editor.move_down(&editor::actions::MoveDown, window, cx);
4070        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4071    });
4072
4073    cx_a.run_until_parked();
4074    cx_b.run_until_parked();
4075
4076    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4077        editor
4078            .breakpoint_store()
4079            .unwrap()
4080            .read(cx)
4081            .all_source_breakpoints(cx)
4082    });
4083    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4084        editor
4085            .breakpoint_store()
4086            .unwrap()
4087            .read(cx)
4088            .all_source_breakpoints(cx)
4089    });
4090
4091    assert_eq!(1, breakpoints_a.len());
4092    assert_eq!(breakpoints_a, breakpoints_b);
4093    assert_eq!(1, breakpoints_a.get(&abs_path).unwrap().len());
4094
4095    // Client B removes first added breakpoint by client A
4096    editor_b.update_in(cx_b, |editor, window, cx| {
4097        editor.move_up(&editor::actions::MoveUp, window, cx);
4098        editor.move_up(&editor::actions::MoveUp, window, cx);
4099        editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
4100    });
4101
4102    cx_a.run_until_parked();
4103    cx_b.run_until_parked();
4104
4105    let breakpoints_a = editor_a.update(cx_a, |editor, cx| {
4106        editor
4107            .breakpoint_store()
4108            .unwrap()
4109            .read(cx)
4110            .all_source_breakpoints(cx)
4111    });
4112    let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
4113        editor
4114            .breakpoint_store()
4115            .unwrap()
4116            .read(cx)
4117            .all_source_breakpoints(cx)
4118    });
4119
4120    assert_eq!(0, breakpoints_a.len());
4121    assert_eq!(breakpoints_a, breakpoints_b);
4122}
4123
4124#[gpui::test]
4125async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4126    let mut server = TestServer::start(cx_a.executor()).await;
4127    let client_a = server.create_client(cx_a, "user_a").await;
4128    let client_b = server.create_client(cx_b, "user_b").await;
4129    server
4130        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4131        .await;
4132    let active_call_a = cx_a.read(ActiveCall::global);
4133    let active_call_b = cx_b.read(ActiveCall::global);
4134
4135    cx_a.update(editor::init);
4136    cx_b.update(editor::init);
4137
4138    client_a.language_registry().add(rust_lang());
4139    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4140        "Rust",
4141        FakeLspAdapter {
4142            name: "rust-analyzer",
4143            ..FakeLspAdapter::default()
4144        },
4145    );
4146    client_b.language_registry().add(rust_lang());
4147    client_b.language_registry().register_fake_lsp_adapter(
4148        "Rust",
4149        FakeLspAdapter {
4150            name: "rust-analyzer",
4151            ..FakeLspAdapter::default()
4152        },
4153    );
4154
4155    client_a
4156        .fs()
4157        .insert_tree(
4158            path!("/a"),
4159            json!({
4160                "main.rs": "fn main() {}",
4161            }),
4162        )
4163        .await;
4164    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4165    active_call_a
4166        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
4167        .await
4168        .unwrap();
4169    let project_id = active_call_a
4170        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4171        .await
4172        .unwrap();
4173
4174    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4175    active_call_b
4176        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
4177        .await
4178        .unwrap();
4179
4180    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4181    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4182
4183    let editor_a = workspace_a
4184        .update_in(cx_a, |workspace, window, cx| {
4185            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
4186        })
4187        .await
4188        .unwrap()
4189        .downcast::<Editor>()
4190        .unwrap();
4191
4192    let editor_b = workspace_b
4193        .update_in(cx_b, |workspace, window, cx| {
4194            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
4195        })
4196        .await
4197        .unwrap()
4198        .downcast::<Editor>()
4199        .unwrap();
4200
4201    let fake_language_server = fake_language_servers.next().await.unwrap();
4202
4203    // host
4204    let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4205        |params, _| async move {
4206            assert_eq!(
4207                params.text_document.uri,
4208                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4209            );
4210            assert_eq!(params.position, lsp::Position::new(0, 0));
4211            Ok(Some(ExpandedMacro {
4212                name: "test_macro_name".to_string(),
4213                expansion: "test_macro_expansion on the host".to_string(),
4214            }))
4215        },
4216    );
4217
4218    editor_a.update_in(cx_a, |editor, window, cx| {
4219        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4220    });
4221    expand_request_a.next().await.unwrap();
4222    cx_a.run_until_parked();
4223
4224    workspace_a.update(cx_a, |workspace, cx| {
4225        workspace.active_pane().update(cx, |pane, cx| {
4226            assert_eq!(
4227                pane.items_len(),
4228                2,
4229                "Should have added a macro expansion to the host's pane"
4230            );
4231            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4232            new_editor.update(cx, |editor, cx| {
4233                assert_eq!(editor.text(cx), "test_macro_expansion on the host");
4234            });
4235        })
4236    });
4237
4238    // client
4239    let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
4240        |params, _| async move {
4241            assert_eq!(
4242                params.text_document.uri,
4243                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
4244            );
4245            assert_eq!(
4246                params.position,
4247                lsp::Position::new(0, 12),
4248                "editor_b has selected the entire text and should query for a different position"
4249            );
4250            Ok(Some(ExpandedMacro {
4251                name: "test_macro_name".to_string(),
4252                expansion: "test_macro_expansion on the client".to_string(),
4253            }))
4254        },
4255    );
4256
4257    editor_b.update_in(cx_b, |editor, window, cx| {
4258        editor.select_all(&SelectAll, window, cx);
4259        expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
4260    });
4261    expand_request_b.next().await.unwrap();
4262    cx_b.run_until_parked();
4263
4264    workspace_b.update(cx_b, |workspace, cx| {
4265        workspace.active_pane().update(cx, |pane, cx| {
4266            assert_eq!(
4267                pane.items_len(),
4268                2,
4269                "Should have added a macro expansion to the client's pane"
4270            );
4271            let new_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
4272            new_editor.update(cx, |editor, cx| {
4273                assert_eq!(editor.text(cx), "test_macro_expansion on the client");
4274            });
4275        })
4276    });
4277}
4278
4279#[gpui::test]
4280async fn test_copy_file_name_without_extension(
4281    cx_a: &mut TestAppContext,
4282    cx_b: &mut TestAppContext,
4283) {
4284    let mut server = TestServer::start(cx_a.executor()).await;
4285    let client_a = server.create_client(cx_a, "user_a").await;
4286    let client_b = server.create_client(cx_b, "user_b").await;
4287    server
4288        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4289        .await;
4290
4291    cx_b.update(editor::init);
4292
4293    client_a
4294        .fs()
4295        .insert_tree(
4296            path!("/root"),
4297            json!({
4298                "src": {
4299                    "main.rs": indoc! {"
4300                        fn main() {
4301                            println!(\"Hello, world!\");
4302                        }
4303                    "},
4304                }
4305            }),
4306        )
4307        .await;
4308
4309    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
4310    let active_call_a = cx_a.read(ActiveCall::global);
4311    let project_id = active_call_a
4312        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4313        .await
4314        .unwrap();
4315
4316    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4317
4318    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4319    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4320
4321    let editor_a = workspace_a
4322        .update_in(cx_a, |workspace, window, cx| {
4323            workspace.open_path(
4324                (worktree_id, rel_path("src/main.rs")),
4325                None,
4326                true,
4327                window,
4328                cx,
4329            )
4330        })
4331        .await
4332        .unwrap()
4333        .downcast::<Editor>()
4334        .unwrap();
4335
4336    let editor_b = workspace_b
4337        .update_in(cx_b, |workspace, window, cx| {
4338            workspace.open_path(
4339                (worktree_id, rel_path("src/main.rs")),
4340                None,
4341                true,
4342                window,
4343                cx,
4344            )
4345        })
4346        .await
4347        .unwrap()
4348        .downcast::<Editor>()
4349        .unwrap();
4350
4351    cx_a.run_until_parked();
4352    cx_b.run_until_parked();
4353
4354    editor_a.update_in(cx_a, |editor, window, cx| {
4355        editor.copy_file_name_without_extension(&CopyFileNameWithoutExtension, window, cx);
4356    });
4357
4358    assert_eq!(
4359        cx_a.read_from_clipboard().and_then(|item| item.text()),
4360        Some("main".to_string())
4361    );
4362
4363    editor_b.update_in(cx_b, |editor, window, cx| {
4364        editor.copy_file_name_without_extension(&CopyFileNameWithoutExtension, window, cx);
4365    });
4366
4367    assert_eq!(
4368        cx_b.read_from_clipboard().and_then(|item| item.text()),
4369        Some("main".to_string())
4370    );
4371}
4372
4373#[gpui::test]
4374async fn test_copy_file_name(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4375    let mut server = TestServer::start(cx_a.executor()).await;
4376    let client_a = server.create_client(cx_a, "user_a").await;
4377    let client_b = server.create_client(cx_b, "user_b").await;
4378    server
4379        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4380        .await;
4381
4382    cx_b.update(editor::init);
4383
4384    client_a
4385        .fs()
4386        .insert_tree(
4387            path!("/root"),
4388            json!({
4389                "src": {
4390                    "main.rs": indoc! {"
4391                        fn main() {
4392                            println!(\"Hello, world!\");
4393                        }
4394                    "},
4395                }
4396            }),
4397        )
4398        .await;
4399
4400    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
4401    let active_call_a = cx_a.read(ActiveCall::global);
4402    let project_id = active_call_a
4403        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4404        .await
4405        .unwrap();
4406
4407    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4408
4409    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4410    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4411
4412    let editor_a = workspace_a
4413        .update_in(cx_a, |workspace, window, cx| {
4414            workspace.open_path(
4415                (worktree_id, rel_path("src/main.rs")),
4416                None,
4417                true,
4418                window,
4419                cx,
4420            )
4421        })
4422        .await
4423        .unwrap()
4424        .downcast::<Editor>()
4425        .unwrap();
4426
4427    let editor_b = workspace_b
4428        .update_in(cx_b, |workspace, window, cx| {
4429            workspace.open_path(
4430                (worktree_id, rel_path("src/main.rs")),
4431                None,
4432                true,
4433                window,
4434                cx,
4435            )
4436        })
4437        .await
4438        .unwrap()
4439        .downcast::<Editor>()
4440        .unwrap();
4441
4442    cx_a.run_until_parked();
4443    cx_b.run_until_parked();
4444
4445    editor_a.update_in(cx_a, |editor, window, cx| {
4446        editor.copy_file_name(&CopyFileName, window, cx);
4447    });
4448
4449    assert_eq!(
4450        cx_a.read_from_clipboard().and_then(|item| item.text()),
4451        Some("main.rs".to_string())
4452    );
4453
4454    editor_b.update_in(cx_b, |editor, window, cx| {
4455        editor.copy_file_name(&CopyFileName, window, cx);
4456    });
4457
4458    assert_eq!(
4459        cx_b.read_from_clipboard().and_then(|item| item.text()),
4460        Some("main.rs".to_string())
4461    );
4462}
4463
4464#[gpui::test]
4465async fn test_copy_file_location(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4466    let mut server = TestServer::start(cx_a.executor()).await;
4467    let client_a = server.create_client(cx_a, "user_a").await;
4468    let client_b = server.create_client(cx_b, "user_b").await;
4469    server
4470        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4471        .await;
4472
4473    cx_b.update(editor::init);
4474
4475    client_a
4476        .fs()
4477        .insert_tree(
4478            path!("/root"),
4479            json!({
4480                "src": {
4481                    "main.rs": indoc! {"
4482                        fn main() {
4483                            println!(\"Hello, world!\");
4484                        }
4485                    "},
4486                }
4487            }),
4488        )
4489        .await;
4490
4491    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
4492    let active_call_a = cx_a.read(ActiveCall::global);
4493    let project_id = active_call_a
4494        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4495        .await
4496        .unwrap();
4497
4498    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4499
4500    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
4501    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
4502
4503    let editor_a = workspace_a
4504        .update_in(cx_a, |workspace, window, cx| {
4505            workspace.open_path(
4506                (worktree_id, rel_path("src/main.rs")),
4507                None,
4508                true,
4509                window,
4510                cx,
4511            )
4512        })
4513        .await
4514        .unwrap()
4515        .downcast::<Editor>()
4516        .unwrap();
4517
4518    let editor_b = workspace_b
4519        .update_in(cx_b, |workspace, window, cx| {
4520            workspace.open_path(
4521                (worktree_id, rel_path("src/main.rs")),
4522                None,
4523                true,
4524                window,
4525                cx,
4526            )
4527        })
4528        .await
4529        .unwrap()
4530        .downcast::<Editor>()
4531        .unwrap();
4532
4533    cx_a.run_until_parked();
4534    cx_b.run_until_parked();
4535
4536    editor_a.update_in(cx_a, |editor, window, cx| {
4537        editor.change_selections(Default::default(), window, cx, |s| {
4538            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(16)]);
4539        });
4540        editor.copy_file_location(&CopyFileLocation, window, cx);
4541    });
4542
4543    assert_eq!(
4544        cx_a.read_from_clipboard().and_then(|item| item.text()),
4545        Some(format!("{}:2", path!("src/main.rs")))
4546    );
4547
4548    editor_b.update_in(cx_b, |editor, window, cx| {
4549        editor.change_selections(Default::default(), window, cx, |s| {
4550            s.select_ranges([MultiBufferOffset(16)..MultiBufferOffset(16)]);
4551        });
4552        editor.copy_file_location(&CopyFileLocation, window, cx);
4553    });
4554
4555    assert_eq!(
4556        cx_b.read_from_clipboard().and_then(|item| item.text()),
4557        Some(format!("{}:2", path!("src/main.rs")))
4558    );
4559}
4560
4561#[track_caller]
4562fn tab_undo_assert(
4563    cx_a: &mut EditorTestContext,
4564    cx_b: &mut EditorTestContext,
4565    expected_initial: &str,
4566    expected_tabbed: &str,
4567    a_tabs: bool,
4568) {
4569    cx_a.assert_editor_state(expected_initial);
4570    cx_b.assert_editor_state(expected_initial);
4571
4572    if a_tabs {
4573        cx_a.update_editor(|editor, window, cx| {
4574            editor.tab(&editor::actions::Tab, window, cx);
4575        });
4576    } else {
4577        cx_b.update_editor(|editor, window, cx| {
4578            editor.tab(&editor::actions::Tab, window, cx);
4579        });
4580    }
4581
4582    cx_a.run_until_parked();
4583    cx_b.run_until_parked();
4584
4585    cx_a.assert_editor_state(expected_tabbed);
4586    cx_b.assert_editor_state(expected_tabbed);
4587
4588    if a_tabs {
4589        cx_a.update_editor(|editor, window, cx| {
4590            editor.undo(&editor::actions::Undo, window, cx);
4591        });
4592    } else {
4593        cx_b.update_editor(|editor, window, cx| {
4594            editor.undo(&editor::actions::Undo, window, cx);
4595        });
4596    }
4597    cx_a.run_until_parked();
4598    cx_b.run_until_parked();
4599    cx_a.assert_editor_state(expected_initial);
4600    cx_b.assert_editor_state(expected_initial);
4601}
4602
4603fn extract_hint_labels(editor: &Editor, cx: &mut App) -> Vec<String> {
4604    let lsp_store = editor.project().unwrap().read(cx).lsp_store();
4605
4606    let mut all_cached_labels = Vec::new();
4607    let mut all_fetched_hints = Vec::new();
4608    for buffer in editor.buffer().read(cx).all_buffers() {
4609        lsp_store.update(cx, |lsp_store, cx| {
4610            let hints = &lsp_store.latest_lsp_data(&buffer, cx).inlay_hints();
4611            all_cached_labels.extend(hints.all_cached_hints().into_iter().map(|hint| {
4612                let mut label = hint.text().to_string();
4613                if hint.padding_left {
4614                    label.insert(0, ' ');
4615                }
4616                if hint.padding_right {
4617                    label.push_str(" ");
4618                }
4619                label
4620            }));
4621            all_fetched_hints.extend(hints.all_fetched_hints());
4622        });
4623    }
4624
4625    assert!(
4626        all_fetched_hints.is_empty(),
4627        "Did not expect background hints fetch tasks, but got {} of them",
4628        all_fetched_hints.len()
4629    );
4630
4631    all_cached_labels
4632}
4633
4634#[track_caller]
4635fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
4636    editor
4637        .all_inlays(cx)
4638        .into_iter()
4639        .filter_map(|inlay| inlay.get_color())
4640        .map(Rgba::from)
4641        .collect()
4642}
4643
4644fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
4645    git::blame::BlameEntry {
4646        sha: sha.parse().unwrap(),
4647        range,
4648        ..Default::default()
4649    }
4650}