integration_tests.rs

   1use crate::{
   2    db::{tests::TestDb, UserId},
   3    rpc::{Executor, Server, Store},
   4    AppState,
   5};
   6use ::rpc::Peer;
   7use anyhow::anyhow;
   8use client::{
   9    self, proto, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection,
  10    Credentials, EstablishConnectionError, UserStore, RECEIVE_TIMEOUT,
  11};
  12use collections::{BTreeMap, HashMap, HashSet};
  13use editor::{
  14    self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Input, Redo, Rename,
  15    ToOffset, ToggleCodeActions, Undo,
  16};
  17use futures::{channel::mpsc, Future, StreamExt as _};
  18use gpui::{
  19    executor::{self, Deterministic},
  20    geometry::vector::vec2f,
  21    ModelHandle, Task, TestAppContext, ViewHandle,
  22};
  23use language::{
  24    range_to_lsp, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
  25    LanguageConfig, LanguageRegistry, OffsetRangeExt, Point, Rope,
  26};
  27use lsp::{self, FakeLanguageServer};
  28use parking_lot::Mutex;
  29use project::{
  30    fs::{FakeFs, Fs as _},
  31    search::SearchQuery,
  32    worktree::WorktreeHandle,
  33    DiagnosticSummary, Project, ProjectPath, ProjectStore, WorktreeId,
  34};
  35use rand::prelude::*;
  36use rpc::PeerId;
  37use serde_json::json;
  38use settings::Settings;
  39use sqlx::types::time::OffsetDateTime;
  40use std::{
  41    cell::RefCell,
  42    env,
  43    ops::Deref,
  44    path::{Path, PathBuf},
  45    rc::Rc,
  46    sync::{
  47        atomic::{AtomicBool, Ordering::SeqCst},
  48        Arc,
  49    },
  50    time::Duration,
  51};
  52use theme::ThemeRegistry;
  53use tokio::sync::RwLockReadGuard;
  54use workspace::{Item, SplitDirection, ToggleFollow, Workspace};
  55
  56#[ctor::ctor]
  57fn init_logger() {
  58    if std::env::var("RUST_LOG").is_ok() {
  59        env_logger::init();
  60    }
  61}
  62
  63#[gpui::test(iterations = 10)]
  64async fn test_share_project(
  65    deterministic: Arc<Deterministic>,
  66    cx_a: &mut TestAppContext,
  67    cx_b: &mut TestAppContext,
  68    cx_b2: &mut TestAppContext,
  69) {
  70    cx_a.foreground().forbid_parking();
  71    let (window_b, _) = cx_b.add_window(|_| EmptyView);
  72    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
  73    let client_a = server.create_client(cx_a, "user_a").await;
  74    let client_b = server.create_client(cx_b, "user_b").await;
  75    server
  76        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
  77        .await;
  78
  79    let fs = FakeFs::new(cx_a.background());
  80    fs.insert_tree(
  81        "/a",
  82        json!({
  83            ".gitignore": "ignored-dir",
  84            "a.txt": "a-contents",
  85            "b.txt": "b-contents",
  86            "ignored-dir": {
  87                "c.txt": "",
  88                "d.txt": "",
  89            }
  90        }),
  91    )
  92    .await;
  93
  94    let (project_a, worktree_id) = client_a.build_local_project(fs, "/a", cx_a).await;
  95    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
  96
  97    // Join that project as client B
  98    let client_b_peer_id = client_b.peer_id;
  99    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 100    let replica_id_b = project_b.read_with(cx_b, |project, _| {
 101        assert_eq!(
 102            project
 103                .collaborators()
 104                .get(&client_a.peer_id)
 105                .unwrap()
 106                .user
 107                .github_login,
 108            "user_a"
 109        );
 110        project.replica_id()
 111    });
 112
 113    deterministic.run_until_parked();
 114    project_a.read_with(cx_a, |project, _| {
 115        let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
 116        assert_eq!(client_b_collaborator.replica_id, replica_id_b);
 117        assert_eq!(client_b_collaborator.user.github_login, "user_b");
 118    });
 119    project_b.read_with(cx_b, |project, cx| {
 120        let worktree = project.worktrees(cx).next().unwrap().read(cx);
 121        assert_eq!(
 122            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
 123            [
 124                Path::new(".gitignore"),
 125                Path::new("a.txt"),
 126                Path::new("b.txt"),
 127                Path::new("ignored-dir"),
 128                Path::new("ignored-dir/c.txt"),
 129                Path::new("ignored-dir/d.txt"),
 130            ]
 131        );
 132    });
 133
 134    // Open the same file as client B and client A.
 135    let buffer_b = project_b
 136        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
 137        .await
 138        .unwrap();
 139    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
 140    project_a.read_with(cx_a, |project, cx| {
 141        assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
 142    });
 143    let buffer_a = project_a
 144        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
 145        .await
 146        .unwrap();
 147
 148    let editor_b = cx_b.add_view(window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
 149
 150    // TODO
 151    // // Create a selection set as client B and see that selection set as client A.
 152    // buffer_a
 153    //     .condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 1)
 154    //     .await;
 155
 156    // Edit the buffer as client B and see that edit as client A.
 157    editor_b.update(cx_b, |editor, cx| {
 158        editor.handle_input(&Input("ok, ".into()), cx)
 159    });
 160    buffer_a
 161        .condition(&cx_a, |buffer, _| buffer.text() == "ok, b-contents")
 162        .await;
 163
 164    // TODO
 165    // // Remove the selection set as client B, see those selections disappear as client A.
 166    cx_b.update(move |_| drop(editor_b));
 167    // buffer_a
 168    //     .condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 0)
 169    //     .await;
 170
 171    // Client B can join again on a different window because they are already a participant.
 172    let client_b2 = server.create_client(cx_b2, "user_b").await;
 173    let project_b2 = Project::remote(
 174        project_id,
 175        client_b2.client.clone(),
 176        client_b2.user_store.clone(),
 177        client_b2.project_store.clone(),
 178        client_b2.language_registry.clone(),
 179        FakeFs::new(cx_b2.background()),
 180        cx_b2.to_async(),
 181    )
 182    .await
 183    .unwrap();
 184    deterministic.run_until_parked();
 185    project_a.read_with(cx_a, |project, _| {
 186        assert_eq!(project.collaborators().len(), 2);
 187    });
 188    project_b.read_with(cx_b, |project, _| {
 189        assert_eq!(project.collaborators().len(), 2);
 190    });
 191    project_b2.read_with(cx_b2, |project, _| {
 192        assert_eq!(project.collaborators().len(), 2);
 193    });
 194
 195    // Dropping client B's first project removes only that from client A's collaborators.
 196    cx_b.update(move |_| drop(project_b));
 197    deterministic.run_until_parked();
 198    project_a.read_with(cx_a, |project, _| {
 199        assert_eq!(project.collaborators().len(), 1);
 200    });
 201    project_b2.read_with(cx_b2, |project, _| {
 202        assert_eq!(project.collaborators().len(), 1);
 203    });
 204}
 205
 206#[gpui::test(iterations = 10)]
 207async fn test_unshare_project(
 208    deterministic: Arc<Deterministic>,
 209    cx_a: &mut TestAppContext,
 210    cx_b: &mut TestAppContext,
 211) {
 212    cx_a.foreground().forbid_parking();
 213    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 214    let client_a = server.create_client(cx_a, "user_a").await;
 215    let client_b = server.create_client(cx_b, "user_b").await;
 216    server
 217        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 218        .await;
 219
 220    let fs = FakeFs::new(cx_a.background());
 221    fs.insert_tree(
 222        "/a",
 223        json!({
 224            "a.txt": "a-contents",
 225            "b.txt": "b-contents",
 226        }),
 227    )
 228    .await;
 229
 230    let (project_a, worktree_id) = client_a.build_local_project(fs, "/a", cx_a).await;
 231    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
 232    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 233    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 234
 235    project_b
 236        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 237        .await
 238        .unwrap();
 239
 240    // When client B leaves the project, it gets automatically unshared.
 241    cx_b.update(|_| drop(project_b));
 242    deterministic.run_until_parked();
 243    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
 244
 245    // When client B joins again, the project gets re-shared.
 246    let project_b2 = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 247    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 248    project_b2
 249        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 250        .await
 251        .unwrap();
 252
 253    // When client A (the host) leaves, the project gets unshared and guests are notified.
 254    cx_a.update(|_| drop(project_a));
 255    deterministic.run_until_parked();
 256    project_b2.read_with(cx_b, |project, _| {
 257        assert!(project.is_read_only());
 258        assert!(project.collaborators().is_empty());
 259    });
 260}
 261
 262#[gpui::test(iterations = 10)]
 263async fn test_host_disconnect(
 264    deterministic: Arc<Deterministic>,
 265    cx_a: &mut TestAppContext,
 266    cx_b: &mut TestAppContext,
 267    cx_c: &mut TestAppContext,
 268) {
 269    cx_a.foreground().forbid_parking();
 270    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 271    let client_a = server.create_client(cx_a, "user_a").await;
 272    let client_b = server.create_client(cx_b, "user_b").await;
 273    let client_c = server.create_client(cx_c, "user_c").await;
 274    server
 275        .make_contacts(vec![
 276            (&client_a, cx_a),
 277            (&client_b, cx_b),
 278            (&client_c, cx_c),
 279        ])
 280        .await;
 281
 282    let fs = FakeFs::new(cx_a.background());
 283    fs.insert_tree(
 284        "/a",
 285        json!({
 286            "a.txt": "a-contents",
 287            "b.txt": "b-contents",
 288        }),
 289    )
 290    .await;
 291
 292    let (project_a, worktree_id) = client_a.build_local_project(fs, "/a", cx_a).await;
 293    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
 294    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
 295
 296    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 297    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 298
 299    project_b
 300        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 301        .await
 302        .unwrap();
 303
 304    // Request to join that project as client C
 305    let project_c = cx_c.spawn(|cx| {
 306        Project::remote(
 307            project_id,
 308            client_c.client.clone(),
 309            client_c.user_store.clone(),
 310            client_c.project_store.clone(),
 311            client_c.language_registry.clone(),
 312            FakeFs::new(cx.background()),
 313            cx,
 314        )
 315    });
 316    deterministic.run_until_parked();
 317
 318    // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
 319    server.disconnect_client(client_a.current_user_id(cx_a));
 320    cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
 321    project_a
 322        .condition(cx_a, |project, _| project.collaborators().is_empty())
 323        .await;
 324    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 325    project_b
 326        .condition(cx_b, |project, _| project.is_read_only())
 327        .await;
 328    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
 329    cx_b.update(|_| {
 330        drop(project_b);
 331    });
 332    assert!(matches!(
 333        project_c.await.unwrap_err(),
 334        project::JoinProjectError::HostWentOffline
 335    ));
 336
 337    // Ensure guests can still join.
 338    let project_b2 = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 339    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 340    project_b2
 341        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 342        .await
 343        .unwrap();
 344}
 345
 346#[gpui::test(iterations = 10)]
 347async fn test_decline_join_request(
 348    deterministic: Arc<Deterministic>,
 349    cx_a: &mut TestAppContext,
 350    cx_b: &mut TestAppContext,
 351) {
 352    cx_a.foreground().forbid_parking();
 353    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 354    let client_a = server.create_client(cx_a, "user_a").await;
 355    let client_b = server.create_client(cx_b, "user_b").await;
 356    server
 357        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 358        .await;
 359
 360    let fs = FakeFs::new(cx_a.background());
 361    fs.insert_tree("/a", json!({})).await;
 362
 363    let (project_a, _) = client_a.build_local_project(fs, "/a", cx_a).await;
 364    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
 365
 366    // Request to join that project as client B
 367    let project_b = cx_b.spawn(|cx| {
 368        Project::remote(
 369            project_id,
 370            client_b.client.clone(),
 371            client_b.user_store.clone(),
 372            client_b.project_store.clone(),
 373            client_b.language_registry.clone(),
 374            FakeFs::new(cx.background()),
 375            cx,
 376        )
 377    });
 378    deterministic.run_until_parked();
 379    project_a.update(cx_a, |project, cx| {
 380        project.respond_to_join_request(client_b.user_id().unwrap(), false, cx)
 381    });
 382    assert!(matches!(
 383        project_b.await.unwrap_err(),
 384        project::JoinProjectError::HostDeclined
 385    ));
 386
 387    // Request to join the project again as client B
 388    let project_b = cx_b.spawn(|cx| {
 389        Project::remote(
 390            project_id,
 391            client_b.client.clone(),
 392            client_b.user_store.clone(),
 393            client_b.project_store.clone(),
 394            client_b.language_registry.clone(),
 395            FakeFs::new(cx.background()),
 396            cx,
 397        )
 398    });
 399
 400    // Close the project on the host
 401    deterministic.run_until_parked();
 402    cx_a.update(|_| drop(project_a));
 403    deterministic.run_until_parked();
 404    assert!(matches!(
 405        project_b.await.unwrap_err(),
 406        project::JoinProjectError::HostClosedProject
 407    ));
 408}
 409
 410#[gpui::test(iterations = 10)]
 411async fn test_cancel_join_request(
 412    deterministic: Arc<Deterministic>,
 413    cx_a: &mut TestAppContext,
 414    cx_b: &mut TestAppContext,
 415) {
 416    cx_a.foreground().forbid_parking();
 417    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 418    let client_a = server.create_client(cx_a, "user_a").await;
 419    let client_b = server.create_client(cx_b, "user_b").await;
 420    server
 421        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 422        .await;
 423
 424    let fs = FakeFs::new(cx_a.background());
 425    fs.insert_tree("/a", json!({})).await;
 426
 427    let (project_a, _) = client_a.build_local_project(fs, "/a", cx_a).await;
 428    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
 429
 430    let user_b = client_a
 431        .user_store
 432        .update(cx_a, |store, cx| {
 433            store.fetch_user(client_b.user_id().unwrap(), cx)
 434        })
 435        .await
 436        .unwrap();
 437
 438    let project_a_events = Rc::new(RefCell::new(Vec::new()));
 439    project_a.update(cx_a, {
 440        let project_a_events = project_a_events.clone();
 441        move |_, cx| {
 442            cx.subscribe(&cx.handle(), move |_, _, event, _| {
 443                project_a_events.borrow_mut().push(event.clone());
 444            })
 445            .detach();
 446        }
 447    });
 448
 449    // Request to join that project as client B
 450    let project_b = cx_b.spawn(|cx| {
 451        Project::remote(
 452            project_id,
 453            client_b.client.clone(),
 454            client_b.user_store.clone(),
 455            client_b.project_store.clone(),
 456            client_b.language_registry.clone().clone(),
 457            FakeFs::new(cx.background()),
 458            cx,
 459        )
 460    });
 461    deterministic.run_until_parked();
 462    assert_eq!(
 463        &*project_a_events.borrow(),
 464        &[project::Event::ContactRequestedJoin(user_b.clone())]
 465    );
 466    project_a_events.borrow_mut().clear();
 467
 468    // Cancel the join request by leaving the project
 469    client_b
 470        .client
 471        .send(proto::LeaveProject { project_id })
 472        .unwrap();
 473    drop(project_b);
 474
 475    deterministic.run_until_parked();
 476    assert_eq!(
 477        &*project_a_events.borrow(),
 478        &[project::Event::ContactCancelledJoinRequest(user_b.clone())]
 479    );
 480}
 481
 482#[gpui::test(iterations = 3)]
 483async fn test_private_projects(
 484    deterministic: Arc<Deterministic>,
 485    cx_a: &mut TestAppContext,
 486    cx_b: &mut TestAppContext,
 487) {
 488    cx_a.foreground().forbid_parking();
 489    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 490    let client_a = server.create_client(cx_a, "user_a").await;
 491    let client_b = server.create_client(cx_b, "user_b").await;
 492    server
 493        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 494        .await;
 495
 496    let user_a = UserId::from_proto(client_a.user_id().unwrap());
 497
 498    let fs = FakeFs::new(cx_a.background());
 499    fs.insert_tree("/a", json!({})).await;
 500
 501    // Create a private project
 502    let project_a = cx_a.update(|cx| {
 503        Project::local(
 504            false,
 505            client_a.client.clone(),
 506            client_a.user_store.clone(),
 507            client_a.project_store.clone(),
 508            client_a.language_registry.clone(),
 509            fs.clone(),
 510            cx,
 511        )
 512    });
 513
 514    // Private projects are not registered with the server.
 515    deterministic.run_until_parked();
 516    assert!(project_a.read_with(cx_a, |project, _| project.remote_id().is_none()));
 517    assert!(client_b
 518        .user_store
 519        .read_with(cx_b, |store, _| { store.contacts()[0].projects.is_empty() }));
 520
 521    // The project is registered when it is made public.
 522    project_a.update(cx_a, |project, cx| project.set_public(true, cx));
 523    deterministic.run_until_parked();
 524    assert!(project_a.read_with(cx_a, |project, _| project.remote_id().is_some()));
 525    assert!(!client_b
 526        .user_store
 527        .read_with(cx_b, |store, _| { store.contacts()[0].projects.is_empty() }));
 528
 529    // The project is registered again when it loses and regains connection.
 530    server.disconnect_client(user_a);
 531    server.forbid_connections();
 532    cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
 533    // deterministic.run_until_parked();
 534    assert!(project_a.read_with(cx_a, |project, _| project.remote_id().is_none()));
 535    assert!(client_b
 536        .user_store
 537        .read_with(cx_b, |store, _| { store.contacts()[0].projects.is_empty() }));
 538    server.allow_connections();
 539    cx_b.foreground().advance_clock(Duration::from_secs(10));
 540    assert!(project_a.read_with(cx_a, |project, _| project.remote_id().is_some()));
 541    assert!(!client_b
 542        .user_store
 543        .read_with(cx_b, |store, _| { store.contacts()[0].projects.is_empty() }));
 544}
 545
 546#[gpui::test(iterations = 10)]
 547async fn test_propagate_saves_and_fs_changes(
 548    cx_a: &mut TestAppContext,
 549    cx_b: &mut TestAppContext,
 550    cx_c: &mut TestAppContext,
 551) {
 552    cx_a.foreground().forbid_parking();
 553    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 554    let client_a = server.create_client(cx_a, "user_a").await;
 555    let client_b = server.create_client(cx_b, "user_b").await;
 556    let client_c = server.create_client(cx_c, "user_c").await;
 557    server
 558        .make_contacts(vec![
 559            (&client_a, cx_a),
 560            (&client_b, cx_b),
 561            (&client_c, cx_c),
 562        ])
 563        .await;
 564
 565    let fs = FakeFs::new(cx_a.background());
 566    fs.insert_tree(
 567        "/a",
 568        json!({
 569            "file1": "",
 570            "file2": ""
 571        }),
 572    )
 573    .await;
 574
 575    let (project_a, worktree_id) = client_a.build_local_project(fs.clone(), "/a", cx_a).await;
 576    let worktree_a = project_a.read_with(cx_a, |p, cx| p.worktrees(cx).next().unwrap());
 577
 578    // Join that worktree as clients B and C.
 579    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 580    let project_c = client_c.build_remote_project(&project_a, cx_a, cx_c).await;
 581    let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
 582    let worktree_c = project_c.read_with(cx_c, |p, cx| p.worktrees(cx).next().unwrap());
 583
 584    // Open and edit a buffer as both guests B and C.
 585    let buffer_b = project_b
 586        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
 587        .await
 588        .unwrap();
 589    let buffer_c = project_c
 590        .update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
 591        .await
 592        .unwrap();
 593    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], cx));
 594    buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], cx));
 595
 596    // Open and edit that buffer as the host.
 597    let buffer_a = project_a
 598        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
 599        .await
 600        .unwrap();
 601
 602    buffer_a
 603        .condition(cx_a, |buf, _| buf.text() == "i-am-c, i-am-b, ")
 604        .await;
 605    buffer_a.update(cx_a, |buf, cx| {
 606        buf.edit([(buf.len()..buf.len(), "i-am-a")], cx)
 607    });
 608
 609    // Wait for edits to propagate
 610    buffer_a
 611        .condition(cx_a, |buf, _| buf.text() == "i-am-c, i-am-b, i-am-a")
 612        .await;
 613    buffer_b
 614        .condition(cx_b, |buf, _| buf.text() == "i-am-c, i-am-b, i-am-a")
 615        .await;
 616    buffer_c
 617        .condition(cx_c, |buf, _| buf.text() == "i-am-c, i-am-b, i-am-a")
 618        .await;
 619
 620    // Edit the buffer as the host and concurrently save as guest B.
 621    let save_b = buffer_b.update(cx_b, |buf, cx| buf.save(cx));
 622    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], cx));
 623    save_b.await.unwrap();
 624    assert_eq!(
 625        fs.load("/a/file1".as_ref()).await.unwrap(),
 626        "hi-a, i-am-c, i-am-b, i-am-a"
 627    );
 628    buffer_a.read_with(cx_a, |buf, _| assert!(!buf.is_dirty()));
 629    buffer_b.read_with(cx_b, |buf, _| assert!(!buf.is_dirty()));
 630    buffer_c.condition(cx_c, |buf, _| !buf.is_dirty()).await;
 631
 632    worktree_a.flush_fs_events(cx_a).await;
 633
 634    // Make changes on host's file system, see those changes on guest worktrees.
 635    fs.rename(
 636        "/a/file1".as_ref(),
 637        "/a/file1-renamed".as_ref(),
 638        Default::default(),
 639    )
 640    .await
 641    .unwrap();
 642
 643    fs.rename("/a/file2".as_ref(), "/a/file3".as_ref(), Default::default())
 644        .await
 645        .unwrap();
 646    fs.insert_file(Path::new("/a/file4"), "4".into()).await;
 647
 648    worktree_a
 649        .condition(&cx_a, |tree, _| {
 650            tree.paths()
 651                .map(|p| p.to_string_lossy())
 652                .collect::<Vec<_>>()
 653                == ["file1-renamed", "file3", "file4"]
 654        })
 655        .await;
 656    worktree_b
 657        .condition(&cx_b, |tree, _| {
 658            tree.paths()
 659                .map(|p| p.to_string_lossy())
 660                .collect::<Vec<_>>()
 661                == ["file1-renamed", "file3", "file4"]
 662        })
 663        .await;
 664    worktree_c
 665        .condition(&cx_c, |tree, _| {
 666            tree.paths()
 667                .map(|p| p.to_string_lossy())
 668                .collect::<Vec<_>>()
 669                == ["file1-renamed", "file3", "file4"]
 670        })
 671        .await;
 672
 673    // Ensure buffer files are updated as well.
 674    buffer_a
 675        .condition(&cx_a, |buf, _| {
 676            buf.file().unwrap().path().to_str() == Some("file1-renamed")
 677        })
 678        .await;
 679    buffer_b
 680        .condition(&cx_b, |buf, _| {
 681            buf.file().unwrap().path().to_str() == Some("file1-renamed")
 682        })
 683        .await;
 684    buffer_c
 685        .condition(&cx_c, |buf, _| {
 686            buf.file().unwrap().path().to_str() == Some("file1-renamed")
 687        })
 688        .await;
 689}
 690
 691#[gpui::test(iterations = 10)]
 692async fn test_fs_operations(
 693    executor: Arc<Deterministic>,
 694    cx_a: &mut TestAppContext,
 695    cx_b: &mut TestAppContext,
 696) {
 697    executor.forbid_parking();
 698    let fs = FakeFs::new(cx_a.background());
 699
 700    // Connect to a server as 2 clients.
 701    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 702    let client_a = server.create_client(cx_a, "user_a").await;
 703    let client_b = server.create_client(cx_b, "user_b").await;
 704    server
 705        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 706        .await;
 707
 708    // Share a project as client A
 709    fs.insert_tree(
 710        "/dir",
 711        json!({
 712            "a.txt": "a-contents",
 713            "b.txt": "b-contents",
 714        }),
 715    )
 716    .await;
 717
 718    let (project_a, worktree_id) = client_a.build_local_project(fs, "/dir", cx_a).await;
 719    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 720
 721    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
 722    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
 723
 724    let entry = project_b
 725        .update(cx_b, |project, cx| {
 726            project
 727                .create_entry((worktree_id, "c.txt"), false, cx)
 728                .unwrap()
 729        })
 730        .await
 731        .unwrap();
 732    worktree_a.read_with(cx_a, |worktree, _| {
 733        assert_eq!(
 734            worktree
 735                .paths()
 736                .map(|p| p.to_string_lossy())
 737                .collect::<Vec<_>>(),
 738            ["a.txt", "b.txt", "c.txt"]
 739        );
 740    });
 741    worktree_b.read_with(cx_b, |worktree, _| {
 742        assert_eq!(
 743            worktree
 744                .paths()
 745                .map(|p| p.to_string_lossy())
 746                .collect::<Vec<_>>(),
 747            ["a.txt", "b.txt", "c.txt"]
 748        );
 749    });
 750
 751    project_b
 752        .update(cx_b, |project, cx| {
 753            project.rename_entry(entry.id, Path::new("d.txt"), cx)
 754        })
 755        .unwrap()
 756        .await
 757        .unwrap();
 758    worktree_a.read_with(cx_a, |worktree, _| {
 759        assert_eq!(
 760            worktree
 761                .paths()
 762                .map(|p| p.to_string_lossy())
 763                .collect::<Vec<_>>(),
 764            ["a.txt", "b.txt", "d.txt"]
 765        );
 766    });
 767    worktree_b.read_with(cx_b, |worktree, _| {
 768        assert_eq!(
 769            worktree
 770                .paths()
 771                .map(|p| p.to_string_lossy())
 772                .collect::<Vec<_>>(),
 773            ["a.txt", "b.txt", "d.txt"]
 774        );
 775    });
 776
 777    let dir_entry = project_b
 778        .update(cx_b, |project, cx| {
 779            project
 780                .create_entry((worktree_id, "DIR"), true, cx)
 781                .unwrap()
 782        })
 783        .await
 784        .unwrap();
 785    worktree_a.read_with(cx_a, |worktree, _| {
 786        assert_eq!(
 787            worktree
 788                .paths()
 789                .map(|p| p.to_string_lossy())
 790                .collect::<Vec<_>>(),
 791            ["DIR", "a.txt", "b.txt", "d.txt"]
 792        );
 793    });
 794    worktree_b.read_with(cx_b, |worktree, _| {
 795        assert_eq!(
 796            worktree
 797                .paths()
 798                .map(|p| p.to_string_lossy())
 799                .collect::<Vec<_>>(),
 800            ["DIR", "a.txt", "b.txt", "d.txt"]
 801        );
 802    });
 803
 804    project_b
 805        .update(cx_b, |project, cx| {
 806            project
 807                .create_entry((worktree_id, "DIR/e.txt"), false, cx)
 808                .unwrap()
 809        })
 810        .await
 811        .unwrap();
 812    project_b
 813        .update(cx_b, |project, cx| {
 814            project
 815                .create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
 816                .unwrap()
 817        })
 818        .await
 819        .unwrap();
 820    project_b
 821        .update(cx_b, |project, cx| {
 822            project
 823                .create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
 824                .unwrap()
 825        })
 826        .await
 827        .unwrap();
 828    worktree_a.read_with(cx_a, |worktree, _| {
 829        assert_eq!(
 830            worktree
 831                .paths()
 832                .map(|p| p.to_string_lossy())
 833                .collect::<Vec<_>>(),
 834            [
 835                "DIR",
 836                "DIR/SUBDIR",
 837                "DIR/SUBDIR/f.txt",
 838                "DIR/e.txt",
 839                "a.txt",
 840                "b.txt",
 841                "d.txt"
 842            ]
 843        );
 844    });
 845    worktree_b.read_with(cx_b, |worktree, _| {
 846        assert_eq!(
 847            worktree
 848                .paths()
 849                .map(|p| p.to_string_lossy())
 850                .collect::<Vec<_>>(),
 851            [
 852                "DIR",
 853                "DIR/SUBDIR",
 854                "DIR/SUBDIR/f.txt",
 855                "DIR/e.txt",
 856                "a.txt",
 857                "b.txt",
 858                "d.txt"
 859            ]
 860        );
 861    });
 862
 863    project_b
 864        .update(cx_b, |project, cx| {
 865            project
 866                .copy_entry(entry.id, Path::new("f.txt"), cx)
 867                .unwrap()
 868        })
 869        .await
 870        .unwrap();
 871    worktree_a.read_with(cx_a, |worktree, _| {
 872        assert_eq!(
 873            worktree
 874                .paths()
 875                .map(|p| p.to_string_lossy())
 876                .collect::<Vec<_>>(),
 877            [
 878                "DIR",
 879                "DIR/SUBDIR",
 880                "DIR/SUBDIR/f.txt",
 881                "DIR/e.txt",
 882                "a.txt",
 883                "b.txt",
 884                "d.txt",
 885                "f.txt"
 886            ]
 887        );
 888    });
 889    worktree_b.read_with(cx_b, |worktree, _| {
 890        assert_eq!(
 891            worktree
 892                .paths()
 893                .map(|p| p.to_string_lossy())
 894                .collect::<Vec<_>>(),
 895            [
 896                "DIR",
 897                "DIR/SUBDIR",
 898                "DIR/SUBDIR/f.txt",
 899                "DIR/e.txt",
 900                "a.txt",
 901                "b.txt",
 902                "d.txt",
 903                "f.txt"
 904            ]
 905        );
 906    });
 907
 908    project_b
 909        .update(cx_b, |project, cx| {
 910            project.delete_entry(dir_entry.id, cx).unwrap()
 911        })
 912        .await
 913        .unwrap();
 914    worktree_a.read_with(cx_a, |worktree, _| {
 915        assert_eq!(
 916            worktree
 917                .paths()
 918                .map(|p| p.to_string_lossy())
 919                .collect::<Vec<_>>(),
 920            ["a.txt", "b.txt", "d.txt", "f.txt"]
 921        );
 922    });
 923    worktree_b.read_with(cx_b, |worktree, _| {
 924        assert_eq!(
 925            worktree
 926                .paths()
 927                .map(|p| p.to_string_lossy())
 928                .collect::<Vec<_>>(),
 929            ["a.txt", "b.txt", "d.txt", "f.txt"]
 930        );
 931    });
 932
 933    project_b
 934        .update(cx_b, |project, cx| {
 935            project.delete_entry(entry.id, cx).unwrap()
 936        })
 937        .await
 938        .unwrap();
 939    worktree_a.read_with(cx_a, |worktree, _| {
 940        assert_eq!(
 941            worktree
 942                .paths()
 943                .map(|p| p.to_string_lossy())
 944                .collect::<Vec<_>>(),
 945            ["a.txt", "b.txt", "f.txt"]
 946        );
 947    });
 948    worktree_b.read_with(cx_b, |worktree, _| {
 949        assert_eq!(
 950            worktree
 951                .paths()
 952                .map(|p| p.to_string_lossy())
 953                .collect::<Vec<_>>(),
 954            ["a.txt", "b.txt", "f.txt"]
 955        );
 956    });
 957}
 958
 959#[gpui::test(iterations = 10)]
 960async fn test_buffer_conflict_after_save(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 961    cx_a.foreground().forbid_parking();
 962    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 963    let client_a = server.create_client(cx_a, "user_a").await;
 964    let client_b = server.create_client(cx_b, "user_b").await;
 965    server
 966        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 967        .await;
 968
 969    let fs = FakeFs::new(cx_a.background());
 970    fs.insert_tree(
 971        "/dir",
 972        json!({
 973            "a.txt": "a-contents",
 974        }),
 975    )
 976    .await;
 977
 978    let (project_a, worktree_id) = client_a.build_local_project(fs, "/dir", cx_a).await;
 979    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 980
 981    // Open a buffer as client B
 982    let buffer_b = project_b
 983        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 984        .await
 985        .unwrap();
 986
 987    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], cx));
 988    buffer_b.read_with(cx_b, |buf, _| {
 989        assert!(buf.is_dirty());
 990        assert!(!buf.has_conflict());
 991    });
 992
 993    buffer_b.update(cx_b, |buf, cx| buf.save(cx)).await.unwrap();
 994    buffer_b
 995        .condition(&cx_b, |buffer_b, _| !buffer_b.is_dirty())
 996        .await;
 997    buffer_b.read_with(cx_b, |buf, _| {
 998        assert!(!buf.has_conflict());
 999    });
1000
1001    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], cx));
1002    buffer_b.read_with(cx_b, |buf, _| {
1003        assert!(buf.is_dirty());
1004        assert!(!buf.has_conflict());
1005    });
1006}
1007
1008#[gpui::test(iterations = 10)]
1009async fn test_buffer_reloading(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1010    cx_a.foreground().forbid_parking();
1011    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1012    let client_a = server.create_client(cx_a, "user_a").await;
1013    let client_b = server.create_client(cx_b, "user_b").await;
1014    server
1015        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1016        .await;
1017
1018    let fs = FakeFs::new(cx_a.background());
1019    fs.insert_tree(
1020        "/dir",
1021        json!({
1022            "a.txt": "a-contents",
1023        }),
1024    )
1025    .await;
1026
1027    let (project_a, worktree_id) = client_a.build_local_project(fs.clone(), "/dir", cx_a).await;
1028    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1029
1030    // Open a buffer as client B
1031    let buffer_b = project_b
1032        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1033        .await
1034        .unwrap();
1035    buffer_b.read_with(cx_b, |buf, _| {
1036        assert!(!buf.is_dirty());
1037        assert!(!buf.has_conflict());
1038    });
1039
1040    fs.save(Path::new("/dir/a.txt"), &"new contents".into())
1041        .await
1042        .unwrap();
1043    buffer_b
1044        .condition(&cx_b, |buf, _| {
1045            buf.text() == "new contents" && !buf.is_dirty()
1046        })
1047        .await;
1048    buffer_b.read_with(cx_b, |buf, _| {
1049        assert!(!buf.has_conflict());
1050    });
1051}
1052
1053#[gpui::test(iterations = 10)]
1054async fn test_editing_while_guest_opens_buffer(
1055    cx_a: &mut TestAppContext,
1056    cx_b: &mut TestAppContext,
1057) {
1058    cx_a.foreground().forbid_parking();
1059    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1060    let client_a = server.create_client(cx_a, "user_a").await;
1061    let client_b = server.create_client(cx_b, "user_b").await;
1062    server
1063        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1064        .await;
1065
1066    let fs = FakeFs::new(cx_a.background());
1067    fs.insert_tree(
1068        "/dir",
1069        json!({
1070            "a.txt": "a-contents",
1071        }),
1072    )
1073    .await;
1074
1075    let (project_a, worktree_id) = client_a.build_local_project(fs, "/dir", cx_a).await;
1076    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1077
1078    // Open a buffer as client A
1079    let buffer_a = project_a
1080        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1081        .await
1082        .unwrap();
1083
1084    // Start opening the same buffer as client B
1085    let buffer_b = cx_b
1086        .background()
1087        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)));
1088
1089    // Edit the buffer as client A while client B is still opening it.
1090    cx_b.background().simulate_random_delay().await;
1091    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], cx));
1092    cx_b.background().simulate_random_delay().await;
1093    buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], cx));
1094
1095    let text = buffer_a.read_with(cx_a, |buf, _| buf.text());
1096    let buffer_b = buffer_b.await.unwrap();
1097    buffer_b.condition(&cx_b, |buf, _| buf.text() == text).await;
1098}
1099
1100#[gpui::test(iterations = 10)]
1101async fn test_leaving_worktree_while_opening_buffer(
1102    cx_a: &mut TestAppContext,
1103    cx_b: &mut TestAppContext,
1104) {
1105    cx_a.foreground().forbid_parking();
1106    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1107    let client_a = server.create_client(cx_a, "user_a").await;
1108    let client_b = server.create_client(cx_b, "user_b").await;
1109    server
1110        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1111        .await;
1112
1113    let fs = FakeFs::new(cx_a.background());
1114    fs.insert_tree(
1115        "/dir",
1116        json!({
1117            "a.txt": "a-contents",
1118        }),
1119    )
1120    .await;
1121
1122    let (project_a, worktree_id) = client_a.build_local_project(fs, "/dir", cx_a).await;
1123    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1124
1125    // See that a guest has joined as client A.
1126    project_a
1127        .condition(&cx_a, |p, _| p.collaborators().len() == 1)
1128        .await;
1129
1130    // Begin opening a buffer as client B, but leave the project before the open completes.
1131    let buffer_b = cx_b
1132        .background()
1133        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)));
1134    cx_b.update(|_| drop(project_b));
1135    drop(buffer_b);
1136
1137    // See that the guest has left.
1138    project_a
1139        .condition(&cx_a, |p, _| p.collaborators().len() == 0)
1140        .await;
1141}
1142
1143#[gpui::test(iterations = 10)]
1144async fn test_leaving_project(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1145    cx_a.foreground().forbid_parking();
1146    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1147    let client_a = server.create_client(cx_a, "user_a").await;
1148    let client_b = server.create_client(cx_b, "user_b").await;
1149    server
1150        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1151        .await;
1152
1153    let fs = FakeFs::new(cx_a.background());
1154    fs.insert_tree(
1155        "/a",
1156        json!({
1157            "a.txt": "a-contents",
1158            "b.txt": "b-contents",
1159        }),
1160    )
1161    .await;
1162
1163    let (project_a, _) = client_a.build_local_project(fs, "/a", cx_a).await;
1164    let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1165
1166    // Client A sees that a guest has joined.
1167    project_a
1168        .condition(cx_a, |p, _| p.collaborators().len() == 1)
1169        .await;
1170
1171    // Drop client B's connection and ensure client A observes client B leaving the project.
1172    client_b.disconnect(&cx_b.to_async()).unwrap();
1173    project_a
1174        .condition(cx_a, |p, _| p.collaborators().len() == 0)
1175        .await;
1176
1177    // Rejoin the project as client B
1178    let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1179
1180    // Client A sees that a guest has re-joined.
1181    project_a
1182        .condition(cx_a, |p, _| p.collaborators().len() == 1)
1183        .await;
1184
1185    // Simulate connection loss for client B and ensure client A observes client B leaving the project.
1186    client_b.wait_for_current_user(cx_b).await;
1187    server.disconnect_client(client_b.current_user_id(cx_b));
1188    cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
1189    project_a
1190        .condition(cx_a, |p, _| p.collaborators().len() == 0)
1191        .await;
1192}
1193
1194#[gpui::test(iterations = 10)]
1195async fn test_collaborating_with_diagnostics(
1196    deterministic: Arc<Deterministic>,
1197    cx_a: &mut TestAppContext,
1198    cx_b: &mut TestAppContext,
1199    cx_c: &mut TestAppContext,
1200) {
1201    deterministic.forbid_parking();
1202    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1203    let client_a = server.create_client(cx_a, "user_a").await;
1204    let client_b = server.create_client(cx_b, "user_b").await;
1205    let client_c = server.create_client(cx_c, "user_c").await;
1206    server
1207        .make_contacts(vec![
1208            (&client_a, cx_a),
1209            (&client_b, cx_b),
1210            (&client_c, cx_c),
1211        ])
1212        .await;
1213
1214    // Set up a fake language server.
1215    let mut language = Language::new(
1216        LanguageConfig {
1217            name: "Rust".into(),
1218            path_suffixes: vec!["rs".to_string()],
1219            ..Default::default()
1220        },
1221        Some(tree_sitter_rust::language()),
1222    );
1223    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
1224    client_a.language_registry.add(Arc::new(language));
1225
1226    // Connect to a server as 2 clients.
1227
1228    // Share a project as client A
1229    let fs = FakeFs::new(cx_a.background());
1230    fs.insert_tree(
1231        "/a",
1232        json!({
1233            "a.rs": "let one = two",
1234            "other.rs": "",
1235        }),
1236    )
1237    .await;
1238    let (project_a, worktree_id) = client_a.build_local_project(fs, "/a", cx_a).await;
1239    let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
1240
1241    // Cause the language server to start.
1242    let _buffer = cx_a
1243        .background()
1244        .spawn(project_a.update(cx_a, |project, cx| {
1245            project.open_buffer(
1246                ProjectPath {
1247                    worktree_id,
1248                    path: Path::new("other.rs").into(),
1249                },
1250                cx,
1251            )
1252        }))
1253        .await
1254        .unwrap();
1255
1256    // Join the worktree as client B.
1257    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1258
1259    // Simulate a language server reporting errors for a file.
1260    let mut fake_language_server = fake_language_servers.next().await.unwrap();
1261    fake_language_server
1262        .receive_notification::<lsp::notification::DidOpenTextDocument>()
1263        .await;
1264    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
1265        lsp::PublishDiagnosticsParams {
1266            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
1267            version: None,
1268            diagnostics: vec![lsp::Diagnostic {
1269                severity: Some(lsp::DiagnosticSeverity::ERROR),
1270                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
1271                message: "message 1".to_string(),
1272                ..Default::default()
1273            }],
1274        },
1275    );
1276
1277    // Wait for server to see the diagnostics update.
1278    deterministic.run_until_parked();
1279    {
1280        let store = server.store.read().await;
1281        let project = store.project(project_id).unwrap();
1282        let worktree = project.worktrees.get(&worktree_id.to_proto()).unwrap();
1283        assert!(!worktree.diagnostic_summaries.is_empty());
1284    }
1285
1286    // Ensure client B observes the new diagnostics.
1287    project_b.read_with(cx_b, |project, cx| {
1288        assert_eq!(
1289            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
1290            &[(
1291                ProjectPath {
1292                    worktree_id,
1293                    path: Arc::from(Path::new("a.rs")),
1294                },
1295                DiagnosticSummary {
1296                    error_count: 1,
1297                    warning_count: 0,
1298                    ..Default::default()
1299                },
1300            )]
1301        )
1302    });
1303
1304    // Join project as client C and observe the diagnostics.
1305    let project_c = client_c.build_remote_project(&project_a, cx_a, cx_c).await;
1306    project_c.read_with(cx_c, |project, cx| {
1307        assert_eq!(
1308            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
1309            &[(
1310                ProjectPath {
1311                    worktree_id,
1312                    path: Arc::from(Path::new("a.rs")),
1313                },
1314                DiagnosticSummary {
1315                    error_count: 1,
1316                    warning_count: 0,
1317                    ..Default::default()
1318                },
1319            )]
1320        )
1321    });
1322
1323    // Simulate a language server reporting more errors for a file.
1324    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
1325        lsp::PublishDiagnosticsParams {
1326            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
1327            version: None,
1328            diagnostics: vec![
1329                lsp::Diagnostic {
1330                    severity: Some(lsp::DiagnosticSeverity::ERROR),
1331                    range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
1332                    message: "message 1".to_string(),
1333                    ..Default::default()
1334                },
1335                lsp::Diagnostic {
1336                    severity: Some(lsp::DiagnosticSeverity::WARNING),
1337                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 13)),
1338                    message: "message 2".to_string(),
1339                    ..Default::default()
1340                },
1341            ],
1342        },
1343    );
1344
1345    // Clients B and C get the updated summaries
1346    deterministic.run_until_parked();
1347    project_b.read_with(cx_b, |project, cx| {
1348        assert_eq!(
1349            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
1350            [(
1351                ProjectPath {
1352                    worktree_id,
1353                    path: Arc::from(Path::new("a.rs")),
1354                },
1355                DiagnosticSummary {
1356                    error_count: 1,
1357                    warning_count: 1,
1358                    ..Default::default()
1359                },
1360            )]
1361        );
1362    });
1363    project_c.read_with(cx_c, |project, cx| {
1364        assert_eq!(
1365            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
1366            [(
1367                ProjectPath {
1368                    worktree_id,
1369                    path: Arc::from(Path::new("a.rs")),
1370                },
1371                DiagnosticSummary {
1372                    error_count: 1,
1373                    warning_count: 1,
1374                    ..Default::default()
1375                },
1376            )]
1377        );
1378    });
1379
1380    // Open the file with the errors on client B. They should be present.
1381    let buffer_b = cx_b
1382        .background()
1383        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
1384        .await
1385        .unwrap();
1386
1387    buffer_b.read_with(cx_b, |buffer, _| {
1388        assert_eq!(
1389            buffer
1390                .snapshot()
1391                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
1392                .map(|entry| entry)
1393                .collect::<Vec<_>>(),
1394            &[
1395                DiagnosticEntry {
1396                    range: Point::new(0, 4)..Point::new(0, 7),
1397                    diagnostic: Diagnostic {
1398                        group_id: 1,
1399                        message: "message 1".to_string(),
1400                        severity: lsp::DiagnosticSeverity::ERROR,
1401                        is_primary: true,
1402                        ..Default::default()
1403                    }
1404                },
1405                DiagnosticEntry {
1406                    range: Point::new(0, 10)..Point::new(0, 13),
1407                    diagnostic: Diagnostic {
1408                        group_id: 2,
1409                        severity: lsp::DiagnosticSeverity::WARNING,
1410                        message: "message 2".to_string(),
1411                        is_primary: true,
1412                        ..Default::default()
1413                    }
1414                }
1415            ]
1416        );
1417    });
1418
1419    // Simulate a language server reporting no errors for a file.
1420    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
1421        lsp::PublishDiagnosticsParams {
1422            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
1423            version: None,
1424            diagnostics: vec![],
1425        },
1426    );
1427    deterministic.run_until_parked();
1428    project_a.read_with(cx_a, |project, cx| {
1429        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
1430    });
1431    project_b.read_with(cx_b, |project, cx| {
1432        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
1433    });
1434    project_c.read_with(cx_c, |project, cx| {
1435        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
1436    });
1437}
1438
1439#[gpui::test(iterations = 10)]
1440async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1441    cx_a.foreground().forbid_parking();
1442    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1443    let client_a = server.create_client(cx_a, "user_a").await;
1444    let client_b = server.create_client(cx_b, "user_b").await;
1445    server
1446        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1447        .await;
1448
1449    // Set up a fake language server.
1450    let mut language = Language::new(
1451        LanguageConfig {
1452            name: "Rust".into(),
1453            path_suffixes: vec!["rs".to_string()],
1454            ..Default::default()
1455        },
1456        Some(tree_sitter_rust::language()),
1457    );
1458    let mut fake_language_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
1459        capabilities: lsp::ServerCapabilities {
1460            completion_provider: Some(lsp::CompletionOptions {
1461                trigger_characters: Some(vec![".".to_string()]),
1462                ..Default::default()
1463            }),
1464            ..Default::default()
1465        },
1466        ..Default::default()
1467    });
1468    client_a.language_registry.add(Arc::new(language));
1469
1470    let fs = FakeFs::new(cx_a.background());
1471    fs.insert_tree(
1472        "/a",
1473        json!({
1474            "main.rs": "fn main() { a }",
1475            "other.rs": "",
1476        }),
1477    )
1478    .await;
1479
1480    let (project_a, worktree_id) = client_a.build_local_project(fs, "/a", cx_a).await;
1481    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1482
1483    // Open a file in an editor as the guest.
1484    let buffer_b = project_b
1485        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1486        .await
1487        .unwrap();
1488    let (window_b, _) = cx_b.add_window(|_| EmptyView);
1489    let editor_b = cx_b.add_view(window_b, |cx| {
1490        Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
1491    });
1492
1493    let fake_language_server = fake_language_servers.next().await.unwrap();
1494    buffer_b
1495        .condition(&cx_b, |buffer, _| !buffer.completion_triggers().is_empty())
1496        .await;
1497
1498    // Type a completion trigger character as the guest.
1499    editor_b.update(cx_b, |editor, cx| {
1500        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1501        editor.handle_input(&Input(".".into()), cx);
1502        cx.focus(&editor_b);
1503    });
1504
1505    // Receive a completion request as the host's language server.
1506    // Return some completions from the host's language server.
1507    cx_a.foreground().start_waiting();
1508    fake_language_server
1509        .handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
1510            assert_eq!(
1511                params.text_document_position.text_document.uri,
1512                lsp::Url::from_file_path("/a/main.rs").unwrap(),
1513            );
1514            assert_eq!(
1515                params.text_document_position.position,
1516                lsp::Position::new(0, 14),
1517            );
1518
1519            Ok(Some(lsp::CompletionResponse::Array(vec![
1520                lsp::CompletionItem {
1521                    label: "first_method(…)".into(),
1522                    detail: Some("fn(&mut self, B) -> C".into()),
1523                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1524                        new_text: "first_method($1)".to_string(),
1525                        range: lsp::Range::new(
1526                            lsp::Position::new(0, 14),
1527                            lsp::Position::new(0, 14),
1528                        ),
1529                    })),
1530                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1531                    ..Default::default()
1532                },
1533                lsp::CompletionItem {
1534                    label: "second_method(…)".into(),
1535                    detail: Some("fn(&mut self, C) -> D<E>".into()),
1536                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1537                        new_text: "second_method()".to_string(),
1538                        range: lsp::Range::new(
1539                            lsp::Position::new(0, 14),
1540                            lsp::Position::new(0, 14),
1541                        ),
1542                    })),
1543                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1544                    ..Default::default()
1545                },
1546            ])))
1547        })
1548        .next()
1549        .await
1550        .unwrap();
1551    cx_a.foreground().finish_waiting();
1552
1553    // Open the buffer on the host.
1554    let buffer_a = project_a
1555        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1556        .await
1557        .unwrap();
1558    buffer_a
1559        .condition(&cx_a, |buffer, _| buffer.text() == "fn main() { a. }")
1560        .await;
1561
1562    // Confirm a completion on the guest.
1563    editor_b
1564        .condition(&cx_b, |editor, _| editor.context_menu_visible())
1565        .await;
1566    editor_b.update(cx_b, |editor, cx| {
1567        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
1568        assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
1569    });
1570
1571    // Return a resolved completion from the host's language server.
1572    // The resolved completion has an additional text edit.
1573    fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
1574        |params, _| async move {
1575            assert_eq!(params.label, "first_method(…)");
1576            Ok(lsp::CompletionItem {
1577                label: "first_method(…)".into(),
1578                detail: Some("fn(&mut self, B) -> C".into()),
1579                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1580                    new_text: "first_method($1)".to_string(),
1581                    range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1582                })),
1583                additional_text_edits: Some(vec![lsp::TextEdit {
1584                    new_text: "use d::SomeTrait;\n".to_string(),
1585                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
1586                }]),
1587                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1588                ..Default::default()
1589            })
1590        },
1591    );
1592
1593    // The additional edit is applied.
1594    buffer_a
1595        .condition(&cx_a, |buffer, _| {
1596            buffer.text() == "use d::SomeTrait;\nfn main() { a.first_method() }"
1597        })
1598        .await;
1599    buffer_b
1600        .condition(&cx_b, |buffer, _| {
1601            buffer.text() == "use d::SomeTrait;\nfn main() { a.first_method() }"
1602        })
1603        .await;
1604}
1605
1606#[gpui::test(iterations = 10)]
1607async fn test_reloading_buffer_manually(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1608    cx_a.foreground().forbid_parking();
1609    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1610    let client_a = server.create_client(cx_a, "user_a").await;
1611    let client_b = server.create_client(cx_b, "user_b").await;
1612    server
1613        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1614        .await;
1615
1616    let fs = FakeFs::new(cx_a.background());
1617    fs.insert_tree(
1618        "/a",
1619        json!({
1620            "a.rs": "let one = 1;",
1621        }),
1622    )
1623    .await;
1624
1625    let (project_a, worktree_id) = client_a.build_local_project(fs.clone(), "/a", cx_a).await;
1626    let buffer_a = project_a
1627        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
1628        .await
1629        .unwrap();
1630
1631    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1632
1633    let buffer_b = cx_b
1634        .background()
1635        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
1636        .await
1637        .unwrap();
1638    buffer_b.update(cx_b, |buffer, cx| {
1639        buffer.edit([(4..7, "six")], cx);
1640        buffer.edit([(10..11, "6")], cx);
1641        assert_eq!(buffer.text(), "let six = 6;");
1642        assert!(buffer.is_dirty());
1643        assert!(!buffer.has_conflict());
1644    });
1645    buffer_a
1646        .condition(cx_a, |buffer, _| buffer.text() == "let six = 6;")
1647        .await;
1648
1649    fs.save(Path::new("/a/a.rs"), &Rope::from("let seven = 7;"))
1650        .await
1651        .unwrap();
1652    buffer_a
1653        .condition(cx_a, |buffer, _| buffer.has_conflict())
1654        .await;
1655    buffer_b
1656        .condition(cx_b, |buffer, _| buffer.has_conflict())
1657        .await;
1658
1659    project_b
1660        .update(cx_b, |project, cx| {
1661            project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
1662        })
1663        .await
1664        .unwrap();
1665    buffer_a.read_with(cx_a, |buffer, _| {
1666        assert_eq!(buffer.text(), "let seven = 7;");
1667        assert!(!buffer.is_dirty());
1668        assert!(!buffer.has_conflict());
1669    });
1670    buffer_b.read_with(cx_b, |buffer, _| {
1671        assert_eq!(buffer.text(), "let seven = 7;");
1672        assert!(!buffer.is_dirty());
1673        assert!(!buffer.has_conflict());
1674    });
1675
1676    buffer_a.update(cx_a, |buffer, cx| {
1677        // Undoing on the host is a no-op when the reload was initiated by the guest.
1678        buffer.undo(cx);
1679        assert_eq!(buffer.text(), "let seven = 7;");
1680        assert!(!buffer.is_dirty());
1681        assert!(!buffer.has_conflict());
1682    });
1683    buffer_b.update(cx_b, |buffer, cx| {
1684        // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
1685        buffer.undo(cx);
1686        assert_eq!(buffer.text(), "let six = 6;");
1687        assert!(buffer.is_dirty());
1688        assert!(!buffer.has_conflict());
1689    });
1690}
1691
1692#[gpui::test(iterations = 10)]
1693async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1694    cx_a.foreground().forbid_parking();
1695    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1696    let client_a = server.create_client(cx_a, "user_a").await;
1697    let client_b = server.create_client(cx_b, "user_b").await;
1698    server
1699        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1700        .await;
1701
1702    // Set up a fake language server.
1703    let mut language = Language::new(
1704        LanguageConfig {
1705            name: "Rust".into(),
1706            path_suffixes: vec!["rs".to_string()],
1707            ..Default::default()
1708        },
1709        Some(tree_sitter_rust::language()),
1710    );
1711    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
1712    client_a.language_registry.add(Arc::new(language));
1713
1714    let fs = FakeFs::new(cx_a.background());
1715    fs.insert_tree(
1716        "/a",
1717        json!({
1718            "a.rs": "let one = two",
1719        }),
1720    )
1721    .await;
1722
1723    let (project_a, worktree_id) = client_a.build_local_project(fs, "/a", cx_a).await;
1724    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1725
1726    let buffer_b = cx_b
1727        .background()
1728        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
1729        .await
1730        .unwrap();
1731
1732    let fake_language_server = fake_language_servers.next().await.unwrap();
1733    fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
1734        Ok(Some(vec![
1735            lsp::TextEdit {
1736                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
1737                new_text: "h".to_string(),
1738            },
1739            lsp::TextEdit {
1740                range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
1741                new_text: "y".to_string(),
1742            },
1743        ]))
1744    });
1745
1746    project_b
1747        .update(cx_b, |project, cx| {
1748            project.format(HashSet::from_iter([buffer_b.clone()]), true, cx)
1749        })
1750        .await
1751        .unwrap();
1752    assert_eq!(
1753        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
1754        "let honey = two"
1755    );
1756}
1757
1758#[gpui::test(iterations = 10)]
1759async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1760    cx_a.foreground().forbid_parking();
1761    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1762    let client_a = server.create_client(cx_a, "user_a").await;
1763    let client_b = server.create_client(cx_b, "user_b").await;
1764    server
1765        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1766        .await;
1767
1768    let fs = FakeFs::new(cx_a.background());
1769    fs.insert_tree(
1770        "/root-1",
1771        json!({
1772            "a.rs": "const ONE: usize = b::TWO + b::THREE;",
1773        }),
1774    )
1775    .await;
1776    fs.insert_tree(
1777        "/root-2",
1778        json!({
1779            "b.rs": "const TWO: usize = 2;\nconst THREE: usize = 3;",
1780        }),
1781    )
1782    .await;
1783
1784    // Set up a fake language server.
1785    let mut language = Language::new(
1786        LanguageConfig {
1787            name: "Rust".into(),
1788            path_suffixes: vec!["rs".to_string()],
1789            ..Default::default()
1790        },
1791        Some(tree_sitter_rust::language()),
1792    );
1793    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
1794    client_a.language_registry.add(Arc::new(language));
1795
1796    let (project_a, worktree_id) = client_a.build_local_project(fs, "/root-1", cx_a).await;
1797    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1798
1799    // Open the file on client B.
1800    let buffer_b = cx_b
1801        .background()
1802        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
1803        .await
1804        .unwrap();
1805
1806    // Request the definition of a symbol as the guest.
1807    let fake_language_server = fake_language_servers.next().await.unwrap();
1808    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
1809        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
1810            lsp::Location::new(
1811                lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
1812                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
1813            ),
1814        )))
1815    });
1816
1817    let definitions_1 = project_b
1818        .update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
1819        .await
1820        .unwrap();
1821    cx_b.read(|cx| {
1822        assert_eq!(definitions_1.len(), 1);
1823        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
1824        let target_buffer = definitions_1[0].buffer.read(cx);
1825        assert_eq!(
1826            target_buffer.text(),
1827            "const TWO: usize = 2;\nconst THREE: usize = 3;"
1828        );
1829        assert_eq!(
1830            definitions_1[0].range.to_point(target_buffer),
1831            Point::new(0, 6)..Point::new(0, 9)
1832        );
1833    });
1834
1835    // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
1836    // the previous call to `definition`.
1837    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
1838        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
1839            lsp::Location::new(
1840                lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
1841                lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
1842            ),
1843        )))
1844    });
1845
1846    let definitions_2 = project_b
1847        .update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
1848        .await
1849        .unwrap();
1850    cx_b.read(|cx| {
1851        assert_eq!(definitions_2.len(), 1);
1852        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
1853        let target_buffer = definitions_2[0].buffer.read(cx);
1854        assert_eq!(
1855            target_buffer.text(),
1856            "const TWO: usize = 2;\nconst THREE: usize = 3;"
1857        );
1858        assert_eq!(
1859            definitions_2[0].range.to_point(target_buffer),
1860            Point::new(1, 6)..Point::new(1, 11)
1861        );
1862    });
1863    assert_eq!(definitions_1[0].buffer, definitions_2[0].buffer);
1864}
1865
1866#[gpui::test(iterations = 10)]
1867async fn test_references(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1868    cx_a.foreground().forbid_parking();
1869    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1870    let client_a = server.create_client(cx_a, "user_a").await;
1871    let client_b = server.create_client(cx_b, "user_b").await;
1872    server
1873        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1874        .await;
1875
1876    let fs = FakeFs::new(cx_a.background());
1877    fs.insert_tree(
1878        "/root-1",
1879        json!({
1880            "one.rs": "const ONE: usize = 1;",
1881            "two.rs": "const TWO: usize = one::ONE + one::ONE;",
1882        }),
1883    )
1884    .await;
1885    fs.insert_tree(
1886        "/root-2",
1887        json!({
1888            "three.rs": "const THREE: usize = two::TWO + one::ONE;",
1889        }),
1890    )
1891    .await;
1892
1893    // Set up a fake language server.
1894    let mut language = Language::new(
1895        LanguageConfig {
1896            name: "Rust".into(),
1897            path_suffixes: vec!["rs".to_string()],
1898            ..Default::default()
1899        },
1900        Some(tree_sitter_rust::language()),
1901    );
1902    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
1903    client_a.language_registry.add(Arc::new(language));
1904
1905    let (project_a, worktree_id) = client_a.build_local_project(fs, "/root-1", cx_a).await;
1906    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1907
1908    // Open the file on client B.
1909    let buffer_b = cx_b
1910        .background()
1911        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
1912        .await
1913        .unwrap();
1914
1915    // Request references to a symbol as the guest.
1916    let fake_language_server = fake_language_servers.next().await.unwrap();
1917    fake_language_server.handle_request::<lsp::request::References, _, _>(|params, _| async move {
1918        assert_eq!(
1919            params.text_document_position.text_document.uri.as_str(),
1920            "file:///root-1/one.rs"
1921        );
1922        Ok(Some(vec![
1923            lsp::Location {
1924                uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
1925                range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
1926            },
1927            lsp::Location {
1928                uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
1929                range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
1930            },
1931            lsp::Location {
1932                uri: lsp::Url::from_file_path("/root-2/three.rs").unwrap(),
1933                range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
1934            },
1935        ]))
1936    });
1937
1938    let references = project_b
1939        .update(cx_b, |p, cx| p.references(&buffer_b, 7, cx))
1940        .await
1941        .unwrap();
1942    cx_b.read(|cx| {
1943        assert_eq!(references.len(), 3);
1944        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
1945
1946        let two_buffer = references[0].buffer.read(cx);
1947        let three_buffer = references[2].buffer.read(cx);
1948        assert_eq!(
1949            two_buffer.file().unwrap().path().as_ref(),
1950            Path::new("two.rs")
1951        );
1952        assert_eq!(references[1].buffer, references[0].buffer);
1953        assert_eq!(
1954            three_buffer.file().unwrap().full_path(cx),
1955            Path::new("three.rs")
1956        );
1957
1958        assert_eq!(references[0].range.to_offset(&two_buffer), 24..27);
1959        assert_eq!(references[1].range.to_offset(&two_buffer), 35..38);
1960        assert_eq!(references[2].range.to_offset(&three_buffer), 37..40);
1961    });
1962}
1963
1964#[gpui::test(iterations = 10)]
1965async fn test_project_search(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1966    cx_a.foreground().forbid_parking();
1967    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1968    let client_a = server.create_client(cx_a, "user_a").await;
1969    let client_b = server.create_client(cx_b, "user_b").await;
1970    server
1971        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1972        .await;
1973
1974    let fs = FakeFs::new(cx_a.background());
1975    fs.insert_tree(
1976        "/root-1",
1977        json!({
1978            "a": "hello world",
1979            "b": "goodnight moon",
1980            "c": "a world of goo",
1981            "d": "world champion of clown world",
1982        }),
1983    )
1984    .await;
1985    fs.insert_tree(
1986        "/root-2",
1987        json!({
1988            "e": "disney world is fun",
1989        }),
1990    )
1991    .await;
1992
1993    let (project_a, _) = client_a.build_local_project(fs, "/root-1", cx_a).await;
1994    let (worktree_2, _) = project_a
1995        .update(cx_a, |p, cx| {
1996            p.find_or_create_local_worktree("/root-2", true, cx)
1997        })
1998        .await
1999        .unwrap();
2000    worktree_2
2001        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
2002        .await;
2003
2004    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2005
2006    // Perform a search as the guest.
2007    let results = project_b
2008        .update(cx_b, |project, cx| {
2009            project.search(SearchQuery::text("world", false, false), cx)
2010        })
2011        .await
2012        .unwrap();
2013
2014    let mut ranges_by_path = results
2015        .into_iter()
2016        .map(|(buffer, ranges)| {
2017            buffer.read_with(cx_b, |buffer, cx| {
2018                let path = buffer.file().unwrap().full_path(cx);
2019                let offset_ranges = ranges
2020                    .into_iter()
2021                    .map(|range| range.to_offset(buffer))
2022                    .collect::<Vec<_>>();
2023                (path, offset_ranges)
2024            })
2025        })
2026        .collect::<Vec<_>>();
2027    ranges_by_path.sort_by_key(|(path, _)| path.clone());
2028
2029    assert_eq!(
2030        ranges_by_path,
2031        &[
2032            (PathBuf::from("root-1/a"), vec![6..11]),
2033            (PathBuf::from("root-1/c"), vec![2..7]),
2034            (PathBuf::from("root-1/d"), vec![0..5, 24..29]),
2035            (PathBuf::from("root-2/e"), vec![7..12]),
2036        ]
2037    );
2038}
2039
2040#[gpui::test(iterations = 10)]
2041async fn test_document_highlights(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2042    cx_a.foreground().forbid_parking();
2043    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2044    let client_a = server.create_client(cx_a, "user_a").await;
2045    let client_b = server.create_client(cx_b, "user_b").await;
2046    server
2047        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2048        .await;
2049
2050    let fs = FakeFs::new(cx_a.background());
2051    fs.insert_tree(
2052        "/root-1",
2053        json!({
2054            "main.rs": "fn double(number: i32) -> i32 { number + number }",
2055        }),
2056    )
2057    .await;
2058
2059    // Set up a fake language server.
2060    let mut language = Language::new(
2061        LanguageConfig {
2062            name: "Rust".into(),
2063            path_suffixes: vec!["rs".to_string()],
2064            ..Default::default()
2065        },
2066        Some(tree_sitter_rust::language()),
2067    );
2068    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
2069    client_a.language_registry.add(Arc::new(language));
2070
2071    let (project_a, worktree_id) = client_a.build_local_project(fs, "/root-1", cx_a).await;
2072    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2073
2074    // Open the file on client B.
2075    let buffer_b = cx_b
2076        .background()
2077        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
2078        .await
2079        .unwrap();
2080
2081    // Request document highlights as the guest.
2082    let fake_language_server = fake_language_servers.next().await.unwrap();
2083    fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
2084        |params, _| async move {
2085            assert_eq!(
2086                params
2087                    .text_document_position_params
2088                    .text_document
2089                    .uri
2090                    .as_str(),
2091                "file:///root-1/main.rs"
2092            );
2093            assert_eq!(
2094                params.text_document_position_params.position,
2095                lsp::Position::new(0, 34)
2096            );
2097            Ok(Some(vec![
2098                lsp::DocumentHighlight {
2099                    kind: Some(lsp::DocumentHighlightKind::WRITE),
2100                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 16)),
2101                },
2102                lsp::DocumentHighlight {
2103                    kind: Some(lsp::DocumentHighlightKind::READ),
2104                    range: lsp::Range::new(lsp::Position::new(0, 32), lsp::Position::new(0, 38)),
2105                },
2106                lsp::DocumentHighlight {
2107                    kind: Some(lsp::DocumentHighlightKind::READ),
2108                    range: lsp::Range::new(lsp::Position::new(0, 41), lsp::Position::new(0, 47)),
2109                },
2110            ]))
2111        },
2112    );
2113
2114    let highlights = project_b
2115        .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
2116        .await
2117        .unwrap();
2118    buffer_b.read_with(cx_b, |buffer, _| {
2119        let snapshot = buffer.snapshot();
2120
2121        let highlights = highlights
2122            .into_iter()
2123            .map(|highlight| (highlight.kind, highlight.range.to_offset(&snapshot)))
2124            .collect::<Vec<_>>();
2125        assert_eq!(
2126            highlights,
2127            &[
2128                (lsp::DocumentHighlightKind::WRITE, 10..16),
2129                (lsp::DocumentHighlightKind::READ, 32..38),
2130                (lsp::DocumentHighlightKind::READ, 41..47)
2131            ]
2132        )
2133    });
2134}
2135
2136#[gpui::test(iterations = 10)]
2137async fn test_project_symbols(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2138    cx_a.foreground().forbid_parking();
2139    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2140    let client_a = server.create_client(cx_a, "user_a").await;
2141    let client_b = server.create_client(cx_b, "user_b").await;
2142    server
2143        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2144        .await;
2145
2146    // Set up a fake language server.
2147    let mut language = Language::new(
2148        LanguageConfig {
2149            name: "Rust".into(),
2150            path_suffixes: vec!["rs".to_string()],
2151            ..Default::default()
2152        },
2153        Some(tree_sitter_rust::language()),
2154    );
2155    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
2156    client_a.language_registry.add(Arc::new(language));
2157
2158    let fs = FakeFs::new(cx_a.background());
2159    fs.insert_tree(
2160        "/code",
2161        json!({
2162            "crate-1": {
2163                "one.rs": "const ONE: usize = 1;",
2164            },
2165            "crate-2": {
2166                "two.rs": "const TWO: usize = 2; const THREE: usize = 3;",
2167            },
2168            "private": {
2169                "passwords.txt": "the-password",
2170            }
2171        }),
2172    )
2173    .await;
2174
2175    let (project_a, worktree_id) = client_a
2176        .build_local_project(fs, "/code/crate-1", cx_a)
2177        .await;
2178    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2179
2180    // Cause the language server to start.
2181    let _buffer = cx_b
2182        .background()
2183        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
2184        .await
2185        .unwrap();
2186
2187    let fake_language_server = fake_language_servers.next().await.unwrap();
2188    fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(|_, _| async move {
2189        #[allow(deprecated)]
2190        Ok(Some(vec![lsp::SymbolInformation {
2191            name: "TWO".into(),
2192            location: lsp::Location {
2193                uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
2194                range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2195            },
2196            kind: lsp::SymbolKind::CONSTANT,
2197            tags: None,
2198            container_name: None,
2199            deprecated: None,
2200        }]))
2201    });
2202
2203    // Request the definition of a symbol as the guest.
2204    let symbols = project_b
2205        .update(cx_b, |p, cx| p.symbols("two", cx))
2206        .await
2207        .unwrap();
2208    assert_eq!(symbols.len(), 1);
2209    assert_eq!(symbols[0].name, "TWO");
2210
2211    // Open one of the returned symbols.
2212    let buffer_b_2 = project_b
2213        .update(cx_b, |project, cx| {
2214            project.open_buffer_for_symbol(&symbols[0], cx)
2215        })
2216        .await
2217        .unwrap();
2218    buffer_b_2.read_with(cx_b, |buffer, _| {
2219        assert_eq!(
2220            buffer.file().unwrap().path().as_ref(),
2221            Path::new("../crate-2/two.rs")
2222        );
2223    });
2224
2225    // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
2226    let mut fake_symbol = symbols[0].clone();
2227    fake_symbol.path = Path::new("/code/secrets").into();
2228    let error = project_b
2229        .update(cx_b, |project, cx| {
2230            project.open_buffer_for_symbol(&fake_symbol, cx)
2231        })
2232        .await
2233        .unwrap_err();
2234    assert!(error.to_string().contains("invalid symbol signature"));
2235}
2236
2237#[gpui::test(iterations = 10)]
2238async fn test_open_buffer_while_getting_definition_pointing_to_it(
2239    cx_a: &mut TestAppContext,
2240    cx_b: &mut TestAppContext,
2241    mut rng: StdRng,
2242) {
2243    cx_a.foreground().forbid_parking();
2244    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2245    let client_a = server.create_client(cx_a, "user_a").await;
2246    let client_b = server.create_client(cx_b, "user_b").await;
2247    server
2248        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2249        .await;
2250
2251    // Set up a fake language server.
2252    let mut language = Language::new(
2253        LanguageConfig {
2254            name: "Rust".into(),
2255            path_suffixes: vec!["rs".to_string()],
2256            ..Default::default()
2257        },
2258        Some(tree_sitter_rust::language()),
2259    );
2260    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
2261    client_a.language_registry.add(Arc::new(language));
2262
2263    let fs = FakeFs::new(cx_a.background());
2264    fs.insert_tree(
2265        "/root",
2266        json!({
2267            "a.rs": "const ONE: usize = b::TWO;",
2268            "b.rs": "const TWO: usize = 2",
2269        }),
2270    )
2271    .await;
2272
2273    let (project_a, worktree_id) = client_a.build_local_project(fs, "/root", cx_a).await;
2274    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2275
2276    let buffer_b1 = cx_b
2277        .background()
2278        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
2279        .await
2280        .unwrap();
2281
2282    let fake_language_server = fake_language_servers.next().await.unwrap();
2283    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
2284        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2285            lsp::Location::new(
2286                lsp::Url::from_file_path("/root/b.rs").unwrap(),
2287                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2288            ),
2289        )))
2290    });
2291
2292    let definitions;
2293    let buffer_b2;
2294    if rng.gen() {
2295        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
2296        buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
2297    } else {
2298        buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
2299        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
2300    }
2301
2302    let buffer_b2 = buffer_b2.await.unwrap();
2303    let definitions = definitions.await.unwrap();
2304    assert_eq!(definitions.len(), 1);
2305    assert_eq!(definitions[0].buffer, buffer_b2);
2306}
2307
2308#[gpui::test(iterations = 10)]
2309async fn test_collaborating_with_code_actions(
2310    cx_a: &mut TestAppContext,
2311    cx_b: &mut TestAppContext,
2312) {
2313    cx_a.foreground().forbid_parking();
2314    cx_b.update(|cx| editor::init(cx));
2315    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2316    let client_a = server.create_client(cx_a, "user_a").await;
2317    let client_b = server.create_client(cx_b, "user_b").await;
2318    server
2319        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2320        .await;
2321
2322    // Set up a fake language server.
2323    let mut language = Language::new(
2324        LanguageConfig {
2325            name: "Rust".into(),
2326            path_suffixes: vec!["rs".to_string()],
2327            ..Default::default()
2328        },
2329        Some(tree_sitter_rust::language()),
2330    );
2331    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
2332    client_a.language_registry.add(Arc::new(language));
2333
2334    let fs = FakeFs::new(cx_a.background());
2335    fs.insert_tree(
2336        "/a",
2337        json!({
2338            "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
2339            "other.rs": "pub fn foo() -> usize { 4 }",
2340        }),
2341    )
2342    .await;
2343    let (project_a, worktree_id) = client_a.build_local_project(fs, "/a", cx_a).await;
2344
2345    // Join the project as client B.
2346    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2347    let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx));
2348    let editor_b = workspace_b
2349        .update(cx_b, |workspace, cx| {
2350            workspace.open_path((worktree_id, "main.rs"), true, cx)
2351        })
2352        .await
2353        .unwrap()
2354        .downcast::<Editor>()
2355        .unwrap();
2356
2357    let mut fake_language_server = fake_language_servers.next().await.unwrap();
2358    fake_language_server
2359        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
2360            assert_eq!(
2361                params.text_document.uri,
2362                lsp::Url::from_file_path("/a/main.rs").unwrap(),
2363            );
2364            assert_eq!(params.range.start, lsp::Position::new(0, 0));
2365            assert_eq!(params.range.end, lsp::Position::new(0, 0));
2366            Ok(None)
2367        })
2368        .next()
2369        .await;
2370
2371    // Move cursor to a location that contains code actions.
2372    editor_b.update(cx_b, |editor, cx| {
2373        editor.change_selections(None, cx, |s| {
2374            s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
2375        });
2376        cx.focus(&editor_b);
2377    });
2378
2379    fake_language_server
2380        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
2381            assert_eq!(
2382                params.text_document.uri,
2383                lsp::Url::from_file_path("/a/main.rs").unwrap(),
2384            );
2385            assert_eq!(params.range.start, lsp::Position::new(1, 31));
2386            assert_eq!(params.range.end, lsp::Position::new(1, 31));
2387
2388            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
2389                lsp::CodeAction {
2390                    title: "Inline into all callers".to_string(),
2391                    edit: Some(lsp::WorkspaceEdit {
2392                        changes: Some(
2393                            [
2394                                (
2395                                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
2396                                    vec![lsp::TextEdit::new(
2397                                        lsp::Range::new(
2398                                            lsp::Position::new(1, 22),
2399                                            lsp::Position::new(1, 34),
2400                                        ),
2401                                        "4".to_string(),
2402                                    )],
2403                                ),
2404                                (
2405                                    lsp::Url::from_file_path("/a/other.rs").unwrap(),
2406                                    vec![lsp::TextEdit::new(
2407                                        lsp::Range::new(
2408                                            lsp::Position::new(0, 0),
2409                                            lsp::Position::new(0, 27),
2410                                        ),
2411                                        "".to_string(),
2412                                    )],
2413                                ),
2414                            ]
2415                            .into_iter()
2416                            .collect(),
2417                        ),
2418                        ..Default::default()
2419                    }),
2420                    data: Some(json!({
2421                        "codeActionParams": {
2422                            "range": {
2423                                "start": {"line": 1, "column": 31},
2424                                "end": {"line": 1, "column": 31},
2425                            }
2426                        }
2427                    })),
2428                    ..Default::default()
2429                },
2430            )]))
2431        })
2432        .next()
2433        .await;
2434
2435    // Toggle code actions and wait for them to display.
2436    editor_b.update(cx_b, |editor, cx| {
2437        editor.toggle_code_actions(
2438            &ToggleCodeActions {
2439                deployed_from_indicator: false,
2440            },
2441            cx,
2442        );
2443    });
2444    editor_b
2445        .condition(&cx_b, |editor, _| editor.context_menu_visible())
2446        .await;
2447
2448    fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
2449
2450    // Confirming the code action will trigger a resolve request.
2451    let confirm_action = workspace_b
2452        .update(cx_b, |workspace, cx| {
2453            Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
2454        })
2455        .unwrap();
2456    fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
2457        |_, _| async move {
2458            Ok(lsp::CodeAction {
2459                title: "Inline into all callers".to_string(),
2460                edit: Some(lsp::WorkspaceEdit {
2461                    changes: Some(
2462                        [
2463                            (
2464                                lsp::Url::from_file_path("/a/main.rs").unwrap(),
2465                                vec![lsp::TextEdit::new(
2466                                    lsp::Range::new(
2467                                        lsp::Position::new(1, 22),
2468                                        lsp::Position::new(1, 34),
2469                                    ),
2470                                    "4".to_string(),
2471                                )],
2472                            ),
2473                            (
2474                                lsp::Url::from_file_path("/a/other.rs").unwrap(),
2475                                vec![lsp::TextEdit::new(
2476                                    lsp::Range::new(
2477                                        lsp::Position::new(0, 0),
2478                                        lsp::Position::new(0, 27),
2479                                    ),
2480                                    "".to_string(),
2481                                )],
2482                            ),
2483                        ]
2484                        .into_iter()
2485                        .collect(),
2486                    ),
2487                    ..Default::default()
2488                }),
2489                ..Default::default()
2490            })
2491        },
2492    );
2493
2494    // After the action is confirmed, an editor containing both modified files is opened.
2495    confirm_action.await.unwrap();
2496    let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| {
2497        workspace
2498            .active_item(cx)
2499            .unwrap()
2500            .downcast::<Editor>()
2501            .unwrap()
2502    });
2503    code_action_editor.update(cx_b, |editor, cx| {
2504        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
2505        editor.undo(&Undo, cx);
2506        assert_eq!(
2507            editor.text(cx),
2508            "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
2509        );
2510        editor.redo(&Redo, cx);
2511        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
2512    });
2513}
2514
2515#[gpui::test(iterations = 10)]
2516async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2517    cx_a.foreground().forbid_parking();
2518    cx_b.update(|cx| editor::init(cx));
2519    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2520    let client_a = server.create_client(cx_a, "user_a").await;
2521    let client_b = server.create_client(cx_b, "user_b").await;
2522    server
2523        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2524        .await;
2525
2526    // Set up a fake language server.
2527    let mut language = Language::new(
2528        LanguageConfig {
2529            name: "Rust".into(),
2530            path_suffixes: vec!["rs".to_string()],
2531            ..Default::default()
2532        },
2533        Some(tree_sitter_rust::language()),
2534    );
2535    let mut fake_language_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
2536        capabilities: lsp::ServerCapabilities {
2537            rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
2538                prepare_provider: Some(true),
2539                work_done_progress_options: Default::default(),
2540            })),
2541            ..Default::default()
2542        },
2543        ..Default::default()
2544    });
2545    client_a.language_registry.add(Arc::new(language));
2546
2547    let fs = FakeFs::new(cx_a.background());
2548    fs.insert_tree(
2549        "/dir",
2550        json!({
2551            "one.rs": "const ONE: usize = 1;",
2552            "two.rs": "const TWO: usize = one::ONE + one::ONE;"
2553        }),
2554    )
2555    .await;
2556
2557    let (project_a, worktree_id) = client_a.build_local_project(fs, "/dir", cx_a).await;
2558    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2559
2560    let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx));
2561    let editor_b = workspace_b
2562        .update(cx_b, |workspace, cx| {
2563            workspace.open_path((worktree_id, "one.rs"), true, cx)
2564        })
2565        .await
2566        .unwrap()
2567        .downcast::<Editor>()
2568        .unwrap();
2569    let fake_language_server = fake_language_servers.next().await.unwrap();
2570
2571    // Move cursor to a location that can be renamed.
2572    let prepare_rename = editor_b.update(cx_b, |editor, cx| {
2573        editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
2574        editor.rename(&Rename, cx).unwrap()
2575    });
2576
2577    fake_language_server
2578        .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
2579            assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
2580            assert_eq!(params.position, lsp::Position::new(0, 7));
2581            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
2582                lsp::Position::new(0, 6),
2583                lsp::Position::new(0, 9),
2584            ))))
2585        })
2586        .next()
2587        .await
2588        .unwrap();
2589    prepare_rename.await.unwrap();
2590    editor_b.update(cx_b, |editor, cx| {
2591        let rename = editor.pending_rename().unwrap();
2592        let buffer = editor.buffer().read(cx).snapshot(cx);
2593        assert_eq!(
2594            rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
2595            6..9
2596        );
2597        rename.editor.update(cx, |rename_editor, cx| {
2598            rename_editor.buffer().update(cx, |rename_buffer, cx| {
2599                rename_buffer.edit([(0..3, "THREE")], cx);
2600            });
2601        });
2602    });
2603
2604    let confirm_rename = workspace_b.update(cx_b, |workspace, cx| {
2605        Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
2606    });
2607    fake_language_server
2608        .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
2609            assert_eq!(
2610                params.text_document_position.text_document.uri.as_str(),
2611                "file:///dir/one.rs"
2612            );
2613            assert_eq!(
2614                params.text_document_position.position,
2615                lsp::Position::new(0, 6)
2616            );
2617            assert_eq!(params.new_name, "THREE");
2618            Ok(Some(lsp::WorkspaceEdit {
2619                changes: Some(
2620                    [
2621                        (
2622                            lsp::Url::from_file_path("/dir/one.rs").unwrap(),
2623                            vec![lsp::TextEdit::new(
2624                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2625                                "THREE".to_string(),
2626                            )],
2627                        ),
2628                        (
2629                            lsp::Url::from_file_path("/dir/two.rs").unwrap(),
2630                            vec![
2631                                lsp::TextEdit::new(
2632                                    lsp::Range::new(
2633                                        lsp::Position::new(0, 24),
2634                                        lsp::Position::new(0, 27),
2635                                    ),
2636                                    "THREE".to_string(),
2637                                ),
2638                                lsp::TextEdit::new(
2639                                    lsp::Range::new(
2640                                        lsp::Position::new(0, 35),
2641                                        lsp::Position::new(0, 38),
2642                                    ),
2643                                    "THREE".to_string(),
2644                                ),
2645                            ],
2646                        ),
2647                    ]
2648                    .into_iter()
2649                    .collect(),
2650                ),
2651                ..Default::default()
2652            }))
2653        })
2654        .next()
2655        .await
2656        .unwrap();
2657    confirm_rename.await.unwrap();
2658
2659    let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| {
2660        workspace
2661            .active_item(cx)
2662            .unwrap()
2663            .downcast::<Editor>()
2664            .unwrap()
2665    });
2666    rename_editor.update(cx_b, |editor, cx| {
2667        assert_eq!(
2668            editor.text(cx),
2669            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
2670        );
2671        editor.undo(&Undo, cx);
2672        assert_eq!(
2673            editor.text(cx),
2674            "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
2675        );
2676        editor.redo(&Redo, cx);
2677        assert_eq!(
2678            editor.text(cx),
2679            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
2680        );
2681    });
2682
2683    // Ensure temporary rename edits cannot be undone/redone.
2684    editor_b.update(cx_b, |editor, cx| {
2685        editor.undo(&Undo, cx);
2686        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
2687        editor.undo(&Undo, cx);
2688        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
2689        editor.redo(&Redo, cx);
2690        assert_eq!(editor.text(cx), "const THREE: usize = 1;");
2691    })
2692}
2693
2694#[gpui::test(iterations = 10)]
2695async fn test_basic_chat(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2696    cx_a.foreground().forbid_parking();
2697    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2698    let client_a = server.create_client(cx_a, "user_a").await;
2699    let client_b = server.create_client(cx_b, "user_b").await;
2700
2701    // Create an org that includes these 2 users.
2702    let db = &server.app_state.db;
2703    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
2704    db.add_org_member(org_id, client_a.current_user_id(&cx_a), false)
2705        .await
2706        .unwrap();
2707    db.add_org_member(org_id, client_b.current_user_id(&cx_b), false)
2708        .await
2709        .unwrap();
2710
2711    // Create a channel that includes all the users.
2712    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
2713    db.add_channel_member(channel_id, client_a.current_user_id(&cx_a), false)
2714        .await
2715        .unwrap();
2716    db.add_channel_member(channel_id, client_b.current_user_id(&cx_b), false)
2717        .await
2718        .unwrap();
2719    db.create_channel_message(
2720        channel_id,
2721        client_b.current_user_id(&cx_b),
2722        "hello A, it's B.",
2723        OffsetDateTime::now_utc(),
2724        1,
2725    )
2726    .await
2727    .unwrap();
2728
2729    let channels_a =
2730        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
2731    channels_a
2732        .condition(cx_a, |list, _| list.available_channels().is_some())
2733        .await;
2734    channels_a.read_with(cx_a, |list, _| {
2735        assert_eq!(
2736            list.available_channels().unwrap(),
2737            &[ChannelDetails {
2738                id: channel_id.to_proto(),
2739                name: "test-channel".to_string()
2740            }]
2741        )
2742    });
2743    let channel_a = channels_a.update(cx_a, |this, cx| {
2744        this.get_channel(channel_id.to_proto(), cx).unwrap()
2745    });
2746    channel_a.read_with(cx_a, |channel, _| assert!(channel.messages().is_empty()));
2747    channel_a
2748        .condition(&cx_a, |channel, _| {
2749            channel_messages(channel)
2750                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
2751        })
2752        .await;
2753
2754    let channels_b =
2755        cx_b.add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx));
2756    channels_b
2757        .condition(cx_b, |list, _| list.available_channels().is_some())
2758        .await;
2759    channels_b.read_with(cx_b, |list, _| {
2760        assert_eq!(
2761            list.available_channels().unwrap(),
2762            &[ChannelDetails {
2763                id: channel_id.to_proto(),
2764                name: "test-channel".to_string()
2765            }]
2766        )
2767    });
2768
2769    let channel_b = channels_b.update(cx_b, |this, cx| {
2770        this.get_channel(channel_id.to_proto(), cx).unwrap()
2771    });
2772    channel_b.read_with(cx_b, |channel, _| assert!(channel.messages().is_empty()));
2773    channel_b
2774        .condition(&cx_b, |channel, _| {
2775            channel_messages(channel)
2776                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
2777        })
2778        .await;
2779
2780    channel_a
2781        .update(cx_a, |channel, cx| {
2782            channel
2783                .send_message("oh, hi B.".to_string(), cx)
2784                .unwrap()
2785                .detach();
2786            let task = channel.send_message("sup".to_string(), cx).unwrap();
2787            assert_eq!(
2788                channel_messages(channel),
2789                &[
2790                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
2791                    ("user_a".to_string(), "oh, hi B.".to_string(), true),
2792                    ("user_a".to_string(), "sup".to_string(), true)
2793                ]
2794            );
2795            task
2796        })
2797        .await
2798        .unwrap();
2799
2800    channel_b
2801        .condition(&cx_b, |channel, _| {
2802            channel_messages(channel)
2803                == [
2804                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
2805                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
2806                    ("user_a".to_string(), "sup".to_string(), false),
2807                ]
2808        })
2809        .await;
2810
2811    assert_eq!(
2812        server
2813            .state()
2814            .await
2815            .channel(channel_id)
2816            .unwrap()
2817            .connection_ids
2818            .len(),
2819        2
2820    );
2821    cx_b.update(|_| drop(channel_b));
2822    server
2823        .condition(|state| state.channel(channel_id).unwrap().connection_ids.len() == 1)
2824        .await;
2825
2826    cx_a.update(|_| drop(channel_a));
2827    server
2828        .condition(|state| state.channel(channel_id).is_none())
2829        .await;
2830}
2831
2832#[gpui::test(iterations = 10)]
2833async fn test_chat_message_validation(cx_a: &mut TestAppContext) {
2834    cx_a.foreground().forbid_parking();
2835    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2836    let client_a = server.create_client(cx_a, "user_a").await;
2837
2838    let db = &server.app_state.db;
2839    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
2840    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
2841    db.add_org_member(org_id, client_a.current_user_id(&cx_a), false)
2842        .await
2843        .unwrap();
2844    db.add_channel_member(channel_id, client_a.current_user_id(&cx_a), false)
2845        .await
2846        .unwrap();
2847
2848    let channels_a =
2849        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
2850    channels_a
2851        .condition(cx_a, |list, _| list.available_channels().is_some())
2852        .await;
2853    let channel_a = channels_a.update(cx_a, |this, cx| {
2854        this.get_channel(channel_id.to_proto(), cx).unwrap()
2855    });
2856
2857    // Messages aren't allowed to be too long.
2858    channel_a
2859        .update(cx_a, |channel, cx| {
2860            let long_body = "this is long.\n".repeat(1024);
2861            channel.send_message(long_body, cx).unwrap()
2862        })
2863        .await
2864        .unwrap_err();
2865
2866    // Messages aren't allowed to be blank.
2867    channel_a.update(cx_a, |channel, cx| {
2868        channel.send_message(String::new(), cx).unwrap_err()
2869    });
2870
2871    // Leading and trailing whitespace are trimmed.
2872    channel_a
2873        .update(cx_a, |channel, cx| {
2874            channel
2875                .send_message("\n surrounded by whitespace  \n".to_string(), cx)
2876                .unwrap()
2877        })
2878        .await
2879        .unwrap();
2880    assert_eq!(
2881        db.get_channel_messages(channel_id, 10, None)
2882            .await
2883            .unwrap()
2884            .iter()
2885            .map(|m| &m.body)
2886            .collect::<Vec<_>>(),
2887        &["surrounded by whitespace"]
2888    );
2889}
2890
2891#[gpui::test(iterations = 10)]
2892async fn test_chat_reconnection(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2893    cx_a.foreground().forbid_parking();
2894    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2895    let client_a = server.create_client(cx_a, "user_a").await;
2896    let client_b = server.create_client(cx_b, "user_b").await;
2897
2898    let mut status_b = client_b.status();
2899
2900    // Create an org that includes these 2 users.
2901    let db = &server.app_state.db;
2902    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
2903    db.add_org_member(org_id, client_a.current_user_id(&cx_a), false)
2904        .await
2905        .unwrap();
2906    db.add_org_member(org_id, client_b.current_user_id(&cx_b), false)
2907        .await
2908        .unwrap();
2909
2910    // Create a channel that includes all the users.
2911    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
2912    db.add_channel_member(channel_id, client_a.current_user_id(&cx_a), false)
2913        .await
2914        .unwrap();
2915    db.add_channel_member(channel_id, client_b.current_user_id(&cx_b), false)
2916        .await
2917        .unwrap();
2918    db.create_channel_message(
2919        channel_id,
2920        client_b.current_user_id(&cx_b),
2921        "hello A, it's B.",
2922        OffsetDateTime::now_utc(),
2923        2,
2924    )
2925    .await
2926    .unwrap();
2927
2928    let channels_a =
2929        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
2930    channels_a
2931        .condition(cx_a, |list, _| list.available_channels().is_some())
2932        .await;
2933
2934    channels_a.read_with(cx_a, |list, _| {
2935        assert_eq!(
2936            list.available_channels().unwrap(),
2937            &[ChannelDetails {
2938                id: channel_id.to_proto(),
2939                name: "test-channel".to_string()
2940            }]
2941        )
2942    });
2943    let channel_a = channels_a.update(cx_a, |this, cx| {
2944        this.get_channel(channel_id.to_proto(), cx).unwrap()
2945    });
2946    channel_a.read_with(cx_a, |channel, _| assert!(channel.messages().is_empty()));
2947    channel_a
2948        .condition(&cx_a, |channel, _| {
2949            channel_messages(channel)
2950                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
2951        })
2952        .await;
2953
2954    let channels_b =
2955        cx_b.add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx));
2956    channels_b
2957        .condition(cx_b, |list, _| list.available_channels().is_some())
2958        .await;
2959    channels_b.read_with(cx_b, |list, _| {
2960        assert_eq!(
2961            list.available_channels().unwrap(),
2962            &[ChannelDetails {
2963                id: channel_id.to_proto(),
2964                name: "test-channel".to_string()
2965            }]
2966        )
2967    });
2968
2969    let channel_b = channels_b.update(cx_b, |this, cx| {
2970        this.get_channel(channel_id.to_proto(), cx).unwrap()
2971    });
2972    channel_b.read_with(cx_b, |channel, _| assert!(channel.messages().is_empty()));
2973    channel_b
2974        .condition(&cx_b, |channel, _| {
2975            channel_messages(channel)
2976                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
2977        })
2978        .await;
2979
2980    // Disconnect client B, ensuring we can still access its cached channel data.
2981    server.forbid_connections();
2982    server.disconnect_client(client_b.current_user_id(&cx_b));
2983    cx_b.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
2984    while !matches!(
2985        status_b.next().await,
2986        Some(client::Status::ReconnectionError { .. })
2987    ) {}
2988
2989    channels_b.read_with(cx_b, |channels, _| {
2990        assert_eq!(
2991            channels.available_channels().unwrap(),
2992            [ChannelDetails {
2993                id: channel_id.to_proto(),
2994                name: "test-channel".to_string()
2995            }]
2996        )
2997    });
2998    channel_b.read_with(cx_b, |channel, _| {
2999        assert_eq!(
3000            channel_messages(channel),
3001            [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3002        )
3003    });
3004
3005    // Send a message from client B while it is disconnected.
3006    channel_b
3007        .update(cx_b, |channel, cx| {
3008            let task = channel
3009                .send_message("can you see this?".to_string(), cx)
3010                .unwrap();
3011            assert_eq!(
3012                channel_messages(channel),
3013                &[
3014                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3015                    ("user_b".to_string(), "can you see this?".to_string(), true)
3016                ]
3017            );
3018            task
3019        })
3020        .await
3021        .unwrap_err();
3022
3023    // Send a message from client A while B is disconnected.
3024    channel_a
3025        .update(cx_a, |channel, cx| {
3026            channel
3027                .send_message("oh, hi B.".to_string(), cx)
3028                .unwrap()
3029                .detach();
3030            let task = channel.send_message("sup".to_string(), cx).unwrap();
3031            assert_eq!(
3032                channel_messages(channel),
3033                &[
3034                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3035                    ("user_a".to_string(), "oh, hi B.".to_string(), true),
3036                    ("user_a".to_string(), "sup".to_string(), true)
3037                ]
3038            );
3039            task
3040        })
3041        .await
3042        .unwrap();
3043
3044    // Give client B a chance to reconnect.
3045    server.allow_connections();
3046    cx_b.foreground().advance_clock(Duration::from_secs(10));
3047
3048    // Verify that B sees the new messages upon reconnection, as well as the message client B
3049    // sent while offline.
3050    channel_b
3051        .condition(&cx_b, |channel, _| {
3052            channel_messages(channel)
3053                == [
3054                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3055                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3056                    ("user_a".to_string(), "sup".to_string(), false),
3057                    ("user_b".to_string(), "can you see this?".to_string(), false),
3058                ]
3059        })
3060        .await;
3061
3062    // Ensure client A and B can communicate normally after reconnection.
3063    channel_a
3064        .update(cx_a, |channel, cx| {
3065            channel.send_message("you online?".to_string(), cx).unwrap()
3066        })
3067        .await
3068        .unwrap();
3069    channel_b
3070        .condition(&cx_b, |channel, _| {
3071            channel_messages(channel)
3072                == [
3073                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3074                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3075                    ("user_a".to_string(), "sup".to_string(), false),
3076                    ("user_b".to_string(), "can you see this?".to_string(), false),
3077                    ("user_a".to_string(), "you online?".to_string(), false),
3078                ]
3079        })
3080        .await;
3081
3082    channel_b
3083        .update(cx_b, |channel, cx| {
3084            channel.send_message("yep".to_string(), cx).unwrap()
3085        })
3086        .await
3087        .unwrap();
3088    channel_a
3089        .condition(&cx_a, |channel, _| {
3090            channel_messages(channel)
3091                == [
3092                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3093                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3094                    ("user_a".to_string(), "sup".to_string(), false),
3095                    ("user_b".to_string(), "can you see this?".to_string(), false),
3096                    ("user_a".to_string(), "you online?".to_string(), false),
3097                    ("user_b".to_string(), "yep".to_string(), false),
3098                ]
3099        })
3100        .await;
3101}
3102
3103#[gpui::test(iterations = 10)]
3104async fn test_contacts(
3105    deterministic: Arc<Deterministic>,
3106    cx_a: &mut TestAppContext,
3107    cx_b: &mut TestAppContext,
3108    cx_c: &mut TestAppContext,
3109) {
3110    cx_a.foreground().forbid_parking();
3111    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3112    let client_a = server.create_client(cx_a, "user_a").await;
3113    let client_b = server.create_client(cx_b, "user_b").await;
3114    let client_c = server.create_client(cx_c, "user_c").await;
3115    server
3116        .make_contacts(vec![
3117            (&client_a, cx_a),
3118            (&client_b, cx_b),
3119            (&client_c, cx_c),
3120        ])
3121        .await;
3122
3123    deterministic.run_until_parked();
3124    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3125        client.user_store.read_with(*cx, |store, _| {
3126            assert_eq!(
3127                contacts(store),
3128                [
3129                    ("user_a", true, vec![]),
3130                    ("user_b", true, vec![]),
3131                    ("user_c", true, vec![])
3132                ],
3133                "{} has the wrong contacts",
3134                client.username
3135            )
3136        });
3137    }
3138
3139    // Share a project as client A.
3140    let fs = FakeFs::new(cx_a.background());
3141    fs.create_dir(Path::new("/a")).await.unwrap();
3142    let (project_a, _) = client_a.build_local_project(fs, "/a", cx_a).await;
3143
3144    deterministic.run_until_parked();
3145    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3146        client.user_store.read_with(*cx, |store, _| {
3147            assert_eq!(
3148                contacts(store),
3149                [
3150                    ("user_a", true, vec![("a", vec![])]),
3151                    ("user_b", true, vec![]),
3152                    ("user_c", true, vec![])
3153                ],
3154                "{} has the wrong contacts",
3155                client.username
3156            )
3157        });
3158    }
3159
3160    let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
3161
3162    deterministic.run_until_parked();
3163    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3164        client.user_store.read_with(*cx, |store, _| {
3165            assert_eq!(
3166                contacts(store),
3167                [
3168                    ("user_a", true, vec![("a", vec!["user_b"])]),
3169                    ("user_b", true, vec![]),
3170                    ("user_c", true, vec![])
3171                ],
3172                "{} has the wrong contacts",
3173                client.username
3174            )
3175        });
3176    }
3177
3178    // Add a local project as client B
3179    let fs = FakeFs::new(cx_b.background());
3180    fs.create_dir(Path::new("/b")).await.unwrap();
3181    let (_project_b, _) = client_b.build_local_project(fs, "/b", cx_b).await;
3182
3183    deterministic.run_until_parked();
3184    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3185        client.user_store.read_with(*cx, |store, _| {
3186            assert_eq!(
3187                contacts(store),
3188                [
3189                    ("user_a", true, vec![("a", vec!["user_b"])]),
3190                    ("user_b", true, vec![("b", vec![])]),
3191                    ("user_c", true, vec![])
3192                ],
3193                "{} has the wrong contacts",
3194                client.username
3195            )
3196        });
3197    }
3198
3199    project_a
3200        .condition(&cx_a, |project, _| {
3201            project.collaborators().contains_key(&client_b.peer_id)
3202        })
3203        .await;
3204
3205    cx_a.update(move |_| drop(project_a));
3206    deterministic.run_until_parked();
3207    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3208        client.user_store.read_with(*cx, |store, _| {
3209            assert_eq!(
3210                contacts(store),
3211                [
3212                    ("user_a", true, vec![]),
3213                    ("user_b", true, vec![("b", vec![])]),
3214                    ("user_c", true, vec![])
3215                ],
3216                "{} has the wrong contacts",
3217                client.username
3218            )
3219        });
3220    }
3221
3222    server.disconnect_client(client_c.current_user_id(cx_c));
3223    server.forbid_connections();
3224    deterministic.advance_clock(rpc::RECEIVE_TIMEOUT);
3225    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b)] {
3226        client.user_store.read_with(*cx, |store, _| {
3227            assert_eq!(
3228                contacts(store),
3229                [
3230                    ("user_a", true, vec![]),
3231                    ("user_b", true, vec![("b", vec![])]),
3232                    ("user_c", false, vec![])
3233                ],
3234                "{} has the wrong contacts",
3235                client.username
3236            )
3237        });
3238    }
3239    client_c
3240        .user_store
3241        .read_with(cx_c, |store, _| assert_eq!(contacts(store), []));
3242
3243    server.allow_connections();
3244    client_c
3245        .authenticate_and_connect(false, &cx_c.to_async())
3246        .await
3247        .unwrap();
3248
3249    deterministic.run_until_parked();
3250    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3251        client.user_store.read_with(*cx, |store, _| {
3252            assert_eq!(
3253                contacts(store),
3254                [
3255                    ("user_a", true, vec![]),
3256                    ("user_b", true, vec![("b", vec![])]),
3257                    ("user_c", true, vec![])
3258                ],
3259                "{} has the wrong contacts",
3260                client.username
3261            )
3262        });
3263    }
3264
3265    fn contacts(user_store: &UserStore) -> Vec<(&str, bool, Vec<(&str, Vec<&str>)>)> {
3266        user_store
3267            .contacts()
3268            .iter()
3269            .map(|contact| {
3270                let projects = contact
3271                    .projects
3272                    .iter()
3273                    .map(|p| {
3274                        (
3275                            p.worktree_root_names[0].as_str(),
3276                            p.guests.iter().map(|p| p.github_login.as_str()).collect(),
3277                        )
3278                    })
3279                    .collect();
3280                (contact.user.github_login.as_str(), contact.online, projects)
3281            })
3282            .collect()
3283    }
3284}
3285
3286#[gpui::test(iterations = 10)]
3287async fn test_contact_requests(
3288    executor: Arc<Deterministic>,
3289    cx_a: &mut TestAppContext,
3290    cx_a2: &mut TestAppContext,
3291    cx_b: &mut TestAppContext,
3292    cx_b2: &mut TestAppContext,
3293    cx_c: &mut TestAppContext,
3294    cx_c2: &mut TestAppContext,
3295) {
3296    cx_a.foreground().forbid_parking();
3297
3298    // Connect to a server as 3 clients.
3299    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3300    let client_a = server.create_client(cx_a, "user_a").await;
3301    let client_a2 = server.create_client(cx_a2, "user_a").await;
3302    let client_b = server.create_client(cx_b, "user_b").await;
3303    let client_b2 = server.create_client(cx_b2, "user_b").await;
3304    let client_c = server.create_client(cx_c, "user_c").await;
3305    let client_c2 = server.create_client(cx_c2, "user_c").await;
3306
3307    assert_eq!(client_a.user_id().unwrap(), client_a2.user_id().unwrap());
3308    assert_eq!(client_b.user_id().unwrap(), client_b2.user_id().unwrap());
3309    assert_eq!(client_c.user_id().unwrap(), client_c2.user_id().unwrap());
3310
3311    // User A and User C request that user B become their contact.
3312    client_a
3313        .user_store
3314        .update(cx_a, |store, cx| {
3315            store.request_contact(client_b.user_id().unwrap(), cx)
3316        })
3317        .await
3318        .unwrap();
3319    client_c
3320        .user_store
3321        .update(cx_c, |store, cx| {
3322            store.request_contact(client_b.user_id().unwrap(), cx)
3323        })
3324        .await
3325        .unwrap();
3326    executor.run_until_parked();
3327
3328    // All users see the pending request appear in all their clients.
3329    assert_eq!(
3330        client_a.summarize_contacts(&cx_a).outgoing_requests,
3331        &["user_b"]
3332    );
3333    assert_eq!(
3334        client_a2.summarize_contacts(&cx_a2).outgoing_requests,
3335        &["user_b"]
3336    );
3337    assert_eq!(
3338        client_b.summarize_contacts(&cx_b).incoming_requests,
3339        &["user_a", "user_c"]
3340    );
3341    assert_eq!(
3342        client_b2.summarize_contacts(&cx_b2).incoming_requests,
3343        &["user_a", "user_c"]
3344    );
3345    assert_eq!(
3346        client_c.summarize_contacts(&cx_c).outgoing_requests,
3347        &["user_b"]
3348    );
3349    assert_eq!(
3350        client_c2.summarize_contacts(&cx_c2).outgoing_requests,
3351        &["user_b"]
3352    );
3353
3354    // Contact requests are present upon connecting (tested here via disconnect/reconnect)
3355    disconnect_and_reconnect(&client_a, cx_a).await;
3356    disconnect_and_reconnect(&client_b, cx_b).await;
3357    disconnect_and_reconnect(&client_c, cx_c).await;
3358    executor.run_until_parked();
3359    assert_eq!(
3360        client_a.summarize_contacts(&cx_a).outgoing_requests,
3361        &["user_b"]
3362    );
3363    assert_eq!(
3364        client_b.summarize_contacts(&cx_b).incoming_requests,
3365        &["user_a", "user_c"]
3366    );
3367    assert_eq!(
3368        client_c.summarize_contacts(&cx_c).outgoing_requests,
3369        &["user_b"]
3370    );
3371
3372    // User B accepts the request from user A.
3373    client_b
3374        .user_store
3375        .update(cx_b, |store, cx| {
3376            store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
3377        })
3378        .await
3379        .unwrap();
3380
3381    executor.run_until_parked();
3382
3383    // User B sees user A as their contact now in all client, and the incoming request from them is removed.
3384    let contacts_b = client_b.summarize_contacts(&cx_b);
3385    assert_eq!(contacts_b.current, &["user_a", "user_b"]);
3386    assert_eq!(contacts_b.incoming_requests, &["user_c"]);
3387    let contacts_b2 = client_b2.summarize_contacts(&cx_b2);
3388    assert_eq!(contacts_b2.current, &["user_a", "user_b"]);
3389    assert_eq!(contacts_b2.incoming_requests, &["user_c"]);
3390
3391    // User A sees user B as their contact now in all clients, and the outgoing request to them is removed.
3392    let contacts_a = client_a.summarize_contacts(&cx_a);
3393    assert_eq!(contacts_a.current, &["user_a", "user_b"]);
3394    assert!(contacts_a.outgoing_requests.is_empty());
3395    let contacts_a2 = client_a2.summarize_contacts(&cx_a2);
3396    assert_eq!(contacts_a2.current, &["user_a", "user_b"]);
3397    assert!(contacts_a2.outgoing_requests.is_empty());
3398
3399    // Contacts are present upon connecting (tested here via disconnect/reconnect)
3400    disconnect_and_reconnect(&client_a, cx_a).await;
3401    disconnect_and_reconnect(&client_b, cx_b).await;
3402    disconnect_and_reconnect(&client_c, cx_c).await;
3403    executor.run_until_parked();
3404    assert_eq!(
3405        client_a.summarize_contacts(&cx_a).current,
3406        &["user_a", "user_b"]
3407    );
3408    assert_eq!(
3409        client_b.summarize_contacts(&cx_b).current,
3410        &["user_a", "user_b"]
3411    );
3412    assert_eq!(
3413        client_b.summarize_contacts(&cx_b).incoming_requests,
3414        &["user_c"]
3415    );
3416    assert_eq!(client_c.summarize_contacts(&cx_c).current, &["user_c"]);
3417    assert_eq!(
3418        client_c.summarize_contacts(&cx_c).outgoing_requests,
3419        &["user_b"]
3420    );
3421
3422    // User B rejects the request from user C.
3423    client_b
3424        .user_store
3425        .update(cx_b, |store, cx| {
3426            store.respond_to_contact_request(client_c.user_id().unwrap(), false, cx)
3427        })
3428        .await
3429        .unwrap();
3430
3431    executor.run_until_parked();
3432
3433    // User B doesn't see user C as their contact, and the incoming request from them is removed.
3434    let contacts_b = client_b.summarize_contacts(&cx_b);
3435    assert_eq!(contacts_b.current, &["user_a", "user_b"]);
3436    assert!(contacts_b.incoming_requests.is_empty());
3437    let contacts_b2 = client_b2.summarize_contacts(&cx_b2);
3438    assert_eq!(contacts_b2.current, &["user_a", "user_b"]);
3439    assert!(contacts_b2.incoming_requests.is_empty());
3440
3441    // User C doesn't see user B as their contact, and the outgoing request to them is removed.
3442    let contacts_c = client_c.summarize_contacts(&cx_c);
3443    assert_eq!(contacts_c.current, &["user_c"]);
3444    assert!(contacts_c.outgoing_requests.is_empty());
3445    let contacts_c2 = client_c2.summarize_contacts(&cx_c2);
3446    assert_eq!(contacts_c2.current, &["user_c"]);
3447    assert!(contacts_c2.outgoing_requests.is_empty());
3448
3449    // Incoming/outgoing requests are not present upon connecting (tested here via disconnect/reconnect)
3450    disconnect_and_reconnect(&client_a, cx_a).await;
3451    disconnect_and_reconnect(&client_b, cx_b).await;
3452    disconnect_and_reconnect(&client_c, cx_c).await;
3453    executor.run_until_parked();
3454    assert_eq!(
3455        client_a.summarize_contacts(&cx_a).current,
3456        &["user_a", "user_b"]
3457    );
3458    assert_eq!(
3459        client_b.summarize_contacts(&cx_b).current,
3460        &["user_a", "user_b"]
3461    );
3462    assert!(client_b
3463        .summarize_contacts(&cx_b)
3464        .incoming_requests
3465        .is_empty());
3466    assert_eq!(client_c.summarize_contacts(&cx_c).current, &["user_c"]);
3467    assert!(client_c
3468        .summarize_contacts(&cx_c)
3469        .outgoing_requests
3470        .is_empty());
3471
3472    async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
3473        client.disconnect(&cx.to_async()).unwrap();
3474        client.clear_contacts(cx).await;
3475        client
3476            .authenticate_and_connect(false, &cx.to_async())
3477            .await
3478            .unwrap();
3479    }
3480}
3481
3482#[gpui::test(iterations = 10)]
3483async fn test_following(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3484    cx_a.foreground().forbid_parking();
3485    let fs = FakeFs::new(cx_a.background());
3486
3487    // 2 clients connect to a server.
3488    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3489    let client_a = server.create_client(cx_a, "user_a").await;
3490    let client_b = server.create_client(cx_b, "user_b").await;
3491    server
3492        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
3493        .await;
3494    cx_a.update(editor::init);
3495    cx_b.update(editor::init);
3496
3497    // Client A shares a project.
3498    fs.insert_tree(
3499        "/a",
3500        json!({
3501            "1.txt": "one",
3502            "2.txt": "two",
3503            "3.txt": "three",
3504        }),
3505    )
3506    .await;
3507    let (project_a, worktree_id) = client_a.build_local_project(fs.clone(), "/a", cx_a).await;
3508
3509    // Client B joins the project.
3510    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
3511
3512    // Client A opens some editors.
3513    let workspace_a = client_a.build_workspace(&project_a, cx_a);
3514    let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
3515    let editor_a1 = workspace_a
3516        .update(cx_a, |workspace, cx| {
3517            workspace.open_path((worktree_id, "1.txt"), true, cx)
3518        })
3519        .await
3520        .unwrap()
3521        .downcast::<Editor>()
3522        .unwrap();
3523    let editor_a2 = workspace_a
3524        .update(cx_a, |workspace, cx| {
3525            workspace.open_path((worktree_id, "2.txt"), true, cx)
3526        })
3527        .await
3528        .unwrap()
3529        .downcast::<Editor>()
3530        .unwrap();
3531
3532    // Client B opens an editor.
3533    let workspace_b = client_b.build_workspace(&project_b, cx_b);
3534    let editor_b1 = workspace_b
3535        .update(cx_b, |workspace, cx| {
3536            workspace.open_path((worktree_id, "1.txt"), true, cx)
3537        })
3538        .await
3539        .unwrap()
3540        .downcast::<Editor>()
3541        .unwrap();
3542
3543    let client_a_id = project_b.read_with(cx_b, |project, _| {
3544        project.collaborators().values().next().unwrap().peer_id
3545    });
3546    let client_b_id = project_a.read_with(cx_a, |project, _| {
3547        project.collaborators().values().next().unwrap().peer_id
3548    });
3549
3550    // When client B starts following client A, all visible view states are replicated to client B.
3551    editor_a1.update(cx_a, |editor, cx| {
3552        editor.change_selections(None, cx, |s| s.select_ranges([0..1]))
3553    });
3554    editor_a2.update(cx_a, |editor, cx| {
3555        editor.change_selections(None, cx, |s| s.select_ranges([2..3]))
3556    });
3557    workspace_b
3558        .update(cx_b, |workspace, cx| {
3559            workspace
3560                .toggle_follow(&ToggleFollow(client_a_id), cx)
3561                .unwrap()
3562        })
3563        .await
3564        .unwrap();
3565
3566    let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
3567        workspace
3568            .active_item(cx)
3569            .unwrap()
3570            .downcast::<Editor>()
3571            .unwrap()
3572    });
3573    assert!(cx_b.read(|cx| editor_b2.is_focused(cx)));
3574    assert_eq!(
3575        editor_b2.read_with(cx_b, |editor, cx| editor.project_path(cx)),
3576        Some((worktree_id, "2.txt").into())
3577    );
3578    assert_eq!(
3579        editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
3580        vec![2..3]
3581    );
3582    assert_eq!(
3583        editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
3584        vec![0..1]
3585    );
3586
3587    // When client A activates a different editor, client B does so as well.
3588    workspace_a.update(cx_a, |workspace, cx| {
3589        workspace.activate_item(&editor_a1, cx)
3590    });
3591    workspace_b
3592        .condition(cx_b, |workspace, cx| {
3593            workspace.active_item(cx).unwrap().id() == editor_b1.id()
3594        })
3595        .await;
3596
3597    // When client A navigates back and forth, client B does so as well.
3598    workspace_a
3599        .update(cx_a, |workspace, cx| {
3600            workspace::Pane::go_back(workspace, None, cx)
3601        })
3602        .await;
3603    workspace_b
3604        .condition(cx_b, |workspace, cx| {
3605            workspace.active_item(cx).unwrap().id() == editor_b2.id()
3606        })
3607        .await;
3608
3609    workspace_a
3610        .update(cx_a, |workspace, cx| {
3611            workspace::Pane::go_forward(workspace, None, cx)
3612        })
3613        .await;
3614    workspace_b
3615        .condition(cx_b, |workspace, cx| {
3616            workspace.active_item(cx).unwrap().id() == editor_b1.id()
3617        })
3618        .await;
3619
3620    // Changes to client A's editor are reflected on client B.
3621    editor_a1.update(cx_a, |editor, cx| {
3622        editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
3623    });
3624    editor_b1
3625        .condition(cx_b, |editor, cx| {
3626            editor.selections.ranges(cx) == vec![1..1, 2..2]
3627        })
3628        .await;
3629
3630    editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
3631    editor_b1
3632        .condition(cx_b, |editor, cx| editor.text(cx) == "TWO")
3633        .await;
3634
3635    editor_a1.update(cx_a, |editor, cx| {
3636        editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3637        editor.set_scroll_position(vec2f(0., 100.), cx);
3638    });
3639    editor_b1
3640        .condition(cx_b, |editor, cx| {
3641            editor.selections.ranges(cx) == vec![3..3]
3642        })
3643        .await;
3644
3645    // After unfollowing, client B stops receiving updates from client A.
3646    workspace_b.update(cx_b, |workspace, cx| {
3647        workspace.unfollow(&workspace.active_pane().clone(), cx)
3648    });
3649    workspace_a.update(cx_a, |workspace, cx| {
3650        workspace.activate_item(&editor_a2, cx)
3651    });
3652    cx_a.foreground().run_until_parked();
3653    assert_eq!(
3654        workspace_b.read_with(cx_b, |workspace, cx| workspace
3655            .active_item(cx)
3656            .unwrap()
3657            .id()),
3658        editor_b1.id()
3659    );
3660
3661    // Client A starts following client B.
3662    workspace_a
3663        .update(cx_a, |workspace, cx| {
3664            workspace
3665                .toggle_follow(&ToggleFollow(client_b_id), cx)
3666                .unwrap()
3667        })
3668        .await
3669        .unwrap();
3670    assert_eq!(
3671        workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
3672        Some(client_b_id)
3673    );
3674    assert_eq!(
3675        workspace_a.read_with(cx_a, |workspace, cx| workspace
3676            .active_item(cx)
3677            .unwrap()
3678            .id()),
3679        editor_a1.id()
3680    );
3681
3682    // Following interrupts when client B disconnects.
3683    client_b.disconnect(&cx_b.to_async()).unwrap();
3684    cx_a.foreground().run_until_parked();
3685    assert_eq!(
3686        workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
3687        None
3688    );
3689}
3690
3691#[gpui::test(iterations = 10)]
3692async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3693    cx_a.foreground().forbid_parking();
3694    let fs = FakeFs::new(cx_a.background());
3695
3696    // 2 clients connect to a server.
3697    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3698    let client_a = server.create_client(cx_a, "user_a").await;
3699    let client_b = server.create_client(cx_b, "user_b").await;
3700    server
3701        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
3702        .await;
3703    cx_a.update(editor::init);
3704    cx_b.update(editor::init);
3705
3706    // Client A shares a project.
3707    fs.insert_tree(
3708        "/a",
3709        json!({
3710            "1.txt": "one",
3711            "2.txt": "two",
3712            "3.txt": "three",
3713            "4.txt": "four",
3714        }),
3715    )
3716    .await;
3717    let (project_a, worktree_id) = client_a.build_local_project(fs.clone(), "/a", cx_a).await;
3718
3719    // Client B joins the project.
3720    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
3721
3722    // Client A opens some editors.
3723    let workspace_a = client_a.build_workspace(&project_a, cx_a);
3724    let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
3725    let _editor_a1 = workspace_a
3726        .update(cx_a, |workspace, cx| {
3727            workspace.open_path((worktree_id, "1.txt"), true, cx)
3728        })
3729        .await
3730        .unwrap()
3731        .downcast::<Editor>()
3732        .unwrap();
3733
3734    // Client B opens an editor.
3735    let workspace_b = client_b.build_workspace(&project_b, cx_b);
3736    let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
3737    let _editor_b1 = workspace_b
3738        .update(cx_b, |workspace, cx| {
3739            workspace.open_path((worktree_id, "2.txt"), true, cx)
3740        })
3741        .await
3742        .unwrap()
3743        .downcast::<Editor>()
3744        .unwrap();
3745
3746    // Clients A and B follow each other in split panes
3747    workspace_a.update(cx_a, |workspace, cx| {
3748        workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
3749        assert_ne!(*workspace.active_pane(), pane_a1);
3750    });
3751    workspace_a
3752        .update(cx_a, |workspace, cx| {
3753            let leader_id = *project_a.read(cx).collaborators().keys().next().unwrap();
3754            workspace
3755                .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
3756                .unwrap()
3757        })
3758        .await
3759        .unwrap();
3760    workspace_b.update(cx_b, |workspace, cx| {
3761        workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
3762        assert_ne!(*workspace.active_pane(), pane_b1);
3763    });
3764    workspace_b
3765        .update(cx_b, |workspace, cx| {
3766            let leader_id = *project_b.read(cx).collaborators().keys().next().unwrap();
3767            workspace
3768                .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
3769                .unwrap()
3770        })
3771        .await
3772        .unwrap();
3773
3774    workspace_a
3775        .update(cx_a, |workspace, cx| {
3776            workspace.activate_next_pane(cx);
3777            assert_eq!(*workspace.active_pane(), pane_a1);
3778            workspace.open_path((worktree_id, "3.txt"), true, cx)
3779        })
3780        .await
3781        .unwrap();
3782    workspace_b
3783        .update(cx_b, |workspace, cx| {
3784            workspace.activate_next_pane(cx);
3785            assert_eq!(*workspace.active_pane(), pane_b1);
3786            workspace.open_path((worktree_id, "4.txt"), true, cx)
3787        })
3788        .await
3789        .unwrap();
3790    cx_a.foreground().run_until_parked();
3791
3792    // Ensure leader updates don't change the active pane of followers
3793    workspace_a.read_with(cx_a, |workspace, _| {
3794        assert_eq!(*workspace.active_pane(), pane_a1);
3795    });
3796    workspace_b.read_with(cx_b, |workspace, _| {
3797        assert_eq!(*workspace.active_pane(), pane_b1);
3798    });
3799
3800    // Ensure peers following each other doesn't cause an infinite loop.
3801    assert_eq!(
3802        workspace_a.read_with(cx_a, |workspace, cx| workspace
3803            .active_item(cx)
3804            .unwrap()
3805            .project_path(cx)),
3806        Some((worktree_id, "3.txt").into())
3807    );
3808    workspace_a.update(cx_a, |workspace, cx| {
3809        assert_eq!(
3810            workspace.active_item(cx).unwrap().project_path(cx),
3811            Some((worktree_id, "3.txt").into())
3812        );
3813        workspace.activate_next_pane(cx);
3814        assert_eq!(
3815            workspace.active_item(cx).unwrap().project_path(cx),
3816            Some((worktree_id, "4.txt").into())
3817        );
3818    });
3819    workspace_b.update(cx_b, |workspace, cx| {
3820        assert_eq!(
3821            workspace.active_item(cx).unwrap().project_path(cx),
3822            Some((worktree_id, "4.txt").into())
3823        );
3824        workspace.activate_next_pane(cx);
3825        assert_eq!(
3826            workspace.active_item(cx).unwrap().project_path(cx),
3827            Some((worktree_id, "3.txt").into())
3828        );
3829    });
3830}
3831
3832#[gpui::test(iterations = 10)]
3833async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3834    cx_a.foreground().forbid_parking();
3835
3836    // 2 clients connect to a server.
3837    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3838    let client_a = server.create_client(cx_a, "user_a").await;
3839    let client_b = server.create_client(cx_b, "user_b").await;
3840    server
3841        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
3842        .await;
3843    cx_a.update(editor::init);
3844    cx_b.update(editor::init);
3845
3846    // Client A shares a project.
3847    let fs = FakeFs::new(cx_a.background());
3848    fs.insert_tree(
3849        "/a",
3850        json!({
3851            "1.txt": "one",
3852            "2.txt": "two",
3853            "3.txt": "three",
3854        }),
3855    )
3856    .await;
3857    let (project_a, worktree_id) = client_a.build_local_project(fs.clone(), "/a", cx_a).await;
3858    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
3859
3860    // Client A opens some editors.
3861    let workspace_a = client_a.build_workspace(&project_a, cx_a);
3862    let _editor_a1 = workspace_a
3863        .update(cx_a, |workspace, cx| {
3864            workspace.open_path((worktree_id, "1.txt"), true, cx)
3865        })
3866        .await
3867        .unwrap()
3868        .downcast::<Editor>()
3869        .unwrap();
3870
3871    // Client B starts following client A.
3872    let workspace_b = client_b.build_workspace(&project_b, cx_b);
3873    let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
3874    let leader_id = project_b.read_with(cx_b, |project, _| {
3875        project.collaborators().values().next().unwrap().peer_id
3876    });
3877    workspace_b
3878        .update(cx_b, |workspace, cx| {
3879            workspace
3880                .toggle_follow(&ToggleFollow(leader_id), cx)
3881                .unwrap()
3882        })
3883        .await
3884        .unwrap();
3885    assert_eq!(
3886        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
3887        Some(leader_id)
3888    );
3889    let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
3890        workspace
3891            .active_item(cx)
3892            .unwrap()
3893            .downcast::<Editor>()
3894            .unwrap()
3895    });
3896
3897    // When client B moves, it automatically stops following client A.
3898    editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
3899    assert_eq!(
3900        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
3901        None
3902    );
3903
3904    workspace_b
3905        .update(cx_b, |workspace, cx| {
3906            workspace
3907                .toggle_follow(&ToggleFollow(leader_id), cx)
3908                .unwrap()
3909        })
3910        .await
3911        .unwrap();
3912    assert_eq!(
3913        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
3914        Some(leader_id)
3915    );
3916
3917    // When client B edits, it automatically stops following client A.
3918    editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
3919    assert_eq!(
3920        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
3921        None
3922    );
3923
3924    workspace_b
3925        .update(cx_b, |workspace, cx| {
3926            workspace
3927                .toggle_follow(&ToggleFollow(leader_id), cx)
3928                .unwrap()
3929        })
3930        .await
3931        .unwrap();
3932    assert_eq!(
3933        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
3934        Some(leader_id)
3935    );
3936
3937    // When client B scrolls, it automatically stops following client A.
3938    editor_b2.update(cx_b, |editor, cx| {
3939        editor.set_scroll_position(vec2f(0., 3.), cx)
3940    });
3941    assert_eq!(
3942        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
3943        None
3944    );
3945
3946    workspace_b
3947        .update(cx_b, |workspace, cx| {
3948            workspace
3949                .toggle_follow(&ToggleFollow(leader_id), cx)
3950                .unwrap()
3951        })
3952        .await
3953        .unwrap();
3954    assert_eq!(
3955        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
3956        Some(leader_id)
3957    );
3958
3959    // When client B activates a different pane, it continues following client A in the original pane.
3960    workspace_b.update(cx_b, |workspace, cx| {
3961        workspace.split_pane(pane_b.clone(), SplitDirection::Right, cx)
3962    });
3963    assert_eq!(
3964        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
3965        Some(leader_id)
3966    );
3967
3968    workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
3969    assert_eq!(
3970        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
3971        Some(leader_id)
3972    );
3973
3974    // When client B activates a different item in the original pane, it automatically stops following client A.
3975    workspace_b
3976        .update(cx_b, |workspace, cx| {
3977            workspace.open_path((worktree_id, "2.txt"), true, cx)
3978        })
3979        .await
3980        .unwrap();
3981    assert_eq!(
3982        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
3983        None
3984    );
3985}
3986
3987#[gpui::test(iterations = 100)]
3988async fn test_random_collaboration(
3989    cx: &mut TestAppContext,
3990    deterministic: Arc<Deterministic>,
3991    rng: StdRng,
3992) {
3993    cx.foreground().forbid_parking();
3994    let max_peers = env::var("MAX_PEERS")
3995        .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
3996        .unwrap_or(5);
3997    assert!(max_peers <= 5);
3998
3999    let max_operations = env::var("OPERATIONS")
4000        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
4001        .unwrap_or(10);
4002
4003    let rng = Arc::new(Mutex::new(rng));
4004
4005    let guest_lang_registry = Arc::new(LanguageRegistry::test());
4006    let host_language_registry = Arc::new(LanguageRegistry::test());
4007
4008    let fs = FakeFs::new(cx.background());
4009    fs.insert_tree("/_collab", json!({"init": ""})).await;
4010
4011    let mut server = TestServer::start(cx.foreground(), cx.background()).await;
4012    let db = server.app_state.db.clone();
4013    let host_user_id = db.create_user("host", None, false).await.unwrap();
4014    for username in ["guest-1", "guest-2", "guest-3", "guest-4"] {
4015        let guest_user_id = db.create_user(username, None, false).await.unwrap();
4016        server
4017            .app_state
4018            .db
4019            .send_contact_request(guest_user_id, host_user_id)
4020            .await
4021            .unwrap();
4022        server
4023            .app_state
4024            .db
4025            .respond_to_contact_request(host_user_id, guest_user_id, true)
4026            .await
4027            .unwrap();
4028    }
4029
4030    let mut clients = Vec::new();
4031    let mut user_ids = Vec::new();
4032    let mut op_start_signals = Vec::new();
4033
4034    let mut next_entity_id = 100000;
4035    let mut host_cx = TestAppContext::new(
4036        cx.foreground_platform(),
4037        cx.platform(),
4038        deterministic.build_foreground(next_entity_id),
4039        deterministic.build_background(),
4040        cx.font_cache(),
4041        cx.leak_detector(),
4042        next_entity_id,
4043    );
4044    let host = server.create_client(&mut host_cx, "host").await;
4045    let host_project = host_cx.update(|cx| {
4046        Project::local(
4047            true,
4048            host.client.clone(),
4049            host.user_store.clone(),
4050            host.project_store.clone(),
4051            host_language_registry.clone(),
4052            fs.clone(),
4053            cx,
4054        )
4055    });
4056    let host_project_id = host_project
4057        .update(&mut host_cx, |p, _| p.next_remote_id())
4058        .await;
4059
4060    let (collab_worktree, _) = host_project
4061        .update(&mut host_cx, |project, cx| {
4062            project.find_or_create_local_worktree("/_collab", true, cx)
4063        })
4064        .await
4065        .unwrap();
4066    collab_worktree
4067        .read_with(&host_cx, |tree, _| tree.as_local().unwrap().scan_complete())
4068        .await;
4069
4070    // Set up fake language servers.
4071    let mut language = Language::new(
4072        LanguageConfig {
4073            name: "Rust".into(),
4074            path_suffixes: vec!["rs".to_string()],
4075            ..Default::default()
4076        },
4077        None,
4078    );
4079    let _fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
4080        name: "the-fake-language-server",
4081        capabilities: lsp::LanguageServer::full_capabilities(),
4082        initializer: Some(Box::new({
4083            let rng = rng.clone();
4084            let fs = fs.clone();
4085            let project = host_project.downgrade();
4086            move |fake_server: &mut FakeLanguageServer| {
4087                fake_server.handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
4088                    Ok(Some(lsp::CompletionResponse::Array(vec![
4089                        lsp::CompletionItem {
4090                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
4091                                range: lsp::Range::new(
4092                                    lsp::Position::new(0, 0),
4093                                    lsp::Position::new(0, 0),
4094                                ),
4095                                new_text: "the-new-text".to_string(),
4096                            })),
4097                            ..Default::default()
4098                        },
4099                    ])))
4100                });
4101
4102                fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
4103                    |_, _| async move {
4104                        Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
4105                            lsp::CodeAction {
4106                                title: "the-code-action".to_string(),
4107                                ..Default::default()
4108                            },
4109                        )]))
4110                    },
4111                );
4112
4113                fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
4114                    |params, _| async move {
4115                        Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
4116                            params.position,
4117                            params.position,
4118                        ))))
4119                    },
4120                );
4121
4122                fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
4123                    let fs = fs.clone();
4124                    let rng = rng.clone();
4125                    move |_, _| {
4126                        let fs = fs.clone();
4127                        let rng = rng.clone();
4128                        async move {
4129                            let files = fs.files().await;
4130                            let mut rng = rng.lock();
4131                            let count = rng.gen_range::<usize, _>(1..3);
4132                            let files = (0..count)
4133                                .map(|_| files.choose(&mut *rng).unwrap())
4134                                .collect::<Vec<_>>();
4135                            log::info!("LSP: Returning definitions in files {:?}", &files);
4136                            Ok(Some(lsp::GotoDefinitionResponse::Array(
4137                                files
4138                                    .into_iter()
4139                                    .map(|file| lsp::Location {
4140                                        uri: lsp::Url::from_file_path(file).unwrap(),
4141                                        range: Default::default(),
4142                                    })
4143                                    .collect(),
4144                            )))
4145                        }
4146                    }
4147                });
4148
4149                fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>({
4150                    let rng = rng.clone();
4151                    let project = project.clone();
4152                    move |params, mut cx| {
4153                        let highlights = if let Some(project) = project.upgrade(&cx) {
4154                            project.update(&mut cx, |project, cx| {
4155                                let path = params
4156                                    .text_document_position_params
4157                                    .text_document
4158                                    .uri
4159                                    .to_file_path()
4160                                    .unwrap();
4161                                let (worktree, relative_path) =
4162                                    project.find_local_worktree(&path, cx)?;
4163                                let project_path =
4164                                    ProjectPath::from((worktree.read(cx).id(), relative_path));
4165                                let buffer = project.get_open_buffer(&project_path, cx)?.read(cx);
4166
4167                                let mut highlights = Vec::new();
4168                                let highlight_count = rng.lock().gen_range(1..=5);
4169                                let mut prev_end = 0;
4170                                for _ in 0..highlight_count {
4171                                    let range =
4172                                        buffer.random_byte_range(prev_end, &mut *rng.lock());
4173
4174                                    highlights.push(lsp::DocumentHighlight {
4175                                        range: range_to_lsp(range.to_point_utf16(buffer)),
4176                                        kind: Some(lsp::DocumentHighlightKind::READ),
4177                                    });
4178                                    prev_end = range.end;
4179                                }
4180                                Some(highlights)
4181                            })
4182                        } else {
4183                            None
4184                        };
4185                        async move { Ok(highlights) }
4186                    }
4187                });
4188            }
4189        })),
4190        ..Default::default()
4191    });
4192    host_language_registry.add(Arc::new(language));
4193
4194    let op_start_signal = futures::channel::mpsc::unbounded();
4195    user_ids.push(host.current_user_id(&host_cx));
4196    op_start_signals.push(op_start_signal.0);
4197    clients.push(host_cx.foreground().spawn(host.simulate_host(
4198        host_project,
4199        op_start_signal.1,
4200        rng.clone(),
4201        host_cx,
4202    )));
4203
4204    let disconnect_host_at = if rng.lock().gen_bool(0.2) {
4205        rng.lock().gen_range(0..max_operations)
4206    } else {
4207        max_operations
4208    };
4209    let mut available_guests = vec![
4210        "guest-1".to_string(),
4211        "guest-2".to_string(),
4212        "guest-3".to_string(),
4213        "guest-4".to_string(),
4214    ];
4215    let mut operations = 0;
4216    while operations < max_operations {
4217        if operations == disconnect_host_at {
4218            server.disconnect_client(user_ids[0]);
4219            cx.foreground().advance_clock(RECEIVE_TIMEOUT);
4220            drop(op_start_signals);
4221            let mut clients = futures::future::join_all(clients).await;
4222            cx.foreground().run_until_parked();
4223
4224            let (host, host_project, mut host_cx, host_err) = clients.remove(0);
4225            if let Some(host_err) = host_err {
4226                log::error!("host error - {:?}", host_err);
4227            }
4228            host_project.read_with(&host_cx, |project, _| assert!(!project.is_shared()));
4229            for (guest, guest_project, mut guest_cx, guest_err) in clients {
4230                if let Some(guest_err) = guest_err {
4231                    log::error!("{} error - {:?}", guest.username, guest_err);
4232                }
4233
4234                let contacts = server
4235                    .app_state
4236                    .db
4237                    .get_contacts(guest.current_user_id(&guest_cx))
4238                    .await
4239                    .unwrap();
4240                let contacts = server
4241                    .store
4242                    .read()
4243                    .await
4244                    .build_initial_contacts_update(contacts)
4245                    .contacts;
4246                assert!(!contacts
4247                    .iter()
4248                    .flat_map(|contact| &contact.projects)
4249                    .any(|project| project.id == host_project_id));
4250                guest_project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
4251                guest_cx.update(|_| drop((guest, guest_project)));
4252            }
4253            host_cx.update(|_| drop((host, host_project)));
4254
4255            return;
4256        }
4257
4258        let distribution = rng.lock().gen_range(0..100);
4259        match distribution {
4260            0..=19 if !available_guests.is_empty() => {
4261                let guest_ix = rng.lock().gen_range(0..available_guests.len());
4262                let guest_username = available_guests.remove(guest_ix);
4263                log::info!("Adding new connection for {}", guest_username);
4264                next_entity_id += 100000;
4265                let mut guest_cx = TestAppContext::new(
4266                    cx.foreground_platform(),
4267                    cx.platform(),
4268                    deterministic.build_foreground(next_entity_id),
4269                    deterministic.build_background(),
4270                    cx.font_cache(),
4271                    cx.leak_detector(),
4272                    next_entity_id,
4273                );
4274                let guest = server.create_client(&mut guest_cx, &guest_username).await;
4275                let guest_project = Project::remote(
4276                    host_project_id,
4277                    guest.client.clone(),
4278                    guest.user_store.clone(),
4279                    guest.project_store.clone(),
4280                    guest_lang_registry.clone(),
4281                    FakeFs::new(cx.background()),
4282                    guest_cx.to_async(),
4283                )
4284                .await
4285                .unwrap();
4286                let op_start_signal = futures::channel::mpsc::unbounded();
4287                user_ids.push(guest.current_user_id(&guest_cx));
4288                op_start_signals.push(op_start_signal.0);
4289                clients.push(guest_cx.foreground().spawn(guest.simulate_guest(
4290                    guest_username.clone(),
4291                    guest_project,
4292                    op_start_signal.1,
4293                    rng.clone(),
4294                    guest_cx,
4295                )));
4296
4297                log::info!("Added connection for {}", guest_username);
4298                operations += 1;
4299            }
4300            20..=29 if clients.len() > 1 => {
4301                let guest_ix = rng.lock().gen_range(1..clients.len());
4302                log::info!("Removing guest {}", user_ids[guest_ix]);
4303                let removed_guest_id = user_ids.remove(guest_ix);
4304                let guest = clients.remove(guest_ix);
4305                op_start_signals.remove(guest_ix);
4306                server.forbid_connections();
4307                server.disconnect_client(removed_guest_id);
4308                cx.foreground().advance_clock(RECEIVE_TIMEOUT);
4309                let (guest, guest_project, mut guest_cx, guest_err) = guest.await;
4310                server.allow_connections();
4311
4312                if let Some(guest_err) = guest_err {
4313                    log::error!("{} error - {:?}", guest.username, guest_err);
4314                }
4315                guest_project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
4316                for user_id in &user_ids {
4317                    let contacts = server.app_state.db.get_contacts(*user_id).await.unwrap();
4318                    let contacts = server
4319                        .store
4320                        .read()
4321                        .await
4322                        .build_initial_contacts_update(contacts)
4323                        .contacts;
4324                    for contact in contacts {
4325                        if contact.online {
4326                            assert_ne!(
4327                                contact.user_id, removed_guest_id.0 as u64,
4328                                "removed guest is still a contact of another peer"
4329                            );
4330                        }
4331                        for project in contact.projects {
4332                            for project_guest_id in project.guests {
4333                                assert_ne!(
4334                                    project_guest_id, removed_guest_id.0 as u64,
4335                                    "removed guest appears as still participating on a project"
4336                                );
4337                            }
4338                        }
4339                    }
4340                }
4341
4342                log::info!("{} removed", guest.username);
4343                available_guests.push(guest.username.clone());
4344                guest_cx.update(|_| drop((guest, guest_project)));
4345
4346                operations += 1;
4347            }
4348            _ => {
4349                while operations < max_operations && rng.lock().gen_bool(0.7) {
4350                    op_start_signals
4351                        .choose(&mut *rng.lock())
4352                        .unwrap()
4353                        .unbounded_send(())
4354                        .unwrap();
4355                    operations += 1;
4356                }
4357
4358                if rng.lock().gen_bool(0.8) {
4359                    cx.foreground().run_until_parked();
4360                }
4361            }
4362        }
4363    }
4364
4365    drop(op_start_signals);
4366    let mut clients = futures::future::join_all(clients).await;
4367    cx.foreground().run_until_parked();
4368
4369    let (host_client, host_project, mut host_cx, host_err) = clients.remove(0);
4370    if let Some(host_err) = host_err {
4371        panic!("host error - {:?}", host_err);
4372    }
4373    let host_worktree_snapshots = host_project.read_with(&host_cx, |project, cx| {
4374        project
4375            .worktrees(cx)
4376            .map(|worktree| {
4377                let snapshot = worktree.read(cx).snapshot();
4378                (snapshot.id(), snapshot)
4379            })
4380            .collect::<BTreeMap<_, _>>()
4381    });
4382
4383    host_project.read_with(&host_cx, |project, cx| project.check_invariants(cx));
4384
4385    for (guest_client, guest_project, mut guest_cx, guest_err) in clients.into_iter() {
4386        if let Some(guest_err) = guest_err {
4387            panic!("{} error - {:?}", guest_client.username, guest_err);
4388        }
4389        let worktree_snapshots = guest_project.read_with(&guest_cx, |project, cx| {
4390            project
4391                .worktrees(cx)
4392                .map(|worktree| {
4393                    let worktree = worktree.read(cx);
4394                    (worktree.id(), worktree.snapshot())
4395                })
4396                .collect::<BTreeMap<_, _>>()
4397        });
4398
4399        assert_eq!(
4400            worktree_snapshots.keys().collect::<Vec<_>>(),
4401            host_worktree_snapshots.keys().collect::<Vec<_>>(),
4402            "{} has different worktrees than the host",
4403            guest_client.username
4404        );
4405        for (id, host_snapshot) in &host_worktree_snapshots {
4406            let guest_snapshot = &worktree_snapshots[id];
4407            assert_eq!(
4408                guest_snapshot.root_name(),
4409                host_snapshot.root_name(),
4410                "{} has different root name than the host for worktree {}",
4411                guest_client.username,
4412                id
4413            );
4414            assert_eq!(
4415                guest_snapshot.entries(false).collect::<Vec<_>>(),
4416                host_snapshot.entries(false).collect::<Vec<_>>(),
4417                "{} has different snapshot than the host for worktree {}",
4418                guest_client.username,
4419                id
4420            );
4421            assert_eq!(guest_snapshot.scan_id(), host_snapshot.scan_id());
4422        }
4423
4424        guest_project.read_with(&guest_cx, |project, cx| project.check_invariants(cx));
4425
4426        for guest_buffer in &guest_client.buffers {
4427            let buffer_id = guest_buffer.read_with(&guest_cx, |buffer, _| buffer.remote_id());
4428            let host_buffer = host_project.read_with(&host_cx, |project, cx| {
4429                project.buffer_for_id(buffer_id, cx).expect(&format!(
4430                    "host does not have buffer for guest:{}, peer:{}, id:{}",
4431                    guest_client.username, guest_client.peer_id, buffer_id
4432                ))
4433            });
4434            let path =
4435                host_buffer.read_with(&host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
4436
4437            assert_eq!(
4438                guest_buffer.read_with(&guest_cx, |buffer, _| buffer.deferred_ops_len()),
4439                0,
4440                "{}, buffer {}, path {:?} has deferred operations",
4441                guest_client.username,
4442                buffer_id,
4443                path,
4444            );
4445            assert_eq!(
4446                guest_buffer.read_with(&guest_cx, |buffer, _| buffer.text()),
4447                host_buffer.read_with(&host_cx, |buffer, _| buffer.text()),
4448                "{}, buffer {}, path {:?}, differs from the host's buffer",
4449                guest_client.username,
4450                buffer_id,
4451                path
4452            );
4453        }
4454
4455        guest_cx.update(|_| drop((guest_project, guest_client)));
4456    }
4457
4458    host_cx.update(|_| drop((host_client, host_project)));
4459}
4460
4461struct TestServer {
4462    peer: Arc<Peer>,
4463    app_state: Arc<AppState>,
4464    server: Arc<Server>,
4465    foreground: Rc<executor::Foreground>,
4466    notifications: mpsc::UnboundedReceiver<()>,
4467    connection_killers: Arc<Mutex<HashMap<UserId, Arc<AtomicBool>>>>,
4468    forbid_connections: Arc<AtomicBool>,
4469    _test_db: TestDb,
4470}
4471
4472impl TestServer {
4473    async fn start(
4474        foreground: Rc<executor::Foreground>,
4475        background: Arc<executor::Background>,
4476    ) -> Self {
4477        let test_db = TestDb::fake(background);
4478        let app_state = Self::build_app_state(&test_db).await;
4479        let peer = Peer::new();
4480        let notifications = mpsc::unbounded();
4481        let server = Server::new(app_state.clone(), Some(notifications.0));
4482        Self {
4483            peer,
4484            app_state,
4485            server,
4486            foreground,
4487            notifications: notifications.1,
4488            connection_killers: Default::default(),
4489            forbid_connections: Default::default(),
4490            _test_db: test_db,
4491        }
4492    }
4493
4494    async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient {
4495        cx.update(|cx| {
4496            let settings = Settings::test(cx);
4497            cx.set_global(settings);
4498        });
4499
4500        let http = FakeHttpClient::with_404_response();
4501        let user_id = if let Ok(Some(user)) = self.app_state.db.get_user_by_github_login(name).await
4502        {
4503            user.id
4504        } else {
4505            self.app_state
4506                .db
4507                .create_user(name, None, false)
4508                .await
4509                .unwrap()
4510        };
4511        let client_name = name.to_string();
4512        let mut client = Client::new(http.clone());
4513        let server = self.server.clone();
4514        let db = self.app_state.db.clone();
4515        let connection_killers = self.connection_killers.clone();
4516        let forbid_connections = self.forbid_connections.clone();
4517        let (connection_id_tx, mut connection_id_rx) = mpsc::channel(16);
4518
4519        Arc::get_mut(&mut client)
4520            .unwrap()
4521            .override_authenticate(move |cx| {
4522                cx.spawn(|_| async move {
4523                    let access_token = "the-token".to_string();
4524                    Ok(Credentials {
4525                        user_id: user_id.0 as u64,
4526                        access_token,
4527                    })
4528                })
4529            })
4530            .override_establish_connection(move |credentials, cx| {
4531                assert_eq!(credentials.user_id, user_id.0 as u64);
4532                assert_eq!(credentials.access_token, "the-token");
4533
4534                let server = server.clone();
4535                let db = db.clone();
4536                let connection_killers = connection_killers.clone();
4537                let forbid_connections = forbid_connections.clone();
4538                let client_name = client_name.clone();
4539                let connection_id_tx = connection_id_tx.clone();
4540                cx.spawn(move |cx| async move {
4541                    if forbid_connections.load(SeqCst) {
4542                        Err(EstablishConnectionError::other(anyhow!(
4543                            "server is forbidding connections"
4544                        )))
4545                    } else {
4546                        let (client_conn, server_conn, killed) =
4547                            Connection::in_memory(cx.background());
4548                        connection_killers.lock().insert(user_id, killed);
4549                        let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
4550                        cx.background()
4551                            .spawn(server.handle_connection(
4552                                server_conn,
4553                                client_name,
4554                                user,
4555                                Some(connection_id_tx),
4556                                cx.background(),
4557                            ))
4558                            .detach();
4559                        Ok(client_conn)
4560                    }
4561                })
4562            });
4563
4564        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
4565        let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
4566        let app_state = Arc::new(workspace::AppState {
4567            client: client.clone(),
4568            user_store: user_store.clone(),
4569            project_store: project_store.clone(),
4570            languages: Arc::new(LanguageRegistry::new(Task::ready(()))),
4571            themes: ThemeRegistry::new((), cx.font_cache()),
4572            fs: FakeFs::new(cx.background()),
4573            build_window_options: || Default::default(),
4574            initialize_workspace: |_, _, _| unimplemented!(),
4575        });
4576
4577        Channel::init(&client);
4578        Project::init(&client);
4579        cx.update(|cx| workspace::init(app_state.clone(), cx));
4580
4581        client
4582            .authenticate_and_connect(false, &cx.to_async())
4583            .await
4584            .unwrap();
4585        let peer_id = PeerId(connection_id_rx.next().await.unwrap().0);
4586
4587        let client = TestClient {
4588            client,
4589            peer_id,
4590            username: name.to_string(),
4591            user_store,
4592            project_store,
4593            language_registry: Arc::new(LanguageRegistry::test()),
4594            buffers: Default::default(),
4595        };
4596        client.wait_for_current_user(cx).await;
4597        client
4598    }
4599
4600    fn disconnect_client(&self, user_id: UserId) {
4601        self.connection_killers
4602            .lock()
4603            .remove(&user_id)
4604            .unwrap()
4605            .store(true, SeqCst);
4606    }
4607
4608    fn forbid_connections(&self) {
4609        self.forbid_connections.store(true, SeqCst);
4610    }
4611
4612    fn allow_connections(&self) {
4613        self.forbid_connections.store(false, SeqCst);
4614    }
4615
4616    async fn make_contacts(&self, mut clients: Vec<(&TestClient, &mut TestAppContext)>) {
4617        while let Some((client_a, cx_a)) = clients.pop() {
4618            for (client_b, cx_b) in &mut clients {
4619                client_a
4620                    .user_store
4621                    .update(cx_a, |store, cx| {
4622                        store.request_contact(client_b.user_id().unwrap(), cx)
4623                    })
4624                    .await
4625                    .unwrap();
4626                cx_a.foreground().run_until_parked();
4627                client_b
4628                    .user_store
4629                    .update(*cx_b, |store, cx| {
4630                        store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
4631                    })
4632                    .await
4633                    .unwrap();
4634            }
4635        }
4636    }
4637
4638    async fn build_app_state(test_db: &TestDb) -> Arc<AppState> {
4639        Arc::new(AppState {
4640            db: test_db.db().clone(),
4641            api_token: Default::default(),
4642            invite_link_prefix: Default::default(),
4643        })
4644    }
4645
4646    async fn state<'a>(&'a self) -> RwLockReadGuard<'a, Store> {
4647        self.server.store.read().await
4648    }
4649
4650    async fn condition<F>(&mut self, mut predicate: F)
4651    where
4652        F: FnMut(&Store) -> bool,
4653    {
4654        assert!(
4655            self.foreground.parking_forbidden(),
4656            "you must call forbid_parking to use server conditions so we don't block indefinitely"
4657        );
4658        while !(predicate)(&*self.server.store.read().await) {
4659            self.foreground.start_waiting();
4660            self.notifications.next().await;
4661            self.foreground.finish_waiting();
4662        }
4663    }
4664}
4665
4666impl Deref for TestServer {
4667    type Target = Server;
4668
4669    fn deref(&self) -> &Self::Target {
4670        &self.server
4671    }
4672}
4673
4674impl Drop for TestServer {
4675    fn drop(&mut self) {
4676        self.peer.reset();
4677    }
4678}
4679
4680struct TestClient {
4681    client: Arc<Client>,
4682    username: String,
4683    pub peer_id: PeerId,
4684    pub user_store: ModelHandle<UserStore>,
4685    pub project_store: ModelHandle<ProjectStore>,
4686    language_registry: Arc<LanguageRegistry>,
4687    buffers: HashSet<ModelHandle<language::Buffer>>,
4688}
4689
4690impl Deref for TestClient {
4691    type Target = Arc<Client>;
4692
4693    fn deref(&self) -> &Self::Target {
4694        &self.client
4695    }
4696}
4697
4698struct ContactsSummary {
4699    pub current: Vec<String>,
4700    pub outgoing_requests: Vec<String>,
4701    pub incoming_requests: Vec<String>,
4702}
4703
4704impl TestClient {
4705    pub fn current_user_id(&self, cx: &TestAppContext) -> UserId {
4706        UserId::from_proto(
4707            self.user_store
4708                .read_with(cx, |user_store, _| user_store.current_user().unwrap().id),
4709        )
4710    }
4711
4712    async fn wait_for_current_user(&self, cx: &TestAppContext) {
4713        let mut authed_user = self
4714            .user_store
4715            .read_with(cx, |user_store, _| user_store.watch_current_user());
4716        while authed_user.next().await.unwrap().is_none() {}
4717    }
4718
4719    async fn clear_contacts(&self, cx: &mut TestAppContext) {
4720        self.user_store
4721            .update(cx, |store, _| store.clear_contacts())
4722            .await;
4723    }
4724
4725    fn summarize_contacts(&self, cx: &TestAppContext) -> ContactsSummary {
4726        self.user_store.read_with(cx, |store, _| ContactsSummary {
4727            current: store
4728                .contacts()
4729                .iter()
4730                .map(|contact| contact.user.github_login.clone())
4731                .collect(),
4732            outgoing_requests: store
4733                .outgoing_contact_requests()
4734                .iter()
4735                .map(|user| user.github_login.clone())
4736                .collect(),
4737            incoming_requests: store
4738                .incoming_contact_requests()
4739                .iter()
4740                .map(|user| user.github_login.clone())
4741                .collect(),
4742        })
4743    }
4744
4745    async fn build_local_project(
4746        &self,
4747        fs: Arc<FakeFs>,
4748        root_path: impl AsRef<Path>,
4749        cx: &mut TestAppContext,
4750    ) -> (ModelHandle<Project>, WorktreeId) {
4751        let project = cx.update(|cx| {
4752            Project::local(
4753                true,
4754                self.client.clone(),
4755                self.user_store.clone(),
4756                self.project_store.clone(),
4757                self.language_registry.clone(),
4758                fs,
4759                cx,
4760            )
4761        });
4762        let (worktree, _) = project
4763            .update(cx, |p, cx| {
4764                p.find_or_create_local_worktree(root_path, true, cx)
4765            })
4766            .await
4767            .unwrap();
4768        worktree
4769            .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
4770            .await;
4771        project
4772            .update(cx, |project, _| project.next_remote_id())
4773            .await;
4774        (project, worktree.read_with(cx, |tree, _| tree.id()))
4775    }
4776
4777    async fn build_remote_project(
4778        &self,
4779        host_project: &ModelHandle<Project>,
4780        host_cx: &mut TestAppContext,
4781        guest_cx: &mut TestAppContext,
4782    ) -> ModelHandle<Project> {
4783        let host_project_id = host_project
4784            .read_with(host_cx, |project, _| project.next_remote_id())
4785            .await;
4786        let guest_user_id = self.user_id().unwrap();
4787        let languages = host_project.read_with(host_cx, |project, _| project.languages().clone());
4788        let project_b = guest_cx.spawn(|cx| {
4789            Project::remote(
4790                host_project_id,
4791                self.client.clone(),
4792                self.user_store.clone(),
4793                self.project_store.clone(),
4794                languages,
4795                FakeFs::new(cx.background()),
4796                cx,
4797            )
4798        });
4799        host_cx.foreground().run_until_parked();
4800        host_project.update(host_cx, |project, cx| {
4801            project.respond_to_join_request(guest_user_id, true, cx)
4802        });
4803        let project = project_b.await.unwrap();
4804        project
4805    }
4806
4807    fn build_workspace(
4808        &self,
4809        project: &ModelHandle<Project>,
4810        cx: &mut TestAppContext,
4811    ) -> ViewHandle<Workspace> {
4812        let (window_id, _) = cx.add_window(|_| EmptyView);
4813        cx.add_view(window_id, |cx| Workspace::new(project.clone(), cx))
4814    }
4815
4816    async fn simulate_host(
4817        mut self,
4818        project: ModelHandle<Project>,
4819        op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
4820        rng: Arc<Mutex<StdRng>>,
4821        mut cx: TestAppContext,
4822    ) -> (
4823        Self,
4824        ModelHandle<Project>,
4825        TestAppContext,
4826        Option<anyhow::Error>,
4827    ) {
4828        async fn simulate_host_internal(
4829            client: &mut TestClient,
4830            project: ModelHandle<Project>,
4831            mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
4832            rng: Arc<Mutex<StdRng>>,
4833            cx: &mut TestAppContext,
4834        ) -> anyhow::Result<()> {
4835            let fs = project.read_with(cx, |project, _| project.fs().clone());
4836
4837            cx.update(|cx| {
4838                cx.subscribe(&project, move |project, event, cx| {
4839                    if let project::Event::ContactRequestedJoin(user) = event {
4840                        log::info!("Host: accepting join request from {}", user.github_login);
4841                        project.update(cx, |project, cx| {
4842                            project.respond_to_join_request(user.id, true, cx)
4843                        });
4844                    }
4845                })
4846                .detach();
4847            });
4848
4849            while op_start_signal.next().await.is_some() {
4850                let distribution = rng.lock().gen_range::<usize, _>(0..100);
4851                let files = fs.as_fake().files().await;
4852                match distribution {
4853                    0..=19 if !files.is_empty() => {
4854                        let path = files.choose(&mut *rng.lock()).unwrap();
4855                        let mut path = path.as_path();
4856                        while let Some(parent_path) = path.parent() {
4857                            path = parent_path;
4858                            if rng.lock().gen() {
4859                                break;
4860                            }
4861                        }
4862
4863                        log::info!("Host: find/create local worktree {:?}", path);
4864                        let find_or_create_worktree = project.update(cx, |project, cx| {
4865                            project.find_or_create_local_worktree(path, true, cx)
4866                        });
4867                        if rng.lock().gen() {
4868                            cx.background().spawn(find_or_create_worktree).detach();
4869                        } else {
4870                            find_or_create_worktree.await?;
4871                        }
4872                    }
4873                    20..=79 if !files.is_empty() => {
4874                        let buffer = if client.buffers.is_empty() || rng.lock().gen() {
4875                            let file = files.choose(&mut *rng.lock()).unwrap();
4876                            let (worktree, path) = project
4877                                .update(cx, |project, cx| {
4878                                    project.find_or_create_local_worktree(file.clone(), true, cx)
4879                                })
4880                                .await?;
4881                            let project_path =
4882                                worktree.read_with(cx, |worktree, _| (worktree.id(), path));
4883                            log::info!(
4884                                "Host: opening path {:?}, worktree {}, relative_path {:?}",
4885                                file,
4886                                project_path.0,
4887                                project_path.1
4888                            );
4889                            let buffer = project
4890                                .update(cx, |project, cx| project.open_buffer(project_path, cx))
4891                                .await
4892                                .unwrap();
4893                            client.buffers.insert(buffer.clone());
4894                            buffer
4895                        } else {
4896                            client
4897                                .buffers
4898                                .iter()
4899                                .choose(&mut *rng.lock())
4900                                .unwrap()
4901                                .clone()
4902                        };
4903
4904                        if rng.lock().gen_bool(0.1) {
4905                            cx.update(|cx| {
4906                                log::info!(
4907                                    "Host: dropping buffer {:?}",
4908                                    buffer.read(cx).file().unwrap().full_path(cx)
4909                                );
4910                                client.buffers.remove(&buffer);
4911                                drop(buffer);
4912                            });
4913                        } else {
4914                            buffer.update(cx, |buffer, cx| {
4915                                log::info!(
4916                                    "Host: updating buffer {:?} ({})",
4917                                    buffer.file().unwrap().full_path(cx),
4918                                    buffer.remote_id()
4919                                );
4920
4921                                if rng.lock().gen_bool(0.7) {
4922                                    buffer.randomly_edit(&mut *rng.lock(), 5, cx);
4923                                } else {
4924                                    buffer.randomly_undo_redo(&mut *rng.lock(), cx);
4925                                }
4926                            });
4927                        }
4928                    }
4929                    _ => loop {
4930                        let path_component_count = rng.lock().gen_range::<usize, _>(1..=5);
4931                        let mut path = PathBuf::new();
4932                        path.push("/");
4933                        for _ in 0..path_component_count {
4934                            let letter = rng.lock().gen_range(b'a'..=b'z');
4935                            path.push(std::str::from_utf8(&[letter]).unwrap());
4936                        }
4937                        path.set_extension("rs");
4938                        let parent_path = path.parent().unwrap();
4939
4940                        log::info!("Host: creating file {:?}", path,);
4941
4942                        if fs.create_dir(&parent_path).await.is_ok()
4943                            && fs.create_file(&path, Default::default()).await.is_ok()
4944                        {
4945                            break;
4946                        } else {
4947                            log::info!("Host: cannot create file");
4948                        }
4949                    },
4950                }
4951
4952                cx.background().simulate_random_delay().await;
4953            }
4954
4955            Ok(())
4956        }
4957
4958        let result =
4959            simulate_host_internal(&mut self, project.clone(), op_start_signal, rng, &mut cx).await;
4960        log::info!("Host done");
4961        (self, project, cx, result.err())
4962    }
4963
4964    pub async fn simulate_guest(
4965        mut self,
4966        guest_username: String,
4967        project: ModelHandle<Project>,
4968        op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
4969        rng: Arc<Mutex<StdRng>>,
4970        mut cx: TestAppContext,
4971    ) -> (
4972        Self,
4973        ModelHandle<Project>,
4974        TestAppContext,
4975        Option<anyhow::Error>,
4976    ) {
4977        async fn simulate_guest_internal(
4978            client: &mut TestClient,
4979            guest_username: &str,
4980            project: ModelHandle<Project>,
4981            mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
4982            rng: Arc<Mutex<StdRng>>,
4983            cx: &mut TestAppContext,
4984        ) -> anyhow::Result<()> {
4985            while op_start_signal.next().await.is_some() {
4986                let buffer = if client.buffers.is_empty() || rng.lock().gen() {
4987                    let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| {
4988                        project
4989                            .worktrees(&cx)
4990                            .filter(|worktree| {
4991                                let worktree = worktree.read(cx);
4992                                worktree.is_visible()
4993                                    && worktree.entries(false).any(|e| e.is_file())
4994                            })
4995                            .choose(&mut *rng.lock())
4996                    }) {
4997                        worktree
4998                    } else {
4999                        cx.background().simulate_random_delay().await;
5000                        continue;
5001                    };
5002
5003                    let (worktree_root_name, project_path) =
5004                        worktree.read_with(cx, |worktree, _| {
5005                            let entry = worktree
5006                                .entries(false)
5007                                .filter(|e| e.is_file())
5008                                .choose(&mut *rng.lock())
5009                                .unwrap();
5010                            (
5011                                worktree.root_name().to_string(),
5012                                (worktree.id(), entry.path.clone()),
5013                            )
5014                        });
5015                    log::info!(
5016                        "{}: opening path {:?} in worktree {} ({})",
5017                        guest_username,
5018                        project_path.1,
5019                        project_path.0,
5020                        worktree_root_name,
5021                    );
5022                    let buffer = project
5023                        .update(cx, |project, cx| {
5024                            project.open_buffer(project_path.clone(), cx)
5025                        })
5026                        .await?;
5027                    log::info!(
5028                        "{}: opened path {:?} in worktree {} ({}) with buffer id {}",
5029                        guest_username,
5030                        project_path.1,
5031                        project_path.0,
5032                        worktree_root_name,
5033                        buffer.read_with(cx, |buffer, _| buffer.remote_id())
5034                    );
5035                    client.buffers.insert(buffer.clone());
5036                    buffer
5037                } else {
5038                    client
5039                        .buffers
5040                        .iter()
5041                        .choose(&mut *rng.lock())
5042                        .unwrap()
5043                        .clone()
5044                };
5045
5046                let choice = rng.lock().gen_range(0..100);
5047                match choice {
5048                    0..=9 => {
5049                        cx.update(|cx| {
5050                            log::info!(
5051                                "{}: dropping buffer {:?}",
5052                                guest_username,
5053                                buffer.read(cx).file().unwrap().full_path(cx)
5054                            );
5055                            client.buffers.remove(&buffer);
5056                            drop(buffer);
5057                        });
5058                    }
5059                    10..=19 => {
5060                        let completions = project.update(cx, |project, cx| {
5061                            log::info!(
5062                                "{}: requesting completions for buffer {} ({:?})",
5063                                guest_username,
5064                                buffer.read(cx).remote_id(),
5065                                buffer.read(cx).file().unwrap().full_path(cx)
5066                            );
5067                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5068                            project.completions(&buffer, offset, cx)
5069                        });
5070                        let completions = cx.background().spawn(async move {
5071                            completions
5072                                .await
5073                                .map_err(|err| anyhow!("completions request failed: {:?}", err))
5074                        });
5075                        if rng.lock().gen_bool(0.3) {
5076                            log::info!("{}: detaching completions request", guest_username);
5077                            cx.update(|cx| completions.detach_and_log_err(cx));
5078                        } else {
5079                            completions.await?;
5080                        }
5081                    }
5082                    20..=29 => {
5083                        let code_actions = project.update(cx, |project, cx| {
5084                            log::info!(
5085                                "{}: requesting code actions for buffer {} ({:?})",
5086                                guest_username,
5087                                buffer.read(cx).remote_id(),
5088                                buffer.read(cx).file().unwrap().full_path(cx)
5089                            );
5090                            let range = buffer.read(cx).random_byte_range(0, &mut *rng.lock());
5091                            project.code_actions(&buffer, range, cx)
5092                        });
5093                        let code_actions = cx.background().spawn(async move {
5094                            code_actions
5095                                .await
5096                                .map_err(|err| anyhow!("code actions request failed: {:?}", err))
5097                        });
5098                        if rng.lock().gen_bool(0.3) {
5099                            log::info!("{}: detaching code actions request", guest_username);
5100                            cx.update(|cx| code_actions.detach_and_log_err(cx));
5101                        } else {
5102                            code_actions.await?;
5103                        }
5104                    }
5105                    30..=39 if buffer.read_with(cx, |buffer, _| buffer.is_dirty()) => {
5106                        let (requested_version, save) = buffer.update(cx, |buffer, cx| {
5107                            log::info!(
5108                                "{}: saving buffer {} ({:?})",
5109                                guest_username,
5110                                buffer.remote_id(),
5111                                buffer.file().unwrap().full_path(cx)
5112                            );
5113                            (buffer.version(), buffer.save(cx))
5114                        });
5115                        let save = cx.background().spawn(async move {
5116                            let (saved_version, _) = save
5117                                .await
5118                                .map_err(|err| anyhow!("save request failed: {:?}", err))?;
5119                            assert!(saved_version.observed_all(&requested_version));
5120                            Ok::<_, anyhow::Error>(())
5121                        });
5122                        if rng.lock().gen_bool(0.3) {
5123                            log::info!("{}: detaching save request", guest_username);
5124                            cx.update(|cx| save.detach_and_log_err(cx));
5125                        } else {
5126                            save.await?;
5127                        }
5128                    }
5129                    40..=44 => {
5130                        let prepare_rename = project.update(cx, |project, cx| {
5131                            log::info!(
5132                                "{}: preparing rename for buffer {} ({:?})",
5133                                guest_username,
5134                                buffer.read(cx).remote_id(),
5135                                buffer.read(cx).file().unwrap().full_path(cx)
5136                            );
5137                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5138                            project.prepare_rename(buffer, offset, cx)
5139                        });
5140                        let prepare_rename = cx.background().spawn(async move {
5141                            prepare_rename
5142                                .await
5143                                .map_err(|err| anyhow!("prepare rename request failed: {:?}", err))
5144                        });
5145                        if rng.lock().gen_bool(0.3) {
5146                            log::info!("{}: detaching prepare rename request", guest_username);
5147                            cx.update(|cx| prepare_rename.detach_and_log_err(cx));
5148                        } else {
5149                            prepare_rename.await?;
5150                        }
5151                    }
5152                    45..=49 => {
5153                        let definitions = project.update(cx, |project, cx| {
5154                            log::info!(
5155                                "{}: requesting definitions for buffer {} ({:?})",
5156                                guest_username,
5157                                buffer.read(cx).remote_id(),
5158                                buffer.read(cx).file().unwrap().full_path(cx)
5159                            );
5160                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5161                            project.definition(&buffer, offset, cx)
5162                        });
5163                        let definitions = cx.background().spawn(async move {
5164                            definitions
5165                                .await
5166                                .map_err(|err| anyhow!("definitions request failed: {:?}", err))
5167                        });
5168                        if rng.lock().gen_bool(0.3) {
5169                            log::info!("{}: detaching definitions request", guest_username);
5170                            cx.update(|cx| definitions.detach_and_log_err(cx));
5171                        } else {
5172                            client
5173                                .buffers
5174                                .extend(definitions.await?.into_iter().map(|loc| loc.buffer));
5175                        }
5176                    }
5177                    50..=54 => {
5178                        let highlights = project.update(cx, |project, cx| {
5179                            log::info!(
5180                                "{}: requesting highlights for buffer {} ({:?})",
5181                                guest_username,
5182                                buffer.read(cx).remote_id(),
5183                                buffer.read(cx).file().unwrap().full_path(cx)
5184                            );
5185                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5186                            project.document_highlights(&buffer, offset, cx)
5187                        });
5188                        let highlights = cx.background().spawn(async move {
5189                            highlights
5190                                .await
5191                                .map_err(|err| anyhow!("highlights request failed: {:?}", err))
5192                        });
5193                        if rng.lock().gen_bool(0.3) {
5194                            log::info!("{}: detaching highlights request", guest_username);
5195                            cx.update(|cx| highlights.detach_and_log_err(cx));
5196                        } else {
5197                            highlights.await?;
5198                        }
5199                    }
5200                    55..=59 => {
5201                        let search = project.update(cx, |project, cx| {
5202                            let query = rng.lock().gen_range('a'..='z');
5203                            log::info!("{}: project-wide search {:?}", guest_username, query);
5204                            project.search(SearchQuery::text(query, false, false), cx)
5205                        });
5206                        let search = cx.background().spawn(async move {
5207                            search
5208                                .await
5209                                .map_err(|err| anyhow!("search request failed: {:?}", err))
5210                        });
5211                        if rng.lock().gen_bool(0.3) {
5212                            log::info!("{}: detaching search request", guest_username);
5213                            cx.update(|cx| search.detach_and_log_err(cx));
5214                        } else {
5215                            client.buffers.extend(search.await?.into_keys());
5216                        }
5217                    }
5218                    60..=69 => {
5219                        let worktree = project
5220                            .read_with(cx, |project, cx| {
5221                                project
5222                                    .worktrees(&cx)
5223                                    .filter(|worktree| {
5224                                        let worktree = worktree.read(cx);
5225                                        worktree.is_visible()
5226                                            && worktree.entries(false).any(|e| e.is_file())
5227                                            && worktree.root_entry().map_or(false, |e| e.is_dir())
5228                                    })
5229                                    .choose(&mut *rng.lock())
5230                            })
5231                            .unwrap();
5232                        let (worktree_id, worktree_root_name) = worktree
5233                            .read_with(cx, |worktree, _| {
5234                                (worktree.id(), worktree.root_name().to_string())
5235                            });
5236
5237                        let mut new_name = String::new();
5238                        for _ in 0..10 {
5239                            let letter = rng.lock().gen_range('a'..='z');
5240                            new_name.push(letter);
5241                        }
5242                        let mut new_path = PathBuf::new();
5243                        new_path.push(new_name);
5244                        new_path.set_extension("rs");
5245                        log::info!(
5246                            "{}: creating {:?} in worktree {} ({})",
5247                            guest_username,
5248                            new_path,
5249                            worktree_id,
5250                            worktree_root_name,
5251                        );
5252                        project
5253                            .update(cx, |project, cx| {
5254                                project.create_entry((worktree_id, new_path), false, cx)
5255                            })
5256                            .unwrap()
5257                            .await?;
5258                    }
5259                    _ => {
5260                        buffer.update(cx, |buffer, cx| {
5261                            log::info!(
5262                                "{}: updating buffer {} ({:?})",
5263                                guest_username,
5264                                buffer.remote_id(),
5265                                buffer.file().unwrap().full_path(cx)
5266                            );
5267                            if rng.lock().gen_bool(0.7) {
5268                                buffer.randomly_edit(&mut *rng.lock(), 5, cx);
5269                            } else {
5270                                buffer.randomly_undo_redo(&mut *rng.lock(), cx);
5271                            }
5272                        });
5273                    }
5274                }
5275                cx.background().simulate_random_delay().await;
5276            }
5277            Ok(())
5278        }
5279
5280        let result = simulate_guest_internal(
5281            &mut self,
5282            &guest_username,
5283            project.clone(),
5284            op_start_signal,
5285            rng,
5286            &mut cx,
5287        )
5288        .await;
5289        log::info!("{}: done", guest_username);
5290
5291        (self, project, cx, result.err())
5292    }
5293}
5294
5295impl Drop for TestClient {
5296    fn drop(&mut self) {
5297        self.client.tear_down();
5298    }
5299}
5300
5301impl Executor for Arc<gpui::executor::Background> {
5302    type Sleep = gpui::executor::Timer;
5303
5304    fn spawn_detached<F: 'static + Send + Future<Output = ()>>(&self, future: F) {
5305        self.spawn(future).detach();
5306    }
5307
5308    fn sleep(&self, duration: Duration) -> Self::Sleep {
5309        self.as_ref().timer(duration)
5310    }
5311}
5312
5313fn channel_messages(channel: &Channel) -> Vec<(String, String, bool)> {
5314    channel
5315        .messages()
5316        .cursor::<()>()
5317        .map(|m| {
5318            (
5319                m.sender.github_login.clone(),
5320                m.body.clone(),
5321                m.is_pending(),
5322            )
5323        })
5324        .collect()
5325}
5326
5327struct EmptyView;
5328
5329impl gpui::Entity for EmptyView {
5330    type Event = ();
5331}
5332
5333impl gpui::View for EmptyView {
5334    fn ui_name() -> &'static str {
5335        "empty view"
5336    }
5337
5338    fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
5339        gpui::Element::boxed(gpui::elements::Empty::new())
5340    }
5341}