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