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