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