integration_tests.rs

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