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