integration_tests.rs

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