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