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