integration_tests.rs

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