integration_tests.rs

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