integration_tests.rs

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