integration_tests.rs

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