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