integration_tests.rs

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