integration_tests.rs

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