integration_tests.rs

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