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