integration_tests.rs

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