integration_tests.rs

   1use crate::{
   2    rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
   3    tests::{
   4        RoomParticipants, TestClient, TestServer, channel_id, following_tests::join_channel,
   5        room_participants, rust_lang,
   6    },
   7};
   8use anyhow::{Result, anyhow};
   9use assistant_context_editor::ContextStore;
  10use assistant_slash_command::SlashCommandWorkingSet;
  11use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, assert_hunks};
  12use call::{ActiveCall, ParticipantLocation, Room, room};
  13use client::{RECEIVE_TIMEOUT, User};
  14use collections::{HashMap, HashSet};
  15use fs::{FakeFs, Fs as _, RemoveOptions};
  16use futures::{StreamExt as _, channel::mpsc};
  17use git::status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode};
  18use gpui::{
  19    App, BackgroundExecutor, Entity, Modifiers, MouseButton, MouseDownEvent, TestAppContext,
  20    UpdateGlobal, px, size,
  21};
  22use language::{
  23    Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher,
  24    LineEnding, OffsetRangeExt, Point, Rope,
  25    language_settings::{
  26        AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter,
  27    },
  28    tree_sitter_rust, tree_sitter_typescript,
  29};
  30use lsp::{LanguageServerId, OneOf};
  31use parking_lot::Mutex;
  32use pretty_assertions::assert_eq;
  33use project::{
  34    DiagnosticSummary, HoverBlockKind, Project, ProjectPath,
  35    lsp_store::{FormatTrigger, LspFormatTarget},
  36    search::{SearchQuery, SearchResult},
  37};
  38use prompt_store::PromptBuilder;
  39use rand::prelude::*;
  40use serde_json::json;
  41use settings::SettingsStore;
  42use std::{
  43    cell::{Cell, RefCell},
  44    env, future, mem,
  45    path::{Path, PathBuf},
  46    rc::Rc,
  47    sync::{
  48        Arc,
  49        atomic::{AtomicBool, Ordering::SeqCst},
  50    },
  51    time::Duration,
  52};
  53use unindent::Unindent as _;
  54use util::{path, separator, uri};
  55use workspace::Pane;
  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_basic_calls(
  66    executor: BackgroundExecutor,
  67    cx_a: &mut TestAppContext,
  68    cx_b: &mut TestAppContext,
  69    cx_b2: &mut TestAppContext,
  70    cx_c: &mut TestAppContext,
  71) {
  72    let mut server = TestServer::start(executor.clone()).await;
  73
  74    let client_a = server.create_client(cx_a, "user_a").await;
  75    let client_b = server.create_client(cx_b, "user_b").await;
  76    let client_c = server.create_client(cx_c, "user_c").await;
  77    server
  78        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
  79        .await;
  80
  81    let active_call_a = cx_a.read(ActiveCall::global);
  82    let active_call_b = cx_b.read(ActiveCall::global);
  83    let active_call_c = cx_c.read(ActiveCall::global);
  84
  85    // Call user B from client A.
  86    active_call_a
  87        .update(cx_a, |call, cx| {
  88            call.invite(client_b.user_id().unwrap(), None, cx)
  89        })
  90        .await
  91        .unwrap();
  92    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
  93    executor.run_until_parked();
  94    assert_eq!(
  95        room_participants(&room_a, cx_a),
  96        RoomParticipants {
  97            remote: Default::default(),
  98            pending: vec!["user_b".to_string()]
  99        }
 100    );
 101
 102    // User B receives the call.
 103
 104    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 105    let call_b = incoming_call_b.next().await.unwrap().unwrap();
 106    assert_eq!(call_b.calling_user.github_login, "user_a");
 107
 108    // User B connects via another client and also receives a ring on the newly-connected client.
 109    let _client_b2 = server.create_client(cx_b2, "user_b").await;
 110    let active_call_b2 = cx_b2.read(ActiveCall::global);
 111
 112    let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
 113    executor.run_until_parked();
 114    let call_b2 = incoming_call_b2.next().await.unwrap().unwrap();
 115    assert_eq!(call_b2.calling_user.github_login, "user_a");
 116
 117    // User B joins the room using the first client.
 118    active_call_b
 119        .update(cx_b, |call, cx| call.accept_incoming(cx))
 120        .await
 121        .unwrap();
 122
 123    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 124    assert!(incoming_call_b.next().await.unwrap().is_none());
 125
 126    executor.run_until_parked();
 127    assert_eq!(
 128        room_participants(&room_a, cx_a),
 129        RoomParticipants {
 130            remote: vec!["user_b".to_string()],
 131            pending: Default::default()
 132        }
 133    );
 134    assert_eq!(
 135        room_participants(&room_b, cx_b),
 136        RoomParticipants {
 137            remote: vec!["user_a".to_string()],
 138            pending: Default::default()
 139        }
 140    );
 141
 142    // Call user C from client B.
 143
 144    let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
 145    active_call_b
 146        .update(cx_b, |call, cx| {
 147            call.invite(client_c.user_id().unwrap(), None, cx)
 148        })
 149        .await
 150        .unwrap();
 151
 152    executor.run_until_parked();
 153    assert_eq!(
 154        room_participants(&room_a, cx_a),
 155        RoomParticipants {
 156            remote: vec!["user_b".to_string()],
 157            pending: vec!["user_c".to_string()]
 158        }
 159    );
 160    assert_eq!(
 161        room_participants(&room_b, cx_b),
 162        RoomParticipants {
 163            remote: vec!["user_a".to_string()],
 164            pending: vec!["user_c".to_string()]
 165        }
 166    );
 167
 168    // User C receives the call, but declines it.
 169    let call_c = incoming_call_c.next().await.unwrap().unwrap();
 170    assert_eq!(call_c.calling_user.github_login, "user_b");
 171    active_call_c.update(cx_c, |call, cx| call.decline_incoming(cx).unwrap());
 172    assert!(incoming_call_c.next().await.unwrap().is_none());
 173
 174    executor.run_until_parked();
 175    assert_eq!(
 176        room_participants(&room_a, cx_a),
 177        RoomParticipants {
 178            remote: vec!["user_b".to_string()],
 179            pending: Default::default()
 180        }
 181    );
 182    assert_eq!(
 183        room_participants(&room_b, cx_b),
 184        RoomParticipants {
 185            remote: vec!["user_a".to_string()],
 186            pending: Default::default()
 187        }
 188    );
 189
 190    // Call user C again from user A.
 191    active_call_a
 192        .update(cx_a, |call, cx| {
 193            call.invite(client_c.user_id().unwrap(), None, cx)
 194        })
 195        .await
 196        .unwrap();
 197
 198    executor.run_until_parked();
 199    assert_eq!(
 200        room_participants(&room_a, cx_a),
 201        RoomParticipants {
 202            remote: vec!["user_b".to_string()],
 203            pending: vec!["user_c".to_string()]
 204        }
 205    );
 206    assert_eq!(
 207        room_participants(&room_b, cx_b),
 208        RoomParticipants {
 209            remote: vec!["user_a".to_string()],
 210            pending: vec!["user_c".to_string()]
 211        }
 212    );
 213
 214    // User C accepts the call.
 215    let call_c = incoming_call_c.next().await.unwrap().unwrap();
 216    assert_eq!(call_c.calling_user.github_login, "user_a");
 217    active_call_c
 218        .update(cx_c, |call, cx| call.accept_incoming(cx))
 219        .await
 220        .unwrap();
 221    assert!(incoming_call_c.next().await.unwrap().is_none());
 222
 223    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 224
 225    executor.run_until_parked();
 226    assert_eq!(
 227        room_participants(&room_a, cx_a),
 228        RoomParticipants {
 229            remote: vec!["user_b".to_string(), "user_c".to_string()],
 230            pending: Default::default()
 231        }
 232    );
 233    assert_eq!(
 234        room_participants(&room_b, cx_b),
 235        RoomParticipants {
 236            remote: vec!["user_a".to_string(), "user_c".to_string()],
 237            pending: Default::default()
 238        }
 239    );
 240    assert_eq!(
 241        room_participants(&room_c, cx_c),
 242        RoomParticipants {
 243            remote: vec!["user_a".to_string(), "user_b".to_string()],
 244            pending: Default::default()
 245        }
 246    );
 247
 248    // User A shares their screen
 249    let display = gpui::TestScreenCaptureSource::new();
 250    let events_b = active_call_events(cx_b);
 251    let events_c = active_call_events(cx_c);
 252    cx_a.set_screen_capture_sources(vec![display]);
 253    active_call_a
 254        .update(cx_a, |call, cx| {
 255            call.room()
 256                .unwrap()
 257                .update(cx, |room, cx| room.share_screen(cx))
 258        })
 259        .await
 260        .unwrap();
 261
 262    executor.run_until_parked();
 263
 264    // User B observes the remote screen sharing track.
 265    assert_eq!(events_b.borrow().len(), 1);
 266    let event_b = events_b.borrow().first().unwrap().clone();
 267    if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_b {
 268        assert_eq!(participant_id, client_a.peer_id().unwrap());
 269
 270        room_b.read_with(cx_b, |room, _| {
 271            assert_eq!(
 272                room.remote_participants()[&client_a.user_id().unwrap()]
 273                    .video_tracks
 274                    .len(),
 275                1
 276            );
 277        });
 278    } else {
 279        panic!("unexpected event")
 280    }
 281
 282    // User C observes the remote screen sharing track.
 283    assert_eq!(events_c.borrow().len(), 1);
 284    let event_c = events_c.borrow().first().unwrap().clone();
 285    if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_c {
 286        assert_eq!(participant_id, client_a.peer_id().unwrap());
 287
 288        room_c.read_with(cx_c, |room, _| {
 289            assert_eq!(
 290                room.remote_participants()[&client_a.user_id().unwrap()]
 291                    .video_tracks
 292                    .len(),
 293                1
 294            );
 295        });
 296    } else {
 297        panic!("unexpected event")
 298    }
 299
 300    // User A leaves the room.
 301    active_call_a
 302        .update(cx_a, |call, cx| {
 303            let hang_up = call.hang_up(cx);
 304            assert!(call.room().is_none());
 305            hang_up
 306        })
 307        .await
 308        .unwrap();
 309    executor.run_until_parked();
 310    assert_eq!(
 311        room_participants(&room_a, cx_a),
 312        RoomParticipants {
 313            remote: Default::default(),
 314            pending: Default::default()
 315        }
 316    );
 317    assert_eq!(
 318        room_participants(&room_b, cx_b),
 319        RoomParticipants {
 320            remote: vec!["user_c".to_string()],
 321            pending: Default::default()
 322        }
 323    );
 324    assert_eq!(
 325        room_participants(&room_c, cx_c),
 326        RoomParticipants {
 327            remote: vec!["user_b".to_string()],
 328            pending: Default::default()
 329        }
 330    );
 331
 332    // User B gets disconnected from the LiveKit server, which causes them
 333    // to automatically leave the room. User C leaves the room as well because
 334    // nobody else is in there.
 335    server
 336        .test_livekit_server
 337        .disconnect_client(client_b.user_id().unwrap().to_string())
 338        .await;
 339    executor.run_until_parked();
 340
 341    active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
 342
 343    active_call_c.read_with(cx_c, |call, _| assert!(call.room().is_none()));
 344    assert_eq!(
 345        room_participants(&room_a, cx_a),
 346        RoomParticipants {
 347            remote: Default::default(),
 348            pending: Default::default()
 349        }
 350    );
 351    assert_eq!(
 352        room_participants(&room_b, cx_b),
 353        RoomParticipants {
 354            remote: Default::default(),
 355            pending: Default::default()
 356        }
 357    );
 358    assert_eq!(
 359        room_participants(&room_c, cx_c),
 360        RoomParticipants {
 361            remote: Default::default(),
 362            pending: Default::default()
 363        }
 364    );
 365}
 366
 367#[gpui::test(iterations = 10)]
 368async fn test_calling_multiple_users_simultaneously(
 369    executor: BackgroundExecutor,
 370    cx_a: &mut TestAppContext,
 371    cx_b: &mut TestAppContext,
 372    cx_c: &mut TestAppContext,
 373    cx_d: &mut TestAppContext,
 374) {
 375    let mut server = TestServer::start(executor.clone()).await;
 376
 377    let client_a = server.create_client(cx_a, "user_a").await;
 378    let client_b = server.create_client(cx_b, "user_b").await;
 379    let client_c = server.create_client(cx_c, "user_c").await;
 380    let client_d = server.create_client(cx_d, "user_d").await;
 381    server
 382        .make_contacts(&mut [
 383            (&client_a, cx_a),
 384            (&client_b, cx_b),
 385            (&client_c, cx_c),
 386            (&client_d, cx_d),
 387        ])
 388        .await;
 389
 390    let active_call_a = cx_a.read(ActiveCall::global);
 391    let active_call_b = cx_b.read(ActiveCall::global);
 392    let active_call_c = cx_c.read(ActiveCall::global);
 393    let active_call_d = cx_d.read(ActiveCall::global);
 394
 395    // Simultaneously call user B and user C from client A.
 396    let b_invite = active_call_a.update(cx_a, |call, cx| {
 397        call.invite(client_b.user_id().unwrap(), None, cx)
 398    });
 399    let c_invite = active_call_a.update(cx_a, |call, cx| {
 400        call.invite(client_c.user_id().unwrap(), None, cx)
 401    });
 402    b_invite.await.unwrap();
 403    c_invite.await.unwrap();
 404
 405    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 406    executor.run_until_parked();
 407    assert_eq!(
 408        room_participants(&room_a, cx_a),
 409        RoomParticipants {
 410            remote: Default::default(),
 411            pending: vec!["user_b".to_string(), "user_c".to_string()]
 412        }
 413    );
 414
 415    // Call client D from client A.
 416    active_call_a
 417        .update(cx_a, |call, cx| {
 418            call.invite(client_d.user_id().unwrap(), None, cx)
 419        })
 420        .await
 421        .unwrap();
 422    executor.run_until_parked();
 423    assert_eq!(
 424        room_participants(&room_a, cx_a),
 425        RoomParticipants {
 426            remote: Default::default(),
 427            pending: vec![
 428                "user_b".to_string(),
 429                "user_c".to_string(),
 430                "user_d".to_string()
 431            ]
 432        }
 433    );
 434
 435    // Accept the call on all clients simultaneously.
 436    let accept_b = active_call_b.update(cx_b, |call, cx| call.accept_incoming(cx));
 437    let accept_c = active_call_c.update(cx_c, |call, cx| call.accept_incoming(cx));
 438    let accept_d = active_call_d.update(cx_d, |call, cx| call.accept_incoming(cx));
 439    accept_b.await.unwrap();
 440    accept_c.await.unwrap();
 441    accept_d.await.unwrap();
 442
 443    executor.run_until_parked();
 444
 445    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 446
 447    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 448
 449    let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
 450    assert_eq!(
 451        room_participants(&room_a, cx_a),
 452        RoomParticipants {
 453            remote: vec![
 454                "user_b".to_string(),
 455                "user_c".to_string(),
 456                "user_d".to_string(),
 457            ],
 458            pending: Default::default()
 459        }
 460    );
 461    assert_eq!(
 462        room_participants(&room_b, cx_b),
 463        RoomParticipants {
 464            remote: vec![
 465                "user_a".to_string(),
 466                "user_c".to_string(),
 467                "user_d".to_string(),
 468            ],
 469            pending: Default::default()
 470        }
 471    );
 472    assert_eq!(
 473        room_participants(&room_c, cx_c),
 474        RoomParticipants {
 475            remote: vec![
 476                "user_a".to_string(),
 477                "user_b".to_string(),
 478                "user_d".to_string(),
 479            ],
 480            pending: Default::default()
 481        }
 482    );
 483    assert_eq!(
 484        room_participants(&room_d, cx_d),
 485        RoomParticipants {
 486            remote: vec![
 487                "user_a".to_string(),
 488                "user_b".to_string(),
 489                "user_c".to_string(),
 490            ],
 491            pending: Default::default()
 492        }
 493    );
 494}
 495
 496#[gpui::test(iterations = 10)]
 497async fn test_joining_channels_and_calling_multiple_users_simultaneously(
 498    executor: BackgroundExecutor,
 499    cx_a: &mut TestAppContext,
 500    cx_b: &mut TestAppContext,
 501    cx_c: &mut TestAppContext,
 502) {
 503    let mut server = TestServer::start(executor.clone()).await;
 504
 505    let client_a = server.create_client(cx_a, "user_a").await;
 506    let client_b = server.create_client(cx_b, "user_b").await;
 507    let client_c = server.create_client(cx_c, "user_c").await;
 508    server
 509        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
 510        .await;
 511
 512    let channel_1 = server
 513        .make_channel(
 514            "channel1",
 515            None,
 516            (&client_a, cx_a),
 517            &mut [(&client_b, cx_b), (&client_c, cx_c)],
 518        )
 519        .await;
 520
 521    let channel_2 = server
 522        .make_channel(
 523            "channel2",
 524            None,
 525            (&client_a, cx_a),
 526            &mut [(&client_b, cx_b), (&client_c, cx_c)],
 527        )
 528        .await;
 529
 530    let active_call_a = cx_a.read(ActiveCall::global);
 531
 532    // Simultaneously join channel 1 and then channel 2
 533    active_call_a
 534        .update(cx_a, |call, cx| call.join_channel(channel_1, cx))
 535        .detach();
 536    let join_channel_2 = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, cx));
 537
 538    join_channel_2.await.unwrap();
 539
 540    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 541    executor.run_until_parked();
 542
 543    assert_eq!(channel_id(&room_a, cx_a), Some(channel_2));
 544
 545    // Leave the room
 546    active_call_a
 547        .update(cx_a, |call, cx| call.hang_up(cx))
 548        .await
 549        .unwrap();
 550
 551    // Initiating invites and then joining a channel should fail gracefully
 552    let b_invite = active_call_a.update(cx_a, |call, cx| {
 553        call.invite(client_b.user_id().unwrap(), None, cx)
 554    });
 555    let c_invite = active_call_a.update(cx_a, |call, cx| {
 556        call.invite(client_c.user_id().unwrap(), None, cx)
 557    });
 558
 559    let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
 560
 561    b_invite.await.unwrap();
 562    c_invite.await.unwrap();
 563    join_channel.await.unwrap();
 564
 565    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 566    executor.run_until_parked();
 567
 568    assert_eq!(
 569        room_participants(&room_a, cx_a),
 570        RoomParticipants {
 571            remote: Default::default(),
 572            pending: vec!["user_b".to_string(), "user_c".to_string()]
 573        }
 574    );
 575
 576    assert_eq!(channel_id(&room_a, cx_a), None);
 577
 578    // Leave the room
 579    active_call_a
 580        .update(cx_a, |call, cx| call.hang_up(cx))
 581        .await
 582        .unwrap();
 583
 584    // Simultaneously join channel 1 and call user B and user C from client A.
 585    let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
 586
 587    let b_invite = active_call_a.update(cx_a, |call, cx| {
 588        call.invite(client_b.user_id().unwrap(), None, cx)
 589    });
 590    let c_invite = active_call_a.update(cx_a, |call, cx| {
 591        call.invite(client_c.user_id().unwrap(), None, cx)
 592    });
 593
 594    join_channel.await.unwrap();
 595    b_invite.await.unwrap();
 596    c_invite.await.unwrap();
 597
 598    active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 599    executor.run_until_parked();
 600}
 601
 602#[gpui::test(iterations = 10)]
 603async fn test_room_uniqueness(
 604    executor: BackgroundExecutor,
 605    cx_a: &mut TestAppContext,
 606    cx_a2: &mut TestAppContext,
 607    cx_b: &mut TestAppContext,
 608    cx_b2: &mut TestAppContext,
 609    cx_c: &mut TestAppContext,
 610) {
 611    let mut server = TestServer::start(executor.clone()).await;
 612    let client_a = server.create_client(cx_a, "user_a").await;
 613    let _client_a2 = server.create_client(cx_a2, "user_a").await;
 614    let client_b = server.create_client(cx_b, "user_b").await;
 615    let _client_b2 = server.create_client(cx_b2, "user_b").await;
 616    let client_c = server.create_client(cx_c, "user_c").await;
 617    server
 618        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
 619        .await;
 620
 621    let active_call_a = cx_a.read(ActiveCall::global);
 622    let active_call_a2 = cx_a2.read(ActiveCall::global);
 623    let active_call_b = cx_b.read(ActiveCall::global);
 624    let active_call_b2 = cx_b2.read(ActiveCall::global);
 625    let active_call_c = cx_c.read(ActiveCall::global);
 626
 627    // Call user B from client A.
 628    active_call_a
 629        .update(cx_a, |call, cx| {
 630            call.invite(client_b.user_id().unwrap(), None, cx)
 631        })
 632        .await
 633        .unwrap();
 634
 635    // Ensure a new room can't be created given user A just created one.
 636    active_call_a2
 637        .update(cx_a2, |call, cx| {
 638            call.invite(client_c.user_id().unwrap(), None, cx)
 639        })
 640        .await
 641        .unwrap_err();
 642
 643    active_call_a2.read_with(cx_a2, |call, _| assert!(call.room().is_none()));
 644
 645    // User B receives the call from user A.
 646
 647    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 648    let call_b1 = incoming_call_b.next().await.unwrap().unwrap();
 649    assert_eq!(call_b1.calling_user.github_login, "user_a");
 650
 651    // Ensure calling users A and B from client C fails.
 652    active_call_c
 653        .update(cx_c, |call, cx| {
 654            call.invite(client_a.user_id().unwrap(), None, cx)
 655        })
 656        .await
 657        .unwrap_err();
 658    active_call_c
 659        .update(cx_c, |call, cx| {
 660            call.invite(client_b.user_id().unwrap(), None, cx)
 661        })
 662        .await
 663        .unwrap_err();
 664
 665    // Ensure User B can't create a room while they still have an incoming call.
 666    active_call_b2
 667        .update(cx_b2, |call, cx| {
 668            call.invite(client_c.user_id().unwrap(), None, cx)
 669        })
 670        .await
 671        .unwrap_err();
 672
 673    active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
 674
 675    // User B joins the room and calling them after they've joined still fails.
 676    active_call_b
 677        .update(cx_b, |call, cx| call.accept_incoming(cx))
 678        .await
 679        .unwrap();
 680    active_call_c
 681        .update(cx_c, |call, cx| {
 682            call.invite(client_b.user_id().unwrap(), None, cx)
 683        })
 684        .await
 685        .unwrap_err();
 686
 687    // Ensure User B can't create a room while they belong to another room.
 688    active_call_b2
 689        .update(cx_b2, |call, cx| {
 690            call.invite(client_c.user_id().unwrap(), None, cx)
 691        })
 692        .await
 693        .unwrap_err();
 694
 695    active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
 696
 697    // Client C can successfully call client B after client B leaves the room.
 698    active_call_b
 699        .update(cx_b, |call, cx| call.hang_up(cx))
 700        .await
 701        .unwrap();
 702    executor.run_until_parked();
 703    active_call_c
 704        .update(cx_c, |call, cx| {
 705            call.invite(client_b.user_id().unwrap(), None, cx)
 706        })
 707        .await
 708        .unwrap();
 709    executor.run_until_parked();
 710    let call_b2 = incoming_call_b.next().await.unwrap().unwrap();
 711    assert_eq!(call_b2.calling_user.github_login, "user_c");
 712}
 713
 714#[gpui::test(iterations = 10)]
 715async fn test_client_disconnecting_from_room(
 716    executor: BackgroundExecutor,
 717    cx_a: &mut TestAppContext,
 718    cx_b: &mut TestAppContext,
 719) {
 720    let mut server = TestServer::start(executor.clone()).await;
 721    let client_a = server.create_client(cx_a, "user_a").await;
 722    let client_b = server.create_client(cx_b, "user_b").await;
 723    server
 724        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 725        .await;
 726
 727    let active_call_a = cx_a.read(ActiveCall::global);
 728    let active_call_b = cx_b.read(ActiveCall::global);
 729
 730    // Call user B from client A.
 731    active_call_a
 732        .update(cx_a, |call, cx| {
 733            call.invite(client_b.user_id().unwrap(), None, cx)
 734        })
 735        .await
 736        .unwrap();
 737
 738    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 739
 740    // User B receives the call and joins the room.
 741
 742    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 743    incoming_call_b.next().await.unwrap().unwrap();
 744    active_call_b
 745        .update(cx_b, |call, cx| call.accept_incoming(cx))
 746        .await
 747        .unwrap();
 748
 749    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 750    executor.run_until_parked();
 751    assert_eq!(
 752        room_participants(&room_a, cx_a),
 753        RoomParticipants {
 754            remote: vec!["user_b".to_string()],
 755            pending: Default::default()
 756        }
 757    );
 758    assert_eq!(
 759        room_participants(&room_b, cx_b),
 760        RoomParticipants {
 761            remote: vec!["user_a".to_string()],
 762            pending: Default::default()
 763        }
 764    );
 765
 766    // User A automatically reconnects to the room upon disconnection.
 767    server.disconnect_client(client_a.peer_id().unwrap());
 768    executor.advance_clock(RECEIVE_TIMEOUT);
 769    executor.run_until_parked();
 770    assert_eq!(
 771        room_participants(&room_a, cx_a),
 772        RoomParticipants {
 773            remote: vec!["user_b".to_string()],
 774            pending: Default::default()
 775        }
 776    );
 777    assert_eq!(
 778        room_participants(&room_b, cx_b),
 779        RoomParticipants {
 780            remote: vec!["user_a".to_string()],
 781            pending: Default::default()
 782        }
 783    );
 784
 785    // When user A disconnects, both client A and B clear their room on the active call.
 786    server.forbid_connections();
 787    server.disconnect_client(client_a.peer_id().unwrap());
 788    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 789
 790    active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
 791
 792    active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
 793    assert_eq!(
 794        room_participants(&room_a, cx_a),
 795        RoomParticipants {
 796            remote: Default::default(),
 797            pending: Default::default()
 798        }
 799    );
 800    assert_eq!(
 801        room_participants(&room_b, cx_b),
 802        RoomParticipants {
 803            remote: Default::default(),
 804            pending: Default::default()
 805        }
 806    );
 807
 808    // Allow user A to reconnect to the server.
 809    server.allow_connections();
 810    executor.advance_clock(RECEIVE_TIMEOUT);
 811
 812    // Call user B again from client A.
 813    active_call_a
 814        .update(cx_a, |call, cx| {
 815            call.invite(client_b.user_id().unwrap(), None, cx)
 816        })
 817        .await
 818        .unwrap();
 819
 820    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 821
 822    // User B receives the call and joins the room.
 823
 824    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 825    incoming_call_b.next().await.unwrap().unwrap();
 826    active_call_b
 827        .update(cx_b, |call, cx| call.accept_incoming(cx))
 828        .await
 829        .unwrap();
 830
 831    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 832    executor.run_until_parked();
 833    assert_eq!(
 834        room_participants(&room_a, cx_a),
 835        RoomParticipants {
 836            remote: vec!["user_b".to_string()],
 837            pending: Default::default()
 838        }
 839    );
 840    assert_eq!(
 841        room_participants(&room_b, cx_b),
 842        RoomParticipants {
 843            remote: vec!["user_a".to_string()],
 844            pending: Default::default()
 845        }
 846    );
 847
 848    // User B gets disconnected from the LiveKit server, which causes it
 849    // to automatically leave the room.
 850    server
 851        .test_livekit_server
 852        .disconnect_client(client_b.user_id().unwrap().to_string())
 853        .await;
 854    executor.run_until_parked();
 855    active_call_a.update(cx_a, |call, _| assert!(call.room().is_none()));
 856    active_call_b.update(cx_b, |call, _| assert!(call.room().is_none()));
 857    assert_eq!(
 858        room_participants(&room_a, cx_a),
 859        RoomParticipants {
 860            remote: Default::default(),
 861            pending: Default::default()
 862        }
 863    );
 864    assert_eq!(
 865        room_participants(&room_b, cx_b),
 866        RoomParticipants {
 867            remote: Default::default(),
 868            pending: Default::default()
 869        }
 870    );
 871}
 872
 873#[gpui::test(iterations = 10)]
 874async fn test_server_restarts(
 875    executor: BackgroundExecutor,
 876    cx_a: &mut TestAppContext,
 877    cx_b: &mut TestAppContext,
 878    cx_c: &mut TestAppContext,
 879    cx_d: &mut TestAppContext,
 880) {
 881    let mut server = TestServer::start(executor.clone()).await;
 882    let client_a = server.create_client(cx_a, "user_a").await;
 883    client_a
 884        .fs()
 885        .insert_tree("/a", json!({ "a.txt": "a-contents" }))
 886        .await;
 887
 888    // Invite client B to collaborate on a project
 889    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
 890
 891    let client_b = server.create_client(cx_b, "user_b").await;
 892    let client_c = server.create_client(cx_c, "user_c").await;
 893    let client_d = server.create_client(cx_d, "user_d").await;
 894    server
 895        .make_contacts(&mut [
 896            (&client_a, cx_a),
 897            (&client_b, cx_b),
 898            (&client_c, cx_c),
 899            (&client_d, cx_d),
 900        ])
 901        .await;
 902
 903    let active_call_a = cx_a.read(ActiveCall::global);
 904    let active_call_b = cx_b.read(ActiveCall::global);
 905    let active_call_c = cx_c.read(ActiveCall::global);
 906    let active_call_d = cx_d.read(ActiveCall::global);
 907
 908    // User A calls users B, C, and D.
 909    active_call_a
 910        .update(cx_a, |call, cx| {
 911            call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
 912        })
 913        .await
 914        .unwrap();
 915    active_call_a
 916        .update(cx_a, |call, cx| {
 917            call.invite(client_c.user_id().unwrap(), Some(project_a.clone()), cx)
 918        })
 919        .await
 920        .unwrap();
 921    active_call_a
 922        .update(cx_a, |call, cx| {
 923            call.invite(client_d.user_id().unwrap(), Some(project_a.clone()), cx)
 924        })
 925        .await
 926        .unwrap();
 927
 928    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 929
 930    // User B receives the call and joins the room.
 931
 932    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 933    assert!(incoming_call_b.next().await.unwrap().is_some());
 934    active_call_b
 935        .update(cx_b, |call, cx| call.accept_incoming(cx))
 936        .await
 937        .unwrap();
 938
 939    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 940
 941    // User C receives the call and joins the room.
 942
 943    let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
 944    assert!(incoming_call_c.next().await.unwrap().is_some());
 945    active_call_c
 946        .update(cx_c, |call, cx| call.accept_incoming(cx))
 947        .await
 948        .unwrap();
 949
 950    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 951
 952    // User D receives the call but doesn't join the room yet.
 953
 954    let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
 955    assert!(incoming_call_d.next().await.unwrap().is_some());
 956
 957    executor.run_until_parked();
 958    assert_eq!(
 959        room_participants(&room_a, cx_a),
 960        RoomParticipants {
 961            remote: vec!["user_b".to_string(), "user_c".to_string()],
 962            pending: vec!["user_d".to_string()]
 963        }
 964    );
 965    assert_eq!(
 966        room_participants(&room_b, cx_b),
 967        RoomParticipants {
 968            remote: vec!["user_a".to_string(), "user_c".to_string()],
 969            pending: vec!["user_d".to_string()]
 970        }
 971    );
 972    assert_eq!(
 973        room_participants(&room_c, cx_c),
 974        RoomParticipants {
 975            remote: vec!["user_a".to_string(), "user_b".to_string()],
 976            pending: vec!["user_d".to_string()]
 977        }
 978    );
 979
 980    // The server is torn down.
 981    server.reset().await;
 982
 983    // Users A and B reconnect to the call. User C has troubles reconnecting, so it leaves the room.
 984    client_c.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
 985    executor.advance_clock(RECONNECT_TIMEOUT);
 986    assert_eq!(
 987        room_participants(&room_a, cx_a),
 988        RoomParticipants {
 989            remote: vec!["user_b".to_string(), "user_c".to_string()],
 990            pending: vec!["user_d".to_string()]
 991        }
 992    );
 993    assert_eq!(
 994        room_participants(&room_b, cx_b),
 995        RoomParticipants {
 996            remote: vec!["user_a".to_string(), "user_c".to_string()],
 997            pending: vec!["user_d".to_string()]
 998        }
 999    );
1000    assert_eq!(
1001        room_participants(&room_c, cx_c),
1002        RoomParticipants {
1003            remote: vec![],
1004            pending: vec![]
1005        }
1006    );
1007
1008    // User D is notified again of the incoming call and accepts it.
1009    assert!(incoming_call_d.next().await.unwrap().is_some());
1010    active_call_d
1011        .update(cx_d, |call, cx| call.accept_incoming(cx))
1012        .await
1013        .unwrap();
1014    executor.run_until_parked();
1015
1016    let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
1017    assert_eq!(
1018        room_participants(&room_a, cx_a),
1019        RoomParticipants {
1020            remote: vec![
1021                "user_b".to_string(),
1022                "user_c".to_string(),
1023                "user_d".to_string(),
1024            ],
1025            pending: vec![]
1026        }
1027    );
1028    assert_eq!(
1029        room_participants(&room_b, cx_b),
1030        RoomParticipants {
1031            remote: vec![
1032                "user_a".to_string(),
1033                "user_c".to_string(),
1034                "user_d".to_string(),
1035            ],
1036            pending: vec![]
1037        }
1038    );
1039    assert_eq!(
1040        room_participants(&room_c, cx_c),
1041        RoomParticipants {
1042            remote: vec![],
1043            pending: vec![]
1044        }
1045    );
1046    assert_eq!(
1047        room_participants(&room_d, cx_d),
1048        RoomParticipants {
1049            remote: vec![
1050                "user_a".to_string(),
1051                "user_b".to_string(),
1052                "user_c".to_string(),
1053            ],
1054            pending: vec![]
1055        }
1056    );
1057
1058    // The server finishes restarting, cleaning up stale connections.
1059    server.start().await.unwrap();
1060    executor.advance_clock(CLEANUP_TIMEOUT);
1061    assert_eq!(
1062        room_participants(&room_a, cx_a),
1063        RoomParticipants {
1064            remote: vec!["user_b".to_string(), "user_d".to_string()],
1065            pending: vec![]
1066        }
1067    );
1068    assert_eq!(
1069        room_participants(&room_b, cx_b),
1070        RoomParticipants {
1071            remote: vec!["user_a".to_string(), "user_d".to_string()],
1072            pending: vec![]
1073        }
1074    );
1075    assert_eq!(
1076        room_participants(&room_c, cx_c),
1077        RoomParticipants {
1078            remote: vec![],
1079            pending: vec![]
1080        }
1081    );
1082    assert_eq!(
1083        room_participants(&room_d, cx_d),
1084        RoomParticipants {
1085            remote: vec!["user_a".to_string(), "user_b".to_string()],
1086            pending: vec![]
1087        }
1088    );
1089
1090    // User D hangs up.
1091    active_call_d
1092        .update(cx_d, |call, cx| call.hang_up(cx))
1093        .await
1094        .unwrap();
1095    executor.run_until_parked();
1096    assert_eq!(
1097        room_participants(&room_a, cx_a),
1098        RoomParticipants {
1099            remote: vec!["user_b".to_string()],
1100            pending: vec![]
1101        }
1102    );
1103    assert_eq!(
1104        room_participants(&room_b, cx_b),
1105        RoomParticipants {
1106            remote: vec!["user_a".to_string()],
1107            pending: vec![]
1108        }
1109    );
1110    assert_eq!(
1111        room_participants(&room_c, cx_c),
1112        RoomParticipants {
1113            remote: vec![],
1114            pending: vec![]
1115        }
1116    );
1117    assert_eq!(
1118        room_participants(&room_d, cx_d),
1119        RoomParticipants {
1120            remote: vec![],
1121            pending: vec![]
1122        }
1123    );
1124
1125    // User B calls user D again.
1126    active_call_b
1127        .update(cx_b, |call, cx| {
1128            call.invite(client_d.user_id().unwrap(), None, cx)
1129        })
1130        .await
1131        .unwrap();
1132
1133    // User D receives the call but doesn't join the room yet.
1134
1135    let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
1136    assert!(incoming_call_d.next().await.unwrap().is_some());
1137    executor.run_until_parked();
1138    assert_eq!(
1139        room_participants(&room_a, cx_a),
1140        RoomParticipants {
1141            remote: vec!["user_b".to_string()],
1142            pending: vec!["user_d".to_string()]
1143        }
1144    );
1145    assert_eq!(
1146        room_participants(&room_b, cx_b),
1147        RoomParticipants {
1148            remote: vec!["user_a".to_string()],
1149            pending: vec!["user_d".to_string()]
1150        }
1151    );
1152
1153    // The server is torn down.
1154    server.reset().await;
1155
1156    // Users A and B have troubles reconnecting, so they leave the room.
1157    client_a.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1158    client_b.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1159    client_c.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1160    executor.advance_clock(RECONNECT_TIMEOUT);
1161    assert_eq!(
1162        room_participants(&room_a, cx_a),
1163        RoomParticipants {
1164            remote: vec![],
1165            pending: vec![]
1166        }
1167    );
1168    assert_eq!(
1169        room_participants(&room_b, cx_b),
1170        RoomParticipants {
1171            remote: vec![],
1172            pending: vec![]
1173        }
1174    );
1175
1176    // User D is notified again of the incoming call but doesn't accept it.
1177    assert!(incoming_call_d.next().await.unwrap().is_some());
1178
1179    // The server finishes restarting, cleaning up stale connections and canceling the
1180    // call to user D because the room has become empty.
1181    server.start().await.unwrap();
1182    executor.advance_clock(CLEANUP_TIMEOUT);
1183    assert!(incoming_call_d.next().await.unwrap().is_none());
1184}
1185
1186#[gpui::test(iterations = 10)]
1187async fn test_calls_on_multiple_connections(
1188    executor: BackgroundExecutor,
1189    cx_a: &mut TestAppContext,
1190    cx_b1: &mut TestAppContext,
1191    cx_b2: &mut TestAppContext,
1192) {
1193    let mut server = TestServer::start(executor.clone()).await;
1194    let client_a = server.create_client(cx_a, "user_a").await;
1195    let client_b1 = server.create_client(cx_b1, "user_b").await;
1196    let client_b2 = server.create_client(cx_b2, "user_b").await;
1197    server
1198        .make_contacts(&mut [(&client_a, cx_a), (&client_b1, cx_b1)])
1199        .await;
1200
1201    let active_call_a = cx_a.read(ActiveCall::global);
1202    let active_call_b1 = cx_b1.read(ActiveCall::global);
1203    let active_call_b2 = cx_b2.read(ActiveCall::global);
1204
1205    let mut incoming_call_b1 = active_call_b1.read_with(cx_b1, |call, _| call.incoming());
1206
1207    let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
1208    assert!(incoming_call_b1.next().await.unwrap().is_none());
1209    assert!(incoming_call_b2.next().await.unwrap().is_none());
1210
1211    // Call user B from client A, ensuring both clients for user B ring.
1212    active_call_a
1213        .update(cx_a, |call, cx| {
1214            call.invite(client_b1.user_id().unwrap(), None, cx)
1215        })
1216        .await
1217        .unwrap();
1218    executor.run_until_parked();
1219    assert!(incoming_call_b1.next().await.unwrap().is_some());
1220    assert!(incoming_call_b2.next().await.unwrap().is_some());
1221
1222    // User B declines the call on one of the two connections, causing both connections
1223    // to stop ringing.
1224    active_call_b2.update(cx_b2, |call, cx| call.decline_incoming(cx).unwrap());
1225    executor.run_until_parked();
1226    assert!(incoming_call_b1.next().await.unwrap().is_none());
1227    assert!(incoming_call_b2.next().await.unwrap().is_none());
1228
1229    // Call user B again from client A.
1230    active_call_a
1231        .update(cx_a, |call, cx| {
1232            call.invite(client_b1.user_id().unwrap(), None, cx)
1233        })
1234        .await
1235        .unwrap();
1236    executor.run_until_parked();
1237    assert!(incoming_call_b1.next().await.unwrap().is_some());
1238    assert!(incoming_call_b2.next().await.unwrap().is_some());
1239
1240    // User B accepts the call on one of the two connections, causing both connections
1241    // to stop ringing.
1242    active_call_b2
1243        .update(cx_b2, |call, cx| call.accept_incoming(cx))
1244        .await
1245        .unwrap();
1246    executor.run_until_parked();
1247    assert!(incoming_call_b1.next().await.unwrap().is_none());
1248    assert!(incoming_call_b2.next().await.unwrap().is_none());
1249
1250    // User B disconnects the client that is not on the call. Everything should be fine.
1251    client_b1.disconnect(&cx_b1.to_async());
1252    executor.advance_clock(RECEIVE_TIMEOUT);
1253    client_b1
1254        .authenticate_and_connect(false, &cx_b1.to_async())
1255        .await
1256        .into_response()
1257        .unwrap();
1258
1259    // User B hangs up, and user A calls them again.
1260    active_call_b2
1261        .update(cx_b2, |call, cx| call.hang_up(cx))
1262        .await
1263        .unwrap();
1264    executor.run_until_parked();
1265    active_call_a
1266        .update(cx_a, |call, cx| {
1267            call.invite(client_b1.user_id().unwrap(), None, cx)
1268        })
1269        .await
1270        .unwrap();
1271    executor.run_until_parked();
1272    assert!(incoming_call_b1.next().await.unwrap().is_some());
1273    assert!(incoming_call_b2.next().await.unwrap().is_some());
1274
1275    // User A cancels the call, causing both connections to stop ringing.
1276    active_call_a
1277        .update(cx_a, |call, cx| {
1278            call.cancel_invite(client_b1.user_id().unwrap(), cx)
1279        })
1280        .await
1281        .unwrap();
1282    executor.run_until_parked();
1283    assert!(incoming_call_b1.next().await.unwrap().is_none());
1284    assert!(incoming_call_b2.next().await.unwrap().is_none());
1285
1286    // User A calls user B again.
1287    active_call_a
1288        .update(cx_a, |call, cx| {
1289            call.invite(client_b1.user_id().unwrap(), None, cx)
1290        })
1291        .await
1292        .unwrap();
1293    executor.run_until_parked();
1294    assert!(incoming_call_b1.next().await.unwrap().is_some());
1295    assert!(incoming_call_b2.next().await.unwrap().is_some());
1296
1297    // User A hangs up, causing both connections to stop ringing.
1298    active_call_a
1299        .update(cx_a, |call, cx| call.hang_up(cx))
1300        .await
1301        .unwrap();
1302    executor.run_until_parked();
1303    assert!(incoming_call_b1.next().await.unwrap().is_none());
1304    assert!(incoming_call_b2.next().await.unwrap().is_none());
1305
1306    // User A calls user B again.
1307    active_call_a
1308        .update(cx_a, |call, cx| {
1309            call.invite(client_b1.user_id().unwrap(), None, cx)
1310        })
1311        .await
1312        .unwrap();
1313    executor.run_until_parked();
1314    assert!(incoming_call_b1.next().await.unwrap().is_some());
1315    assert!(incoming_call_b2.next().await.unwrap().is_some());
1316
1317    // User A disconnects, causing both connections to stop ringing.
1318    server.forbid_connections();
1319    server.disconnect_client(client_a.peer_id().unwrap());
1320    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
1321    assert!(incoming_call_b1.next().await.unwrap().is_none());
1322    assert!(incoming_call_b2.next().await.unwrap().is_none());
1323
1324    // User A reconnects automatically, then calls user B again.
1325    server.allow_connections();
1326    executor.advance_clock(RECEIVE_TIMEOUT);
1327    active_call_a
1328        .update(cx_a, |call, cx| {
1329            call.invite(client_b1.user_id().unwrap(), None, cx)
1330        })
1331        .await
1332        .unwrap();
1333    executor.run_until_parked();
1334    assert!(incoming_call_b1.next().await.unwrap().is_some());
1335    assert!(incoming_call_b2.next().await.unwrap().is_some());
1336
1337    // User B disconnects all clients, causing user A to no longer see a pending call for them.
1338    server.forbid_connections();
1339    server.disconnect_client(client_b1.peer_id().unwrap());
1340    server.disconnect_client(client_b2.peer_id().unwrap());
1341    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
1342
1343    active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
1344}
1345
1346#[gpui::test(iterations = 10)]
1347async fn test_unshare_project(
1348    executor: BackgroundExecutor,
1349    cx_a: &mut TestAppContext,
1350    cx_b: &mut TestAppContext,
1351    cx_c: &mut TestAppContext,
1352) {
1353    let mut server = TestServer::start(executor.clone()).await;
1354    let client_a = server.create_client(cx_a, "user_a").await;
1355    let client_b = server.create_client(cx_b, "user_b").await;
1356    let client_c = server.create_client(cx_c, "user_c").await;
1357    server
1358        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1359        .await;
1360
1361    let active_call_a = cx_a.read(ActiveCall::global);
1362    let active_call_b = cx_b.read(ActiveCall::global);
1363
1364    client_a
1365        .fs()
1366        .insert_tree(
1367            "/a",
1368            json!({
1369                "a.txt": "a-contents",
1370                "b.txt": "b-contents",
1371            }),
1372        )
1373        .await;
1374
1375    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1376    let project_id = active_call_a
1377        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1378        .await
1379        .unwrap();
1380
1381    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1382    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1383    executor.run_until_parked();
1384
1385    assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
1386
1387    project_b
1388        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1389        .await
1390        .unwrap();
1391
1392    // When client B leaves the room, the project becomes read-only.
1393    active_call_b
1394        .update(cx_b, |call, cx| call.hang_up(cx))
1395        .await
1396        .unwrap();
1397    executor.run_until_parked();
1398
1399    assert!(project_b.read_with(cx_b, |project, cx| project.is_disconnected(cx)));
1400
1401    // Client C opens the project.
1402    let project_c = client_c.join_remote_project(project_id, cx_c).await;
1403
1404    // When client A unshares the project, client C's project becomes read-only.
1405    project_a
1406        .update(cx_a, |project, cx| project.unshare(cx))
1407        .unwrap();
1408    executor.run_until_parked();
1409
1410    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
1411
1412    assert!(project_c.read_with(cx_c, |project, cx| project.is_disconnected(cx)));
1413
1414    // Client C can open the project again after client A re-shares.
1415    let project_id = active_call_a
1416        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1417        .await
1418        .unwrap();
1419    let project_c2 = client_c.join_remote_project(project_id, cx_c).await;
1420    executor.run_until_parked();
1421
1422    assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
1423    project_c2
1424        .update(cx_c, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1425        .await
1426        .unwrap();
1427
1428    // When client A (the host) leaves the room, the project gets unshared and guests are notified.
1429    active_call_a
1430        .update(cx_a, |call, cx| call.hang_up(cx))
1431        .await
1432        .unwrap();
1433    executor.run_until_parked();
1434
1435    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
1436
1437    project_c2.read_with(cx_c, |project, cx| {
1438        assert!(project.is_disconnected(cx));
1439        assert!(project.collaborators().is_empty());
1440    });
1441}
1442
1443#[gpui::test(iterations = 10)]
1444async fn test_project_reconnect(
1445    executor: BackgroundExecutor,
1446    cx_a: &mut TestAppContext,
1447    cx_b: &mut TestAppContext,
1448) {
1449    let mut server = TestServer::start(executor.clone()).await;
1450    let client_a = server.create_client(cx_a, "user_a").await;
1451    let client_b = server.create_client(cx_b, "user_b").await;
1452    server
1453        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1454        .await;
1455
1456    cx_b.update(editor::init);
1457
1458    client_a
1459        .fs()
1460        .insert_tree(
1461            path!("/root-1"),
1462            json!({
1463                "dir1": {
1464                    "a.txt": "a",
1465                    "b.txt": "b",
1466                    "subdir1": {
1467                        "c.txt": "c",
1468                        "d.txt": "d",
1469                        "e.txt": "e",
1470                    }
1471                },
1472                "dir2": {
1473                    "v.txt": "v",
1474                },
1475                "dir3": {
1476                    "w.txt": "w",
1477                    "x.txt": "x",
1478                    "y.txt": "y",
1479                },
1480                "dir4": {
1481                    "z.txt": "z",
1482                },
1483            }),
1484        )
1485        .await;
1486    client_a
1487        .fs()
1488        .insert_tree(
1489            path!("/root-2"),
1490            json!({
1491                "2.txt": "2",
1492            }),
1493        )
1494        .await;
1495    client_a
1496        .fs()
1497        .insert_tree(
1498            path!("/root-3"),
1499            json!({
1500                "3.txt": "3",
1501            }),
1502        )
1503        .await;
1504
1505    let active_call_a = cx_a.read(ActiveCall::global);
1506    let (project_a1, _) = client_a
1507        .build_local_project(path!("/root-1/dir1"), cx_a)
1508        .await;
1509    let (project_a2, _) = client_a.build_local_project(path!("/root-2"), cx_a).await;
1510    let (project_a3, _) = client_a.build_local_project(path!("/root-3"), cx_a).await;
1511    let worktree_a1 =
1512        project_a1.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1513    let project1_id = active_call_a
1514        .update(cx_a, |call, cx| call.share_project(project_a1.clone(), cx))
1515        .await
1516        .unwrap();
1517    let project2_id = active_call_a
1518        .update(cx_a, |call, cx| call.share_project(project_a2.clone(), cx))
1519        .await
1520        .unwrap();
1521    let project3_id = active_call_a
1522        .update(cx_a, |call, cx| call.share_project(project_a3.clone(), cx))
1523        .await
1524        .unwrap();
1525
1526    let project_b1 = client_b.join_remote_project(project1_id, cx_b).await;
1527    let project_b2 = client_b.join_remote_project(project2_id, cx_b).await;
1528    let project_b3 = client_b.join_remote_project(project3_id, cx_b).await;
1529    executor.run_until_parked();
1530
1531    let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| {
1532        assert!(worktree.has_update_observer());
1533        worktree.id()
1534    });
1535    let (worktree_a2, _) = project_a1
1536        .update(cx_a, |p, cx| {
1537            p.find_or_create_worktree(path!("/root-1/dir2"), true, cx)
1538        })
1539        .await
1540        .unwrap();
1541    executor.run_until_parked();
1542
1543    let worktree2_id = worktree_a2.read_with(cx_a, |tree, _| {
1544        assert!(tree.has_update_observer());
1545        tree.id()
1546    });
1547    executor.run_until_parked();
1548
1549    project_b1.read_with(cx_b, |project, cx| {
1550        assert!(project.worktree_for_id(worktree2_id, cx).is_some())
1551    });
1552
1553    let buffer_a1 = project_a1
1554        .update(cx_a, |p, cx| p.open_buffer((worktree1_id, "a.txt"), cx))
1555        .await
1556        .unwrap();
1557    let buffer_b1 = project_b1
1558        .update(cx_b, |p, cx| p.open_buffer((worktree1_id, "a.txt"), cx))
1559        .await
1560        .unwrap();
1561
1562    // Drop client A's connection.
1563    server.forbid_connections();
1564    server.disconnect_client(client_a.peer_id().unwrap());
1565    executor.advance_clock(RECEIVE_TIMEOUT);
1566
1567    project_a1.read_with(cx_a, |project, _| {
1568        assert!(project.is_shared());
1569        assert_eq!(project.collaborators().len(), 1);
1570    });
1571
1572    project_b1.read_with(cx_b, |project, cx| {
1573        assert!(!project.is_disconnected(cx));
1574        assert_eq!(project.collaborators().len(), 1);
1575    });
1576
1577    worktree_a1.read_with(cx_a, |tree, _| assert!(tree.has_update_observer()));
1578
1579    // While client A is disconnected, add and remove files from client A's project.
1580    client_a
1581        .fs()
1582        .insert_tree(
1583            path!("/root-1/dir1/subdir2"),
1584            json!({
1585                "f.txt": "f-contents",
1586                "g.txt": "g-contents",
1587                "h.txt": "h-contents",
1588                "i.txt": "i-contents",
1589            }),
1590        )
1591        .await;
1592    client_a
1593        .fs()
1594        .remove_dir(
1595            path!("/root-1/dir1/subdir1").as_ref(),
1596            RemoveOptions {
1597                recursive: true,
1598                ..Default::default()
1599            },
1600        )
1601        .await
1602        .unwrap();
1603
1604    // While client A is disconnected, add and remove worktrees from client A's project.
1605    project_a1.update(cx_a, |project, cx| {
1606        project.remove_worktree(worktree2_id, cx)
1607    });
1608    let (worktree_a3, _) = project_a1
1609        .update(cx_a, |p, cx| {
1610            p.find_or_create_worktree(path!("/root-1/dir3"), true, cx)
1611        })
1612        .await
1613        .unwrap();
1614    worktree_a3
1615        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
1616        .await;
1617
1618    let worktree3_id = worktree_a3.read_with(cx_a, |tree, _| {
1619        assert!(!tree.has_update_observer());
1620        tree.id()
1621    });
1622    executor.run_until_parked();
1623
1624    // While client A is disconnected, close project 2
1625    cx_a.update(|_| drop(project_a2));
1626
1627    // While client A is disconnected, mutate a buffer on both the host and the guest.
1628    buffer_a1.update(cx_a, |buf, cx| buf.edit([(0..0, "W")], None, cx));
1629    buffer_b1.update(cx_b, |buf, cx| buf.edit([(1..1, "Z")], None, cx));
1630    executor.run_until_parked();
1631
1632    // Client A reconnects. Their project is re-shared, and client B re-joins it.
1633    server.allow_connections();
1634    client_a
1635        .authenticate_and_connect(false, &cx_a.to_async())
1636        .await
1637        .into_response()
1638        .unwrap();
1639    executor.run_until_parked();
1640
1641    project_a1.read_with(cx_a, |project, cx| {
1642        assert!(project.is_shared());
1643        assert!(worktree_a1.read(cx).has_update_observer());
1644        assert_eq!(
1645            worktree_a1
1646                .read(cx)
1647                .snapshot()
1648                .paths()
1649                .map(|p| p.to_str().unwrap())
1650                .collect::<Vec<_>>(),
1651            vec![
1652                separator!("a.txt"),
1653                separator!("b.txt"),
1654                separator!("subdir2"),
1655                separator!("subdir2/f.txt"),
1656                separator!("subdir2/g.txt"),
1657                separator!("subdir2/h.txt"),
1658                separator!("subdir2/i.txt")
1659            ]
1660        );
1661        assert!(worktree_a3.read(cx).has_update_observer());
1662        assert_eq!(
1663            worktree_a3
1664                .read(cx)
1665                .snapshot()
1666                .paths()
1667                .map(|p| p.to_str().unwrap())
1668                .collect::<Vec<_>>(),
1669            vec!["w.txt", "x.txt", "y.txt"]
1670        );
1671    });
1672
1673    project_b1.read_with(cx_b, |project, cx| {
1674        assert!(!project.is_disconnected(cx));
1675        assert_eq!(
1676            project
1677                .worktree_for_id(worktree1_id, cx)
1678                .unwrap()
1679                .read(cx)
1680                .snapshot()
1681                .paths()
1682                .map(|p| p.to_str().unwrap())
1683                .collect::<Vec<_>>(),
1684            vec![
1685                separator!("a.txt"),
1686                separator!("b.txt"),
1687                separator!("subdir2"),
1688                separator!("subdir2/f.txt"),
1689                separator!("subdir2/g.txt"),
1690                separator!("subdir2/h.txt"),
1691                separator!("subdir2/i.txt")
1692            ]
1693        );
1694        assert!(project.worktree_for_id(worktree2_id, cx).is_none());
1695        assert_eq!(
1696            project
1697                .worktree_for_id(worktree3_id, cx)
1698                .unwrap()
1699                .read(cx)
1700                .snapshot()
1701                .paths()
1702                .map(|p| p.to_str().unwrap())
1703                .collect::<Vec<_>>(),
1704            vec!["w.txt", "x.txt", "y.txt"]
1705        );
1706    });
1707
1708    project_b2.read_with(cx_b, |project, cx| assert!(project.is_disconnected(cx)));
1709
1710    project_b3.read_with(cx_b, |project, cx| assert!(!project.is_disconnected(cx)));
1711
1712    buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
1713
1714    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
1715
1716    // Drop client B's connection.
1717    server.forbid_connections();
1718    server.disconnect_client(client_b.peer_id().unwrap());
1719    executor.advance_clock(RECEIVE_TIMEOUT);
1720
1721    // While client B is disconnected, add and remove files from client A's project
1722    client_a
1723        .fs()
1724        .insert_file(path!("/root-1/dir1/subdir2/j.txt"), "j-contents".into())
1725        .await;
1726    client_a
1727        .fs()
1728        .remove_file(
1729            path!("/root-1/dir1/subdir2/i.txt").as_ref(),
1730            Default::default(),
1731        )
1732        .await
1733        .unwrap();
1734
1735    // While client B is disconnected, add and remove worktrees from client A's project.
1736    let (worktree_a4, _) = project_a1
1737        .update(cx_a, |p, cx| {
1738            p.find_or_create_worktree(path!("/root-1/dir4"), true, cx)
1739        })
1740        .await
1741        .unwrap();
1742    executor.run_until_parked();
1743
1744    let worktree4_id = worktree_a4.read_with(cx_a, |tree, _| {
1745        assert!(tree.has_update_observer());
1746        tree.id()
1747    });
1748    project_a1.update(cx_a, |project, cx| {
1749        project.remove_worktree(worktree3_id, cx)
1750    });
1751    executor.run_until_parked();
1752
1753    // While client B is disconnected, mutate a buffer on both the host and the guest.
1754    buffer_a1.update(cx_a, |buf, cx| buf.edit([(1..1, "X")], None, cx));
1755    buffer_b1.update(cx_b, |buf, cx| buf.edit([(2..2, "Y")], None, cx));
1756    executor.run_until_parked();
1757
1758    // While disconnected, close project 3
1759    cx_a.update(|_| drop(project_a3));
1760
1761    // Client B reconnects. They re-join the room and the remaining shared project.
1762    server.allow_connections();
1763    client_b
1764        .authenticate_and_connect(false, &cx_b.to_async())
1765        .await
1766        .into_response()
1767        .unwrap();
1768    executor.run_until_parked();
1769
1770    project_b1.read_with(cx_b, |project, cx| {
1771        assert!(!project.is_disconnected(cx));
1772        assert_eq!(
1773            project
1774                .worktree_for_id(worktree1_id, cx)
1775                .unwrap()
1776                .read(cx)
1777                .snapshot()
1778                .paths()
1779                .map(|p| p.to_str().unwrap())
1780                .collect::<Vec<_>>(),
1781            vec![
1782                separator!("a.txt"),
1783                separator!("b.txt"),
1784                separator!("subdir2"),
1785                separator!("subdir2/f.txt"),
1786                separator!("subdir2/g.txt"),
1787                separator!("subdir2/h.txt"),
1788                separator!("subdir2/j.txt")
1789            ]
1790        );
1791        assert!(project.worktree_for_id(worktree2_id, cx).is_none());
1792        assert_eq!(
1793            project
1794                .worktree_for_id(worktree4_id, cx)
1795                .unwrap()
1796                .read(cx)
1797                .snapshot()
1798                .paths()
1799                .map(|p| p.to_str().unwrap())
1800                .collect::<Vec<_>>(),
1801            vec!["z.txt"]
1802        );
1803    });
1804
1805    project_b3.read_with(cx_b, |project, cx| assert!(project.is_disconnected(cx)));
1806
1807    buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
1808
1809    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
1810}
1811
1812#[gpui::test(iterations = 10)]
1813async fn test_active_call_events(
1814    executor: BackgroundExecutor,
1815    cx_a: &mut TestAppContext,
1816    cx_b: &mut TestAppContext,
1817) {
1818    let mut server = TestServer::start(executor.clone()).await;
1819    let client_a = server.create_client(cx_a, "user_a").await;
1820    let client_b = server.create_client(cx_b, "user_b").await;
1821    client_a.fs().insert_tree("/a", json!({})).await;
1822    client_b.fs().insert_tree("/b", json!({})).await;
1823
1824    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
1825    let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
1826
1827    server
1828        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1829        .await;
1830    executor.run_until_parked();
1831
1832    let active_call_a = cx_a.read(ActiveCall::global);
1833    let active_call_b = cx_b.read(ActiveCall::global);
1834
1835    let events_a = active_call_events(cx_a);
1836    let events_b = active_call_events(cx_b);
1837
1838    let project_a_id = active_call_a
1839        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1840        .await
1841        .unwrap();
1842    executor.run_until_parked();
1843    assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
1844    assert_eq!(
1845        mem::take(&mut *events_b.borrow_mut()),
1846        vec![room::Event::RemoteProjectShared {
1847            owner: Arc::new(User {
1848                id: client_a.user_id().unwrap(),
1849                github_login: "user_a".to_string(),
1850                avatar_uri: "avatar_a".into(),
1851                name: None,
1852                email: None,
1853            }),
1854            project_id: project_a_id,
1855            worktree_root_names: vec!["a".to_string()],
1856        }]
1857    );
1858
1859    let project_b_id = active_call_b
1860        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1861        .await
1862        .unwrap();
1863    executor.run_until_parked();
1864    assert_eq!(
1865        mem::take(&mut *events_a.borrow_mut()),
1866        vec![room::Event::RemoteProjectShared {
1867            owner: Arc::new(User {
1868                id: client_b.user_id().unwrap(),
1869                github_login: "user_b".to_string(),
1870                avatar_uri: "avatar_b".into(),
1871                name: None,
1872                email: None,
1873            }),
1874            project_id: project_b_id,
1875            worktree_root_names: vec!["b".to_string()]
1876        }]
1877    );
1878    assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
1879
1880    // Sharing a project twice is idempotent.
1881    let project_b_id_2 = active_call_b
1882        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1883        .await
1884        .unwrap();
1885    assert_eq!(project_b_id_2, project_b_id);
1886    executor.run_until_parked();
1887    assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
1888    assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
1889
1890    // Unsharing a project should dispatch the RemoteProjectUnshared event.
1891    active_call_a
1892        .update(cx_a, |call, cx| call.hang_up(cx))
1893        .await
1894        .unwrap();
1895    executor.run_until_parked();
1896
1897    assert_eq!(
1898        mem::take(&mut *events_a.borrow_mut()),
1899        vec![room::Event::RoomLeft { channel_id: None }]
1900    );
1901    assert_eq!(
1902        mem::take(&mut *events_b.borrow_mut()),
1903        vec![room::Event::RemoteProjectUnshared {
1904            project_id: project_a_id,
1905        }]
1906    );
1907}
1908
1909fn active_call_events(cx: &mut TestAppContext) -> Rc<RefCell<Vec<room::Event>>> {
1910    let events = Rc::new(RefCell::new(Vec::new()));
1911    let active_call = cx.read(ActiveCall::global);
1912    cx.update({
1913        let events = events.clone();
1914        |cx| {
1915            cx.subscribe(&active_call, move |_, event, _| {
1916                events.borrow_mut().push(event.clone())
1917            })
1918            .detach()
1919        }
1920    });
1921    events
1922}
1923
1924#[gpui::test]
1925async fn test_mute_deafen(
1926    executor: BackgroundExecutor,
1927    cx_a: &mut TestAppContext,
1928    cx_b: &mut TestAppContext,
1929    cx_c: &mut TestAppContext,
1930) {
1931    let mut server = TestServer::start(executor.clone()).await;
1932    let client_a = server.create_client(cx_a, "user_a").await;
1933    let client_b = server.create_client(cx_b, "user_b").await;
1934    let client_c = server.create_client(cx_c, "user_c").await;
1935
1936    server
1937        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1938        .await;
1939
1940    let active_call_a = cx_a.read(ActiveCall::global);
1941    let active_call_b = cx_b.read(ActiveCall::global);
1942    let active_call_c = cx_c.read(ActiveCall::global);
1943
1944    // User A calls user B, B answers.
1945    active_call_a
1946        .update(cx_a, |call, cx| {
1947            call.invite(client_b.user_id().unwrap(), None, cx)
1948        })
1949        .await
1950        .unwrap();
1951    executor.run_until_parked();
1952    active_call_b
1953        .update(cx_b, |call, cx| call.accept_incoming(cx))
1954        .await
1955        .unwrap();
1956    executor.run_until_parked();
1957
1958    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
1959    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
1960
1961    room_a.read_with(cx_a, |room, _| assert!(!room.is_muted()));
1962    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
1963
1964    // Users A and B are both unmuted.
1965    assert_eq!(
1966        participant_audio_state(&room_a, cx_a),
1967        &[ParticipantAudioState {
1968            user_id: client_b.user_id().unwrap(),
1969            is_muted: false,
1970            audio_tracks_playing: vec![true],
1971        }]
1972    );
1973    assert_eq!(
1974        participant_audio_state(&room_b, cx_b),
1975        &[ParticipantAudioState {
1976            user_id: client_a.user_id().unwrap(),
1977            is_muted: false,
1978            audio_tracks_playing: vec![true],
1979        }]
1980    );
1981
1982    // User A mutes
1983    room_a.update(cx_a, |room, cx| room.toggle_mute(cx));
1984    executor.run_until_parked();
1985
1986    // User A hears user B, but B doesn't hear A.
1987    room_a.read_with(cx_a, |room, _| assert!(room.is_muted()));
1988    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
1989    assert_eq!(
1990        participant_audio_state(&room_a, cx_a),
1991        &[ParticipantAudioState {
1992            user_id: client_b.user_id().unwrap(),
1993            is_muted: false,
1994            audio_tracks_playing: vec![true],
1995        }]
1996    );
1997    assert_eq!(
1998        participant_audio_state(&room_b, cx_b),
1999        &[ParticipantAudioState {
2000            user_id: client_a.user_id().unwrap(),
2001            is_muted: true,
2002            audio_tracks_playing: vec![true],
2003        }]
2004    );
2005
2006    // User A deafens
2007    room_a.update(cx_a, |room, cx| room.toggle_deafen(cx));
2008    executor.run_until_parked();
2009
2010    // User A does not hear user B.
2011    room_a.read_with(cx_a, |room, _| assert!(room.is_muted()));
2012    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
2013    assert_eq!(
2014        participant_audio_state(&room_a, cx_a),
2015        &[ParticipantAudioState {
2016            user_id: client_b.user_id().unwrap(),
2017            is_muted: false,
2018            audio_tracks_playing: vec![false],
2019        }]
2020    );
2021    assert_eq!(
2022        participant_audio_state(&room_b, cx_b),
2023        &[ParticipantAudioState {
2024            user_id: client_a.user_id().unwrap(),
2025            is_muted: true,
2026            audio_tracks_playing: vec![true],
2027        }]
2028    );
2029
2030    // User B calls user C, C joins.
2031    active_call_b
2032        .update(cx_b, |call, cx| {
2033            call.invite(client_c.user_id().unwrap(), None, cx)
2034        })
2035        .await
2036        .unwrap();
2037    executor.run_until_parked();
2038    active_call_c
2039        .update(cx_c, |call, cx| call.accept_incoming(cx))
2040        .await
2041        .unwrap();
2042    executor.run_until_parked();
2043
2044    // User A does not hear users B or C.
2045    assert_eq!(
2046        participant_audio_state(&room_a, cx_a),
2047        &[
2048            ParticipantAudioState {
2049                user_id: client_b.user_id().unwrap(),
2050                is_muted: false,
2051                audio_tracks_playing: vec![false],
2052            },
2053            ParticipantAudioState {
2054                user_id: client_c.user_id().unwrap(),
2055                is_muted: false,
2056                audio_tracks_playing: vec![false],
2057            }
2058        ]
2059    );
2060    assert_eq!(
2061        participant_audio_state(&room_b, cx_b),
2062        &[
2063            ParticipantAudioState {
2064                user_id: client_a.user_id().unwrap(),
2065                is_muted: true,
2066                audio_tracks_playing: vec![true],
2067            },
2068            ParticipantAudioState {
2069                user_id: client_c.user_id().unwrap(),
2070                is_muted: false,
2071                audio_tracks_playing: vec![true],
2072            }
2073        ]
2074    );
2075
2076    #[derive(PartialEq, Eq, Debug)]
2077    struct ParticipantAudioState {
2078        user_id: u64,
2079        is_muted: bool,
2080        audio_tracks_playing: Vec<bool>,
2081    }
2082
2083    fn participant_audio_state(
2084        room: &Entity<Room>,
2085        cx: &TestAppContext,
2086    ) -> Vec<ParticipantAudioState> {
2087        room.read_with(cx, |room, _| {
2088            room.remote_participants()
2089                .iter()
2090                .map(|(user_id, participant)| ParticipantAudioState {
2091                    user_id: *user_id,
2092                    is_muted: participant.muted,
2093                    audio_tracks_playing: participant
2094                        .audio_tracks
2095                        .values()
2096                        .map(|(track, _)| track.enabled())
2097                        .collect(),
2098                })
2099                .collect::<Vec<_>>()
2100        })
2101    }
2102}
2103
2104#[gpui::test(iterations = 10)]
2105async fn test_room_location(
2106    executor: BackgroundExecutor,
2107    cx_a: &mut TestAppContext,
2108    cx_b: &mut TestAppContext,
2109) {
2110    let mut server = TestServer::start(executor.clone()).await;
2111    let client_a = server.create_client(cx_a, "user_a").await;
2112    let client_b = server.create_client(cx_b, "user_b").await;
2113    client_a.fs().insert_tree("/a", json!({})).await;
2114    client_b.fs().insert_tree("/b", json!({})).await;
2115
2116    let active_call_a = cx_a.read(ActiveCall::global);
2117    let active_call_b = cx_b.read(ActiveCall::global);
2118
2119    let a_notified = Rc::new(Cell::new(false));
2120    cx_a.update({
2121        let notified = a_notified.clone();
2122        |cx| {
2123            cx.observe(&active_call_a, move |_, _| notified.set(true))
2124                .detach()
2125        }
2126    });
2127
2128    let b_notified = Rc::new(Cell::new(false));
2129    cx_b.update({
2130        let b_notified = b_notified.clone();
2131        |cx| {
2132            cx.observe(&active_call_b, move |_, _| b_notified.set(true))
2133                .detach()
2134        }
2135    });
2136
2137    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
2138    active_call_a
2139        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2140        .await
2141        .unwrap();
2142    let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
2143
2144    server
2145        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2146        .await;
2147
2148    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
2149
2150    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
2151    executor.run_until_parked();
2152    assert!(a_notified.take());
2153    assert_eq!(
2154        participant_locations(&room_a, cx_a),
2155        vec![("user_b".to_string(), ParticipantLocation::External)]
2156    );
2157    assert!(b_notified.take());
2158    assert_eq!(
2159        participant_locations(&room_b, cx_b),
2160        vec![("user_a".to_string(), ParticipantLocation::UnsharedProject)]
2161    );
2162
2163    let project_a_id = active_call_a
2164        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2165        .await
2166        .unwrap();
2167    executor.run_until_parked();
2168    assert!(a_notified.take());
2169    assert_eq!(
2170        participant_locations(&room_a, cx_a),
2171        vec![("user_b".to_string(), ParticipantLocation::External)]
2172    );
2173    assert!(b_notified.take());
2174    assert_eq!(
2175        participant_locations(&room_b, cx_b),
2176        vec![(
2177            "user_a".to_string(),
2178            ParticipantLocation::SharedProject {
2179                project_id: project_a_id
2180            }
2181        )]
2182    );
2183
2184    let project_b_id = active_call_b
2185        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
2186        .await
2187        .unwrap();
2188    executor.run_until_parked();
2189    assert!(a_notified.take());
2190    assert_eq!(
2191        participant_locations(&room_a, cx_a),
2192        vec![("user_b".to_string(), ParticipantLocation::External)]
2193    );
2194    assert!(b_notified.take());
2195    assert_eq!(
2196        participant_locations(&room_b, cx_b),
2197        vec![(
2198            "user_a".to_string(),
2199            ParticipantLocation::SharedProject {
2200                project_id: project_a_id
2201            }
2202        )]
2203    );
2204
2205    active_call_b
2206        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2207        .await
2208        .unwrap();
2209    executor.run_until_parked();
2210    assert!(a_notified.take());
2211    assert_eq!(
2212        participant_locations(&room_a, cx_a),
2213        vec![(
2214            "user_b".to_string(),
2215            ParticipantLocation::SharedProject {
2216                project_id: project_b_id
2217            }
2218        )]
2219    );
2220    assert!(b_notified.take());
2221    assert_eq!(
2222        participant_locations(&room_b, cx_b),
2223        vec![(
2224            "user_a".to_string(),
2225            ParticipantLocation::SharedProject {
2226                project_id: project_a_id
2227            }
2228        )]
2229    );
2230
2231    active_call_b
2232        .update(cx_b, |call, cx| call.set_location(None, cx))
2233        .await
2234        .unwrap();
2235    executor.run_until_parked();
2236    assert!(a_notified.take());
2237    assert_eq!(
2238        participant_locations(&room_a, cx_a),
2239        vec![("user_b".to_string(), ParticipantLocation::External)]
2240    );
2241    assert!(b_notified.take());
2242    assert_eq!(
2243        participant_locations(&room_b, cx_b),
2244        vec![(
2245            "user_a".to_string(),
2246            ParticipantLocation::SharedProject {
2247                project_id: project_a_id
2248            }
2249        )]
2250    );
2251
2252    fn participant_locations(
2253        room: &Entity<Room>,
2254        cx: &TestAppContext,
2255    ) -> Vec<(String, ParticipantLocation)> {
2256        room.read_with(cx, |room, _| {
2257            room.remote_participants()
2258                .values()
2259                .map(|participant| {
2260                    (
2261                        participant.user.github_login.to_string(),
2262                        participant.location,
2263                    )
2264                })
2265                .collect()
2266        })
2267    }
2268}
2269
2270#[gpui::test(iterations = 10)]
2271async fn test_propagate_saves_and_fs_changes(
2272    executor: BackgroundExecutor,
2273    cx_a: &mut TestAppContext,
2274    cx_b: &mut TestAppContext,
2275    cx_c: &mut TestAppContext,
2276) {
2277    let mut server = TestServer::start(executor.clone()).await;
2278    let client_a = server.create_client(cx_a, "user_a").await;
2279    let client_b = server.create_client(cx_b, "user_b").await;
2280    let client_c = server.create_client(cx_c, "user_c").await;
2281
2282    server
2283        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2284        .await;
2285    let active_call_a = cx_a.read(ActiveCall::global);
2286
2287    let rust = Arc::new(Language::new(
2288        LanguageConfig {
2289            name: "Rust".into(),
2290            matcher: LanguageMatcher {
2291                path_suffixes: vec!["rs".to_string()],
2292                ..Default::default()
2293            },
2294            ..Default::default()
2295        },
2296        Some(tree_sitter_rust::LANGUAGE.into()),
2297    ));
2298    let javascript = Arc::new(Language::new(
2299        LanguageConfig {
2300            name: "JavaScript".into(),
2301            matcher: LanguageMatcher {
2302                path_suffixes: vec!["js".to_string()],
2303                ..Default::default()
2304            },
2305            ..Default::default()
2306        },
2307        Some(tree_sitter_rust::LANGUAGE.into()),
2308    ));
2309    for client in [&client_a, &client_b, &client_c] {
2310        client.language_registry().add(rust.clone());
2311        client.language_registry().add(javascript.clone());
2312    }
2313
2314    client_a
2315        .fs()
2316        .insert_tree(
2317            path!("/a"),
2318            json!({
2319                "file1.rs": "",
2320                "file2": ""
2321            }),
2322        )
2323        .await;
2324    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2325
2326    let worktree_a = project_a.read_with(cx_a, |p, cx| p.worktrees(cx).next().unwrap());
2327    let project_id = active_call_a
2328        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2329        .await
2330        .unwrap();
2331
2332    // Join that worktree as clients B and C.
2333    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2334    let project_c = client_c.join_remote_project(project_id, cx_c).await;
2335
2336    let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
2337
2338    let worktree_c = project_c.read_with(cx_c, |p, cx| p.worktrees(cx).next().unwrap());
2339
2340    // Open and edit a buffer as both guests B and C.
2341    let buffer_b = project_b
2342        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
2343        .await
2344        .unwrap();
2345    let buffer_c = project_c
2346        .update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
2347        .await
2348        .unwrap();
2349
2350    buffer_b.read_with(cx_b, |buffer, _| {
2351        assert_eq!(buffer.language().unwrap().name(), "Rust".into());
2352    });
2353
2354    buffer_c.read_with(cx_c, |buffer, _| {
2355        assert_eq!(buffer.language().unwrap().name(), "Rust".into());
2356    });
2357    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], None, cx));
2358    buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], None, cx));
2359
2360    // Open and edit that buffer as the host.
2361    let buffer_a = project_a
2362        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
2363        .await
2364        .unwrap();
2365
2366    executor.run_until_parked();
2367
2368    buffer_a.read_with(cx_a, |buf, _| assert_eq!(buf.text(), "i-am-c, i-am-b, "));
2369    buffer_a.update(cx_a, |buf, cx| {
2370        buf.edit([(buf.len()..buf.len(), "i-am-a")], None, cx)
2371    });
2372
2373    executor.run_until_parked();
2374
2375    buffer_a.read_with(cx_a, |buf, _| {
2376        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
2377    });
2378
2379    buffer_b.read_with(cx_b, |buf, _| {
2380        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
2381    });
2382
2383    buffer_c.read_with(cx_c, |buf, _| {
2384        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
2385    });
2386
2387    // Edit the buffer as the host and concurrently save as guest B.
2388    let save_b = project_b.update(cx_b, |project, cx| {
2389        project.save_buffer(buffer_b.clone(), cx)
2390    });
2391    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], None, cx));
2392    save_b.await.unwrap();
2393    assert_eq!(
2394        client_a.fs().load("/a/file1.rs".as_ref()).await.unwrap(),
2395        "hi-a, i-am-c, i-am-b, i-am-a"
2396    );
2397
2398    executor.run_until_parked();
2399
2400    buffer_a.read_with(cx_a, |buf, _| assert!(!buf.is_dirty()));
2401
2402    buffer_b.read_with(cx_b, |buf, _| assert!(!buf.is_dirty()));
2403
2404    buffer_c.read_with(cx_c, |buf, _| assert!(!buf.is_dirty()));
2405
2406    // Make changes on host's file system, see those changes on guest worktrees.
2407    client_a
2408        .fs()
2409        .rename(
2410            path!("/a/file1.rs").as_ref(),
2411            path!("/a/file1.js").as_ref(),
2412            Default::default(),
2413        )
2414        .await
2415        .unwrap();
2416    client_a
2417        .fs()
2418        .rename(
2419            path!("/a/file2").as_ref(),
2420            path!("/a/file3").as_ref(),
2421            Default::default(),
2422        )
2423        .await
2424        .unwrap();
2425    client_a
2426        .fs()
2427        .insert_file(path!("/a/file4"), "4".into())
2428        .await;
2429    executor.run_until_parked();
2430
2431    worktree_a.read_with(cx_a, |tree, _| {
2432        assert_eq!(
2433            tree.paths()
2434                .map(|p| p.to_string_lossy())
2435                .collect::<Vec<_>>(),
2436            ["file1.js", "file3", "file4"]
2437        )
2438    });
2439
2440    worktree_b.read_with(cx_b, |tree, _| {
2441        assert_eq!(
2442            tree.paths()
2443                .map(|p| p.to_string_lossy())
2444                .collect::<Vec<_>>(),
2445            ["file1.js", "file3", "file4"]
2446        )
2447    });
2448
2449    worktree_c.read_with(cx_c, |tree, _| {
2450        assert_eq!(
2451            tree.paths()
2452                .map(|p| p.to_string_lossy())
2453                .collect::<Vec<_>>(),
2454            ["file1.js", "file3", "file4"]
2455        )
2456    });
2457
2458    // Ensure buffer files are updated as well.
2459
2460    buffer_a.read_with(cx_a, |buffer, _| {
2461        assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
2462        assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
2463    });
2464
2465    buffer_b.read_with(cx_b, |buffer, _| {
2466        assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
2467        assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
2468    });
2469
2470    buffer_c.read_with(cx_c, |buffer, _| {
2471        assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
2472        assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
2473    });
2474
2475    let new_buffer_a = project_a
2476        .update(cx_a, |p, cx| p.create_buffer(cx))
2477        .await
2478        .unwrap();
2479
2480    let new_buffer_id = new_buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id());
2481    let new_buffer_b = project_b
2482        .update(cx_b, |p, cx| p.open_buffer_by_id(new_buffer_id, cx))
2483        .await
2484        .unwrap();
2485
2486    new_buffer_b.read_with(cx_b, |buffer, _| {
2487        assert!(buffer.file().is_none());
2488    });
2489
2490    new_buffer_a.update(cx_a, |buffer, cx| {
2491        buffer.edit([(0..0, "ok")], None, cx);
2492    });
2493    project_a
2494        .update(cx_a, |project, cx| {
2495            let path = ProjectPath {
2496                path: Arc::from(Path::new("file3.rs")),
2497                worktree_id: worktree_a.read(cx).id(),
2498            };
2499
2500            project.save_buffer_as(new_buffer_a.clone(), path, cx)
2501        })
2502        .await
2503        .unwrap();
2504
2505    executor.run_until_parked();
2506
2507    new_buffer_b.read_with(cx_b, |buffer_b, _| {
2508        assert_eq!(
2509            buffer_b.file().unwrap().path().as_ref(),
2510            Path::new("file3.rs")
2511        );
2512
2513        new_buffer_a.read_with(cx_a, |buffer_a, _| {
2514            assert_eq!(buffer_b.saved_mtime(), buffer_a.saved_mtime());
2515            assert_eq!(buffer_b.saved_version(), buffer_a.saved_version());
2516        });
2517    });
2518}
2519
2520#[gpui::test(iterations = 10)]
2521async fn test_git_diff_base_change(
2522    executor: BackgroundExecutor,
2523    cx_a: &mut TestAppContext,
2524    cx_b: &mut TestAppContext,
2525) {
2526    let mut server = TestServer::start(executor.clone()).await;
2527    let client_a = server.create_client(cx_a, "user_a").await;
2528    let client_b = server.create_client(cx_b, "user_b").await;
2529    server
2530        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2531        .await;
2532    let active_call_a = cx_a.read(ActiveCall::global);
2533
2534    client_a
2535        .fs()
2536        .insert_tree(
2537            "/dir",
2538            json!({
2539            ".git": {},
2540            "sub": {
2541                ".git": {},
2542                "b.txt": "
2543                    one
2544                    two
2545                    three
2546                ".unindent(),
2547            },
2548            "a.txt": "
2549                    one
2550                    two
2551                    three
2552                ".unindent(),
2553            }),
2554        )
2555        .await;
2556
2557    let (project_local, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2558    let project_id = active_call_a
2559        .update(cx_a, |call, cx| {
2560            call.share_project(project_local.clone(), cx)
2561        })
2562        .await
2563        .unwrap();
2564
2565    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
2566
2567    let staged_text = "
2568        one
2569        three
2570    "
2571    .unindent();
2572
2573    let committed_text = "
2574        one
2575        TWO
2576        three
2577    "
2578    .unindent();
2579
2580    let new_committed_text = "
2581        one
2582        TWO_HUNDRED
2583        three
2584    "
2585    .unindent();
2586
2587    let new_staged_text = "
2588        one
2589        two
2590    "
2591    .unindent();
2592
2593    client_a.fs().set_index_for_repo(
2594        Path::new("/dir/.git"),
2595        &[("a.txt".into(), staged_text.clone())],
2596    );
2597    client_a.fs().set_head_for_repo(
2598        Path::new("/dir/.git"),
2599        &[("a.txt".into(), committed_text.clone())],
2600    );
2601
2602    // Create the buffer
2603    let buffer_local_a = project_local
2604        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2605        .await
2606        .unwrap();
2607    let local_unstaged_diff_a = project_local
2608        .update(cx_a, |p, cx| {
2609            p.open_unstaged_diff(buffer_local_a.clone(), cx)
2610        })
2611        .await
2612        .unwrap();
2613
2614    // Wait for it to catch up to the new diff
2615    executor.run_until_parked();
2616    local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
2617        let buffer = buffer_local_a.read(cx);
2618        assert_eq!(
2619            diff.base_text_string().as_deref(),
2620            Some(staged_text.as_str())
2621        );
2622        assert_hunks(
2623            diff.hunks_in_row_range(0..4, buffer, cx),
2624            buffer,
2625            &diff.base_text_string().unwrap(),
2626            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2627        );
2628    });
2629
2630    // Create remote buffer
2631    let remote_buffer_a = project_remote
2632        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2633        .await
2634        .unwrap();
2635    let remote_unstaged_diff_a = project_remote
2636        .update(cx_b, |p, cx| {
2637            p.open_unstaged_diff(remote_buffer_a.clone(), cx)
2638        })
2639        .await
2640        .unwrap();
2641
2642    // Wait remote buffer to catch up to the new diff
2643    executor.run_until_parked();
2644    remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
2645        let buffer = remote_buffer_a.read(cx);
2646        assert_eq!(
2647            diff.base_text_string().as_deref(),
2648            Some(staged_text.as_str())
2649        );
2650        assert_hunks(
2651            diff.hunks_in_row_range(0..4, buffer, cx),
2652            buffer,
2653            &diff.base_text_string().unwrap(),
2654            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2655        );
2656    });
2657
2658    // Open uncommitted changes on the guest, without opening them on the host first
2659    let remote_uncommitted_diff_a = project_remote
2660        .update(cx_b, |p, cx| {
2661            p.open_uncommitted_diff(remote_buffer_a.clone(), cx)
2662        })
2663        .await
2664        .unwrap();
2665    executor.run_until_parked();
2666    remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
2667        let buffer = remote_buffer_a.read(cx);
2668        assert_eq!(
2669            diff.base_text_string().as_deref(),
2670            Some(committed_text.as_str())
2671        );
2672        assert_hunks(
2673            diff.hunks_in_row_range(0..4, buffer, cx),
2674            buffer,
2675            &diff.base_text_string().unwrap(),
2676            &[(
2677                1..2,
2678                "TWO\n",
2679                "two\n",
2680                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
2681            )],
2682        );
2683    });
2684
2685    // Update the index text of the open buffer
2686    client_a.fs().set_index_for_repo(
2687        Path::new("/dir/.git"),
2688        &[("a.txt".into(), new_staged_text.clone())],
2689    );
2690    client_a.fs().set_head_for_repo(
2691        Path::new("/dir/.git"),
2692        &[("a.txt".into(), new_committed_text.clone())],
2693    );
2694
2695    // Wait for buffer_local_a to receive it
2696    executor.run_until_parked();
2697    local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
2698        let buffer = buffer_local_a.read(cx);
2699        assert_eq!(
2700            diff.base_text_string().as_deref(),
2701            Some(new_staged_text.as_str())
2702        );
2703        assert_hunks(
2704            diff.hunks_in_row_range(0..4, buffer, cx),
2705            buffer,
2706            &diff.base_text_string().unwrap(),
2707            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2708        );
2709    });
2710
2711    // Guest receives index text update
2712    remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
2713        let buffer = remote_buffer_a.read(cx);
2714        assert_eq!(
2715            diff.base_text_string().as_deref(),
2716            Some(new_staged_text.as_str())
2717        );
2718        assert_hunks(
2719            diff.hunks_in_row_range(0..4, buffer, cx),
2720            buffer,
2721            &diff.base_text_string().unwrap(),
2722            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2723        );
2724    });
2725
2726    remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
2727        let buffer = remote_buffer_a.read(cx);
2728        assert_eq!(
2729            diff.base_text_string().as_deref(),
2730            Some(new_committed_text.as_str())
2731        );
2732        assert_hunks(
2733            diff.hunks_in_row_range(0..4, buffer, cx),
2734            buffer,
2735            &diff.base_text_string().unwrap(),
2736            &[(
2737                1..2,
2738                "TWO_HUNDRED\n",
2739                "two\n",
2740                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
2741            )],
2742        );
2743    });
2744
2745    // Nested git dir
2746    let staged_text = "
2747        one
2748        three
2749    "
2750    .unindent();
2751
2752    let new_staged_text = "
2753        one
2754        two
2755    "
2756    .unindent();
2757
2758    client_a.fs().set_index_for_repo(
2759        Path::new("/dir/sub/.git"),
2760        &[("b.txt".into(), staged_text.clone())],
2761    );
2762
2763    // Create the buffer
2764    let buffer_local_b = project_local
2765        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2766        .await
2767        .unwrap();
2768    let local_unstaged_diff_b = project_local
2769        .update(cx_a, |p, cx| {
2770            p.open_unstaged_diff(buffer_local_b.clone(), cx)
2771        })
2772        .await
2773        .unwrap();
2774
2775    // Wait for it to catch up to the new diff
2776    executor.run_until_parked();
2777    local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
2778        let buffer = buffer_local_b.read(cx);
2779        assert_eq!(
2780            diff.base_text_string().as_deref(),
2781            Some(staged_text.as_str())
2782        );
2783        assert_hunks(
2784            diff.hunks_in_row_range(0..4, buffer, cx),
2785            buffer,
2786            &diff.base_text_string().unwrap(),
2787            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2788        );
2789    });
2790
2791    // Create remote buffer
2792    let remote_buffer_b = project_remote
2793        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2794        .await
2795        .unwrap();
2796    let remote_unstaged_diff_b = project_remote
2797        .update(cx_b, |p, cx| {
2798            p.open_unstaged_diff(remote_buffer_b.clone(), cx)
2799        })
2800        .await
2801        .unwrap();
2802
2803    executor.run_until_parked();
2804    remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
2805        let buffer = remote_buffer_b.read(cx);
2806        assert_eq!(
2807            diff.base_text_string().as_deref(),
2808            Some(staged_text.as_str())
2809        );
2810        assert_hunks(
2811            diff.hunks_in_row_range(0..4, buffer, cx),
2812            buffer,
2813            &staged_text,
2814            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2815        );
2816    });
2817
2818    // Updatet the staged text
2819    client_a.fs().set_index_for_repo(
2820        Path::new("/dir/sub/.git"),
2821        &[("b.txt".into(), new_staged_text.clone())],
2822    );
2823
2824    // Wait for buffer_local_b to receive it
2825    executor.run_until_parked();
2826    local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
2827        let buffer = buffer_local_b.read(cx);
2828        assert_eq!(
2829            diff.base_text_string().as_deref(),
2830            Some(new_staged_text.as_str())
2831        );
2832        assert_hunks(
2833            diff.hunks_in_row_range(0..4, buffer, cx),
2834            buffer,
2835            &new_staged_text,
2836            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2837        );
2838    });
2839
2840    remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
2841        let buffer = remote_buffer_b.read(cx);
2842        assert_eq!(
2843            diff.base_text_string().as_deref(),
2844            Some(new_staged_text.as_str())
2845        );
2846        assert_hunks(
2847            diff.hunks_in_row_range(0..4, buffer, cx),
2848            buffer,
2849            &new_staged_text,
2850            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2851        );
2852    });
2853}
2854
2855#[gpui::test(iterations = 10)]
2856async fn test_git_branch_name(
2857    executor: BackgroundExecutor,
2858    cx_a: &mut TestAppContext,
2859    cx_b: &mut TestAppContext,
2860    cx_c: &mut TestAppContext,
2861) {
2862    let mut server = TestServer::start(executor.clone()).await;
2863    let client_a = server.create_client(cx_a, "user_a").await;
2864    let client_b = server.create_client(cx_b, "user_b").await;
2865    let client_c = server.create_client(cx_c, "user_c").await;
2866    server
2867        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2868        .await;
2869    let active_call_a = cx_a.read(ActiveCall::global);
2870
2871    client_a
2872        .fs()
2873        .insert_tree(
2874            "/dir",
2875            json!({
2876            ".git": {},
2877            }),
2878        )
2879        .await;
2880
2881    let (project_local, _worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2882    let project_id = active_call_a
2883        .update(cx_a, |call, cx| {
2884            call.share_project(project_local.clone(), cx)
2885        })
2886        .await
2887        .unwrap();
2888
2889    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
2890    client_a
2891        .fs()
2892        .set_branch_name(Path::new("/dir/.git"), Some("branch-1"));
2893
2894    // Wait for it to catch up to the new branch
2895    executor.run_until_parked();
2896
2897    #[track_caller]
2898    fn assert_branch(branch_name: Option<impl Into<String>>, project: &Project, cx: &App) {
2899        let branch_name = branch_name.map(Into::into);
2900        let repositories = project.repositories(cx).values().collect::<Vec<_>>();
2901        assert_eq!(repositories.len(), 1);
2902        let repository = repositories[0].clone();
2903        assert_eq!(
2904            repository
2905                .read(cx)
2906                .branch
2907                .as_ref()
2908                .map(|branch| branch.name().to_owned()),
2909            branch_name
2910        )
2911    }
2912
2913    // Smoke test branch reading
2914
2915    project_local.read_with(cx_a, |project, cx| {
2916        assert_branch(Some("branch-1"), project, cx)
2917    });
2918
2919    project_remote.read_with(cx_b, |project, cx| {
2920        assert_branch(Some("branch-1"), project, cx)
2921    });
2922
2923    client_a
2924        .fs()
2925        .set_branch_name(Path::new("/dir/.git"), Some("branch-2"));
2926
2927    // Wait for buffer_local_a to receive it
2928    executor.run_until_parked();
2929
2930    // Smoke test branch reading
2931
2932    project_local.read_with(cx_a, |project, cx| {
2933        assert_branch(Some("branch-2"), project, cx)
2934    });
2935
2936    project_remote.read_with(cx_b, |project, cx| {
2937        assert_branch(Some("branch-2"), project, cx)
2938    });
2939
2940    let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
2941    executor.run_until_parked();
2942
2943    project_remote_c.read_with(cx_c, |project, cx| {
2944        assert_branch(Some("branch-2"), project, cx)
2945    });
2946}
2947
2948#[gpui::test]
2949async fn test_git_status_sync(
2950    executor: BackgroundExecutor,
2951    cx_a: &mut TestAppContext,
2952    cx_b: &mut TestAppContext,
2953    cx_c: &mut TestAppContext,
2954) {
2955    let mut server = TestServer::start(executor.clone()).await;
2956    let client_a = server.create_client(cx_a, "user_a").await;
2957    let client_b = server.create_client(cx_b, "user_b").await;
2958    let client_c = server.create_client(cx_c, "user_c").await;
2959    server
2960        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2961        .await;
2962    let active_call_a = cx_a.read(ActiveCall::global);
2963
2964    client_a
2965        .fs()
2966        .insert_tree(
2967            path!("/dir"),
2968            json!({
2969                ".git": {},
2970                "a.txt": "a",
2971                "b.txt": "b",
2972                "c.txt": "c",
2973            }),
2974        )
2975        .await;
2976
2977    // Initially, a.txt is uncommitted, but present in the index,
2978    // and b.txt is unmerged.
2979    client_a.fs().set_head_for_repo(
2980        path!("/dir/.git").as_ref(),
2981        &[("b.txt".into(), "B".into()), ("c.txt".into(), "c".into())],
2982    );
2983    client_a.fs().set_index_for_repo(
2984        path!("/dir/.git").as_ref(),
2985        &[
2986            ("a.txt".into(), "".into()),
2987            ("b.txt".into(), "B".into()),
2988            ("c.txt".into(), "c".into()),
2989        ],
2990    );
2991    client_a.fs().set_unmerged_paths_for_repo(
2992        path!("/dir/.git").as_ref(),
2993        &[(
2994            "b.txt".into(),
2995            UnmergedStatus {
2996                first_head: UnmergedStatusCode::Updated,
2997                second_head: UnmergedStatusCode::Deleted,
2998            },
2999        )],
3000    );
3001
3002    const A_STATUS_START: FileStatus = FileStatus::Tracked(TrackedStatus {
3003        index_status: StatusCode::Added,
3004        worktree_status: StatusCode::Modified,
3005    });
3006    const B_STATUS_START: FileStatus = FileStatus::Unmerged(UnmergedStatus {
3007        first_head: UnmergedStatusCode::Updated,
3008        second_head: UnmergedStatusCode::Deleted,
3009    });
3010
3011    let (project_local, _worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3012    let project_id = active_call_a
3013        .update(cx_a, |call, cx| {
3014            call.share_project(project_local.clone(), cx)
3015        })
3016        .await
3017        .unwrap();
3018
3019    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
3020
3021    // Wait for it to catch up to the new status
3022    executor.run_until_parked();
3023
3024    #[track_caller]
3025    fn assert_status(
3026        file: impl AsRef<Path>,
3027        status: Option<FileStatus>,
3028        project: &Project,
3029        cx: &App,
3030    ) {
3031        let file = file.as_ref();
3032        let repos = project
3033            .repositories(cx)
3034            .values()
3035            .cloned()
3036            .collect::<Vec<_>>();
3037        assert_eq!(repos.len(), 1);
3038        let repo = repos.into_iter().next().unwrap();
3039        assert_eq!(
3040            repo.read(cx)
3041                .status_for_path(&file.into())
3042                .map(|entry| entry.status),
3043            status
3044        );
3045    }
3046
3047    project_local.read_with(cx_a, |project, cx| {
3048        assert_status("a.txt", Some(A_STATUS_START), project, cx);
3049        assert_status("b.txt", Some(B_STATUS_START), project, cx);
3050        assert_status("c.txt", None, project, cx);
3051    });
3052
3053    project_remote.read_with(cx_b, |project, cx| {
3054        assert_status("a.txt", Some(A_STATUS_START), project, cx);
3055        assert_status("b.txt", Some(B_STATUS_START), project, cx);
3056        assert_status("c.txt", None, project, cx);
3057    });
3058
3059    const A_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3060        index_status: StatusCode::Added,
3061        worktree_status: StatusCode::Unmodified,
3062    });
3063    const B_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3064        index_status: StatusCode::Deleted,
3065        worktree_status: StatusCode::Added,
3066    });
3067    const C_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3068        index_status: StatusCode::Unmodified,
3069        worktree_status: StatusCode::Modified,
3070    });
3071
3072    // Delete b.txt from the index, mark conflict as resolved,
3073    // and modify c.txt in the working copy.
3074    client_a.fs().set_index_for_repo(
3075        path!("/dir/.git").as_ref(),
3076        &[("a.txt".into(), "a".into()), ("c.txt".into(), "c".into())],
3077    );
3078    client_a
3079        .fs()
3080        .set_unmerged_paths_for_repo(path!("/dir/.git").as_ref(), &[]);
3081    client_a
3082        .fs()
3083        .atomic_write(path!("/dir/c.txt").into(), "CC".into())
3084        .await
3085        .unwrap();
3086
3087    // Wait for buffer_local_a to receive it
3088    executor.run_until_parked();
3089
3090    // Smoke test status reading
3091    project_local.read_with(cx_a, |project, cx| {
3092        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3093        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3094        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3095    });
3096
3097    project_remote.read_with(cx_b, |project, cx| {
3098        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3099        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3100        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3101    });
3102
3103    // And synchronization while joining
3104    let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
3105    executor.run_until_parked();
3106
3107    project_remote_c.read_with(cx_c, |project, cx| {
3108        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3109        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3110        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3111    });
3112
3113    // Now remove the original git repository and check that collaborators are notified.
3114    client_a
3115        .fs()
3116        .remove_dir(path!("/dir/.git").as_ref(), RemoveOptions::default())
3117        .await
3118        .unwrap();
3119
3120    executor.run_until_parked();
3121    project_remote.update(cx_b, |project, cx| {
3122        pretty_assertions::assert_eq!(
3123            project.git_store().read(cx).repo_snapshots(cx),
3124            HashMap::default()
3125        );
3126    });
3127    project_remote_c.update(cx_c, |project, cx| {
3128        pretty_assertions::assert_eq!(
3129            project.git_store().read(cx).repo_snapshots(cx),
3130            HashMap::default()
3131        );
3132    });
3133}
3134
3135#[gpui::test(iterations = 10)]
3136async fn test_fs_operations(
3137    executor: BackgroundExecutor,
3138    cx_a: &mut TestAppContext,
3139    cx_b: &mut TestAppContext,
3140) {
3141    let mut server = TestServer::start(executor.clone()).await;
3142    let client_a = server.create_client(cx_a, "user_a").await;
3143    let client_b = server.create_client(cx_b, "user_b").await;
3144    server
3145        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3146        .await;
3147    let active_call_a = cx_a.read(ActiveCall::global);
3148
3149    client_a
3150        .fs()
3151        .insert_tree(
3152            path!("/dir"),
3153            json!({
3154                "a.txt": "a-contents",
3155                "b.txt": "b-contents",
3156            }),
3157        )
3158        .await;
3159    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3160    let project_id = active_call_a
3161        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3162        .await
3163        .unwrap();
3164    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3165
3166    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
3167    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
3168
3169    let entry = project_b
3170        .update(cx_b, |project, cx| {
3171            project.create_entry((worktree_id, "c.txt"), false, cx)
3172        })
3173        .await
3174        .unwrap()
3175        .to_included()
3176        .unwrap();
3177
3178    worktree_a.read_with(cx_a, |worktree, _| {
3179        assert_eq!(
3180            worktree
3181                .paths()
3182                .map(|p| p.to_string_lossy())
3183                .collect::<Vec<_>>(),
3184            ["a.txt", "b.txt", "c.txt"]
3185        );
3186    });
3187
3188    worktree_b.read_with(cx_b, |worktree, _| {
3189        assert_eq!(
3190            worktree
3191                .paths()
3192                .map(|p| p.to_string_lossy())
3193                .collect::<Vec<_>>(),
3194            ["a.txt", "b.txt", "c.txt"]
3195        );
3196    });
3197
3198    project_b
3199        .update(cx_b, |project, cx| {
3200            project.rename_entry(entry.id, Path::new("d.txt"), cx)
3201        })
3202        .await
3203        .unwrap()
3204        .to_included()
3205        .unwrap();
3206
3207    worktree_a.read_with(cx_a, |worktree, _| {
3208        assert_eq!(
3209            worktree
3210                .paths()
3211                .map(|p| p.to_string_lossy())
3212                .collect::<Vec<_>>(),
3213            ["a.txt", "b.txt", "d.txt"]
3214        );
3215    });
3216
3217    worktree_b.read_with(cx_b, |worktree, _| {
3218        assert_eq!(
3219            worktree
3220                .paths()
3221                .map(|p| p.to_string_lossy())
3222                .collect::<Vec<_>>(),
3223            ["a.txt", "b.txt", "d.txt"]
3224        );
3225    });
3226
3227    let dir_entry = project_b
3228        .update(cx_b, |project, cx| {
3229            project.create_entry((worktree_id, "DIR"), true, cx)
3230        })
3231        .await
3232        .unwrap()
3233        .to_included()
3234        .unwrap();
3235
3236    worktree_a.read_with(cx_a, |worktree, _| {
3237        assert_eq!(
3238            worktree
3239                .paths()
3240                .map(|p| p.to_string_lossy())
3241                .collect::<Vec<_>>(),
3242            ["DIR", "a.txt", "b.txt", "d.txt"]
3243        );
3244    });
3245
3246    worktree_b.read_with(cx_b, |worktree, _| {
3247        assert_eq!(
3248            worktree
3249                .paths()
3250                .map(|p| p.to_string_lossy())
3251                .collect::<Vec<_>>(),
3252            ["DIR", "a.txt", "b.txt", "d.txt"]
3253        );
3254    });
3255
3256    project_b
3257        .update(cx_b, |project, cx| {
3258            project.create_entry((worktree_id, "DIR/e.txt"), false, cx)
3259        })
3260        .await
3261        .unwrap()
3262        .to_included()
3263        .unwrap();
3264
3265    project_b
3266        .update(cx_b, |project, cx| {
3267            project.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
3268        })
3269        .await
3270        .unwrap()
3271        .to_included()
3272        .unwrap();
3273
3274    project_b
3275        .update(cx_b, |project, cx| {
3276            project.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
3277        })
3278        .await
3279        .unwrap()
3280        .to_included()
3281        .unwrap();
3282
3283    worktree_a.read_with(cx_a, |worktree, _| {
3284        assert_eq!(
3285            worktree
3286                .paths()
3287                .map(|p| p.to_string_lossy())
3288                .collect::<Vec<_>>(),
3289            [
3290                separator!("DIR"),
3291                separator!("DIR/SUBDIR"),
3292                separator!("DIR/SUBDIR/f.txt"),
3293                separator!("DIR/e.txt"),
3294                separator!("a.txt"),
3295                separator!("b.txt"),
3296                separator!("d.txt")
3297            ]
3298        );
3299    });
3300
3301    worktree_b.read_with(cx_b, |worktree, _| {
3302        assert_eq!(
3303            worktree
3304                .paths()
3305                .map(|p| p.to_string_lossy())
3306                .collect::<Vec<_>>(),
3307            [
3308                separator!("DIR"),
3309                separator!("DIR/SUBDIR"),
3310                separator!("DIR/SUBDIR/f.txt"),
3311                separator!("DIR/e.txt"),
3312                separator!("a.txt"),
3313                separator!("b.txt"),
3314                separator!("d.txt")
3315            ]
3316        );
3317    });
3318
3319    project_b
3320        .update(cx_b, |project, cx| {
3321            project.copy_entry(entry.id, None, Path::new("f.txt"), cx)
3322        })
3323        .await
3324        .unwrap()
3325        .unwrap();
3326
3327    worktree_a.read_with(cx_a, |worktree, _| {
3328        assert_eq!(
3329            worktree
3330                .paths()
3331                .map(|p| p.to_string_lossy())
3332                .collect::<Vec<_>>(),
3333            [
3334                separator!("DIR"),
3335                separator!("DIR/SUBDIR"),
3336                separator!("DIR/SUBDIR/f.txt"),
3337                separator!("DIR/e.txt"),
3338                separator!("a.txt"),
3339                separator!("b.txt"),
3340                separator!("d.txt"),
3341                separator!("f.txt")
3342            ]
3343        );
3344    });
3345
3346    worktree_b.read_with(cx_b, |worktree, _| {
3347        assert_eq!(
3348            worktree
3349                .paths()
3350                .map(|p| p.to_string_lossy())
3351                .collect::<Vec<_>>(),
3352            [
3353                separator!("DIR"),
3354                separator!("DIR/SUBDIR"),
3355                separator!("DIR/SUBDIR/f.txt"),
3356                separator!("DIR/e.txt"),
3357                separator!("a.txt"),
3358                separator!("b.txt"),
3359                separator!("d.txt"),
3360                separator!("f.txt")
3361            ]
3362        );
3363    });
3364
3365    project_b
3366        .update(cx_b, |project, cx| {
3367            project.delete_entry(dir_entry.id, false, cx).unwrap()
3368        })
3369        .await
3370        .unwrap();
3371    executor.run_until_parked();
3372
3373    worktree_a.read_with(cx_a, |worktree, _| {
3374        assert_eq!(
3375            worktree
3376                .paths()
3377                .map(|p| p.to_string_lossy())
3378                .collect::<Vec<_>>(),
3379            ["a.txt", "b.txt", "d.txt", "f.txt"]
3380        );
3381    });
3382
3383    worktree_b.read_with(cx_b, |worktree, _| {
3384        assert_eq!(
3385            worktree
3386                .paths()
3387                .map(|p| p.to_string_lossy())
3388                .collect::<Vec<_>>(),
3389            ["a.txt", "b.txt", "d.txt", "f.txt"]
3390        );
3391    });
3392
3393    project_b
3394        .update(cx_b, |project, cx| {
3395            project.delete_entry(entry.id, false, cx).unwrap()
3396        })
3397        .await
3398        .unwrap();
3399
3400    worktree_a.read_with(cx_a, |worktree, _| {
3401        assert_eq!(
3402            worktree
3403                .paths()
3404                .map(|p| p.to_string_lossy())
3405                .collect::<Vec<_>>(),
3406            ["a.txt", "b.txt", "f.txt"]
3407        );
3408    });
3409
3410    worktree_b.read_with(cx_b, |worktree, _| {
3411        assert_eq!(
3412            worktree
3413                .paths()
3414                .map(|p| p.to_string_lossy())
3415                .collect::<Vec<_>>(),
3416            ["a.txt", "b.txt", "f.txt"]
3417        );
3418    });
3419}
3420
3421#[gpui::test(iterations = 10)]
3422async fn test_local_settings(
3423    executor: BackgroundExecutor,
3424    cx_a: &mut TestAppContext,
3425    cx_b: &mut TestAppContext,
3426) {
3427    let mut server = TestServer::start(executor.clone()).await;
3428    let client_a = server.create_client(cx_a, "user_a").await;
3429    let client_b = server.create_client(cx_b, "user_b").await;
3430    server
3431        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3432        .await;
3433    let active_call_a = cx_a.read(ActiveCall::global);
3434
3435    // As client A, open a project that contains some local settings files
3436    client_a
3437        .fs()
3438        .insert_tree(
3439            "/dir",
3440            json!({
3441                ".zed": {
3442                    "settings.json": r#"{ "tab_size": 2 }"#
3443                },
3444                "a": {
3445                    ".zed": {
3446                        "settings.json": r#"{ "tab_size": 8 }"#
3447                    },
3448                    "a.txt": "a-contents",
3449                },
3450                "b": {
3451                    "b.txt": "b-contents",
3452                }
3453            }),
3454        )
3455        .await;
3456    let (project_a, _) = client_a.build_local_project("/dir", cx_a).await;
3457    executor.run_until_parked();
3458    let project_id = active_call_a
3459        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3460        .await
3461        .unwrap();
3462    executor.run_until_parked();
3463
3464    // As client B, join that project and observe the local settings.
3465    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3466
3467    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
3468    executor.run_until_parked();
3469    cx_b.read(|cx| {
3470        let store = cx.global::<SettingsStore>();
3471        assert_eq!(
3472            store
3473                .local_settings(worktree_b.read(cx).id())
3474                .collect::<Vec<_>>(),
3475            &[
3476                (Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
3477                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3478            ]
3479        )
3480    });
3481
3482    // As client A, update a settings file. As Client B, see the changed settings.
3483    client_a
3484        .fs()
3485        .insert_file("/dir/.zed/settings.json", r#"{}"#.into())
3486        .await;
3487    executor.run_until_parked();
3488    cx_b.read(|cx| {
3489        let store = cx.global::<SettingsStore>();
3490        assert_eq!(
3491            store
3492                .local_settings(worktree_b.read(cx).id())
3493                .collect::<Vec<_>>(),
3494            &[
3495                (Path::new("").into(), r#"{}"#.to_string()),
3496                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3497            ]
3498        )
3499    });
3500
3501    // As client A, create and remove some settings files. As client B, see the changed settings.
3502    client_a
3503        .fs()
3504        .remove_file("/dir/.zed/settings.json".as_ref(), Default::default())
3505        .await
3506        .unwrap();
3507    client_a
3508        .fs()
3509        .create_dir("/dir/b/.zed".as_ref())
3510        .await
3511        .unwrap();
3512    client_a
3513        .fs()
3514        .insert_file("/dir/b/.zed/settings.json", r#"{"tab_size": 4}"#.into())
3515        .await;
3516    executor.run_until_parked();
3517    cx_b.read(|cx| {
3518        let store = cx.global::<SettingsStore>();
3519        assert_eq!(
3520            store
3521                .local_settings(worktree_b.read(cx).id())
3522                .collect::<Vec<_>>(),
3523            &[
3524                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3525                (Path::new("b").into(), r#"{"tab_size":4}"#.to_string()),
3526            ]
3527        )
3528    });
3529
3530    // As client B, disconnect.
3531    server.forbid_connections();
3532    server.disconnect_client(client_b.peer_id().unwrap());
3533
3534    // As client A, change and remove settings files while client B is disconnected.
3535    client_a
3536        .fs()
3537        .insert_file("/dir/a/.zed/settings.json", r#"{"hard_tabs":true}"#.into())
3538        .await;
3539    client_a
3540        .fs()
3541        .remove_file("/dir/b/.zed/settings.json".as_ref(), Default::default())
3542        .await
3543        .unwrap();
3544    executor.run_until_parked();
3545
3546    // As client B, reconnect and see the changed settings.
3547    server.allow_connections();
3548    executor.advance_clock(RECEIVE_TIMEOUT);
3549    cx_b.read(|cx| {
3550        let store = cx.global::<SettingsStore>();
3551        assert_eq!(
3552            store
3553                .local_settings(worktree_b.read(cx).id())
3554                .collect::<Vec<_>>(),
3555            &[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
3556        )
3557    });
3558}
3559
3560#[gpui::test(iterations = 10)]
3561async fn test_buffer_conflict_after_save(
3562    executor: BackgroundExecutor,
3563    cx_a: &mut TestAppContext,
3564    cx_b: &mut TestAppContext,
3565) {
3566    let mut server = TestServer::start(executor.clone()).await;
3567    let client_a = server.create_client(cx_a, "user_a").await;
3568    let client_b = server.create_client(cx_b, "user_b").await;
3569    server
3570        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3571        .await;
3572    let active_call_a = cx_a.read(ActiveCall::global);
3573
3574    client_a
3575        .fs()
3576        .insert_tree(
3577            path!("/dir"),
3578            json!({
3579                "a.txt": "a-contents",
3580            }),
3581        )
3582        .await;
3583    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3584    let project_id = active_call_a
3585        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3586        .await
3587        .unwrap();
3588    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3589
3590    // Open a buffer as client B
3591    let buffer_b = project_b
3592        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3593        .await
3594        .unwrap();
3595
3596    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], None, cx));
3597
3598    buffer_b.read_with(cx_b, |buf, _| {
3599        assert!(buf.is_dirty());
3600        assert!(!buf.has_conflict());
3601    });
3602
3603    project_b
3604        .update(cx_b, |project, cx| {
3605            project.save_buffer(buffer_b.clone(), cx)
3606        })
3607        .await
3608        .unwrap();
3609
3610    buffer_b.read_with(cx_b, |buffer_b, _| assert!(!buffer_b.is_dirty()));
3611
3612    buffer_b.read_with(cx_b, |buf, _| {
3613        assert!(!buf.has_conflict());
3614    });
3615
3616    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], None, cx));
3617
3618    buffer_b.read_with(cx_b, |buf, _| {
3619        assert!(buf.is_dirty());
3620        assert!(!buf.has_conflict());
3621    });
3622}
3623
3624#[gpui::test(iterations = 10)]
3625async fn test_buffer_reloading(
3626    executor: BackgroundExecutor,
3627    cx_a: &mut TestAppContext,
3628    cx_b: &mut TestAppContext,
3629) {
3630    let mut server = TestServer::start(executor.clone()).await;
3631    let client_a = server.create_client(cx_a, "user_a").await;
3632    let client_b = server.create_client(cx_b, "user_b").await;
3633    server
3634        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3635        .await;
3636    let active_call_a = cx_a.read(ActiveCall::global);
3637
3638    client_a
3639        .fs()
3640        .insert_tree(
3641            path!("/dir"),
3642            json!({
3643                "a.txt": "a\nb\nc",
3644            }),
3645        )
3646        .await;
3647    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3648    let project_id = active_call_a
3649        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3650        .await
3651        .unwrap();
3652    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3653
3654    // Open a buffer as client B
3655    let buffer_b = project_b
3656        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3657        .await
3658        .unwrap();
3659
3660    buffer_b.read_with(cx_b, |buf, _| {
3661        assert!(!buf.is_dirty());
3662        assert!(!buf.has_conflict());
3663        assert_eq!(buf.line_ending(), LineEnding::Unix);
3664    });
3665
3666    let new_contents = Rope::from("d\ne\nf");
3667    client_a
3668        .fs()
3669        .save(
3670            path!("/dir/a.txt").as_ref(),
3671            &new_contents,
3672            LineEnding::Windows,
3673        )
3674        .await
3675        .unwrap();
3676
3677    executor.run_until_parked();
3678
3679    buffer_b.read_with(cx_b, |buf, _| {
3680        assert_eq!(buf.text(), new_contents.to_string());
3681        assert!(!buf.is_dirty());
3682        assert!(!buf.has_conflict());
3683        assert_eq!(buf.line_ending(), LineEnding::Windows);
3684    });
3685}
3686
3687#[gpui::test(iterations = 10)]
3688async fn test_editing_while_guest_opens_buffer(
3689    executor: BackgroundExecutor,
3690    cx_a: &mut TestAppContext,
3691    cx_b: &mut TestAppContext,
3692) {
3693    let mut server = TestServer::start(executor.clone()).await;
3694    let client_a = server.create_client(cx_a, "user_a").await;
3695    let client_b = server.create_client(cx_b, "user_b").await;
3696    server
3697        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3698        .await;
3699    let active_call_a = cx_a.read(ActiveCall::global);
3700
3701    client_a
3702        .fs()
3703        .insert_tree(path!("/dir"), json!({ "a.txt": "a-contents" }))
3704        .await;
3705    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3706    let project_id = active_call_a
3707        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3708        .await
3709        .unwrap();
3710    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3711
3712    // Open a buffer as client A
3713    let buffer_a = project_a
3714        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3715        .await
3716        .unwrap();
3717
3718    // Start opening the same buffer as client B
3719    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
3720    let buffer_b = cx_b.executor().spawn(open_buffer);
3721
3722    // Edit the buffer as client A while client B is still opening it.
3723    cx_b.executor().simulate_random_delay().await;
3724    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], None, cx));
3725    cx_b.executor().simulate_random_delay().await;
3726    buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], None, cx));
3727
3728    let text = buffer_a.read_with(cx_a, |buf, _| buf.text());
3729    let buffer_b = buffer_b.await.unwrap();
3730    executor.run_until_parked();
3731
3732    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), text));
3733}
3734
3735#[gpui::test(iterations = 10)]
3736async fn test_leaving_worktree_while_opening_buffer(
3737    executor: BackgroundExecutor,
3738    cx_a: &mut TestAppContext,
3739    cx_b: &mut TestAppContext,
3740) {
3741    let mut server = TestServer::start(executor.clone()).await;
3742    let client_a = server.create_client(cx_a, "user_a").await;
3743    let client_b = server.create_client(cx_b, "user_b").await;
3744    server
3745        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3746        .await;
3747    let active_call_a = cx_a.read(ActiveCall::global);
3748
3749    client_a
3750        .fs()
3751        .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
3752        .await;
3753    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3754    let project_id = active_call_a
3755        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3756        .await
3757        .unwrap();
3758    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3759
3760    // See that a guest has joined as client A.
3761    executor.run_until_parked();
3762
3763    project_a.read_with(cx_a, |p, _| assert_eq!(p.collaborators().len(), 1));
3764
3765    // Begin opening a buffer as client B, but leave the project before the open completes.
3766    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
3767    let buffer_b = cx_b.executor().spawn(open_buffer);
3768    cx_b.update(|_| drop(project_b));
3769    drop(buffer_b);
3770
3771    // See that the guest has left.
3772    executor.run_until_parked();
3773
3774    project_a.read_with(cx_a, |p, _| assert!(p.collaborators().is_empty()));
3775}
3776
3777#[gpui::test(iterations = 10)]
3778async fn test_canceling_buffer_opening(
3779    executor: BackgroundExecutor,
3780    cx_a: &mut TestAppContext,
3781    cx_b: &mut TestAppContext,
3782) {
3783    let mut server = TestServer::start(executor.clone()).await;
3784    let client_a = server.create_client(cx_a, "user_a").await;
3785    let client_b = server.create_client(cx_b, "user_b").await;
3786    server
3787        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3788        .await;
3789    let active_call_a = cx_a.read(ActiveCall::global);
3790
3791    client_a
3792        .fs()
3793        .insert_tree(
3794            "/dir",
3795            json!({
3796                "a.txt": "abc",
3797            }),
3798        )
3799        .await;
3800    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3801    let project_id = active_call_a
3802        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3803        .await
3804        .unwrap();
3805    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3806
3807    let buffer_a = project_a
3808        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3809        .await
3810        .unwrap();
3811
3812    // Open a buffer as client B but cancel after a random amount of time.
3813    let buffer_b = project_b.update(cx_b, |p, cx| {
3814        p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
3815    });
3816    executor.simulate_random_delay().await;
3817    drop(buffer_b);
3818
3819    // Try opening the same buffer again as client B, and ensure we can
3820    // still do it despite the cancellation above.
3821    let buffer_b = project_b
3822        .update(cx_b, |p, cx| {
3823            p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
3824        })
3825        .await
3826        .unwrap();
3827
3828    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "abc"));
3829}
3830
3831#[gpui::test(iterations = 10)]
3832async fn test_leaving_project(
3833    executor: BackgroundExecutor,
3834    cx_a: &mut TestAppContext,
3835    cx_b: &mut TestAppContext,
3836    cx_c: &mut TestAppContext,
3837) {
3838    let mut server = TestServer::start(executor.clone()).await;
3839    let client_a = server.create_client(cx_a, "user_a").await;
3840    let client_b = server.create_client(cx_b, "user_b").await;
3841    let client_c = server.create_client(cx_c, "user_c").await;
3842    server
3843        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
3844        .await;
3845    let active_call_a = cx_a.read(ActiveCall::global);
3846
3847    client_a
3848        .fs()
3849        .insert_tree(
3850            "/a",
3851            json!({
3852                "a.txt": "a-contents",
3853                "b.txt": "b-contents",
3854            }),
3855        )
3856        .await;
3857    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
3858    let project_id = active_call_a
3859        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3860        .await
3861        .unwrap();
3862    let project_b1 = client_b.join_remote_project(project_id, cx_b).await;
3863    let project_c = client_c.join_remote_project(project_id, cx_c).await;
3864
3865    // Client A sees that a guest has joined.
3866    executor.run_until_parked();
3867
3868    project_a.read_with(cx_a, |project, _| {
3869        assert_eq!(project.collaborators().len(), 2);
3870    });
3871
3872    project_b1.read_with(cx_b, |project, _| {
3873        assert_eq!(project.collaborators().len(), 2);
3874    });
3875
3876    project_c.read_with(cx_c, |project, _| {
3877        assert_eq!(project.collaborators().len(), 2);
3878    });
3879
3880    // Client B opens a buffer.
3881    let buffer_b1 = project_b1
3882        .update(cx_b, |project, cx| {
3883            let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
3884            project.open_buffer((worktree_id, "a.txt"), cx)
3885        })
3886        .await
3887        .unwrap();
3888
3889    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
3890
3891    // Drop client B's project and ensure client A and client C observe client B leaving.
3892    cx_b.update(|_| drop(project_b1));
3893    executor.run_until_parked();
3894
3895    project_a.read_with(cx_a, |project, _| {
3896        assert_eq!(project.collaborators().len(), 1);
3897    });
3898
3899    project_c.read_with(cx_c, |project, _| {
3900        assert_eq!(project.collaborators().len(), 1);
3901    });
3902
3903    // Client B re-joins the project and can open buffers as before.
3904    let project_b2 = client_b.join_remote_project(project_id, cx_b).await;
3905    executor.run_until_parked();
3906
3907    project_a.read_with(cx_a, |project, _| {
3908        assert_eq!(project.collaborators().len(), 2);
3909    });
3910
3911    project_b2.read_with(cx_b, |project, _| {
3912        assert_eq!(project.collaborators().len(), 2);
3913    });
3914
3915    project_c.read_with(cx_c, |project, _| {
3916        assert_eq!(project.collaborators().len(), 2);
3917    });
3918
3919    let buffer_b2 = project_b2
3920        .update(cx_b, |project, cx| {
3921            let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
3922            project.open_buffer((worktree_id, "a.txt"), cx)
3923        })
3924        .await
3925        .unwrap();
3926
3927    buffer_b2.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
3928
3929    project_a.read_with(cx_a, |project, _| {
3930        assert_eq!(project.collaborators().len(), 2);
3931    });
3932
3933    // Drop client B's connection and ensure client A and client C observe client B leaving.
3934    client_b.disconnect(&cx_b.to_async());
3935    executor.advance_clock(RECONNECT_TIMEOUT);
3936
3937    project_a.read_with(cx_a, |project, _| {
3938        assert_eq!(project.collaborators().len(), 1);
3939    });
3940
3941    project_b2.read_with(cx_b, |project, cx| {
3942        assert!(project.is_disconnected(cx));
3943    });
3944
3945    project_c.read_with(cx_c, |project, _| {
3946        assert_eq!(project.collaborators().len(), 1);
3947    });
3948
3949    // Client B can't join the project, unless they re-join the room.
3950    cx_b.spawn(|cx| {
3951        Project::in_room(
3952            project_id,
3953            client_b.app_state.client.clone(),
3954            client_b.user_store().clone(),
3955            client_b.language_registry().clone(),
3956            FakeFs::new(cx.background_executor().clone()),
3957            cx,
3958        )
3959    })
3960    .await
3961    .unwrap_err();
3962
3963    // Simulate connection loss for client C and ensure client A observes client C leaving the project.
3964    client_c.wait_for_current_user(cx_c).await;
3965    server.forbid_connections();
3966    server.disconnect_client(client_c.peer_id().unwrap());
3967    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
3968    executor.run_until_parked();
3969
3970    project_a.read_with(cx_a, |project, _| {
3971        assert_eq!(project.collaborators().len(), 0);
3972    });
3973
3974    project_b2.read_with(cx_b, |project, cx| {
3975        assert!(project.is_disconnected(cx));
3976    });
3977
3978    project_c.read_with(cx_c, |project, cx| {
3979        assert!(project.is_disconnected(cx));
3980    });
3981}
3982
3983#[gpui::test(iterations = 10)]
3984async fn test_collaborating_with_diagnostics(
3985    executor: BackgroundExecutor,
3986    cx_a: &mut TestAppContext,
3987    cx_b: &mut TestAppContext,
3988    cx_c: &mut TestAppContext,
3989) {
3990    let mut server = TestServer::start(executor.clone()).await;
3991    let client_a = server.create_client(cx_a, "user_a").await;
3992    let client_b = server.create_client(cx_b, "user_b").await;
3993    let client_c = server.create_client(cx_c, "user_c").await;
3994    server
3995        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
3996        .await;
3997    let active_call_a = cx_a.read(ActiveCall::global);
3998
3999    client_a.language_registry().add(Arc::new(Language::new(
4000        LanguageConfig {
4001            name: "Rust".into(),
4002            matcher: LanguageMatcher {
4003                path_suffixes: vec!["rs".to_string()],
4004                ..Default::default()
4005            },
4006            ..Default::default()
4007        },
4008        Some(tree_sitter_rust::LANGUAGE.into()),
4009    )));
4010    let mut fake_language_servers = client_a
4011        .language_registry()
4012        .register_fake_lsp("Rust", Default::default());
4013
4014    // Share a project as client A
4015    client_a
4016        .fs()
4017        .insert_tree(
4018            path!("/a"),
4019            json!({
4020                "a.rs": "let one = two",
4021                "other.rs": "",
4022            }),
4023        )
4024        .await;
4025    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4026
4027    // Cause the language server to start.
4028    let _buffer = project_a
4029        .update(cx_a, |project, cx| {
4030            project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
4031        })
4032        .await
4033        .unwrap();
4034
4035    // Simulate a language server reporting errors for a file.
4036    let mut fake_language_server = fake_language_servers.next().await.unwrap();
4037    fake_language_server
4038        .receive_notification::<lsp::notification::DidOpenTextDocument>()
4039        .await;
4040    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4041        &lsp::PublishDiagnosticsParams {
4042            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4043            version: None,
4044            diagnostics: vec![lsp::Diagnostic {
4045                severity: Some(lsp::DiagnosticSeverity::WARNING),
4046                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4047                message: "message 0".to_string(),
4048                ..Default::default()
4049            }],
4050        },
4051    );
4052
4053    // Client A shares the project and, simultaneously, the language server
4054    // publishes a diagnostic. This is done to ensure that the server always
4055    // observes the latest diagnostics for a worktree.
4056    let project_id = active_call_a
4057        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4058        .await
4059        .unwrap();
4060    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4061        &lsp::PublishDiagnosticsParams {
4062            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4063            version: None,
4064            diagnostics: vec![lsp::Diagnostic {
4065                severity: Some(lsp::DiagnosticSeverity::ERROR),
4066                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4067                message: "message 1".to_string(),
4068                ..Default::default()
4069            }],
4070        },
4071    );
4072
4073    // Join the worktree as client B.
4074    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4075
4076    // Wait for server to see the diagnostics update.
4077    executor.run_until_parked();
4078
4079    // Ensure client B observes the new diagnostics.
4080
4081    project_b.read_with(cx_b, |project, cx| {
4082        assert_eq!(
4083            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4084            &[(
4085                ProjectPath {
4086                    worktree_id,
4087                    path: Arc::from(Path::new("a.rs")),
4088                },
4089                LanguageServerId(0),
4090                DiagnosticSummary {
4091                    error_count: 1,
4092                    warning_count: 0,
4093                },
4094            )]
4095        )
4096    });
4097
4098    // Join project as client C and observe the diagnostics.
4099    let project_c = client_c.join_remote_project(project_id, cx_c).await;
4100    executor.run_until_parked();
4101    let project_c_diagnostic_summaries =
4102        Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
4103            project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
4104        })));
4105    project_c.update(cx_c, |_, cx| {
4106        let summaries = project_c_diagnostic_summaries.clone();
4107        cx.subscribe(&project_c, {
4108            move |p, _, event, cx| {
4109                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
4110                    *summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
4111                }
4112            }
4113        })
4114        .detach();
4115    });
4116
4117    executor.run_until_parked();
4118    assert_eq!(
4119        project_c_diagnostic_summaries.borrow().as_slice(),
4120        &[(
4121            ProjectPath {
4122                worktree_id,
4123                path: Arc::from(Path::new("a.rs")),
4124            },
4125            LanguageServerId(0),
4126            DiagnosticSummary {
4127                error_count: 1,
4128                warning_count: 0,
4129            },
4130        )]
4131    );
4132
4133    // Simulate a language server reporting more errors for a file.
4134    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4135        &lsp::PublishDiagnosticsParams {
4136            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4137            version: None,
4138            diagnostics: vec![
4139                lsp::Diagnostic {
4140                    severity: Some(lsp::DiagnosticSeverity::ERROR),
4141                    range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4142                    message: "message 1".to_string(),
4143                    ..Default::default()
4144                },
4145                lsp::Diagnostic {
4146                    severity: Some(lsp::DiagnosticSeverity::WARNING),
4147                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 13)),
4148                    message: "message 2".to_string(),
4149                    ..Default::default()
4150                },
4151            ],
4152        },
4153    );
4154
4155    // Clients B and C get the updated summaries
4156    executor.run_until_parked();
4157
4158    project_b.read_with(cx_b, |project, cx| {
4159        assert_eq!(
4160            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4161            [(
4162                ProjectPath {
4163                    worktree_id,
4164                    path: Arc::from(Path::new("a.rs")),
4165                },
4166                LanguageServerId(0),
4167                DiagnosticSummary {
4168                    error_count: 1,
4169                    warning_count: 1,
4170                },
4171            )]
4172        );
4173    });
4174
4175    project_c.read_with(cx_c, |project, cx| {
4176        assert_eq!(
4177            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4178            [(
4179                ProjectPath {
4180                    worktree_id,
4181                    path: Arc::from(Path::new("a.rs")),
4182                },
4183                LanguageServerId(0),
4184                DiagnosticSummary {
4185                    error_count: 1,
4186                    warning_count: 1,
4187                },
4188            )]
4189        );
4190    });
4191
4192    // Open the file with the errors on client B. They should be present.
4193    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
4194    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
4195
4196    buffer_b.read_with(cx_b, |buffer, _| {
4197        assert_eq!(
4198            buffer
4199                .snapshot()
4200                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
4201                .collect::<Vec<_>>(),
4202            &[
4203                DiagnosticEntry {
4204                    range: Point::new(0, 4)..Point::new(0, 7),
4205                    diagnostic: Diagnostic {
4206                        group_id: 2,
4207                        message: "message 1".to_string(),
4208                        severity: lsp::DiagnosticSeverity::ERROR,
4209                        is_primary: true,
4210                        ..Default::default()
4211                    }
4212                },
4213                DiagnosticEntry {
4214                    range: Point::new(0, 10)..Point::new(0, 13),
4215                    diagnostic: Diagnostic {
4216                        group_id: 3,
4217                        severity: lsp::DiagnosticSeverity::WARNING,
4218                        message: "message 2".to_string(),
4219                        is_primary: true,
4220                        ..Default::default()
4221                    }
4222                }
4223            ]
4224        );
4225    });
4226
4227    // Simulate a language server reporting no errors for a file.
4228    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4229        &lsp::PublishDiagnosticsParams {
4230            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4231            version: None,
4232            diagnostics: vec![],
4233        },
4234    );
4235    executor.run_until_parked();
4236
4237    project_a.read_with(cx_a, |project, cx| {
4238        assert_eq!(
4239            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4240            []
4241        )
4242    });
4243
4244    project_b.read_with(cx_b, |project, cx| {
4245        assert_eq!(
4246            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4247            []
4248        )
4249    });
4250
4251    project_c.read_with(cx_c, |project, cx| {
4252        assert_eq!(
4253            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4254            []
4255        )
4256    });
4257}
4258
4259#[gpui::test(iterations = 10)]
4260async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
4261    executor: BackgroundExecutor,
4262    cx_a: &mut TestAppContext,
4263    cx_b: &mut TestAppContext,
4264) {
4265    let mut server = TestServer::start(executor.clone()).await;
4266    let client_a = server.create_client(cx_a, "user_a").await;
4267    let client_b = server.create_client(cx_b, "user_b").await;
4268    server
4269        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4270        .await;
4271
4272    client_a.language_registry().add(rust_lang());
4273    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4274        "Rust",
4275        FakeLspAdapter {
4276            disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
4277            disk_based_diagnostics_sources: vec!["the-disk-based-diagnostics-source".into()],
4278            ..Default::default()
4279        },
4280    );
4281
4282    let file_names = &["one.rs", "two.rs", "three.rs", "four.rs", "five.rs"];
4283    client_a
4284        .fs()
4285        .insert_tree(
4286            path!("/test"),
4287            json!({
4288                "one.rs": "const ONE: usize = 1;",
4289                "two.rs": "const TWO: usize = 2;",
4290                "three.rs": "const THREE: usize = 3;",
4291                "four.rs": "const FOUR: usize = 3;",
4292                "five.rs": "const FIVE: usize = 3;",
4293            }),
4294        )
4295        .await;
4296
4297    let (project_a, worktree_id) = client_a.build_local_project(path!("/test"), cx_a).await;
4298
4299    // Share a project as client A
4300    let active_call_a = cx_a.read(ActiveCall::global);
4301    let project_id = active_call_a
4302        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4303        .await
4304        .unwrap();
4305
4306    // Join the project as client B and open all three files.
4307    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4308    let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
4309        project_b.update(cx_b, |p, cx| {
4310            p.open_buffer_with_lsp((worktree_id, file_name), cx)
4311        })
4312    }))
4313    .await
4314    .unwrap();
4315
4316    // Simulate a language server reporting errors for a file.
4317    let fake_language_server = fake_language_servers.next().await.unwrap();
4318    fake_language_server
4319        .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
4320            token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4321        })
4322        .await
4323        .into_response()
4324        .unwrap();
4325    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
4326        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4327        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
4328            lsp::WorkDoneProgressBegin {
4329                title: "Progress Began".into(),
4330                ..Default::default()
4331            },
4332        )),
4333    });
4334    for file_name in file_names {
4335        fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4336            &lsp::PublishDiagnosticsParams {
4337                uri: lsp::Url::from_file_path(Path::new(path!("/test")).join(file_name)).unwrap(),
4338                version: None,
4339                diagnostics: vec![lsp::Diagnostic {
4340                    severity: Some(lsp::DiagnosticSeverity::WARNING),
4341                    source: Some("the-disk-based-diagnostics-source".into()),
4342                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
4343                    message: "message one".to_string(),
4344                    ..Default::default()
4345                }],
4346            },
4347        );
4348    }
4349    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
4350        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4351        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
4352            lsp::WorkDoneProgressEnd { message: None },
4353        )),
4354    });
4355
4356    // When the "disk base diagnostics finished" message is received, the buffers'
4357    // diagnostics are expected to be present.
4358    let disk_based_diagnostics_finished = Arc::new(AtomicBool::new(false));
4359    project_b.update(cx_b, {
4360        let project_b = project_b.clone();
4361        let disk_based_diagnostics_finished = disk_based_diagnostics_finished.clone();
4362        move |_, cx| {
4363            cx.subscribe(&project_b, move |_, _, event, cx| {
4364                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
4365                    disk_based_diagnostics_finished.store(true, SeqCst);
4366                    for (buffer, _) in &guest_buffers {
4367                        assert_eq!(
4368                            buffer
4369                                .read(cx)
4370                                .snapshot()
4371                                .diagnostics_in_range::<_, usize>(0..5, false)
4372                                .count(),
4373                            1,
4374                            "expected a diagnostic for buffer {:?}",
4375                            buffer.read(cx).file().unwrap().path(),
4376                        );
4377                    }
4378                }
4379            })
4380            .detach();
4381        }
4382    });
4383
4384    executor.run_until_parked();
4385    assert!(disk_based_diagnostics_finished.load(SeqCst));
4386}
4387
4388#[gpui::test(iterations = 10)]
4389async fn test_reloading_buffer_manually(
4390    executor: BackgroundExecutor,
4391    cx_a: &mut TestAppContext,
4392    cx_b: &mut TestAppContext,
4393) {
4394    let mut server = TestServer::start(executor.clone()).await;
4395    let client_a = server.create_client(cx_a, "user_a").await;
4396    let client_b = server.create_client(cx_b, "user_b").await;
4397    server
4398        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4399        .await;
4400    let active_call_a = cx_a.read(ActiveCall::global);
4401
4402    client_a
4403        .fs()
4404        .insert_tree(path!("/a"), json!({ "a.rs": "let one = 1;" }))
4405        .await;
4406    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4407    let buffer_a = project_a
4408        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
4409        .await
4410        .unwrap();
4411    let project_id = active_call_a
4412        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4413        .await
4414        .unwrap();
4415
4416    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4417
4418    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
4419    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
4420    buffer_b.update(cx_b, |buffer, cx| {
4421        buffer.edit([(4..7, "six")], None, cx);
4422        buffer.edit([(10..11, "6")], None, cx);
4423        assert_eq!(buffer.text(), "let six = 6;");
4424        assert!(buffer.is_dirty());
4425        assert!(!buffer.has_conflict());
4426    });
4427    executor.run_until_parked();
4428
4429    buffer_a.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "let six = 6;"));
4430
4431    client_a
4432        .fs()
4433        .save(
4434            path!("/a/a.rs").as_ref(),
4435            &Rope::from("let seven = 7;"),
4436            LineEnding::Unix,
4437        )
4438        .await
4439        .unwrap();
4440    executor.run_until_parked();
4441
4442    buffer_a.read_with(cx_a, |buffer, _| assert!(buffer.has_conflict()));
4443
4444    buffer_b.read_with(cx_b, |buffer, _| assert!(buffer.has_conflict()));
4445
4446    project_b
4447        .update(cx_b, |project, cx| {
4448            project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
4449        })
4450        .await
4451        .unwrap();
4452
4453    buffer_a.read_with(cx_a, |buffer, _| {
4454        assert_eq!(buffer.text(), "let seven = 7;");
4455        assert!(!buffer.is_dirty());
4456        assert!(!buffer.has_conflict());
4457    });
4458
4459    buffer_b.read_with(cx_b, |buffer, _| {
4460        assert_eq!(buffer.text(), "let seven = 7;");
4461        assert!(!buffer.is_dirty());
4462        assert!(!buffer.has_conflict());
4463    });
4464
4465    buffer_a.update(cx_a, |buffer, cx| {
4466        // Undoing on the host is a no-op when the reload was initiated by the guest.
4467        buffer.undo(cx);
4468        assert_eq!(buffer.text(), "let seven = 7;");
4469        assert!(!buffer.is_dirty());
4470        assert!(!buffer.has_conflict());
4471    });
4472    buffer_b.update(cx_b, |buffer, cx| {
4473        // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
4474        buffer.undo(cx);
4475        assert_eq!(buffer.text(), "let six = 6;");
4476        assert!(buffer.is_dirty());
4477        assert!(!buffer.has_conflict());
4478    });
4479}
4480
4481#[gpui::test(iterations = 10)]
4482async fn test_formatting_buffer(
4483    executor: BackgroundExecutor,
4484    cx_a: &mut TestAppContext,
4485    cx_b: &mut TestAppContext,
4486) {
4487    let mut server = TestServer::start(executor.clone()).await;
4488    let client_a = server.create_client(cx_a, "user_a").await;
4489    let client_b = server.create_client(cx_b, "user_b").await;
4490    server
4491        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4492        .await;
4493    let active_call_a = cx_a.read(ActiveCall::global);
4494
4495    client_a.language_registry().add(rust_lang());
4496    let mut fake_language_servers = client_a
4497        .language_registry()
4498        .register_fake_lsp("Rust", FakeLspAdapter::default());
4499
4500    // Here we insert a fake tree with a directory that exists on disk. This is needed
4501    // because later we'll invoke a command, which requires passing a working directory
4502    // that points to a valid location on disk.
4503    let directory = env::current_dir().unwrap();
4504    client_a
4505        .fs()
4506        .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
4507        .await;
4508    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
4509    let project_id = active_call_a
4510        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4511        .await
4512        .unwrap();
4513    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4514
4515    let buffer_b = project_b
4516        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
4517        .await
4518        .unwrap();
4519
4520    let _handle = project_b.update(cx_b, |project, cx| {
4521        project.register_buffer_with_language_servers(&buffer_b, cx)
4522    });
4523    let fake_language_server = fake_language_servers.next().await.unwrap();
4524    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(|_, _| async move {
4525        Ok(Some(vec![
4526            lsp::TextEdit {
4527                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
4528                new_text: "h".to_string(),
4529            },
4530            lsp::TextEdit {
4531                range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
4532                new_text: "y".to_string(),
4533            },
4534        ]))
4535    });
4536
4537    project_b
4538        .update(cx_b, |project, cx| {
4539            project.format(
4540                HashSet::from_iter([buffer_b.clone()]),
4541                LspFormatTarget::Buffers,
4542                true,
4543                FormatTrigger::Save,
4544                cx,
4545            )
4546        })
4547        .await
4548        .unwrap();
4549
4550    // The edits from the LSP are applied, and a final newline is added.
4551    assert_eq!(
4552        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4553        "let honey = \"two\"\n"
4554    );
4555
4556    // There is no `awk` command on Windows.
4557    #[cfg(not(target_os = "windows"))]
4558    {
4559        // Ensure buffer can be formatted using an external command. Notice how the
4560        // host's configuration is honored as opposed to using the guest's settings.
4561        cx_a.update(|cx| {
4562            SettingsStore::update_global(cx, |store, cx| {
4563                store.update_user_settings::<AllLanguageSettings>(cx, |file| {
4564                    file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
4565                        vec![Formatter::External {
4566                            command: "awk".into(),
4567                            arguments: Some(
4568                                vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
4569                            ),
4570                        }]
4571                        .into(),
4572                    )));
4573                });
4574            });
4575        });
4576
4577        executor.allow_parking();
4578        project_b
4579            .update(cx_b, |project, cx| {
4580                project.format(
4581                    HashSet::from_iter([buffer_b.clone()]),
4582                    LspFormatTarget::Buffers,
4583                    true,
4584                    FormatTrigger::Save,
4585                    cx,
4586                )
4587            })
4588            .await
4589            .unwrap();
4590        assert_eq!(
4591            buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4592            format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap())
4593        );
4594    }
4595}
4596
4597#[gpui::test(iterations = 10)]
4598async fn test_prettier_formatting_buffer(
4599    executor: BackgroundExecutor,
4600    cx_a: &mut TestAppContext,
4601    cx_b: &mut TestAppContext,
4602) {
4603    let mut server = TestServer::start(executor.clone()).await;
4604    let client_a = server.create_client(cx_a, "user_a").await;
4605    let client_b = server.create_client(cx_b, "user_b").await;
4606    server
4607        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4608        .await;
4609    let active_call_a = cx_a.read(ActiveCall::global);
4610
4611    let test_plugin = "test_plugin";
4612
4613    client_a.language_registry().add(Arc::new(Language::new(
4614        LanguageConfig {
4615            name: "TypeScript".into(),
4616            matcher: LanguageMatcher {
4617                path_suffixes: vec!["ts".to_string()],
4618                ..Default::default()
4619            },
4620            ..Default::default()
4621        },
4622        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
4623    )));
4624    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4625        "TypeScript",
4626        FakeLspAdapter {
4627            prettier_plugins: vec![test_plugin],
4628            ..Default::default()
4629        },
4630    );
4631
4632    // Here we insert a fake tree with a directory that exists on disk. This is needed
4633    // because later we'll invoke a command, which requires passing a working directory
4634    // that points to a valid location on disk.
4635    let directory = env::current_dir().unwrap();
4636    let buffer_text = "let one = \"two\"";
4637    client_a
4638        .fs()
4639        .insert_tree(&directory, json!({ "a.ts": buffer_text }))
4640        .await;
4641    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
4642    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
4643    let open_buffer = project_a.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx));
4644    let buffer_a = cx_a.executor().spawn(open_buffer).await.unwrap();
4645
4646    let project_id = active_call_a
4647        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4648        .await
4649        .unwrap();
4650    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4651    let (buffer_b, _) = project_b
4652        .update(cx_b, |p, cx| {
4653            p.open_buffer_with_lsp((worktree_id, "a.ts"), cx)
4654        })
4655        .await
4656        .unwrap();
4657
4658    cx_a.update(|cx| {
4659        SettingsStore::update_global(cx, |store, cx| {
4660            store.update_user_settings::<AllLanguageSettings>(cx, |file| {
4661                file.defaults.formatter = Some(SelectedFormatter::Auto);
4662                file.defaults.prettier = Some(PrettierSettings {
4663                    allowed: true,
4664                    ..PrettierSettings::default()
4665                });
4666            });
4667        });
4668    });
4669    cx_b.update(|cx| {
4670        SettingsStore::update_global(cx, |store, cx| {
4671            store.update_user_settings::<AllLanguageSettings>(cx, |file| {
4672                file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
4673                    vec![Formatter::LanguageServer { name: None }].into(),
4674                )));
4675                file.defaults.prettier = Some(PrettierSettings {
4676                    allowed: true,
4677                    ..PrettierSettings::default()
4678                });
4679            });
4680        });
4681    });
4682    let fake_language_server = fake_language_servers.next().await.unwrap();
4683    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(|_, _| async move {
4684        panic!(
4685            "Unexpected: prettier should be preferred since it's enabled and language supports it"
4686        )
4687    });
4688
4689    project_b
4690        .update(cx_b, |project, cx| {
4691            project.format(
4692                HashSet::from_iter([buffer_b.clone()]),
4693                LspFormatTarget::Buffers,
4694                true,
4695                FormatTrigger::Save,
4696                cx,
4697            )
4698        })
4699        .await
4700        .unwrap();
4701
4702    executor.run_until_parked();
4703    assert_eq!(
4704        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4705        buffer_text.to_string() + "\n" + prettier_format_suffix,
4706        "Prettier formatting was not applied to client buffer after client's request"
4707    );
4708
4709    project_a
4710        .update(cx_a, |project, cx| {
4711            project.format(
4712                HashSet::from_iter([buffer_a.clone()]),
4713                LspFormatTarget::Buffers,
4714                true,
4715                FormatTrigger::Manual,
4716                cx,
4717            )
4718        })
4719        .await
4720        .unwrap();
4721
4722    executor.run_until_parked();
4723    assert_eq!(
4724        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4725        buffer_text.to_string() + "\n" + prettier_format_suffix + "\n" + prettier_format_suffix,
4726        "Prettier formatting was not applied to client buffer after host's request"
4727    );
4728}
4729
4730#[gpui::test(iterations = 10)]
4731async fn test_definition(
4732    executor: BackgroundExecutor,
4733    cx_a: &mut TestAppContext,
4734    cx_b: &mut TestAppContext,
4735) {
4736    let mut server = TestServer::start(executor.clone()).await;
4737    let client_a = server.create_client(cx_a, "user_a").await;
4738    let client_b = server.create_client(cx_b, "user_b").await;
4739    server
4740        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4741        .await;
4742    let active_call_a = cx_a.read(ActiveCall::global);
4743
4744    let mut fake_language_servers = client_a
4745        .language_registry()
4746        .register_fake_lsp("Rust", Default::default());
4747    client_a.language_registry().add(rust_lang());
4748
4749    client_a
4750        .fs()
4751        .insert_tree(
4752            path!("/root"),
4753            json!({
4754                "dir-1": {
4755                    "a.rs": "const ONE: usize = b::TWO + b::THREE;",
4756                },
4757                "dir-2": {
4758                    "b.rs": "const TWO: c::T2 = 2;\nconst THREE: usize = 3;",
4759                    "c.rs": "type T2 = usize;",
4760                }
4761            }),
4762        )
4763        .await;
4764    let (project_a, worktree_id) = client_a
4765        .build_local_project(path!("/root/dir-1"), cx_a)
4766        .await;
4767    let project_id = active_call_a
4768        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4769        .await
4770        .unwrap();
4771    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4772
4773    // Open the file on client B.
4774    let (buffer_b, _handle) = project_b
4775        .update(cx_b, |p, cx| {
4776            p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
4777        })
4778        .await
4779        .unwrap();
4780
4781    // Request the definition of a symbol as the guest.
4782    let fake_language_server = fake_language_servers.next().await.unwrap();
4783    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
4784        |_, _| async move {
4785            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4786                lsp::Location::new(
4787                    lsp::Url::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
4788                    lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
4789                ),
4790            )))
4791        },
4792    );
4793
4794    let definitions_1 = project_b
4795        .update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
4796        .await
4797        .unwrap();
4798    cx_b.read(|cx| {
4799        assert_eq!(definitions_1.len(), 1);
4800        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
4801        let target_buffer = definitions_1[0].target.buffer.read(cx);
4802        assert_eq!(
4803            target_buffer.text(),
4804            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
4805        );
4806        assert_eq!(
4807            definitions_1[0].target.range.to_point(target_buffer),
4808            Point::new(0, 6)..Point::new(0, 9)
4809        );
4810    });
4811
4812    // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
4813    // the previous call to `definition`.
4814    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
4815        |_, _| async move {
4816            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4817                lsp::Location::new(
4818                    lsp::Url::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
4819                    lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
4820                ),
4821            )))
4822        },
4823    );
4824
4825    let definitions_2 = project_b
4826        .update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
4827        .await
4828        .unwrap();
4829    cx_b.read(|cx| {
4830        assert_eq!(definitions_2.len(), 1);
4831        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
4832        let target_buffer = definitions_2[0].target.buffer.read(cx);
4833        assert_eq!(
4834            target_buffer.text(),
4835            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
4836        );
4837        assert_eq!(
4838            definitions_2[0].target.range.to_point(target_buffer),
4839            Point::new(1, 6)..Point::new(1, 11)
4840        );
4841    });
4842    assert_eq!(
4843        definitions_1[0].target.buffer,
4844        definitions_2[0].target.buffer
4845    );
4846
4847    fake_language_server.set_request_handler::<lsp::request::GotoTypeDefinition, _, _>(
4848        |req, _| async move {
4849            assert_eq!(
4850                req.text_document_position_params.position,
4851                lsp::Position::new(0, 7)
4852            );
4853            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4854                lsp::Location::new(
4855                    lsp::Url::from_file_path(path!("/root/dir-2/c.rs")).unwrap(),
4856                    lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
4857                ),
4858            )))
4859        },
4860    );
4861
4862    let type_definitions = project_b
4863        .update(cx_b, |p, cx| p.type_definition(&buffer_b, 7, cx))
4864        .await
4865        .unwrap();
4866    cx_b.read(|cx| {
4867        assert_eq!(type_definitions.len(), 1);
4868        let target_buffer = type_definitions[0].target.buffer.read(cx);
4869        assert_eq!(target_buffer.text(), "type T2 = usize;");
4870        assert_eq!(
4871            type_definitions[0].target.range.to_point(target_buffer),
4872            Point::new(0, 5)..Point::new(0, 7)
4873        );
4874    });
4875}
4876
4877#[gpui::test(iterations = 10)]
4878async fn test_references(
4879    executor: BackgroundExecutor,
4880    cx_a: &mut TestAppContext,
4881    cx_b: &mut TestAppContext,
4882) {
4883    let mut server = TestServer::start(executor.clone()).await;
4884    let client_a = server.create_client(cx_a, "user_a").await;
4885    let client_b = server.create_client(cx_b, "user_b").await;
4886    server
4887        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4888        .await;
4889    let active_call_a = cx_a.read(ActiveCall::global);
4890
4891    client_a.language_registry().add(rust_lang());
4892    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4893        "Rust",
4894        FakeLspAdapter {
4895            name: "my-fake-lsp-adapter",
4896            capabilities: lsp::ServerCapabilities {
4897                references_provider: Some(lsp::OneOf::Left(true)),
4898                ..Default::default()
4899            },
4900            ..Default::default()
4901        },
4902    );
4903
4904    client_a
4905        .fs()
4906        .insert_tree(
4907            path!("/root"),
4908            json!({
4909                "dir-1": {
4910                    "one.rs": "const ONE: usize = 1;",
4911                    "two.rs": "const TWO: usize = one::ONE + one::ONE;",
4912                },
4913                "dir-2": {
4914                    "three.rs": "const THREE: usize = two::TWO + one::ONE;",
4915                }
4916            }),
4917        )
4918        .await;
4919    let (project_a, worktree_id) = client_a
4920        .build_local_project(path!("/root/dir-1"), cx_a)
4921        .await;
4922    let project_id = active_call_a
4923        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4924        .await
4925        .unwrap();
4926    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4927
4928    // Open the file on client B.
4929    let (buffer_b, _handle) = project_b
4930        .update(cx_b, |p, cx| {
4931            p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
4932        })
4933        .await
4934        .unwrap();
4935
4936    // Request references to a symbol as the guest.
4937    let fake_language_server = fake_language_servers.next().await.unwrap();
4938    let (lsp_response_tx, rx) = mpsc::unbounded::<Result<Option<Vec<lsp::Location>>>>();
4939    fake_language_server.set_request_handler::<lsp::request::References, _, _>({
4940        let rx = Arc::new(Mutex::new(Some(rx)));
4941        move |params, _| {
4942            assert_eq!(
4943                params.text_document_position.text_document.uri.as_str(),
4944                uri!("file:///root/dir-1/one.rs")
4945            );
4946            let rx = rx.clone();
4947            async move {
4948                let mut response_rx = rx.lock().take().unwrap();
4949                let result = response_rx.next().await.unwrap();
4950                *rx.lock() = Some(response_rx);
4951                result
4952            }
4953        }
4954    });
4955
4956    let references = project_b.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx));
4957
4958    // User is informed that a request is pending.
4959    executor.run_until_parked();
4960    project_b.read_with(cx_b, |project, cx| {
4961        let status = project.language_server_statuses(cx).next().unwrap().1;
4962        assert_eq!(status.name, "my-fake-lsp-adapter");
4963        assert_eq!(
4964            status.pending_work.values().next().unwrap().message,
4965            Some("Finding references...".into())
4966        );
4967    });
4968
4969    // Cause the language server to respond.
4970    lsp_response_tx
4971        .unbounded_send(Ok(Some(vec![
4972            lsp::Location {
4973                uri: lsp::Url::from_file_path(path!("/root/dir-1/two.rs")).unwrap(),
4974                range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
4975            },
4976            lsp::Location {
4977                uri: lsp::Url::from_file_path(path!("/root/dir-1/two.rs")).unwrap(),
4978                range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
4979            },
4980            lsp::Location {
4981                uri: lsp::Url::from_file_path(path!("/root/dir-2/three.rs")).unwrap(),
4982                range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
4983            },
4984        ])))
4985        .unwrap();
4986
4987    let references = references.await.unwrap();
4988    executor.run_until_parked();
4989    project_b.read_with(cx_b, |project, cx| {
4990        // User is informed that a request is no longer pending.
4991        let status = project.language_server_statuses(cx).next().unwrap().1;
4992        assert!(status.pending_work.is_empty());
4993
4994        assert_eq!(references.len(), 3);
4995        assert_eq!(project.worktrees(cx).count(), 2);
4996
4997        let two_buffer = references[0].buffer.read(cx);
4998        let three_buffer = references[2].buffer.read(cx);
4999        assert_eq!(
5000            two_buffer.file().unwrap().path().as_ref(),
5001            Path::new("two.rs")
5002        );
5003        assert_eq!(references[1].buffer, references[0].buffer);
5004        assert_eq!(
5005            three_buffer.file().unwrap().full_path(cx),
5006            Path::new(path!("/root/dir-2/three.rs"))
5007        );
5008
5009        assert_eq!(references[0].range.to_offset(two_buffer), 24..27);
5010        assert_eq!(references[1].range.to_offset(two_buffer), 35..38);
5011        assert_eq!(references[2].range.to_offset(three_buffer), 37..40);
5012    });
5013
5014    let references = project_b.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx));
5015
5016    // User is informed that a request is pending.
5017    executor.run_until_parked();
5018    project_b.read_with(cx_b, |project, cx| {
5019        let status = project.language_server_statuses(cx).next().unwrap().1;
5020        assert_eq!(status.name, "my-fake-lsp-adapter");
5021        assert_eq!(
5022            status.pending_work.values().next().unwrap().message,
5023            Some("Finding references...".into())
5024        );
5025    });
5026
5027    // Cause the LSP request to fail.
5028    lsp_response_tx
5029        .unbounded_send(Err(anyhow!("can't find references")))
5030        .unwrap();
5031    references.await.unwrap_err();
5032
5033    // User is informed that the request is no longer pending.
5034    executor.run_until_parked();
5035    project_b.read_with(cx_b, |project, cx| {
5036        let status = project.language_server_statuses(cx).next().unwrap().1;
5037        assert!(status.pending_work.is_empty());
5038    });
5039}
5040
5041#[gpui::test(iterations = 10)]
5042async fn test_project_search(
5043    executor: BackgroundExecutor,
5044    cx_a: &mut TestAppContext,
5045    cx_b: &mut TestAppContext,
5046) {
5047    let mut server = TestServer::start(executor.clone()).await;
5048    let client_a = server.create_client(cx_a, "user_a").await;
5049    let client_b = server.create_client(cx_b, "user_b").await;
5050    server
5051        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5052        .await;
5053    let active_call_a = cx_a.read(ActiveCall::global);
5054
5055    client_a
5056        .fs()
5057        .insert_tree(
5058            "/root",
5059            json!({
5060                "dir-1": {
5061                    "a": "hello world",
5062                    "b": "goodnight moon",
5063                    "c": "a world of goo",
5064                    "d": "world champion of clown world",
5065                },
5066                "dir-2": {
5067                    "e": "disney world is fun",
5068                }
5069            }),
5070        )
5071        .await;
5072    let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
5073    let (worktree_2, _) = project_a
5074        .update(cx_a, |p, cx| {
5075            p.find_or_create_worktree("/root/dir-2", true, cx)
5076        })
5077        .await
5078        .unwrap();
5079    worktree_2
5080        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
5081        .await;
5082    let project_id = active_call_a
5083        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5084        .await
5085        .unwrap();
5086
5087    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5088
5089    // Perform a search as the guest.
5090    let mut results = HashMap::default();
5091    let search_rx = project_b.update(cx_b, |project, cx| {
5092        project.search(
5093            SearchQuery::text(
5094                "world",
5095                false,
5096                false,
5097                false,
5098                Default::default(),
5099                Default::default(),
5100                false,
5101                None,
5102            )
5103            .unwrap(),
5104            cx,
5105        )
5106    });
5107    while let Ok(result) = search_rx.recv().await {
5108        match result {
5109            SearchResult::Buffer { buffer, ranges } => {
5110                results.entry(buffer).or_insert(ranges);
5111            }
5112            SearchResult::LimitReached => {
5113                panic!(
5114                    "Unexpectedly reached search limit in tests. If you do want to assert limit-reached, change this panic call."
5115                )
5116            }
5117        };
5118    }
5119
5120    let mut ranges_by_path = results
5121        .into_iter()
5122        .map(|(buffer, ranges)| {
5123            buffer.read_with(cx_b, |buffer, cx| {
5124                let path = buffer.file().unwrap().full_path(cx);
5125                let offset_ranges = ranges
5126                    .into_iter()
5127                    .map(|range| range.to_offset(buffer))
5128                    .collect::<Vec<_>>();
5129                (path, offset_ranges)
5130            })
5131        })
5132        .collect::<Vec<_>>();
5133    ranges_by_path.sort_by_key(|(path, _)| path.clone());
5134
5135    assert_eq!(
5136        ranges_by_path,
5137        &[
5138            (PathBuf::from("dir-1/a"), vec![6..11]),
5139            (PathBuf::from("dir-1/c"), vec![2..7]),
5140            (PathBuf::from("dir-1/d"), vec![0..5, 24..29]),
5141            (PathBuf::from("dir-2/e"), vec![7..12]),
5142        ]
5143    );
5144}
5145
5146#[gpui::test(iterations = 10)]
5147async fn test_document_highlights(
5148    executor: BackgroundExecutor,
5149    cx_a: &mut TestAppContext,
5150    cx_b: &mut TestAppContext,
5151) {
5152    let mut server = TestServer::start(executor.clone()).await;
5153    let client_a = server.create_client(cx_a, "user_a").await;
5154    let client_b = server.create_client(cx_b, "user_b").await;
5155    server
5156        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5157        .await;
5158    let active_call_a = cx_a.read(ActiveCall::global);
5159
5160    client_a
5161        .fs()
5162        .insert_tree(
5163            path!("/root-1"),
5164            json!({
5165                "main.rs": "fn double(number: i32) -> i32 { number + number }",
5166            }),
5167        )
5168        .await;
5169
5170    let mut fake_language_servers = client_a
5171        .language_registry()
5172        .register_fake_lsp("Rust", Default::default());
5173    client_a.language_registry().add(rust_lang());
5174
5175    let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await;
5176    let project_id = active_call_a
5177        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5178        .await
5179        .unwrap();
5180    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5181
5182    // Open the file on client B.
5183    let (buffer_b, _handle) = project_b
5184        .update(cx_b, |p, cx| {
5185            p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
5186        })
5187        .await
5188        .unwrap();
5189
5190    // Request document highlights as the guest.
5191    let fake_language_server = fake_language_servers.next().await.unwrap();
5192    fake_language_server.set_request_handler::<lsp::request::DocumentHighlightRequest, _, _>(
5193        |params, _| async move {
5194            assert_eq!(
5195                params
5196                    .text_document_position_params
5197                    .text_document
5198                    .uri
5199                    .as_str(),
5200                uri!("file:///root-1/main.rs")
5201            );
5202            assert_eq!(
5203                params.text_document_position_params.position,
5204                lsp::Position::new(0, 34)
5205            );
5206            Ok(Some(vec![
5207                lsp::DocumentHighlight {
5208                    kind: Some(lsp::DocumentHighlightKind::WRITE),
5209                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 16)),
5210                },
5211                lsp::DocumentHighlight {
5212                    kind: Some(lsp::DocumentHighlightKind::READ),
5213                    range: lsp::Range::new(lsp::Position::new(0, 32), lsp::Position::new(0, 38)),
5214                },
5215                lsp::DocumentHighlight {
5216                    kind: Some(lsp::DocumentHighlightKind::READ),
5217                    range: lsp::Range::new(lsp::Position::new(0, 41), lsp::Position::new(0, 47)),
5218                },
5219            ]))
5220        },
5221    );
5222
5223    let highlights = project_b
5224        .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
5225        .await
5226        .unwrap();
5227
5228    buffer_b.read_with(cx_b, |buffer, _| {
5229        let snapshot = buffer.snapshot();
5230
5231        let highlights = highlights
5232            .into_iter()
5233            .map(|highlight| (highlight.kind, highlight.range.to_offset(&snapshot)))
5234            .collect::<Vec<_>>();
5235        assert_eq!(
5236            highlights,
5237            &[
5238                (lsp::DocumentHighlightKind::WRITE, 10..16),
5239                (lsp::DocumentHighlightKind::READ, 32..38),
5240                (lsp::DocumentHighlightKind::READ, 41..47)
5241            ]
5242        )
5243    });
5244}
5245
5246#[gpui::test(iterations = 10)]
5247async fn test_lsp_hover(
5248    executor: BackgroundExecutor,
5249    cx_a: &mut TestAppContext,
5250    cx_b: &mut TestAppContext,
5251) {
5252    let mut server = TestServer::start(executor.clone()).await;
5253    let client_a = server.create_client(cx_a, "user_a").await;
5254    let client_b = server.create_client(cx_b, "user_b").await;
5255    server
5256        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5257        .await;
5258    let active_call_a = cx_a.read(ActiveCall::global);
5259
5260    client_a
5261        .fs()
5262        .insert_tree(
5263            path!("/root-1"),
5264            json!({
5265                "main.rs": "use std::collections::HashMap;",
5266            }),
5267        )
5268        .await;
5269
5270    client_a.language_registry().add(rust_lang());
5271    let language_server_names = ["rust-analyzer", "CrabLang-ls"];
5272    let mut language_servers = [
5273        client_a.language_registry().register_fake_lsp(
5274            "Rust",
5275            FakeLspAdapter {
5276                name: "rust-analyzer",
5277                capabilities: lsp::ServerCapabilities {
5278                    hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
5279                    ..lsp::ServerCapabilities::default()
5280                },
5281                ..FakeLspAdapter::default()
5282            },
5283        ),
5284        client_a.language_registry().register_fake_lsp(
5285            "Rust",
5286            FakeLspAdapter {
5287                name: "CrabLang-ls",
5288                capabilities: lsp::ServerCapabilities {
5289                    hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
5290                    ..lsp::ServerCapabilities::default()
5291                },
5292                ..FakeLspAdapter::default()
5293            },
5294        ),
5295    ];
5296
5297    let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await;
5298    let project_id = active_call_a
5299        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5300        .await
5301        .unwrap();
5302    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5303
5304    // Open the file as the guest
5305    let (buffer_b, _handle) = project_b
5306        .update(cx_b, |p, cx| {
5307            p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
5308        })
5309        .await
5310        .unwrap();
5311
5312    let mut servers_with_hover_requests = HashMap::default();
5313    for i in 0..language_server_names.len() {
5314        let new_server = language_servers[i].next().await.unwrap_or_else(|| {
5315            panic!(
5316                "Failed to get language server #{i} with name {}",
5317                &language_server_names[i]
5318            )
5319        });
5320        let new_server_name = new_server.server.name();
5321        assert!(
5322            !servers_with_hover_requests.contains_key(&new_server_name),
5323            "Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
5324        );
5325        match new_server_name.as_ref() {
5326            "CrabLang-ls" => {
5327                servers_with_hover_requests.insert(
5328                    new_server_name.clone(),
5329                    new_server.set_request_handler::<lsp::request::HoverRequest, _, _>(
5330                        move |params, _| {
5331                            assert_eq!(
5332                                params
5333                                    .text_document_position_params
5334                                    .text_document
5335                                    .uri
5336                                    .as_str(),
5337                                uri!("file:///root-1/main.rs")
5338                            );
5339                            let name = new_server_name.clone();
5340                            async move {
5341                                Ok(Some(lsp::Hover {
5342                                    contents: lsp::HoverContents::Scalar(
5343                                        lsp::MarkedString::String(format!("{name} hover")),
5344                                    ),
5345                                    range: None,
5346                                }))
5347                            }
5348                        },
5349                    ),
5350                );
5351            }
5352            "rust-analyzer" => {
5353                servers_with_hover_requests.insert(
5354                    new_server_name.clone(),
5355                    new_server.set_request_handler::<lsp::request::HoverRequest, _, _>(
5356                        |params, _| async move {
5357                            assert_eq!(
5358                                params
5359                                    .text_document_position_params
5360                                    .text_document
5361                                    .uri
5362                                    .as_str(),
5363                                uri!("file:///root-1/main.rs")
5364                            );
5365                            assert_eq!(
5366                                params.text_document_position_params.position,
5367                                lsp::Position::new(0, 22)
5368                            );
5369                            Ok(Some(lsp::Hover {
5370                                contents: lsp::HoverContents::Array(vec![
5371                                    lsp::MarkedString::String("Test hover content.".to_string()),
5372                                    lsp::MarkedString::LanguageString(lsp::LanguageString {
5373                                        language: "Rust".to_string(),
5374                                        value: "let foo = 42;".to_string(),
5375                                    }),
5376                                ]),
5377                                range: Some(lsp::Range::new(
5378                                    lsp::Position::new(0, 22),
5379                                    lsp::Position::new(0, 29),
5380                                )),
5381                            }))
5382                        },
5383                    ),
5384                );
5385            }
5386            unexpected => panic!("Unexpected server name: {unexpected}"),
5387        }
5388    }
5389
5390    // Request hover information as the guest.
5391    let mut hovers = project_b
5392        .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
5393        .await;
5394    assert_eq!(
5395        hovers.len(),
5396        2,
5397        "Expected two hovers from both language servers, but got: {hovers:?}"
5398    );
5399
5400    let _: Vec<()> = futures::future::join_all(servers_with_hover_requests.into_values().map(
5401        |mut hover_request| async move {
5402            hover_request
5403                .next()
5404                .await
5405                .expect("All hover requests should have been triggered")
5406        },
5407    ))
5408    .await;
5409
5410    hovers.sort_by_key(|hover| hover.contents.len());
5411    let first_hover = hovers.first().cloned().unwrap();
5412    assert_eq!(
5413        first_hover.contents,
5414        vec![project::HoverBlock {
5415            text: "CrabLang-ls hover".to_string(),
5416            kind: HoverBlockKind::Markdown,
5417        },]
5418    );
5419    let second_hover = hovers.last().cloned().unwrap();
5420    assert_eq!(
5421        second_hover.contents,
5422        vec![
5423            project::HoverBlock {
5424                text: "Test hover content.".to_string(),
5425                kind: HoverBlockKind::Markdown,
5426            },
5427            project::HoverBlock {
5428                text: "let foo = 42;".to_string(),
5429                kind: HoverBlockKind::Code {
5430                    language: "Rust".to_string()
5431                },
5432            }
5433        ]
5434    );
5435    buffer_b.read_with(cx_b, |buffer, _| {
5436        let snapshot = buffer.snapshot();
5437        assert_eq!(second_hover.range.unwrap().to_offset(&snapshot), 22..29);
5438    });
5439}
5440
5441#[gpui::test(iterations = 10)]
5442async fn test_project_symbols(
5443    executor: BackgroundExecutor,
5444    cx_a: &mut TestAppContext,
5445    cx_b: &mut TestAppContext,
5446) {
5447    let mut server = TestServer::start(executor.clone()).await;
5448    let client_a = server.create_client(cx_a, "user_a").await;
5449    let client_b = server.create_client(cx_b, "user_b").await;
5450    server
5451        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5452        .await;
5453    let active_call_a = cx_a.read(ActiveCall::global);
5454
5455    client_a.language_registry().add(rust_lang());
5456    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5457        "Rust",
5458        FakeLspAdapter {
5459            capabilities: lsp::ServerCapabilities {
5460                workspace_symbol_provider: Some(OneOf::Left(true)),
5461                ..Default::default()
5462            },
5463            ..Default::default()
5464        },
5465    );
5466
5467    client_a
5468        .fs()
5469        .insert_tree(
5470            path!("/code"),
5471            json!({
5472                "crate-1": {
5473                    "one.rs": "const ONE: usize = 1;",
5474                },
5475                "crate-2": {
5476                    "two.rs": "const TWO: usize = 2; const THREE: usize = 3;",
5477                },
5478                "private": {
5479                    "passwords.txt": "the-password",
5480                }
5481            }),
5482        )
5483        .await;
5484    let (project_a, worktree_id) = client_a
5485        .build_local_project(path!("/code/crate-1"), cx_a)
5486        .await;
5487    let project_id = active_call_a
5488        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5489        .await
5490        .unwrap();
5491    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5492
5493    // Cause the language server to start.
5494    let _buffer = project_b
5495        .update(cx_b, |p, cx| {
5496            p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
5497        })
5498        .await
5499        .unwrap();
5500
5501    let fake_language_server = fake_language_servers.next().await.unwrap();
5502    fake_language_server.set_request_handler::<lsp::WorkspaceSymbolRequest, _, _>(
5503        |_, _| async move {
5504            Ok(Some(lsp::WorkspaceSymbolResponse::Flat(vec![
5505                #[allow(deprecated)]
5506                lsp::SymbolInformation {
5507                    name: "TWO".into(),
5508                    location: lsp::Location {
5509                        uri: lsp::Url::from_file_path(path!("/code/crate-2/two.rs")).unwrap(),
5510                        range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
5511                    },
5512                    kind: lsp::SymbolKind::CONSTANT,
5513                    tags: None,
5514                    container_name: None,
5515                    deprecated: None,
5516                },
5517            ])))
5518        },
5519    );
5520
5521    // Request the definition of a symbol as the guest.
5522    let symbols = project_b
5523        .update(cx_b, |p, cx| p.symbols("two", cx))
5524        .await
5525        .unwrap();
5526    assert_eq!(symbols.len(), 1);
5527    assert_eq!(symbols[0].name, "TWO");
5528
5529    // Open one of the returned symbols.
5530    let buffer_b_2 = project_b
5531        .update(cx_b, |project, cx| {
5532            project.open_buffer_for_symbol(&symbols[0], cx)
5533        })
5534        .await
5535        .unwrap();
5536
5537    buffer_b_2.read_with(cx_b, |buffer, cx| {
5538        assert_eq!(
5539            buffer.file().unwrap().full_path(cx),
5540            Path::new(path!("/code/crate-2/two.rs"))
5541        );
5542    });
5543
5544    // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
5545    let mut fake_symbol = symbols[0].clone();
5546    fake_symbol.path.path = Path::new(path!("/code/secrets")).into();
5547    let error = project_b
5548        .update(cx_b, |project, cx| {
5549            project.open_buffer_for_symbol(&fake_symbol, cx)
5550        })
5551        .await
5552        .unwrap_err();
5553    assert!(error.to_string().contains("invalid symbol signature"));
5554}
5555
5556#[gpui::test(iterations = 10)]
5557async fn test_open_buffer_while_getting_definition_pointing_to_it(
5558    executor: BackgroundExecutor,
5559    cx_a: &mut TestAppContext,
5560    cx_b: &mut TestAppContext,
5561    mut rng: StdRng,
5562) {
5563    let mut server = TestServer::start(executor.clone()).await;
5564    let client_a = server.create_client(cx_a, "user_a").await;
5565    let client_b = server.create_client(cx_b, "user_b").await;
5566    server
5567        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5568        .await;
5569    let active_call_a = cx_a.read(ActiveCall::global);
5570
5571    client_a.language_registry().add(rust_lang());
5572    let mut fake_language_servers = client_a
5573        .language_registry()
5574        .register_fake_lsp("Rust", Default::default());
5575
5576    client_a
5577        .fs()
5578        .insert_tree(
5579            path!("/root"),
5580            json!({
5581                "a.rs": "const ONE: usize = b::TWO;",
5582                "b.rs": "const TWO: usize = 2",
5583            }),
5584        )
5585        .await;
5586    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
5587    let project_id = active_call_a
5588        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5589        .await
5590        .unwrap();
5591    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5592
5593    let (buffer_b1, _lsp) = project_b
5594        .update(cx_b, |p, cx| {
5595            p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
5596        })
5597        .await
5598        .unwrap();
5599
5600    let fake_language_server = fake_language_servers.next().await.unwrap();
5601    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
5602        |_, _| async move {
5603            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
5604                lsp::Location::new(
5605                    lsp::Url::from_file_path(path!("/root/b.rs")).unwrap(),
5606                    lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
5607                ),
5608            )))
5609        },
5610    );
5611
5612    let definitions;
5613    let buffer_b2;
5614    if rng.r#gen() {
5615        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
5616        (buffer_b2, _) = project_b
5617            .update(cx_b, |p, cx| {
5618                p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
5619            })
5620            .await
5621            .unwrap();
5622    } else {
5623        (buffer_b2, _) = project_b
5624            .update(cx_b, |p, cx| {
5625                p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
5626            })
5627            .await
5628            .unwrap();
5629        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
5630    }
5631
5632    let definitions = definitions.await.unwrap();
5633    assert_eq!(definitions.len(), 1);
5634    assert_eq!(definitions[0].target.buffer, buffer_b2);
5635}
5636
5637#[gpui::test(iterations = 10)]
5638async fn test_contacts(
5639    executor: BackgroundExecutor,
5640    cx_a: &mut TestAppContext,
5641    cx_b: &mut TestAppContext,
5642    cx_c: &mut TestAppContext,
5643    cx_d: &mut TestAppContext,
5644) {
5645    let mut server = TestServer::start(executor.clone()).await;
5646    let client_a = server.create_client(cx_a, "user_a").await;
5647    let client_b = server.create_client(cx_b, "user_b").await;
5648    let client_c = server.create_client(cx_c, "user_c").await;
5649    let client_d = server.create_client(cx_d, "user_d").await;
5650    server
5651        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
5652        .await;
5653    let active_call_a = cx_a.read(ActiveCall::global);
5654    let active_call_b = cx_b.read(ActiveCall::global);
5655    let active_call_c = cx_c.read(ActiveCall::global);
5656    let _active_call_d = cx_d.read(ActiveCall::global);
5657
5658    executor.run_until_parked();
5659    assert_eq!(
5660        contacts(&client_a, cx_a),
5661        [
5662            ("user_b".to_string(), "online", "free"),
5663            ("user_c".to_string(), "online", "free")
5664        ]
5665    );
5666    assert_eq!(
5667        contacts(&client_b, cx_b),
5668        [
5669            ("user_a".to_string(), "online", "free"),
5670            ("user_c".to_string(), "online", "free")
5671        ]
5672    );
5673    assert_eq!(
5674        contacts(&client_c, cx_c),
5675        [
5676            ("user_a".to_string(), "online", "free"),
5677            ("user_b".to_string(), "online", "free")
5678        ]
5679    );
5680    assert_eq!(contacts(&client_d, cx_d), []);
5681
5682    server.disconnect_client(client_c.peer_id().unwrap());
5683    server.forbid_connections();
5684    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
5685    assert_eq!(
5686        contacts(&client_a, cx_a),
5687        [
5688            ("user_b".to_string(), "online", "free"),
5689            ("user_c".to_string(), "offline", "free")
5690        ]
5691    );
5692    assert_eq!(
5693        contacts(&client_b, cx_b),
5694        [
5695            ("user_a".to_string(), "online", "free"),
5696            ("user_c".to_string(), "offline", "free")
5697        ]
5698    );
5699    assert_eq!(contacts(&client_c, cx_c), []);
5700    assert_eq!(contacts(&client_d, cx_d), []);
5701
5702    server.allow_connections();
5703    client_c
5704        .authenticate_and_connect(false, &cx_c.to_async())
5705        .await
5706        .into_response()
5707        .unwrap();
5708
5709    executor.run_until_parked();
5710    assert_eq!(
5711        contacts(&client_a, cx_a),
5712        [
5713            ("user_b".to_string(), "online", "free"),
5714            ("user_c".to_string(), "online", "free")
5715        ]
5716    );
5717    assert_eq!(
5718        contacts(&client_b, cx_b),
5719        [
5720            ("user_a".to_string(), "online", "free"),
5721            ("user_c".to_string(), "online", "free")
5722        ]
5723    );
5724    assert_eq!(
5725        contacts(&client_c, cx_c),
5726        [
5727            ("user_a".to_string(), "online", "free"),
5728            ("user_b".to_string(), "online", "free")
5729        ]
5730    );
5731    assert_eq!(contacts(&client_d, cx_d), []);
5732
5733    active_call_a
5734        .update(cx_a, |call, cx| {
5735            call.invite(client_b.user_id().unwrap(), None, cx)
5736        })
5737        .await
5738        .unwrap();
5739    executor.run_until_parked();
5740    assert_eq!(
5741        contacts(&client_a, cx_a),
5742        [
5743            ("user_b".to_string(), "online", "busy"),
5744            ("user_c".to_string(), "online", "free")
5745        ]
5746    );
5747    assert_eq!(
5748        contacts(&client_b, cx_b),
5749        [
5750            ("user_a".to_string(), "online", "busy"),
5751            ("user_c".to_string(), "online", "free")
5752        ]
5753    );
5754    assert_eq!(
5755        contacts(&client_c, cx_c),
5756        [
5757            ("user_a".to_string(), "online", "busy"),
5758            ("user_b".to_string(), "online", "busy")
5759        ]
5760    );
5761    assert_eq!(contacts(&client_d, cx_d), []);
5762
5763    // Client B and client D become contacts while client B is being called.
5764    server
5765        .make_contacts(&mut [(&client_b, cx_b), (&client_d, cx_d)])
5766        .await;
5767    executor.run_until_parked();
5768    assert_eq!(
5769        contacts(&client_a, cx_a),
5770        [
5771            ("user_b".to_string(), "online", "busy"),
5772            ("user_c".to_string(), "online", "free")
5773        ]
5774    );
5775    assert_eq!(
5776        contacts(&client_b, cx_b),
5777        [
5778            ("user_a".to_string(), "online", "busy"),
5779            ("user_c".to_string(), "online", "free"),
5780            ("user_d".to_string(), "online", "free"),
5781        ]
5782    );
5783    assert_eq!(
5784        contacts(&client_c, cx_c),
5785        [
5786            ("user_a".to_string(), "online", "busy"),
5787            ("user_b".to_string(), "online", "busy")
5788        ]
5789    );
5790    assert_eq!(
5791        contacts(&client_d, cx_d),
5792        [("user_b".to_string(), "online", "busy")]
5793    );
5794
5795    active_call_b.update(cx_b, |call, cx| call.decline_incoming(cx).unwrap());
5796    executor.run_until_parked();
5797    assert_eq!(
5798        contacts(&client_a, cx_a),
5799        [
5800            ("user_b".to_string(), "online", "free"),
5801            ("user_c".to_string(), "online", "free")
5802        ]
5803    );
5804    assert_eq!(
5805        contacts(&client_b, cx_b),
5806        [
5807            ("user_a".to_string(), "online", "free"),
5808            ("user_c".to_string(), "online", "free"),
5809            ("user_d".to_string(), "online", "free")
5810        ]
5811    );
5812    assert_eq!(
5813        contacts(&client_c, cx_c),
5814        [
5815            ("user_a".to_string(), "online", "free"),
5816            ("user_b".to_string(), "online", "free")
5817        ]
5818    );
5819    assert_eq!(
5820        contacts(&client_d, cx_d),
5821        [("user_b".to_string(), "online", "free")]
5822    );
5823
5824    active_call_c
5825        .update(cx_c, |call, cx| {
5826            call.invite(client_a.user_id().unwrap(), None, cx)
5827        })
5828        .await
5829        .unwrap();
5830    executor.run_until_parked();
5831    assert_eq!(
5832        contacts(&client_a, cx_a),
5833        [
5834            ("user_b".to_string(), "online", "free"),
5835            ("user_c".to_string(), "online", "busy")
5836        ]
5837    );
5838    assert_eq!(
5839        contacts(&client_b, cx_b),
5840        [
5841            ("user_a".to_string(), "online", "busy"),
5842            ("user_c".to_string(), "online", "busy"),
5843            ("user_d".to_string(), "online", "free")
5844        ]
5845    );
5846    assert_eq!(
5847        contacts(&client_c, cx_c),
5848        [
5849            ("user_a".to_string(), "online", "busy"),
5850            ("user_b".to_string(), "online", "free")
5851        ]
5852    );
5853    assert_eq!(
5854        contacts(&client_d, cx_d),
5855        [("user_b".to_string(), "online", "free")]
5856    );
5857
5858    active_call_a
5859        .update(cx_a, |call, cx| call.accept_incoming(cx))
5860        .await
5861        .unwrap();
5862    executor.run_until_parked();
5863    assert_eq!(
5864        contacts(&client_a, cx_a),
5865        [
5866            ("user_b".to_string(), "online", "free"),
5867            ("user_c".to_string(), "online", "busy")
5868        ]
5869    );
5870    assert_eq!(
5871        contacts(&client_b, cx_b),
5872        [
5873            ("user_a".to_string(), "online", "busy"),
5874            ("user_c".to_string(), "online", "busy"),
5875            ("user_d".to_string(), "online", "free")
5876        ]
5877    );
5878    assert_eq!(
5879        contacts(&client_c, cx_c),
5880        [
5881            ("user_a".to_string(), "online", "busy"),
5882            ("user_b".to_string(), "online", "free")
5883        ]
5884    );
5885    assert_eq!(
5886        contacts(&client_d, cx_d),
5887        [("user_b".to_string(), "online", "free")]
5888    );
5889
5890    active_call_a
5891        .update(cx_a, |call, cx| {
5892            call.invite(client_b.user_id().unwrap(), None, cx)
5893        })
5894        .await
5895        .unwrap();
5896    executor.run_until_parked();
5897    assert_eq!(
5898        contacts(&client_a, cx_a),
5899        [
5900            ("user_b".to_string(), "online", "busy"),
5901            ("user_c".to_string(), "online", "busy")
5902        ]
5903    );
5904    assert_eq!(
5905        contacts(&client_b, cx_b),
5906        [
5907            ("user_a".to_string(), "online", "busy"),
5908            ("user_c".to_string(), "online", "busy"),
5909            ("user_d".to_string(), "online", "free")
5910        ]
5911    );
5912    assert_eq!(
5913        contacts(&client_c, cx_c),
5914        [
5915            ("user_a".to_string(), "online", "busy"),
5916            ("user_b".to_string(), "online", "busy")
5917        ]
5918    );
5919    assert_eq!(
5920        contacts(&client_d, cx_d),
5921        [("user_b".to_string(), "online", "busy")]
5922    );
5923
5924    active_call_a
5925        .update(cx_a, |call, cx| call.hang_up(cx))
5926        .await
5927        .unwrap();
5928    executor.run_until_parked();
5929    assert_eq!(
5930        contacts(&client_a, cx_a),
5931        [
5932            ("user_b".to_string(), "online", "free"),
5933            ("user_c".to_string(), "online", "free")
5934        ]
5935    );
5936    assert_eq!(
5937        contacts(&client_b, cx_b),
5938        [
5939            ("user_a".to_string(), "online", "free"),
5940            ("user_c".to_string(), "online", "free"),
5941            ("user_d".to_string(), "online", "free")
5942        ]
5943    );
5944    assert_eq!(
5945        contacts(&client_c, cx_c),
5946        [
5947            ("user_a".to_string(), "online", "free"),
5948            ("user_b".to_string(), "online", "free")
5949        ]
5950    );
5951    assert_eq!(
5952        contacts(&client_d, cx_d),
5953        [("user_b".to_string(), "online", "free")]
5954    );
5955
5956    active_call_a
5957        .update(cx_a, |call, cx| {
5958            call.invite(client_b.user_id().unwrap(), None, cx)
5959        })
5960        .await
5961        .unwrap();
5962    executor.run_until_parked();
5963    assert_eq!(
5964        contacts(&client_a, cx_a),
5965        [
5966            ("user_b".to_string(), "online", "busy"),
5967            ("user_c".to_string(), "online", "free")
5968        ]
5969    );
5970    assert_eq!(
5971        contacts(&client_b, cx_b),
5972        [
5973            ("user_a".to_string(), "online", "busy"),
5974            ("user_c".to_string(), "online", "free"),
5975            ("user_d".to_string(), "online", "free")
5976        ]
5977    );
5978    assert_eq!(
5979        contacts(&client_c, cx_c),
5980        [
5981            ("user_a".to_string(), "online", "busy"),
5982            ("user_b".to_string(), "online", "busy")
5983        ]
5984    );
5985    assert_eq!(
5986        contacts(&client_d, cx_d),
5987        [("user_b".to_string(), "online", "busy")]
5988    );
5989
5990    server.forbid_connections();
5991    server.disconnect_client(client_a.peer_id().unwrap());
5992    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
5993    assert_eq!(contacts(&client_a, cx_a), []);
5994    assert_eq!(
5995        contacts(&client_b, cx_b),
5996        [
5997            ("user_a".to_string(), "offline", "free"),
5998            ("user_c".to_string(), "online", "free"),
5999            ("user_d".to_string(), "online", "free")
6000        ]
6001    );
6002    assert_eq!(
6003        contacts(&client_c, cx_c),
6004        [
6005            ("user_a".to_string(), "offline", "free"),
6006            ("user_b".to_string(), "online", "free")
6007        ]
6008    );
6009    assert_eq!(
6010        contacts(&client_d, cx_d),
6011        [("user_b".to_string(), "online", "free")]
6012    );
6013
6014    // Test removing a contact
6015    client_b
6016        .user_store()
6017        .update(cx_b, |store, cx| {
6018            store.remove_contact(client_c.user_id().unwrap(), cx)
6019        })
6020        .await
6021        .unwrap();
6022    executor.run_until_parked();
6023    assert_eq!(
6024        contacts(&client_b, cx_b),
6025        [
6026            ("user_a".to_string(), "offline", "free"),
6027            ("user_d".to_string(), "online", "free")
6028        ]
6029    );
6030    assert_eq!(
6031        contacts(&client_c, cx_c),
6032        [("user_a".to_string(), "offline", "free"),]
6033    );
6034
6035    fn contacts(
6036        client: &TestClient,
6037        cx: &TestAppContext,
6038    ) -> Vec<(String, &'static str, &'static str)> {
6039        client.user_store().read_with(cx, |store, _| {
6040            store
6041                .contacts()
6042                .iter()
6043                .map(|contact| {
6044                    (
6045                        contact.user.github_login.clone(),
6046                        if contact.online { "online" } else { "offline" },
6047                        if contact.busy { "busy" } else { "free" },
6048                    )
6049                })
6050                .collect()
6051        })
6052    }
6053}
6054
6055#[gpui::test(iterations = 10)]
6056async fn test_contact_requests(
6057    executor: BackgroundExecutor,
6058    cx_a: &mut TestAppContext,
6059    cx_a2: &mut TestAppContext,
6060    cx_b: &mut TestAppContext,
6061    cx_b2: &mut TestAppContext,
6062    cx_c: &mut TestAppContext,
6063    cx_c2: &mut TestAppContext,
6064) {
6065    // Connect to a server as 3 clients.
6066    let mut server = TestServer::start(executor.clone()).await;
6067    let client_a = server.create_client(cx_a, "user_a").await;
6068    let client_a2 = server.create_client(cx_a2, "user_a").await;
6069    let client_b = server.create_client(cx_b, "user_b").await;
6070    let client_b2 = server.create_client(cx_b2, "user_b").await;
6071    let client_c = server.create_client(cx_c, "user_c").await;
6072    let client_c2 = server.create_client(cx_c2, "user_c").await;
6073
6074    assert_eq!(client_a.user_id().unwrap(), client_a2.user_id().unwrap());
6075    assert_eq!(client_b.user_id().unwrap(), client_b2.user_id().unwrap());
6076    assert_eq!(client_c.user_id().unwrap(), client_c2.user_id().unwrap());
6077
6078    // User A and User C request that user B become their contact.
6079    client_a
6080        .user_store()
6081        .update(cx_a, |store, cx| {
6082            store.request_contact(client_b.user_id().unwrap(), cx)
6083        })
6084        .await
6085        .unwrap();
6086    client_c
6087        .user_store()
6088        .update(cx_c, |store, cx| {
6089            store.request_contact(client_b.user_id().unwrap(), cx)
6090        })
6091        .await
6092        .unwrap();
6093    executor.run_until_parked();
6094
6095    // All users see the pending request appear in all their clients.
6096    assert_eq!(
6097        client_a.summarize_contacts(cx_a).outgoing_requests,
6098        &["user_b"]
6099    );
6100    assert_eq!(
6101        client_a2.summarize_contacts(cx_a2).outgoing_requests,
6102        &["user_b"]
6103    );
6104    assert_eq!(
6105        client_b.summarize_contacts(cx_b).incoming_requests,
6106        &["user_a", "user_c"]
6107    );
6108    assert_eq!(
6109        client_b2.summarize_contacts(cx_b2).incoming_requests,
6110        &["user_a", "user_c"]
6111    );
6112    assert_eq!(
6113        client_c.summarize_contacts(cx_c).outgoing_requests,
6114        &["user_b"]
6115    );
6116    assert_eq!(
6117        client_c2.summarize_contacts(cx_c2).outgoing_requests,
6118        &["user_b"]
6119    );
6120
6121    // Contact requests are present upon connecting (tested here via disconnect/reconnect)
6122    disconnect_and_reconnect(&client_a, cx_a).await;
6123    disconnect_and_reconnect(&client_b, cx_b).await;
6124    disconnect_and_reconnect(&client_c, cx_c).await;
6125    executor.run_until_parked();
6126    assert_eq!(
6127        client_a.summarize_contacts(cx_a).outgoing_requests,
6128        &["user_b"]
6129    );
6130    assert_eq!(
6131        client_b.summarize_contacts(cx_b).incoming_requests,
6132        &["user_a", "user_c"]
6133    );
6134    assert_eq!(
6135        client_c.summarize_contacts(cx_c).outgoing_requests,
6136        &["user_b"]
6137    );
6138
6139    // User B accepts the request from user A.
6140    client_b
6141        .user_store()
6142        .update(cx_b, |store, cx| {
6143            store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
6144        })
6145        .await
6146        .unwrap();
6147
6148    executor.run_until_parked();
6149
6150    // User B sees user A as their contact now in all client, and the incoming request from them is removed.
6151    let contacts_b = client_b.summarize_contacts(cx_b);
6152    assert_eq!(contacts_b.current, &["user_a"]);
6153    assert_eq!(contacts_b.incoming_requests, &["user_c"]);
6154    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
6155    assert_eq!(contacts_b2.current, &["user_a"]);
6156    assert_eq!(contacts_b2.incoming_requests, &["user_c"]);
6157
6158    // User A sees user B as their contact now in all clients, and the outgoing request to them is removed.
6159    let contacts_a = client_a.summarize_contacts(cx_a);
6160    assert_eq!(contacts_a.current, &["user_b"]);
6161    assert!(contacts_a.outgoing_requests.is_empty());
6162    let contacts_a2 = client_a2.summarize_contacts(cx_a2);
6163    assert_eq!(contacts_a2.current, &["user_b"]);
6164    assert!(contacts_a2.outgoing_requests.is_empty());
6165
6166    // Contacts are present upon connecting (tested here via disconnect/reconnect)
6167    disconnect_and_reconnect(&client_a, cx_a).await;
6168    disconnect_and_reconnect(&client_b, cx_b).await;
6169    disconnect_and_reconnect(&client_c, cx_c).await;
6170    executor.run_until_parked();
6171    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
6172    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
6173    assert_eq!(
6174        client_b.summarize_contacts(cx_b).incoming_requests,
6175        &["user_c"]
6176    );
6177    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
6178    assert_eq!(
6179        client_c.summarize_contacts(cx_c).outgoing_requests,
6180        &["user_b"]
6181    );
6182
6183    // User B rejects the request from user C.
6184    client_b
6185        .user_store()
6186        .update(cx_b, |store, cx| {
6187            store.respond_to_contact_request(client_c.user_id().unwrap(), false, cx)
6188        })
6189        .await
6190        .unwrap();
6191
6192    executor.run_until_parked();
6193
6194    // User B doesn't see user C as their contact, and the incoming request from them is removed.
6195    let contacts_b = client_b.summarize_contacts(cx_b);
6196    assert_eq!(contacts_b.current, &["user_a"]);
6197    assert!(contacts_b.incoming_requests.is_empty());
6198    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
6199    assert_eq!(contacts_b2.current, &["user_a"]);
6200    assert!(contacts_b2.incoming_requests.is_empty());
6201
6202    // User C doesn't see user B as their contact, and the outgoing request to them is removed.
6203    let contacts_c = client_c.summarize_contacts(cx_c);
6204    assert!(contacts_c.current.is_empty());
6205    assert!(contacts_c.outgoing_requests.is_empty());
6206    let contacts_c2 = client_c2.summarize_contacts(cx_c2);
6207    assert!(contacts_c2.current.is_empty());
6208    assert!(contacts_c2.outgoing_requests.is_empty());
6209
6210    // Incoming/outgoing requests are not present upon connecting (tested here via disconnect/reconnect)
6211    disconnect_and_reconnect(&client_a, cx_a).await;
6212    disconnect_and_reconnect(&client_b, cx_b).await;
6213    disconnect_and_reconnect(&client_c, cx_c).await;
6214    executor.run_until_parked();
6215    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
6216    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
6217    assert!(
6218        client_b
6219            .summarize_contacts(cx_b)
6220            .incoming_requests
6221            .is_empty()
6222    );
6223    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
6224    assert!(
6225        client_c
6226            .summarize_contacts(cx_c)
6227            .outgoing_requests
6228            .is_empty()
6229    );
6230
6231    async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
6232        client.disconnect(&cx.to_async());
6233        client.clear_contacts(cx).await;
6234        client
6235            .authenticate_and_connect(false, &cx.to_async())
6236            .await
6237            .into_response()
6238            .unwrap();
6239    }
6240}
6241
6242#[gpui::test(iterations = 10)]
6243async fn test_join_call_after_screen_was_shared(
6244    executor: BackgroundExecutor,
6245    cx_a: &mut TestAppContext,
6246    cx_b: &mut TestAppContext,
6247) {
6248    let mut server = TestServer::start(executor.clone()).await;
6249
6250    let client_a = server.create_client(cx_a, "user_a").await;
6251    let client_b = server.create_client(cx_b, "user_b").await;
6252    server
6253        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6254        .await;
6255
6256    let active_call_a = cx_a.read(ActiveCall::global);
6257    let active_call_b = cx_b.read(ActiveCall::global);
6258
6259    // Call users B and C from client A.
6260    active_call_a
6261        .update(cx_a, |call, cx| {
6262            call.invite(client_b.user_id().unwrap(), None, cx)
6263        })
6264        .await
6265        .unwrap();
6266
6267    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
6268    executor.run_until_parked();
6269    assert_eq!(
6270        room_participants(&room_a, cx_a),
6271        RoomParticipants {
6272            remote: Default::default(),
6273            pending: vec!["user_b".to_string()]
6274        }
6275    );
6276
6277    // User B receives the call.
6278
6279    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
6280    let call_b = incoming_call_b.next().await.unwrap().unwrap();
6281    assert_eq!(call_b.calling_user.github_login, "user_a");
6282
6283    // User A shares their screen
6284    let display = gpui::TestScreenCaptureSource::new();
6285    cx_a.set_screen_capture_sources(vec![display]);
6286    active_call_a
6287        .update(cx_a, |call, cx| {
6288            call.room()
6289                .unwrap()
6290                .update(cx, |room, cx| room.share_screen(cx))
6291        })
6292        .await
6293        .unwrap();
6294
6295    client_b.user_store().update(cx_b, |user_store, _| {
6296        user_store.clear_cache();
6297    });
6298
6299    // User B joins the room
6300    active_call_b
6301        .update(cx_b, |call, cx| call.accept_incoming(cx))
6302        .await
6303        .unwrap();
6304
6305    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
6306    assert!(incoming_call_b.next().await.unwrap().is_none());
6307
6308    executor.run_until_parked();
6309    assert_eq!(
6310        room_participants(&room_a, cx_a),
6311        RoomParticipants {
6312            remote: vec!["user_b".to_string()],
6313            pending: vec![],
6314        }
6315    );
6316    assert_eq!(
6317        room_participants(&room_b, cx_b),
6318        RoomParticipants {
6319            remote: vec!["user_a".to_string()],
6320            pending: vec![],
6321        }
6322    );
6323
6324    // Ensure User B sees User A's screenshare.
6325
6326    room_b.read_with(cx_b, |room, _| {
6327        assert_eq!(
6328            room.remote_participants()
6329                .get(&client_a.user_id().unwrap())
6330                .unwrap()
6331                .video_tracks
6332                .len(),
6333            1
6334        );
6335    });
6336}
6337
6338#[gpui::test]
6339async fn test_right_click_menu_behind_collab_panel(cx: &mut TestAppContext) {
6340    let mut server = TestServer::start(cx.executor().clone()).await;
6341    let client_a = server.create_client(cx, "user_a").await;
6342    let (_workspace_a, cx) = client_a.build_test_workspace(cx).await;
6343
6344    cx.simulate_resize(size(px(300.), px(300.)));
6345
6346    cx.simulate_keystrokes("cmd-n cmd-n cmd-n");
6347    cx.update(|window, _cx| window.refresh());
6348
6349    let tab_bounds = cx.debug_bounds("TAB-2").unwrap();
6350    let new_tab_button_bounds = cx.debug_bounds("ICON-Plus").unwrap();
6351
6352    assert!(
6353        tab_bounds.intersects(&new_tab_button_bounds),
6354        "Tab should overlap with the new tab button, if this is failing check if there's been a redesign!"
6355    );
6356
6357    cx.simulate_event(MouseDownEvent {
6358        button: MouseButton::Right,
6359        position: new_tab_button_bounds.center(),
6360        modifiers: Modifiers::default(),
6361        click_count: 1,
6362        first_mouse: false,
6363    });
6364
6365    // regression test that the right click menu for tabs does not open.
6366    assert!(cx.debug_bounds("MENU_ITEM-Close").is_none());
6367
6368    let tab_bounds = cx.debug_bounds("TAB-1").unwrap();
6369    cx.simulate_event(MouseDownEvent {
6370        button: MouseButton::Right,
6371        position: tab_bounds.center(),
6372        modifiers: Modifiers::default(),
6373        click_count: 1,
6374        first_mouse: false,
6375    });
6376    assert!(cx.debug_bounds("MENU_ITEM-Close").is_some());
6377}
6378
6379#[gpui::test]
6380async fn test_pane_split_left(cx: &mut TestAppContext) {
6381    let (_, client) = TestServer::start1(cx).await;
6382    let (workspace, cx) = client.build_test_workspace(cx).await;
6383
6384    cx.simulate_keystrokes("cmd-n");
6385    workspace.update(cx, |workspace, cx| {
6386        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 1);
6387    });
6388    cx.simulate_keystrokes("cmd-k left");
6389    workspace.update(cx, |workspace, cx| {
6390        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
6391    });
6392    cx.simulate_keystrokes("cmd-k");
6393    // sleep for longer than the timeout in keyboard shortcut handling
6394    // to verify that it doesn't fire in this case.
6395    cx.executor().advance_clock(Duration::from_secs(2));
6396    cx.simulate_keystrokes("left");
6397    workspace.update(cx, |workspace, cx| {
6398        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
6399    });
6400}
6401
6402#[gpui::test]
6403async fn test_join_after_restart(cx1: &mut TestAppContext, cx2: &mut TestAppContext) {
6404    let (mut server, client) = TestServer::start1(cx1).await;
6405    let channel1 = server.make_public_channel("channel1", &client, cx1).await;
6406    let channel2 = server.make_public_channel("channel2", &client, cx1).await;
6407
6408    join_channel(channel1, &client, cx1).await.unwrap();
6409    drop(client);
6410
6411    let client2 = server.create_client(cx2, "user_a").await;
6412    join_channel(channel2, &client2, cx2).await.unwrap();
6413}
6414
6415#[gpui::test]
6416async fn test_preview_tabs(cx: &mut TestAppContext) {
6417    let (_server, client) = TestServer::start1(cx).await;
6418    let (workspace, cx) = client.build_test_workspace(cx).await;
6419    let project = workspace.update(cx, |workspace, _| workspace.project().clone());
6420
6421    let worktree_id = project.update(cx, |project, cx| {
6422        project.worktrees(cx).next().unwrap().read(cx).id()
6423    });
6424
6425    let path_1 = ProjectPath {
6426        worktree_id,
6427        path: Path::new("1.txt").into(),
6428    };
6429    let path_2 = ProjectPath {
6430        worktree_id,
6431        path: Path::new("2.js").into(),
6432    };
6433    let path_3 = ProjectPath {
6434        worktree_id,
6435        path: Path::new("3.rs").into(),
6436    };
6437
6438    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6439
6440    let get_path = |pane: &Pane, idx: usize, cx: &App| {
6441        pane.item_for_index(idx).unwrap().project_path(cx).unwrap()
6442    };
6443
6444    // Opening item 3 as a "permanent" tab
6445    workspace
6446        .update_in(cx, |workspace, window, cx| {
6447            workspace.open_path(path_3.clone(), None, false, window, cx)
6448        })
6449        .await
6450        .unwrap();
6451
6452    pane.update(cx, |pane, cx| {
6453        assert_eq!(pane.items_len(), 1);
6454        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6455        assert_eq!(pane.preview_item_id(), None);
6456
6457        assert!(!pane.can_navigate_backward());
6458        assert!(!pane.can_navigate_forward());
6459    });
6460
6461    // Open item 1 as preview
6462    workspace
6463        .update_in(cx, |workspace, window, cx| {
6464            workspace.open_path_preview(path_1.clone(), None, true, true, true, window, cx)
6465        })
6466        .await
6467        .unwrap();
6468
6469    pane.update(cx, |pane, cx| {
6470        assert_eq!(pane.items_len(), 2);
6471        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6472        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6473        assert_eq!(
6474            pane.preview_item_id(),
6475            Some(pane.items().nth(1).unwrap().item_id())
6476        );
6477
6478        assert!(pane.can_navigate_backward());
6479        assert!(!pane.can_navigate_forward());
6480    });
6481
6482    // Open item 2 as preview
6483    workspace
6484        .update_in(cx, |workspace, window, cx| {
6485            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
6486        })
6487        .await
6488        .unwrap();
6489
6490    pane.update(cx, |pane, cx| {
6491        assert_eq!(pane.items_len(), 2);
6492        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6493        assert_eq!(get_path(pane, 1, cx), path_2.clone());
6494        assert_eq!(
6495            pane.preview_item_id(),
6496            Some(pane.items().nth(1).unwrap().item_id())
6497        );
6498
6499        assert!(pane.can_navigate_backward());
6500        assert!(!pane.can_navigate_forward());
6501    });
6502
6503    // Going back should show item 1 as preview
6504    workspace
6505        .update_in(cx, |workspace, window, cx| {
6506            workspace.go_back(pane.downgrade(), window, cx)
6507        })
6508        .await
6509        .unwrap();
6510
6511    pane.update(cx, |pane, cx| {
6512        assert_eq!(pane.items_len(), 2);
6513        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6514        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6515        assert_eq!(
6516            pane.preview_item_id(),
6517            Some(pane.items().nth(1).unwrap().item_id())
6518        );
6519
6520        assert!(pane.can_navigate_backward());
6521        assert!(pane.can_navigate_forward());
6522    });
6523
6524    // Closing item 1
6525    pane.update_in(cx, |pane, window, cx| {
6526        pane.close_item_by_id(
6527            pane.active_item().unwrap().item_id(),
6528            workspace::SaveIntent::Skip,
6529            window,
6530            cx,
6531        )
6532    })
6533    .await
6534    .unwrap();
6535
6536    pane.update(cx, |pane, cx| {
6537        assert_eq!(pane.items_len(), 1);
6538        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6539        assert_eq!(pane.preview_item_id(), None);
6540
6541        assert!(pane.can_navigate_backward());
6542        assert!(!pane.can_navigate_forward());
6543    });
6544
6545    // Going back should show item 1 as preview
6546    workspace
6547        .update_in(cx, |workspace, window, cx| {
6548            workspace.go_back(pane.downgrade(), window, cx)
6549        })
6550        .await
6551        .unwrap();
6552
6553    pane.update(cx, |pane, cx| {
6554        assert_eq!(pane.items_len(), 2);
6555        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6556        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6557        assert_eq!(
6558            pane.preview_item_id(),
6559            Some(pane.items().nth(1).unwrap().item_id())
6560        );
6561
6562        assert!(pane.can_navigate_backward());
6563        assert!(pane.can_navigate_forward());
6564    });
6565
6566    // Close permanent tab
6567    pane.update_in(cx, |pane, window, cx| {
6568        let id = pane.items().next().unwrap().item_id();
6569        pane.close_item_by_id(id, workspace::SaveIntent::Skip, window, cx)
6570    })
6571    .await
6572    .unwrap();
6573
6574    pane.update(cx, |pane, cx| {
6575        assert_eq!(pane.items_len(), 1);
6576        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6577        assert_eq!(
6578            pane.preview_item_id(),
6579            Some(pane.items().next().unwrap().item_id())
6580        );
6581
6582        assert!(pane.can_navigate_backward());
6583        assert!(pane.can_navigate_forward());
6584    });
6585
6586    // Split pane to the right
6587    pane.update(cx, |pane, cx| {
6588        pane.split(workspace::SplitDirection::Right, cx);
6589    });
6590
6591    let right_pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6592
6593    pane.update(cx, |pane, cx| {
6594        assert_eq!(pane.items_len(), 1);
6595        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6596        assert_eq!(
6597            pane.preview_item_id(),
6598            Some(pane.items().next().unwrap().item_id())
6599        );
6600
6601        assert!(pane.can_navigate_backward());
6602        assert!(pane.can_navigate_forward());
6603    });
6604
6605    right_pane.update(cx, |pane, cx| {
6606        assert_eq!(pane.items_len(), 1);
6607        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6608        assert_eq!(pane.preview_item_id(), None);
6609
6610        assert!(!pane.can_navigate_backward());
6611        assert!(!pane.can_navigate_forward());
6612    });
6613
6614    // Open item 2 as preview in right pane
6615    workspace
6616        .update_in(cx, |workspace, window, cx| {
6617            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
6618        })
6619        .await
6620        .unwrap();
6621
6622    pane.update(cx, |pane, cx| {
6623        assert_eq!(pane.items_len(), 1);
6624        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6625        assert_eq!(
6626            pane.preview_item_id(),
6627            Some(pane.items().next().unwrap().item_id())
6628        );
6629
6630        assert!(pane.can_navigate_backward());
6631        assert!(pane.can_navigate_forward());
6632    });
6633
6634    right_pane.update(cx, |pane, cx| {
6635        assert_eq!(pane.items_len(), 2);
6636        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6637        assert_eq!(get_path(pane, 1, cx), path_2.clone());
6638        assert_eq!(
6639            pane.preview_item_id(),
6640            Some(pane.items().nth(1).unwrap().item_id())
6641        );
6642
6643        assert!(pane.can_navigate_backward());
6644        assert!(!pane.can_navigate_forward());
6645    });
6646
6647    // Focus left pane
6648    workspace.update_in(cx, |workspace, window, cx| {
6649        workspace.activate_pane_in_direction(workspace::SplitDirection::Left, window, cx)
6650    });
6651
6652    // Open item 2 as preview in left pane
6653    workspace
6654        .update_in(cx, |workspace, window, cx| {
6655            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
6656        })
6657        .await
6658        .unwrap();
6659
6660    pane.update(cx, |pane, cx| {
6661        assert_eq!(pane.items_len(), 1);
6662        assert_eq!(get_path(pane, 0, cx), path_2.clone());
6663        assert_eq!(
6664            pane.preview_item_id(),
6665            Some(pane.items().next().unwrap().item_id())
6666        );
6667
6668        assert!(pane.can_navigate_backward());
6669        assert!(!pane.can_navigate_forward());
6670    });
6671
6672    right_pane.update(cx, |pane, cx| {
6673        assert_eq!(pane.items_len(), 2);
6674        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6675        assert_eq!(get_path(pane, 1, cx), path_2.clone());
6676        assert_eq!(
6677            pane.preview_item_id(),
6678            Some(pane.items().nth(1).unwrap().item_id())
6679        );
6680
6681        assert!(pane.can_navigate_backward());
6682        assert!(!pane.can_navigate_forward());
6683    });
6684}
6685
6686#[gpui::test(iterations = 10)]
6687async fn test_context_collaboration_with_reconnect(
6688    executor: BackgroundExecutor,
6689    cx_a: &mut TestAppContext,
6690    cx_b: &mut TestAppContext,
6691) {
6692    let mut server = TestServer::start(executor.clone()).await;
6693    let client_a = server.create_client(cx_a, "user_a").await;
6694    let client_b = server.create_client(cx_b, "user_b").await;
6695    server
6696        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6697        .await;
6698    let active_call_a = cx_a.read(ActiveCall::global);
6699
6700    client_a.fs().insert_tree("/a", Default::default()).await;
6701    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
6702    let project_id = active_call_a
6703        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
6704        .await
6705        .unwrap();
6706    let project_b = client_b.join_remote_project(project_id, cx_b).await;
6707
6708    // Client A sees that a guest has joined.
6709    executor.run_until_parked();
6710
6711    project_a.read_with(cx_a, |project, _| {
6712        assert_eq!(project.collaborators().len(), 1);
6713    });
6714    project_b.read_with(cx_b, |project, _| {
6715        assert_eq!(project.collaborators().len(), 1);
6716    });
6717
6718    let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
6719    let context_store_a = cx_a
6720        .update(|cx| {
6721            ContextStore::new(
6722                project_a.clone(),
6723                prompt_builder.clone(),
6724                Arc::new(SlashCommandWorkingSet::default()),
6725                cx,
6726            )
6727        })
6728        .await
6729        .unwrap();
6730    let context_store_b = cx_b
6731        .update(|cx| {
6732            ContextStore::new(
6733                project_b.clone(),
6734                prompt_builder.clone(),
6735                Arc::new(SlashCommandWorkingSet::default()),
6736                cx,
6737            )
6738        })
6739        .await
6740        .unwrap();
6741
6742    // Client A creates a new chats.
6743    let context_a = context_store_a.update(cx_a, |store, cx| store.create(cx));
6744    executor.run_until_parked();
6745
6746    // Client B retrieves host's contexts and joins one.
6747    let context_b = context_store_b
6748        .update(cx_b, |store, cx| {
6749            let host_contexts = store.host_contexts().to_vec();
6750            assert_eq!(host_contexts.len(), 1);
6751            store.open_remote_context(host_contexts[0].id.clone(), cx)
6752        })
6753        .await
6754        .unwrap();
6755
6756    // Host and guest make changes
6757    context_a.update(cx_a, |context, cx| {
6758        context.buffer().update(cx, |buffer, cx| {
6759            buffer.edit([(0..0, "Host change\n")], None, cx)
6760        })
6761    });
6762    context_b.update(cx_b, |context, cx| {
6763        context.buffer().update(cx, |buffer, cx| {
6764            buffer.edit([(0..0, "Guest change\n")], None, cx)
6765        })
6766    });
6767    executor.run_until_parked();
6768    assert_eq!(
6769        context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
6770        "Guest change\nHost change\n"
6771    );
6772    assert_eq!(
6773        context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
6774        "Guest change\nHost change\n"
6775    );
6776
6777    // Disconnect client A and make some changes while disconnected.
6778    server.disconnect_client(client_a.peer_id().unwrap());
6779    server.forbid_connections();
6780    context_a.update(cx_a, |context, cx| {
6781        context.buffer().update(cx, |buffer, cx| {
6782            buffer.edit([(0..0, "Host offline change\n")], None, cx)
6783        })
6784    });
6785    context_b.update(cx_b, |context, cx| {
6786        context.buffer().update(cx, |buffer, cx| {
6787            buffer.edit([(0..0, "Guest offline change\n")], None, cx)
6788        })
6789    });
6790    executor.run_until_parked();
6791    assert_eq!(
6792        context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
6793        "Host offline change\nGuest change\nHost change\n"
6794    );
6795    assert_eq!(
6796        context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
6797        "Guest offline change\nGuest change\nHost change\n"
6798    );
6799
6800    // Allow client A to reconnect and verify that contexts converge.
6801    server.allow_connections();
6802    executor.advance_clock(RECEIVE_TIMEOUT);
6803    assert_eq!(
6804        context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
6805        "Guest offline change\nHost offline change\nGuest change\nHost change\n"
6806    );
6807    assert_eq!(
6808        context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
6809        "Guest offline change\nHost offline change\nGuest change\nHost change\n"
6810    );
6811
6812    // Client A disconnects without being able to reconnect. Context B becomes readonly.
6813    server.forbid_connections();
6814    server.disconnect_client(client_a.peer_id().unwrap());
6815    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
6816    context_b.read_with(cx_b, |context, cx| {
6817        assert!(context.buffer().read(cx).read_only());
6818    });
6819}
6820
6821#[gpui::test]
6822async fn test_remote_git_branches(
6823    executor: BackgroundExecutor,
6824    cx_a: &mut TestAppContext,
6825    cx_b: &mut TestAppContext,
6826) {
6827    let mut server = TestServer::start(executor.clone()).await;
6828    let client_a = server.create_client(cx_a, "user_a").await;
6829    let client_b = server.create_client(cx_b, "user_b").await;
6830    server
6831        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6832        .await;
6833    let active_call_a = cx_a.read(ActiveCall::global);
6834
6835    client_a
6836        .fs()
6837        .insert_tree("/project", serde_json::json!({ ".git":{} }))
6838        .await;
6839    let branches = ["main", "dev", "feature-1"];
6840    client_a
6841        .fs()
6842        .insert_branches(Path::new("/project/.git"), &branches);
6843    let branches_set = branches
6844        .into_iter()
6845        .map(ToString::to_string)
6846        .collect::<HashSet<_>>();
6847
6848    let (project_a, _) = client_a.build_local_project("/project", cx_a).await;
6849
6850    let project_id = active_call_a
6851        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
6852        .await
6853        .unwrap();
6854    let project_b = client_b.join_remote_project(project_id, cx_b).await;
6855
6856    // Client A sees that a guest has joined and the repo has been populated
6857    executor.run_until_parked();
6858
6859    let repo_b = cx_b.update(|cx| project_b.read(cx).active_repository(cx).unwrap());
6860
6861    let branches_b = cx_b
6862        .update(|cx| repo_b.update(cx, |repository, _| repository.branches()))
6863        .await
6864        .unwrap()
6865        .unwrap();
6866
6867    let new_branch = branches[2];
6868
6869    let branches_b = branches_b
6870        .into_iter()
6871        .map(|branch| branch.name().to_string())
6872        .collect::<HashSet<_>>();
6873
6874    assert_eq!(branches_b, branches_set);
6875
6876    cx_b.update(|cx| {
6877        repo_b.update(cx, |repository, _cx| {
6878            repository.change_branch(new_branch.to_string())
6879        })
6880    })
6881    .await
6882    .unwrap()
6883    .unwrap();
6884
6885    executor.run_until_parked();
6886
6887    let host_branch = cx_a.update(|cx| {
6888        project_a.update(cx, |project, cx| {
6889            project
6890                .repositories(cx)
6891                .values()
6892                .next()
6893                .unwrap()
6894                .read(cx)
6895                .branch
6896                .as_ref()
6897                .unwrap()
6898                .clone()
6899        })
6900    });
6901
6902    assert_eq!(host_branch.name(), branches[2]);
6903
6904    // Also try creating a new branch
6905    cx_b.update(|cx| {
6906        repo_b.update(cx, |repository, _cx| {
6907            repository.create_branch("totally-new-branch".to_string())
6908        })
6909    })
6910    .await
6911    .unwrap()
6912    .unwrap();
6913
6914    cx_b.update(|cx| {
6915        repo_b.update(cx, |repository, _cx| {
6916            repository.change_branch("totally-new-branch".to_string())
6917        })
6918    })
6919    .await
6920    .unwrap()
6921    .unwrap();
6922
6923    executor.run_until_parked();
6924
6925    let host_branch = cx_a.update(|cx| {
6926        project_a.update(cx, |project, cx| {
6927            project
6928                .repositories(cx)
6929                .values()
6930                .next()
6931                .unwrap()
6932                .read(cx)
6933                .branch
6934                .as_ref()
6935                .unwrap()
6936                .clone()
6937        })
6938    });
6939
6940    assert_eq!(host_branch.name(), "totally-new-branch");
6941}