integration_tests.rs

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