integration_tests.rs

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