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, DiagnosticSourceKind, FakeLspAdapter, Language, LanguageConfig,
  24    LanguageMatcher, 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        "deadbeef",
2628    );
2629
2630    // Create the buffer
2631    let buffer_local_a = project_local
2632        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2633        .await
2634        .unwrap();
2635    let local_unstaged_diff_a = project_local
2636        .update(cx_a, |p, cx| {
2637            p.open_unstaged_diff(buffer_local_a.clone(), cx)
2638        })
2639        .await
2640        .unwrap();
2641
2642    // Wait for it to catch up to the new diff
2643    executor.run_until_parked();
2644    local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
2645        let buffer = buffer_local_a.read(cx);
2646        assert_eq!(
2647            diff.base_text_string().as_deref(),
2648            Some(staged_text.as_str())
2649        );
2650        assert_hunks(
2651            diff.hunks_in_row_range(0..4, buffer, cx),
2652            buffer,
2653            &diff.base_text_string().unwrap(),
2654            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2655        );
2656    });
2657
2658    // Create remote buffer
2659    let remote_buffer_a = project_remote
2660        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2661        .await
2662        .unwrap();
2663    let remote_unstaged_diff_a = project_remote
2664        .update(cx_b, |p, cx| {
2665            p.open_unstaged_diff(remote_buffer_a.clone(), cx)
2666        })
2667        .await
2668        .unwrap();
2669
2670    // Wait remote buffer to catch up to the new diff
2671    executor.run_until_parked();
2672    remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
2673        let buffer = remote_buffer_a.read(cx);
2674        assert_eq!(
2675            diff.base_text_string().as_deref(),
2676            Some(staged_text.as_str())
2677        );
2678        assert_hunks(
2679            diff.hunks_in_row_range(0..4, buffer, cx),
2680            buffer,
2681            &diff.base_text_string().unwrap(),
2682            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2683        );
2684    });
2685
2686    // Open uncommitted changes on the guest, without opening them on the host first
2687    let remote_uncommitted_diff_a = project_remote
2688        .update(cx_b, |p, cx| {
2689            p.open_uncommitted_diff(remote_buffer_a.clone(), cx)
2690        })
2691        .await
2692        .unwrap();
2693    executor.run_until_parked();
2694    remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
2695        let buffer = remote_buffer_a.read(cx);
2696        assert_eq!(
2697            diff.base_text_string().as_deref(),
2698            Some(committed_text.as_str())
2699        );
2700        assert_hunks(
2701            diff.hunks_in_row_range(0..4, buffer, cx),
2702            buffer,
2703            &diff.base_text_string().unwrap(),
2704            &[(
2705                1..2,
2706                "TWO\n",
2707                "two\n",
2708                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
2709            )],
2710        );
2711    });
2712
2713    // Update the index text of the open buffer
2714    client_a.fs().set_index_for_repo(
2715        Path::new("/dir/.git"),
2716        &[("a.txt".into(), new_staged_text.clone())],
2717    );
2718    client_a.fs().set_head_for_repo(
2719        Path::new("/dir/.git"),
2720        &[("a.txt".into(), new_committed_text.clone())],
2721        "deadbeef",
2722    );
2723
2724    // Wait for buffer_local_a to receive it
2725    executor.run_until_parked();
2726    local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
2727        let buffer = buffer_local_a.read(cx);
2728        assert_eq!(
2729            diff.base_text_string().as_deref(),
2730            Some(new_staged_text.as_str())
2731        );
2732        assert_hunks(
2733            diff.hunks_in_row_range(0..4, buffer, cx),
2734            buffer,
2735            &diff.base_text_string().unwrap(),
2736            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2737        );
2738    });
2739
2740    // Guest receives index text update
2741    remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
2742        let buffer = remote_buffer_a.read(cx);
2743        assert_eq!(
2744            diff.base_text_string().as_deref(),
2745            Some(new_staged_text.as_str())
2746        );
2747        assert_hunks(
2748            diff.hunks_in_row_range(0..4, buffer, cx),
2749            buffer,
2750            &diff.base_text_string().unwrap(),
2751            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2752        );
2753    });
2754
2755    remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
2756        let buffer = remote_buffer_a.read(cx);
2757        assert_eq!(
2758            diff.base_text_string().as_deref(),
2759            Some(new_committed_text.as_str())
2760        );
2761        assert_hunks(
2762            diff.hunks_in_row_range(0..4, buffer, cx),
2763            buffer,
2764            &diff.base_text_string().unwrap(),
2765            &[(
2766                1..2,
2767                "TWO_HUNDRED\n",
2768                "two\n",
2769                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
2770            )],
2771        );
2772    });
2773
2774    // Nested git dir
2775    let staged_text = "
2776        one
2777        three
2778    "
2779    .unindent();
2780
2781    let new_staged_text = "
2782        one
2783        two
2784    "
2785    .unindent();
2786
2787    client_a.fs().set_index_for_repo(
2788        Path::new("/dir/sub/.git"),
2789        &[("b.txt".into(), staged_text.clone())],
2790    );
2791
2792    // Create the buffer
2793    let buffer_local_b = project_local
2794        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2795        .await
2796        .unwrap();
2797    let local_unstaged_diff_b = project_local
2798        .update(cx_a, |p, cx| {
2799            p.open_unstaged_diff(buffer_local_b.clone(), cx)
2800        })
2801        .await
2802        .unwrap();
2803
2804    // Wait for it to catch up to the new diff
2805    executor.run_until_parked();
2806    local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
2807        let buffer = buffer_local_b.read(cx);
2808        assert_eq!(
2809            diff.base_text_string().as_deref(),
2810            Some(staged_text.as_str())
2811        );
2812        assert_hunks(
2813            diff.hunks_in_row_range(0..4, buffer, cx),
2814            buffer,
2815            &diff.base_text_string().unwrap(),
2816            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2817        );
2818    });
2819
2820    // Create remote buffer
2821    let remote_buffer_b = project_remote
2822        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2823        .await
2824        .unwrap();
2825    let remote_unstaged_diff_b = project_remote
2826        .update(cx_b, |p, cx| {
2827            p.open_unstaged_diff(remote_buffer_b.clone(), cx)
2828        })
2829        .await
2830        .unwrap();
2831
2832    executor.run_until_parked();
2833    remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
2834        let buffer = remote_buffer_b.read(cx);
2835        assert_eq!(
2836            diff.base_text_string().as_deref(),
2837            Some(staged_text.as_str())
2838        );
2839        assert_hunks(
2840            diff.hunks_in_row_range(0..4, buffer, cx),
2841            buffer,
2842            &staged_text,
2843            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2844        );
2845    });
2846
2847    // Updatet the staged text
2848    client_a.fs().set_index_for_repo(
2849        Path::new("/dir/sub/.git"),
2850        &[("b.txt".into(), new_staged_text.clone())],
2851    );
2852
2853    // Wait for buffer_local_b to receive it
2854    executor.run_until_parked();
2855    local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
2856        let buffer = buffer_local_b.read(cx);
2857        assert_eq!(
2858            diff.base_text_string().as_deref(),
2859            Some(new_staged_text.as_str())
2860        );
2861        assert_hunks(
2862            diff.hunks_in_row_range(0..4, buffer, cx),
2863            buffer,
2864            &new_staged_text,
2865            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2866        );
2867    });
2868
2869    remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
2870        let buffer = remote_buffer_b.read(cx);
2871        assert_eq!(
2872            diff.base_text_string().as_deref(),
2873            Some(new_staged_text.as_str())
2874        );
2875        assert_hunks(
2876            diff.hunks_in_row_range(0..4, buffer, cx),
2877            buffer,
2878            &new_staged_text,
2879            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2880        );
2881    });
2882}
2883
2884#[gpui::test(iterations = 10)]
2885async fn test_git_branch_name(
2886    executor: BackgroundExecutor,
2887    cx_a: &mut TestAppContext,
2888    cx_b: &mut TestAppContext,
2889    cx_c: &mut TestAppContext,
2890) {
2891    let mut server = TestServer::start(executor.clone()).await;
2892    let client_a = server.create_client(cx_a, "user_a").await;
2893    let client_b = server.create_client(cx_b, "user_b").await;
2894    let client_c = server.create_client(cx_c, "user_c").await;
2895    server
2896        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2897        .await;
2898    let active_call_a = cx_a.read(ActiveCall::global);
2899
2900    client_a
2901        .fs()
2902        .insert_tree(
2903            "/dir",
2904            json!({
2905            ".git": {},
2906            }),
2907        )
2908        .await;
2909
2910    let (project_local, _worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2911    let project_id = active_call_a
2912        .update(cx_a, |call, cx| {
2913            call.share_project(project_local.clone(), cx)
2914        })
2915        .await
2916        .unwrap();
2917
2918    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
2919    client_a
2920        .fs()
2921        .set_branch_name(Path::new("/dir/.git"), Some("branch-1"));
2922
2923    // Wait for it to catch up to the new branch
2924    executor.run_until_parked();
2925
2926    #[track_caller]
2927    fn assert_branch(branch_name: Option<impl Into<String>>, project: &Project, cx: &App) {
2928        let branch_name = branch_name.map(Into::into);
2929        let repositories = project.repositories(cx).values().collect::<Vec<_>>();
2930        assert_eq!(repositories.len(), 1);
2931        let repository = repositories[0].clone();
2932        assert_eq!(
2933            repository
2934                .read(cx)
2935                .branch
2936                .as_ref()
2937                .map(|branch| branch.name().to_owned()),
2938            branch_name
2939        )
2940    }
2941
2942    // Smoke test branch reading
2943
2944    project_local.read_with(cx_a, |project, cx| {
2945        assert_branch(Some("branch-1"), project, cx)
2946    });
2947
2948    project_remote.read_with(cx_b, |project, cx| {
2949        assert_branch(Some("branch-1"), project, cx)
2950    });
2951
2952    client_a
2953        .fs()
2954        .set_branch_name(Path::new("/dir/.git"), Some("branch-2"));
2955
2956    // Wait for buffer_local_a to receive it
2957    executor.run_until_parked();
2958
2959    // Smoke test branch reading
2960
2961    project_local.read_with(cx_a, |project, cx| {
2962        assert_branch(Some("branch-2"), project, cx)
2963    });
2964
2965    project_remote.read_with(cx_b, |project, cx| {
2966        assert_branch(Some("branch-2"), project, cx)
2967    });
2968
2969    let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
2970    executor.run_until_parked();
2971
2972    project_remote_c.read_with(cx_c, |project, cx| {
2973        assert_branch(Some("branch-2"), project, cx)
2974    });
2975}
2976
2977#[gpui::test]
2978async fn test_git_status_sync(
2979    executor: BackgroundExecutor,
2980    cx_a: &mut TestAppContext,
2981    cx_b: &mut TestAppContext,
2982    cx_c: &mut TestAppContext,
2983) {
2984    let mut server = TestServer::start(executor.clone()).await;
2985    let client_a = server.create_client(cx_a, "user_a").await;
2986    let client_b = server.create_client(cx_b, "user_b").await;
2987    let client_c = server.create_client(cx_c, "user_c").await;
2988    server
2989        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2990        .await;
2991    let active_call_a = cx_a.read(ActiveCall::global);
2992
2993    client_a
2994        .fs()
2995        .insert_tree(
2996            path!("/dir"),
2997            json!({
2998                ".git": {},
2999                "a.txt": "a",
3000                "b.txt": "b",
3001                "c.txt": "c",
3002            }),
3003        )
3004        .await;
3005
3006    // Initially, a.txt is uncommitted, but present in the index,
3007    // and b.txt is unmerged.
3008    client_a.fs().set_head_for_repo(
3009        path!("/dir/.git").as_ref(),
3010        &[("b.txt".into(), "B".into()), ("c.txt".into(), "c".into())],
3011        "deadbeef",
3012    );
3013    client_a.fs().set_index_for_repo(
3014        path!("/dir/.git").as_ref(),
3015        &[
3016            ("a.txt".into(), "".into()),
3017            ("b.txt".into(), "B".into()),
3018            ("c.txt".into(), "c".into()),
3019        ],
3020    );
3021    client_a.fs().set_unmerged_paths_for_repo(
3022        path!("/dir/.git").as_ref(),
3023        &[(
3024            "b.txt".into(),
3025            UnmergedStatus {
3026                first_head: UnmergedStatusCode::Updated,
3027                second_head: UnmergedStatusCode::Deleted,
3028            },
3029        )],
3030    );
3031
3032    const A_STATUS_START: FileStatus = FileStatus::Tracked(TrackedStatus {
3033        index_status: StatusCode::Added,
3034        worktree_status: StatusCode::Modified,
3035    });
3036    const B_STATUS_START: FileStatus = FileStatus::Unmerged(UnmergedStatus {
3037        first_head: UnmergedStatusCode::Updated,
3038        second_head: UnmergedStatusCode::Deleted,
3039    });
3040
3041    let (project_local, _worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3042    let project_id = active_call_a
3043        .update(cx_a, |call, cx| {
3044            call.share_project(project_local.clone(), cx)
3045        })
3046        .await
3047        .unwrap();
3048
3049    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
3050
3051    // Wait for it to catch up to the new status
3052    executor.run_until_parked();
3053
3054    #[track_caller]
3055    fn assert_status(
3056        file: impl AsRef<Path>,
3057        status: Option<FileStatus>,
3058        project: &Project,
3059        cx: &App,
3060    ) {
3061        let file = file.as_ref();
3062        let repos = project
3063            .repositories(cx)
3064            .values()
3065            .cloned()
3066            .collect::<Vec<_>>();
3067        assert_eq!(repos.len(), 1);
3068        let repo = repos.into_iter().next().unwrap();
3069        assert_eq!(
3070            repo.read(cx)
3071                .status_for_path(&file.into())
3072                .map(|entry| entry.status),
3073            status
3074        );
3075    }
3076
3077    project_local.read_with(cx_a, |project, cx| {
3078        assert_status("a.txt", Some(A_STATUS_START), project, cx);
3079        assert_status("b.txt", Some(B_STATUS_START), project, cx);
3080        assert_status("c.txt", None, project, cx);
3081    });
3082
3083    project_remote.read_with(cx_b, |project, cx| {
3084        assert_status("a.txt", Some(A_STATUS_START), project, cx);
3085        assert_status("b.txt", Some(B_STATUS_START), project, cx);
3086        assert_status("c.txt", None, project, cx);
3087    });
3088
3089    const A_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3090        index_status: StatusCode::Added,
3091        worktree_status: StatusCode::Unmodified,
3092    });
3093    const B_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3094        index_status: StatusCode::Deleted,
3095        worktree_status: StatusCode::Added,
3096    });
3097    const C_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3098        index_status: StatusCode::Unmodified,
3099        worktree_status: StatusCode::Modified,
3100    });
3101
3102    // Delete b.txt from the index, mark conflict as resolved,
3103    // and modify c.txt in the working copy.
3104    client_a.fs().set_index_for_repo(
3105        path!("/dir/.git").as_ref(),
3106        &[("a.txt".into(), "a".into()), ("c.txt".into(), "c".into())],
3107    );
3108    client_a
3109        .fs()
3110        .set_unmerged_paths_for_repo(path!("/dir/.git").as_ref(), &[]);
3111    client_a
3112        .fs()
3113        .atomic_write(path!("/dir/c.txt").into(), "CC".into())
3114        .await
3115        .unwrap();
3116
3117    // Wait for buffer_local_a to receive it
3118    executor.run_until_parked();
3119
3120    // Smoke test status reading
3121    project_local.read_with(cx_a, |project, cx| {
3122        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3123        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3124        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3125    });
3126
3127    project_remote.read_with(cx_b, |project, cx| {
3128        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3129        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3130        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3131    });
3132
3133    // And synchronization while joining
3134    let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
3135    executor.run_until_parked();
3136
3137    project_remote_c.read_with(cx_c, |project, cx| {
3138        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3139        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3140        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3141    });
3142
3143    // Now remove the original git repository and check that collaborators are notified.
3144    client_a
3145        .fs()
3146        .remove_dir(path!("/dir/.git").as_ref(), RemoveOptions::default())
3147        .await
3148        .unwrap();
3149
3150    executor.run_until_parked();
3151    project_remote.update(cx_b, |project, cx| {
3152        pretty_assertions::assert_eq!(
3153            project.git_store().read(cx).repo_snapshots(cx),
3154            HashMap::default()
3155        );
3156    });
3157    project_remote_c.update(cx_c, |project, cx| {
3158        pretty_assertions::assert_eq!(
3159            project.git_store().read(cx).repo_snapshots(cx),
3160            HashMap::default()
3161        );
3162    });
3163}
3164
3165#[gpui::test(iterations = 10)]
3166async fn test_fs_operations(
3167    executor: BackgroundExecutor,
3168    cx_a: &mut TestAppContext,
3169    cx_b: &mut TestAppContext,
3170) {
3171    let mut server = TestServer::start(executor.clone()).await;
3172    let client_a = server.create_client(cx_a, "user_a").await;
3173    let client_b = server.create_client(cx_b, "user_b").await;
3174    server
3175        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3176        .await;
3177    let active_call_a = cx_a.read(ActiveCall::global);
3178
3179    client_a
3180        .fs()
3181        .insert_tree(
3182            path!("/dir"),
3183            json!({
3184                "a.txt": "a-contents",
3185                "b.txt": "b-contents",
3186            }),
3187        )
3188        .await;
3189    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3190    let project_id = active_call_a
3191        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3192        .await
3193        .unwrap();
3194    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3195
3196    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
3197    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
3198
3199    let entry = project_b
3200        .update(cx_b, |project, cx| {
3201            project.create_entry((worktree_id, "c.txt"), false, cx)
3202        })
3203        .await
3204        .unwrap()
3205        .to_included()
3206        .unwrap();
3207
3208    worktree_a.read_with(cx_a, |worktree, _| {
3209        assert_eq!(
3210            worktree
3211                .paths()
3212                .map(|p| p.to_string_lossy())
3213                .collect::<Vec<_>>(),
3214            ["a.txt", "b.txt", "c.txt"]
3215        );
3216    });
3217
3218    worktree_b.read_with(cx_b, |worktree, _| {
3219        assert_eq!(
3220            worktree
3221                .paths()
3222                .map(|p| p.to_string_lossy())
3223                .collect::<Vec<_>>(),
3224            ["a.txt", "b.txt", "c.txt"]
3225        );
3226    });
3227
3228    project_b
3229        .update(cx_b, |project, cx| {
3230            project.rename_entry(entry.id, Path::new("d.txt"), cx)
3231        })
3232        .await
3233        .unwrap()
3234        .to_included()
3235        .unwrap();
3236
3237    worktree_a.read_with(cx_a, |worktree, _| {
3238        assert_eq!(
3239            worktree
3240                .paths()
3241                .map(|p| p.to_string_lossy())
3242                .collect::<Vec<_>>(),
3243            ["a.txt", "b.txt", "d.txt"]
3244        );
3245    });
3246
3247    worktree_b.read_with(cx_b, |worktree, _| {
3248        assert_eq!(
3249            worktree
3250                .paths()
3251                .map(|p| p.to_string_lossy())
3252                .collect::<Vec<_>>(),
3253            ["a.txt", "b.txt", "d.txt"]
3254        );
3255    });
3256
3257    let dir_entry = project_b
3258        .update(cx_b, |project, cx| {
3259            project.create_entry((worktree_id, "DIR"), true, cx)
3260        })
3261        .await
3262        .unwrap()
3263        .to_included()
3264        .unwrap();
3265
3266    worktree_a.read_with(cx_a, |worktree, _| {
3267        assert_eq!(
3268            worktree
3269                .paths()
3270                .map(|p| p.to_string_lossy())
3271                .collect::<Vec<_>>(),
3272            ["DIR", "a.txt", "b.txt", "d.txt"]
3273        );
3274    });
3275
3276    worktree_b.read_with(cx_b, |worktree, _| {
3277        assert_eq!(
3278            worktree
3279                .paths()
3280                .map(|p| p.to_string_lossy())
3281                .collect::<Vec<_>>(),
3282            ["DIR", "a.txt", "b.txt", "d.txt"]
3283        );
3284    });
3285
3286    project_b
3287        .update(cx_b, |project, cx| {
3288            project.create_entry((worktree_id, "DIR/e.txt"), false, cx)
3289        })
3290        .await
3291        .unwrap()
3292        .to_included()
3293        .unwrap();
3294
3295    project_b
3296        .update(cx_b, |project, cx| {
3297            project.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
3298        })
3299        .await
3300        .unwrap()
3301        .to_included()
3302        .unwrap();
3303
3304    project_b
3305        .update(cx_b, |project, cx| {
3306            project.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
3307        })
3308        .await
3309        .unwrap()
3310        .to_included()
3311        .unwrap();
3312
3313    worktree_a.read_with(cx_a, |worktree, _| {
3314        assert_eq!(
3315            worktree
3316                .paths()
3317                .map(|p| p.to_string_lossy())
3318                .collect::<Vec<_>>(),
3319            [
3320                separator!("DIR"),
3321                separator!("DIR/SUBDIR"),
3322                separator!("DIR/SUBDIR/f.txt"),
3323                separator!("DIR/e.txt"),
3324                separator!("a.txt"),
3325                separator!("b.txt"),
3326                separator!("d.txt")
3327            ]
3328        );
3329    });
3330
3331    worktree_b.read_with(cx_b, |worktree, _| {
3332        assert_eq!(
3333            worktree
3334                .paths()
3335                .map(|p| p.to_string_lossy())
3336                .collect::<Vec<_>>(),
3337            [
3338                separator!("DIR"),
3339                separator!("DIR/SUBDIR"),
3340                separator!("DIR/SUBDIR/f.txt"),
3341                separator!("DIR/e.txt"),
3342                separator!("a.txt"),
3343                separator!("b.txt"),
3344                separator!("d.txt")
3345            ]
3346        );
3347    });
3348
3349    project_b
3350        .update(cx_b, |project, cx| {
3351            project.copy_entry(entry.id, None, Path::new("f.txt"), cx)
3352        })
3353        .await
3354        .unwrap()
3355        .unwrap();
3356
3357    worktree_a.read_with(cx_a, |worktree, _| {
3358        assert_eq!(
3359            worktree
3360                .paths()
3361                .map(|p| p.to_string_lossy())
3362                .collect::<Vec<_>>(),
3363            [
3364                separator!("DIR"),
3365                separator!("DIR/SUBDIR"),
3366                separator!("DIR/SUBDIR/f.txt"),
3367                separator!("DIR/e.txt"),
3368                separator!("a.txt"),
3369                separator!("b.txt"),
3370                separator!("d.txt"),
3371                separator!("f.txt")
3372            ]
3373        );
3374    });
3375
3376    worktree_b.read_with(cx_b, |worktree, _| {
3377        assert_eq!(
3378            worktree
3379                .paths()
3380                .map(|p| p.to_string_lossy())
3381                .collect::<Vec<_>>(),
3382            [
3383                separator!("DIR"),
3384                separator!("DIR/SUBDIR"),
3385                separator!("DIR/SUBDIR/f.txt"),
3386                separator!("DIR/e.txt"),
3387                separator!("a.txt"),
3388                separator!("b.txt"),
3389                separator!("d.txt"),
3390                separator!("f.txt")
3391            ]
3392        );
3393    });
3394
3395    project_b
3396        .update(cx_b, |project, cx| {
3397            project.delete_entry(dir_entry.id, false, cx).unwrap()
3398        })
3399        .await
3400        .unwrap();
3401    executor.run_until_parked();
3402
3403    worktree_a.read_with(cx_a, |worktree, _| {
3404        assert_eq!(
3405            worktree
3406                .paths()
3407                .map(|p| p.to_string_lossy())
3408                .collect::<Vec<_>>(),
3409            ["a.txt", "b.txt", "d.txt", "f.txt"]
3410        );
3411    });
3412
3413    worktree_b.read_with(cx_b, |worktree, _| {
3414        assert_eq!(
3415            worktree
3416                .paths()
3417                .map(|p| p.to_string_lossy())
3418                .collect::<Vec<_>>(),
3419            ["a.txt", "b.txt", "d.txt", "f.txt"]
3420        );
3421    });
3422
3423    project_b
3424        .update(cx_b, |project, cx| {
3425            project.delete_entry(entry.id, false, cx).unwrap()
3426        })
3427        .await
3428        .unwrap();
3429
3430    worktree_a.read_with(cx_a, |worktree, _| {
3431        assert_eq!(
3432            worktree
3433                .paths()
3434                .map(|p| p.to_string_lossy())
3435                .collect::<Vec<_>>(),
3436            ["a.txt", "b.txt", "f.txt"]
3437        );
3438    });
3439
3440    worktree_b.read_with(cx_b, |worktree, _| {
3441        assert_eq!(
3442            worktree
3443                .paths()
3444                .map(|p| p.to_string_lossy())
3445                .collect::<Vec<_>>(),
3446            ["a.txt", "b.txt", "f.txt"]
3447        );
3448    });
3449}
3450
3451#[gpui::test(iterations = 10)]
3452async fn test_local_settings(
3453    executor: BackgroundExecutor,
3454    cx_a: &mut TestAppContext,
3455    cx_b: &mut TestAppContext,
3456) {
3457    let mut server = TestServer::start(executor.clone()).await;
3458    let client_a = server.create_client(cx_a, "user_a").await;
3459    let client_b = server.create_client(cx_b, "user_b").await;
3460    server
3461        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3462        .await;
3463    let active_call_a = cx_a.read(ActiveCall::global);
3464
3465    // As client A, open a project that contains some local settings files
3466    client_a
3467        .fs()
3468        .insert_tree(
3469            "/dir",
3470            json!({
3471                ".zed": {
3472                    "settings.json": r#"{ "tab_size": 2 }"#
3473                },
3474                "a": {
3475                    ".zed": {
3476                        "settings.json": r#"{ "tab_size": 8 }"#
3477                    },
3478                    "a.txt": "a-contents",
3479                },
3480                "b": {
3481                    "b.txt": "b-contents",
3482                }
3483            }),
3484        )
3485        .await;
3486    let (project_a, _) = client_a.build_local_project("/dir", cx_a).await;
3487    executor.run_until_parked();
3488    let project_id = active_call_a
3489        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3490        .await
3491        .unwrap();
3492    executor.run_until_parked();
3493
3494    // As client B, join that project and observe the local settings.
3495    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3496
3497    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
3498    executor.run_until_parked();
3499    cx_b.read(|cx| {
3500        let store = cx.global::<SettingsStore>();
3501        assert_eq!(
3502            store
3503                .local_settings(worktree_b.read(cx).id())
3504                .collect::<Vec<_>>(),
3505            &[
3506                (Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
3507                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3508            ]
3509        )
3510    });
3511
3512    // As client A, update a settings file. As Client B, see the changed settings.
3513    client_a
3514        .fs()
3515        .insert_file("/dir/.zed/settings.json", r#"{}"#.into())
3516        .await;
3517    executor.run_until_parked();
3518    cx_b.read(|cx| {
3519        let store = cx.global::<SettingsStore>();
3520        assert_eq!(
3521            store
3522                .local_settings(worktree_b.read(cx).id())
3523                .collect::<Vec<_>>(),
3524            &[
3525                (Path::new("").into(), r#"{}"#.to_string()),
3526                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3527            ]
3528        )
3529    });
3530
3531    // As client A, create and remove some settings files. As client B, see the changed settings.
3532    client_a
3533        .fs()
3534        .remove_file("/dir/.zed/settings.json".as_ref(), Default::default())
3535        .await
3536        .unwrap();
3537    client_a
3538        .fs()
3539        .create_dir("/dir/b/.zed".as_ref())
3540        .await
3541        .unwrap();
3542    client_a
3543        .fs()
3544        .insert_file("/dir/b/.zed/settings.json", r#"{"tab_size": 4}"#.into())
3545        .await;
3546    executor.run_until_parked();
3547    cx_b.read(|cx| {
3548        let store = cx.global::<SettingsStore>();
3549        assert_eq!(
3550            store
3551                .local_settings(worktree_b.read(cx).id())
3552                .collect::<Vec<_>>(),
3553            &[
3554                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3555                (Path::new("b").into(), r#"{"tab_size":4}"#.to_string()),
3556            ]
3557        )
3558    });
3559
3560    // As client B, disconnect.
3561    server.forbid_connections();
3562    server.disconnect_client(client_b.peer_id().unwrap());
3563
3564    // As client A, change and remove settings files while client B is disconnected.
3565    client_a
3566        .fs()
3567        .insert_file("/dir/a/.zed/settings.json", r#"{"hard_tabs":true}"#.into())
3568        .await;
3569    client_a
3570        .fs()
3571        .remove_file("/dir/b/.zed/settings.json".as_ref(), Default::default())
3572        .await
3573        .unwrap();
3574    executor.run_until_parked();
3575
3576    // As client B, reconnect and see the changed settings.
3577    server.allow_connections();
3578    executor.advance_clock(RECEIVE_TIMEOUT);
3579    cx_b.read(|cx| {
3580        let store = cx.global::<SettingsStore>();
3581        assert_eq!(
3582            store
3583                .local_settings(worktree_b.read(cx).id())
3584                .collect::<Vec<_>>(),
3585            &[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
3586        )
3587    });
3588}
3589
3590#[gpui::test(iterations = 10)]
3591async fn test_buffer_conflict_after_save(
3592    executor: BackgroundExecutor,
3593    cx_a: &mut TestAppContext,
3594    cx_b: &mut TestAppContext,
3595) {
3596    let mut server = TestServer::start(executor.clone()).await;
3597    let client_a = server.create_client(cx_a, "user_a").await;
3598    let client_b = server.create_client(cx_b, "user_b").await;
3599    server
3600        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3601        .await;
3602    let active_call_a = cx_a.read(ActiveCall::global);
3603
3604    client_a
3605        .fs()
3606        .insert_tree(
3607            path!("/dir"),
3608            json!({
3609                "a.txt": "a-contents",
3610            }),
3611        )
3612        .await;
3613    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3614    let project_id = active_call_a
3615        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3616        .await
3617        .unwrap();
3618    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3619
3620    // Open a buffer as client B
3621    let buffer_b = project_b
3622        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3623        .await
3624        .unwrap();
3625
3626    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], None, cx));
3627
3628    buffer_b.read_with(cx_b, |buf, _| {
3629        assert!(buf.is_dirty());
3630        assert!(!buf.has_conflict());
3631    });
3632
3633    project_b
3634        .update(cx_b, |project, cx| {
3635            project.save_buffer(buffer_b.clone(), cx)
3636        })
3637        .await
3638        .unwrap();
3639
3640    buffer_b.read_with(cx_b, |buffer_b, _| assert!(!buffer_b.is_dirty()));
3641
3642    buffer_b.read_with(cx_b, |buf, _| {
3643        assert!(!buf.has_conflict());
3644    });
3645
3646    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], None, cx));
3647
3648    buffer_b.read_with(cx_b, |buf, _| {
3649        assert!(buf.is_dirty());
3650        assert!(!buf.has_conflict());
3651    });
3652}
3653
3654#[gpui::test(iterations = 10)]
3655async fn test_buffer_reloading(
3656    executor: BackgroundExecutor,
3657    cx_a: &mut TestAppContext,
3658    cx_b: &mut TestAppContext,
3659) {
3660    let mut server = TestServer::start(executor.clone()).await;
3661    let client_a = server.create_client(cx_a, "user_a").await;
3662    let client_b = server.create_client(cx_b, "user_b").await;
3663    server
3664        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3665        .await;
3666    let active_call_a = cx_a.read(ActiveCall::global);
3667
3668    client_a
3669        .fs()
3670        .insert_tree(
3671            path!("/dir"),
3672            json!({
3673                "a.txt": "a\nb\nc",
3674            }),
3675        )
3676        .await;
3677    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3678    let project_id = active_call_a
3679        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3680        .await
3681        .unwrap();
3682    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3683
3684    // Open a buffer as client B
3685    let buffer_b = project_b
3686        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3687        .await
3688        .unwrap();
3689
3690    buffer_b.read_with(cx_b, |buf, _| {
3691        assert!(!buf.is_dirty());
3692        assert!(!buf.has_conflict());
3693        assert_eq!(buf.line_ending(), LineEnding::Unix);
3694    });
3695
3696    let new_contents = Rope::from("d\ne\nf");
3697    client_a
3698        .fs()
3699        .save(
3700            path!("/dir/a.txt").as_ref(),
3701            &new_contents,
3702            LineEnding::Windows,
3703        )
3704        .await
3705        .unwrap();
3706
3707    executor.run_until_parked();
3708
3709    buffer_b.read_with(cx_b, |buf, _| {
3710        assert_eq!(buf.text(), new_contents.to_string());
3711        assert!(!buf.is_dirty());
3712        assert!(!buf.has_conflict());
3713        assert_eq!(buf.line_ending(), LineEnding::Windows);
3714    });
3715}
3716
3717#[gpui::test(iterations = 10)]
3718async fn test_editing_while_guest_opens_buffer(
3719    executor: BackgroundExecutor,
3720    cx_a: &mut TestAppContext,
3721    cx_b: &mut TestAppContext,
3722) {
3723    let mut server = TestServer::start(executor.clone()).await;
3724    let client_a = server.create_client(cx_a, "user_a").await;
3725    let client_b = server.create_client(cx_b, "user_b").await;
3726    server
3727        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3728        .await;
3729    let active_call_a = cx_a.read(ActiveCall::global);
3730
3731    client_a
3732        .fs()
3733        .insert_tree(path!("/dir"), json!({ "a.txt": "a-contents" }))
3734        .await;
3735    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3736    let project_id = active_call_a
3737        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3738        .await
3739        .unwrap();
3740    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3741
3742    // Open a buffer as client A
3743    let buffer_a = project_a
3744        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3745        .await
3746        .unwrap();
3747
3748    // Start opening the same buffer as client B
3749    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
3750    let buffer_b = cx_b.executor().spawn(open_buffer);
3751
3752    // Edit the buffer as client A while client B is still opening it.
3753    cx_b.executor().simulate_random_delay().await;
3754    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], None, cx));
3755    cx_b.executor().simulate_random_delay().await;
3756    buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], None, cx));
3757
3758    let text = buffer_a.read_with(cx_a, |buf, _| buf.text());
3759    let buffer_b = buffer_b.await.unwrap();
3760    executor.run_until_parked();
3761
3762    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), text));
3763}
3764
3765#[gpui::test(iterations = 10)]
3766async fn test_leaving_worktree_while_opening_buffer(
3767    executor: BackgroundExecutor,
3768    cx_a: &mut TestAppContext,
3769    cx_b: &mut TestAppContext,
3770) {
3771    let mut server = TestServer::start(executor.clone()).await;
3772    let client_a = server.create_client(cx_a, "user_a").await;
3773    let client_b = server.create_client(cx_b, "user_b").await;
3774    server
3775        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3776        .await;
3777    let active_call_a = cx_a.read(ActiveCall::global);
3778
3779    client_a
3780        .fs()
3781        .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
3782        .await;
3783    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3784    let project_id = active_call_a
3785        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3786        .await
3787        .unwrap();
3788    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3789
3790    // See that a guest has joined as client A.
3791    executor.run_until_parked();
3792
3793    project_a.read_with(cx_a, |p, _| assert_eq!(p.collaborators().len(), 1));
3794
3795    // Begin opening a buffer as client B, but leave the project before the open completes.
3796    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
3797    let buffer_b = cx_b.executor().spawn(open_buffer);
3798    cx_b.update(|_| drop(project_b));
3799    drop(buffer_b);
3800
3801    // See that the guest has left.
3802    executor.run_until_parked();
3803
3804    project_a.read_with(cx_a, |p, _| assert!(p.collaborators().is_empty()));
3805}
3806
3807#[gpui::test(iterations = 10)]
3808async fn test_canceling_buffer_opening(
3809    executor: BackgroundExecutor,
3810    cx_a: &mut TestAppContext,
3811    cx_b: &mut TestAppContext,
3812) {
3813    let mut server = TestServer::start(executor.clone()).await;
3814    let client_a = server.create_client(cx_a, "user_a").await;
3815    let client_b = server.create_client(cx_b, "user_b").await;
3816    server
3817        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3818        .await;
3819    let active_call_a = cx_a.read(ActiveCall::global);
3820
3821    client_a
3822        .fs()
3823        .insert_tree(
3824            "/dir",
3825            json!({
3826                "a.txt": "abc",
3827            }),
3828        )
3829        .await;
3830    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3831    let project_id = active_call_a
3832        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3833        .await
3834        .unwrap();
3835    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3836
3837    let buffer_a = project_a
3838        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3839        .await
3840        .unwrap();
3841
3842    // Open a buffer as client B but cancel after a random amount of time.
3843    let buffer_b = project_b.update(cx_b, |p, cx| {
3844        p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
3845    });
3846    executor.simulate_random_delay().await;
3847    drop(buffer_b);
3848
3849    // Try opening the same buffer again as client B, and ensure we can
3850    // still do it despite the cancellation above.
3851    let buffer_b = project_b
3852        .update(cx_b, |p, cx| {
3853            p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
3854        })
3855        .await
3856        .unwrap();
3857
3858    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "abc"));
3859}
3860
3861#[gpui::test(iterations = 10)]
3862async fn test_leaving_project(
3863    executor: BackgroundExecutor,
3864    cx_a: &mut TestAppContext,
3865    cx_b: &mut TestAppContext,
3866    cx_c: &mut TestAppContext,
3867) {
3868    let mut server = TestServer::start(executor.clone()).await;
3869    let client_a = server.create_client(cx_a, "user_a").await;
3870    let client_b = server.create_client(cx_b, "user_b").await;
3871    let client_c = server.create_client(cx_c, "user_c").await;
3872    server
3873        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
3874        .await;
3875    let active_call_a = cx_a.read(ActiveCall::global);
3876
3877    client_a
3878        .fs()
3879        .insert_tree(
3880            "/a",
3881            json!({
3882                "a.txt": "a-contents",
3883                "b.txt": "b-contents",
3884            }),
3885        )
3886        .await;
3887    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
3888    let project_id = active_call_a
3889        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3890        .await
3891        .unwrap();
3892    let project_b1 = client_b.join_remote_project(project_id, cx_b).await;
3893    let project_c = client_c.join_remote_project(project_id, cx_c).await;
3894
3895    // Client A sees that a guest has joined.
3896    executor.run_until_parked();
3897
3898    project_a.read_with(cx_a, |project, _| {
3899        assert_eq!(project.collaborators().len(), 2);
3900    });
3901
3902    project_b1.read_with(cx_b, |project, _| {
3903        assert_eq!(project.collaborators().len(), 2);
3904    });
3905
3906    project_c.read_with(cx_c, |project, _| {
3907        assert_eq!(project.collaborators().len(), 2);
3908    });
3909
3910    // Client B opens a buffer.
3911    let buffer_b1 = project_b1
3912        .update(cx_b, |project, cx| {
3913            let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
3914            project.open_buffer((worktree_id, "a.txt"), cx)
3915        })
3916        .await
3917        .unwrap();
3918
3919    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
3920
3921    // Drop client B's project and ensure client A and client C observe client B leaving.
3922    cx_b.update(|_| drop(project_b1));
3923    executor.run_until_parked();
3924
3925    project_a.read_with(cx_a, |project, _| {
3926        assert_eq!(project.collaborators().len(), 1);
3927    });
3928
3929    project_c.read_with(cx_c, |project, _| {
3930        assert_eq!(project.collaborators().len(), 1);
3931    });
3932
3933    // Client B re-joins the project and can open buffers as before.
3934    let project_b2 = client_b.join_remote_project(project_id, cx_b).await;
3935    executor.run_until_parked();
3936
3937    project_a.read_with(cx_a, |project, _| {
3938        assert_eq!(project.collaborators().len(), 2);
3939    });
3940
3941    project_b2.read_with(cx_b, |project, _| {
3942        assert_eq!(project.collaborators().len(), 2);
3943    });
3944
3945    project_c.read_with(cx_c, |project, _| {
3946        assert_eq!(project.collaborators().len(), 2);
3947    });
3948
3949    let buffer_b2 = project_b2
3950        .update(cx_b, |project, cx| {
3951            let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
3952            project.open_buffer((worktree_id, "a.txt"), cx)
3953        })
3954        .await
3955        .unwrap();
3956
3957    buffer_b2.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
3958
3959    project_a.read_with(cx_a, |project, _| {
3960        assert_eq!(project.collaborators().len(), 2);
3961    });
3962
3963    // Drop client B's connection and ensure client A and client C observe client B leaving.
3964    client_b.disconnect(&cx_b.to_async());
3965    executor.advance_clock(RECONNECT_TIMEOUT);
3966
3967    project_a.read_with(cx_a, |project, _| {
3968        assert_eq!(project.collaborators().len(), 1);
3969    });
3970
3971    project_b2.read_with(cx_b, |project, cx| {
3972        assert!(project.is_disconnected(cx));
3973    });
3974
3975    project_c.read_with(cx_c, |project, _| {
3976        assert_eq!(project.collaborators().len(), 1);
3977    });
3978
3979    // Client B can't join the project, unless they re-join the room.
3980    cx_b.spawn(|cx| {
3981        Project::in_room(
3982            project_id,
3983            client_b.app_state.client.clone(),
3984            client_b.user_store().clone(),
3985            client_b.language_registry().clone(),
3986            FakeFs::new(cx.background_executor().clone()),
3987            cx,
3988        )
3989    })
3990    .await
3991    .unwrap_err();
3992
3993    // Simulate connection loss for client C and ensure client A observes client C leaving the project.
3994    client_c.wait_for_current_user(cx_c).await;
3995    server.forbid_connections();
3996    server.disconnect_client(client_c.peer_id().unwrap());
3997    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
3998    executor.run_until_parked();
3999
4000    project_a.read_with(cx_a, |project, _| {
4001        assert_eq!(project.collaborators().len(), 0);
4002    });
4003
4004    project_b2.read_with(cx_b, |project, cx| {
4005        assert!(project.is_disconnected(cx));
4006    });
4007
4008    project_c.read_with(cx_c, |project, cx| {
4009        assert!(project.is_disconnected(cx));
4010    });
4011}
4012
4013#[gpui::test(iterations = 10)]
4014async fn test_collaborating_with_diagnostics(
4015    executor: BackgroundExecutor,
4016    cx_a: &mut TestAppContext,
4017    cx_b: &mut TestAppContext,
4018    cx_c: &mut TestAppContext,
4019) {
4020    let mut server = TestServer::start(executor.clone()).await;
4021    let client_a = server.create_client(cx_a, "user_a").await;
4022    let client_b = server.create_client(cx_b, "user_b").await;
4023    let client_c = server.create_client(cx_c, "user_c").await;
4024    server
4025        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
4026        .await;
4027    let active_call_a = cx_a.read(ActiveCall::global);
4028
4029    client_a.language_registry().add(Arc::new(Language::new(
4030        LanguageConfig {
4031            name: "Rust".into(),
4032            matcher: LanguageMatcher {
4033                path_suffixes: vec!["rs".to_string()],
4034                ..Default::default()
4035            },
4036            ..Default::default()
4037        },
4038        Some(tree_sitter_rust::LANGUAGE.into()),
4039    )));
4040    let mut fake_language_servers = client_a
4041        .language_registry()
4042        .register_fake_lsp("Rust", Default::default());
4043
4044    // Share a project as client A
4045    client_a
4046        .fs()
4047        .insert_tree(
4048            path!("/a"),
4049            json!({
4050                "a.rs": "let one = two",
4051                "other.rs": "",
4052            }),
4053        )
4054        .await;
4055    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4056
4057    // Cause the language server to start.
4058    let _buffer = project_a
4059        .update(cx_a, |project, cx| {
4060            project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
4061        })
4062        .await
4063        .unwrap();
4064
4065    // Simulate a language server reporting errors for a file.
4066    let mut fake_language_server = fake_language_servers.next().await.unwrap();
4067    fake_language_server
4068        .receive_notification::<lsp::notification::DidOpenTextDocument>()
4069        .await;
4070    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4071        &lsp::PublishDiagnosticsParams {
4072            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4073            version: None,
4074            diagnostics: vec![lsp::Diagnostic {
4075                severity: Some(lsp::DiagnosticSeverity::WARNING),
4076                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4077                message: "message 0".to_string(),
4078                ..Default::default()
4079            }],
4080        },
4081    );
4082
4083    // Client A shares the project and, simultaneously, the language server
4084    // publishes a diagnostic. This is done to ensure that the server always
4085    // observes the latest diagnostics for a worktree.
4086    let project_id = active_call_a
4087        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4088        .await
4089        .unwrap();
4090    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4091        &lsp::PublishDiagnosticsParams {
4092            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4093            version: None,
4094            diagnostics: vec![lsp::Diagnostic {
4095                severity: Some(lsp::DiagnosticSeverity::ERROR),
4096                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4097                message: "message 1".to_string(),
4098                ..Default::default()
4099            }],
4100        },
4101    );
4102
4103    // Join the worktree as client B.
4104    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4105
4106    // Wait for server to see the diagnostics update.
4107    executor.run_until_parked();
4108
4109    // Ensure client B observes the new diagnostics.
4110
4111    project_b.read_with(cx_b, |project, cx| {
4112        assert_eq!(
4113            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4114            &[(
4115                ProjectPath {
4116                    worktree_id,
4117                    path: Arc::from(Path::new("a.rs")),
4118                },
4119                LanguageServerId(0),
4120                DiagnosticSummary {
4121                    error_count: 1,
4122                    warning_count: 0,
4123                },
4124            )]
4125        )
4126    });
4127
4128    // Join project as client C and observe the diagnostics.
4129    let project_c = client_c.join_remote_project(project_id, cx_c).await;
4130    executor.run_until_parked();
4131    let project_c_diagnostic_summaries =
4132        Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
4133            project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
4134        })));
4135    project_c.update(cx_c, |_, cx| {
4136        let summaries = project_c_diagnostic_summaries.clone();
4137        cx.subscribe(&project_c, {
4138            move |p, _, event, cx| {
4139                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
4140                    *summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
4141                }
4142            }
4143        })
4144        .detach();
4145    });
4146
4147    executor.run_until_parked();
4148    assert_eq!(
4149        project_c_diagnostic_summaries.borrow().as_slice(),
4150        &[(
4151            ProjectPath {
4152                worktree_id,
4153                path: Arc::from(Path::new("a.rs")),
4154            },
4155            LanguageServerId(0),
4156            DiagnosticSummary {
4157                error_count: 1,
4158                warning_count: 0,
4159            },
4160        )]
4161    );
4162
4163    // Simulate a language server reporting more errors for a file.
4164    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4165        &lsp::PublishDiagnosticsParams {
4166            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4167            version: None,
4168            diagnostics: vec![
4169                lsp::Diagnostic {
4170                    severity: Some(lsp::DiagnosticSeverity::ERROR),
4171                    range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4172                    message: "message 1".to_string(),
4173                    ..Default::default()
4174                },
4175                lsp::Diagnostic {
4176                    severity: Some(lsp::DiagnosticSeverity::WARNING),
4177                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 13)),
4178                    message: "message 2".to_string(),
4179                    ..Default::default()
4180                },
4181            ],
4182        },
4183    );
4184
4185    // Clients B and C get the updated summaries
4186    executor.run_until_parked();
4187
4188    project_b.read_with(cx_b, |project, cx| {
4189        assert_eq!(
4190            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4191            [(
4192                ProjectPath {
4193                    worktree_id,
4194                    path: Arc::from(Path::new("a.rs")),
4195                },
4196                LanguageServerId(0),
4197                DiagnosticSummary {
4198                    error_count: 1,
4199                    warning_count: 1,
4200                },
4201            )]
4202        );
4203    });
4204
4205    project_c.read_with(cx_c, |project, cx| {
4206        assert_eq!(
4207            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4208            [(
4209                ProjectPath {
4210                    worktree_id,
4211                    path: Arc::from(Path::new("a.rs")),
4212                },
4213                LanguageServerId(0),
4214                DiagnosticSummary {
4215                    error_count: 1,
4216                    warning_count: 1,
4217                },
4218            )]
4219        );
4220    });
4221
4222    // Open the file with the errors on client B. They should be present.
4223    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
4224    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
4225
4226    buffer_b.read_with(cx_b, |buffer, _| {
4227        assert_eq!(
4228            buffer
4229                .snapshot()
4230                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
4231                .collect::<Vec<_>>(),
4232            &[
4233                DiagnosticEntry {
4234                    range: Point::new(0, 4)..Point::new(0, 7),
4235                    diagnostic: Diagnostic {
4236                        group_id: 2,
4237                        message: "message 1".to_string(),
4238                        severity: lsp::DiagnosticSeverity::ERROR,
4239                        is_primary: true,
4240                        source_kind: DiagnosticSourceKind::Pushed,
4241                        ..Diagnostic::default()
4242                    }
4243                },
4244                DiagnosticEntry {
4245                    range: Point::new(0, 10)..Point::new(0, 13),
4246                    diagnostic: Diagnostic {
4247                        group_id: 3,
4248                        severity: lsp::DiagnosticSeverity::WARNING,
4249                        message: "message 2".to_string(),
4250                        is_primary: true,
4251                        source_kind: DiagnosticSourceKind::Pushed,
4252                        ..Diagnostic::default()
4253                    }
4254                }
4255            ]
4256        );
4257    });
4258
4259    // Simulate a language server reporting no errors for a file.
4260    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4261        &lsp::PublishDiagnosticsParams {
4262            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4263            version: None,
4264            diagnostics: Vec::new(),
4265        },
4266    );
4267    executor.run_until_parked();
4268
4269    project_a.read_with(cx_a, |project, cx| {
4270        assert_eq!(
4271            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4272            []
4273        )
4274    });
4275
4276    project_b.read_with(cx_b, |project, cx| {
4277        assert_eq!(
4278            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4279            []
4280        )
4281    });
4282
4283    project_c.read_with(cx_c, |project, cx| {
4284        assert_eq!(
4285            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4286            []
4287        )
4288    });
4289}
4290
4291#[gpui::test(iterations = 10)]
4292async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
4293    executor: BackgroundExecutor,
4294    cx_a: &mut TestAppContext,
4295    cx_b: &mut TestAppContext,
4296) {
4297    let mut server = TestServer::start(executor.clone()).await;
4298    let client_a = server.create_client(cx_a, "user_a").await;
4299    let client_b = server.create_client(cx_b, "user_b").await;
4300    server
4301        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4302        .await;
4303
4304    client_a.language_registry().add(rust_lang());
4305    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4306        "Rust",
4307        FakeLspAdapter {
4308            disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
4309            disk_based_diagnostics_sources: vec!["the-disk-based-diagnostics-source".into()],
4310            ..Default::default()
4311        },
4312    );
4313
4314    let file_names = &["one.rs", "two.rs", "three.rs", "four.rs", "five.rs"];
4315    client_a
4316        .fs()
4317        .insert_tree(
4318            path!("/test"),
4319            json!({
4320                "one.rs": "const ONE: usize = 1;",
4321                "two.rs": "const TWO: usize = 2;",
4322                "three.rs": "const THREE: usize = 3;",
4323                "four.rs": "const FOUR: usize = 3;",
4324                "five.rs": "const FIVE: usize = 3;",
4325            }),
4326        )
4327        .await;
4328
4329    let (project_a, worktree_id) = client_a.build_local_project(path!("/test"), cx_a).await;
4330
4331    // Share a project as client A
4332    let active_call_a = cx_a.read(ActiveCall::global);
4333    let project_id = active_call_a
4334        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4335        .await
4336        .unwrap();
4337
4338    // Join the project as client B and open all three files.
4339    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4340    let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
4341        project_b.update(cx_b, |p, cx| {
4342            p.open_buffer_with_lsp((worktree_id, file_name), cx)
4343        })
4344    }))
4345    .await
4346    .unwrap();
4347
4348    // Simulate a language server reporting errors for a file.
4349    let fake_language_server = fake_language_servers.next().await.unwrap();
4350    fake_language_server
4351        .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
4352            token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4353        })
4354        .await
4355        .into_response()
4356        .unwrap();
4357    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
4358        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4359        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
4360            lsp::WorkDoneProgressBegin {
4361                title: "Progress Began".into(),
4362                ..Default::default()
4363            },
4364        )),
4365    });
4366    for file_name in file_names {
4367        fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4368            &lsp::PublishDiagnosticsParams {
4369                uri: lsp::Url::from_file_path(Path::new(path!("/test")).join(file_name)).unwrap(),
4370                version: None,
4371                diagnostics: vec![lsp::Diagnostic {
4372                    severity: Some(lsp::DiagnosticSeverity::WARNING),
4373                    source: Some("the-disk-based-diagnostics-source".into()),
4374                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
4375                    message: "message one".to_string(),
4376                    ..Default::default()
4377                }],
4378            },
4379        );
4380    }
4381    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
4382        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4383        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
4384            lsp::WorkDoneProgressEnd { message: None },
4385        )),
4386    });
4387
4388    // When the "disk base diagnostics finished" message is received, the buffers'
4389    // diagnostics are expected to be present.
4390    let disk_based_diagnostics_finished = Arc::new(AtomicBool::new(false));
4391    project_b.update(cx_b, {
4392        let project_b = project_b.clone();
4393        let disk_based_diagnostics_finished = disk_based_diagnostics_finished.clone();
4394        move |_, cx| {
4395            cx.subscribe(&project_b, move |_, _, event, cx| {
4396                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
4397                    disk_based_diagnostics_finished.store(true, SeqCst);
4398                    for (buffer, _) in &guest_buffers {
4399                        assert_eq!(
4400                            buffer
4401                                .read(cx)
4402                                .snapshot()
4403                                .diagnostics_in_range::<_, usize>(0..5, false)
4404                                .count(),
4405                            1,
4406                            "expected a diagnostic for buffer {:?}",
4407                            buffer.read(cx).file().unwrap().path(),
4408                        );
4409                    }
4410                }
4411            })
4412            .detach();
4413        }
4414    });
4415
4416    executor.run_until_parked();
4417    assert!(disk_based_diagnostics_finished.load(SeqCst));
4418}
4419
4420#[gpui::test(iterations = 10)]
4421async fn test_reloading_buffer_manually(
4422    executor: BackgroundExecutor,
4423    cx_a: &mut TestAppContext,
4424    cx_b: &mut TestAppContext,
4425) {
4426    let mut server = TestServer::start(executor.clone()).await;
4427    let client_a = server.create_client(cx_a, "user_a").await;
4428    let client_b = server.create_client(cx_b, "user_b").await;
4429    server
4430        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4431        .await;
4432    let active_call_a = cx_a.read(ActiveCall::global);
4433
4434    client_a
4435        .fs()
4436        .insert_tree(path!("/a"), json!({ "a.rs": "let one = 1;" }))
4437        .await;
4438    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4439    let buffer_a = project_a
4440        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
4441        .await
4442        .unwrap();
4443    let project_id = active_call_a
4444        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4445        .await
4446        .unwrap();
4447
4448    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4449
4450    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
4451    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
4452    buffer_b.update(cx_b, |buffer, cx| {
4453        buffer.edit([(4..7, "six")], None, cx);
4454        buffer.edit([(10..11, "6")], None, cx);
4455        assert_eq!(buffer.text(), "let six = 6;");
4456        assert!(buffer.is_dirty());
4457        assert!(!buffer.has_conflict());
4458    });
4459    executor.run_until_parked();
4460
4461    buffer_a.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "let six = 6;"));
4462
4463    client_a
4464        .fs()
4465        .save(
4466            path!("/a/a.rs").as_ref(),
4467            &Rope::from("let seven = 7;"),
4468            LineEnding::Unix,
4469        )
4470        .await
4471        .unwrap();
4472    executor.run_until_parked();
4473
4474    buffer_a.read_with(cx_a, |buffer, _| assert!(buffer.has_conflict()));
4475
4476    buffer_b.read_with(cx_b, |buffer, _| assert!(buffer.has_conflict()));
4477
4478    project_b
4479        .update(cx_b, |project, cx| {
4480            project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
4481        })
4482        .await
4483        .unwrap();
4484
4485    buffer_a.read_with(cx_a, |buffer, _| {
4486        assert_eq!(buffer.text(), "let seven = 7;");
4487        assert!(!buffer.is_dirty());
4488        assert!(!buffer.has_conflict());
4489    });
4490
4491    buffer_b.read_with(cx_b, |buffer, _| {
4492        assert_eq!(buffer.text(), "let seven = 7;");
4493        assert!(!buffer.is_dirty());
4494        assert!(!buffer.has_conflict());
4495    });
4496
4497    buffer_a.update(cx_a, |buffer, cx| {
4498        // Undoing on the host is a no-op when the reload was initiated by the guest.
4499        buffer.undo(cx);
4500        assert_eq!(buffer.text(), "let seven = 7;");
4501        assert!(!buffer.is_dirty());
4502        assert!(!buffer.has_conflict());
4503    });
4504    buffer_b.update(cx_b, |buffer, cx| {
4505        // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
4506        buffer.undo(cx);
4507        assert_eq!(buffer.text(), "let six = 6;");
4508        assert!(buffer.is_dirty());
4509        assert!(!buffer.has_conflict());
4510    });
4511}
4512
4513#[gpui::test(iterations = 10)]
4514async fn test_formatting_buffer(
4515    executor: BackgroundExecutor,
4516    cx_a: &mut TestAppContext,
4517    cx_b: &mut TestAppContext,
4518) {
4519    let mut server = TestServer::start(executor.clone()).await;
4520    let client_a = server.create_client(cx_a, "user_a").await;
4521    let client_b = server.create_client(cx_b, "user_b").await;
4522    server
4523        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4524        .await;
4525    let active_call_a = cx_a.read(ActiveCall::global);
4526
4527    client_a.language_registry().add(rust_lang());
4528    let mut fake_language_servers = client_a
4529        .language_registry()
4530        .register_fake_lsp("Rust", FakeLspAdapter::default());
4531
4532    // Here we insert a fake tree with a directory that exists on disk. This is needed
4533    // because later we'll invoke a command, which requires passing a working directory
4534    // that points to a valid location on disk.
4535    let directory = env::current_dir().unwrap();
4536    client_a
4537        .fs()
4538        .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
4539        .await;
4540    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
4541    let project_id = active_call_a
4542        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4543        .await
4544        .unwrap();
4545    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4546
4547    let buffer_b = project_b
4548        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
4549        .await
4550        .unwrap();
4551
4552    let _handle = project_b.update(cx_b, |project, cx| {
4553        project.register_buffer_with_language_servers(&buffer_b, cx)
4554    });
4555    let fake_language_server = fake_language_servers.next().await.unwrap();
4556    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(|_, _| async move {
4557        Ok(Some(vec![
4558            lsp::TextEdit {
4559                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
4560                new_text: "h".to_string(),
4561            },
4562            lsp::TextEdit {
4563                range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
4564                new_text: "y".to_string(),
4565            },
4566        ]))
4567    });
4568
4569    project_b
4570        .update(cx_b, |project, cx| {
4571            project.format(
4572                HashSet::from_iter([buffer_b.clone()]),
4573                LspFormatTarget::Buffers,
4574                true,
4575                FormatTrigger::Save,
4576                cx,
4577            )
4578        })
4579        .await
4580        .unwrap();
4581
4582    // The edits from the LSP are applied, and a final newline is added.
4583    assert_eq!(
4584        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4585        "let honey = \"two\"\n"
4586    );
4587
4588    // There is no `awk` command on Windows.
4589    #[cfg(not(target_os = "windows"))]
4590    {
4591        // Ensure buffer can be formatted using an external command. Notice how the
4592        // host's configuration is honored as opposed to using the guest's settings.
4593        cx_a.update(|cx| {
4594            SettingsStore::update_global(cx, |store, cx| {
4595                store.update_user_settings::<AllLanguageSettings>(cx, |file| {
4596                    file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
4597                        vec![Formatter::External {
4598                            command: "awk".into(),
4599                            arguments: Some(
4600                                vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
4601                            ),
4602                        }]
4603                        .into(),
4604                    )));
4605                });
4606            });
4607        });
4608
4609        executor.allow_parking();
4610        project_b
4611            .update(cx_b, |project, cx| {
4612                project.format(
4613                    HashSet::from_iter([buffer_b.clone()]),
4614                    LspFormatTarget::Buffers,
4615                    true,
4616                    FormatTrigger::Save,
4617                    cx,
4618                )
4619            })
4620            .await
4621            .unwrap();
4622        assert_eq!(
4623            buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4624            format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap())
4625        );
4626    }
4627}
4628
4629#[gpui::test(iterations = 10)]
4630async fn test_prettier_formatting_buffer(
4631    executor: BackgroundExecutor,
4632    cx_a: &mut TestAppContext,
4633    cx_b: &mut TestAppContext,
4634) {
4635    let mut server = TestServer::start(executor.clone()).await;
4636    let client_a = server.create_client(cx_a, "user_a").await;
4637    let client_b = server.create_client(cx_b, "user_b").await;
4638    server
4639        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4640        .await;
4641    let active_call_a = cx_a.read(ActiveCall::global);
4642
4643    let test_plugin = "test_plugin";
4644
4645    client_a.language_registry().add(Arc::new(Language::new(
4646        LanguageConfig {
4647            name: "TypeScript".into(),
4648            matcher: LanguageMatcher {
4649                path_suffixes: vec!["ts".to_string()],
4650                ..Default::default()
4651            },
4652            ..Default::default()
4653        },
4654        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
4655    )));
4656    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4657        "TypeScript",
4658        FakeLspAdapter {
4659            prettier_plugins: vec![test_plugin],
4660            ..Default::default()
4661        },
4662    );
4663
4664    // Here we insert a fake tree with a directory that exists on disk. This is needed
4665    // because later we'll invoke a command, which requires passing a working directory
4666    // that points to a valid location on disk.
4667    let directory = env::current_dir().unwrap();
4668    let buffer_text = "let one = \"two\"";
4669    client_a
4670        .fs()
4671        .insert_tree(&directory, json!({ "a.ts": buffer_text }))
4672        .await;
4673    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
4674    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
4675    let open_buffer = project_a.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx));
4676    let buffer_a = cx_a.executor().spawn(open_buffer).await.unwrap();
4677
4678    let project_id = active_call_a
4679        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4680        .await
4681        .unwrap();
4682    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4683    let (buffer_b, _) = project_b
4684        .update(cx_b, |p, cx| {
4685            p.open_buffer_with_lsp((worktree_id, "a.ts"), cx)
4686        })
4687        .await
4688        .unwrap();
4689
4690    cx_a.update(|cx| {
4691        SettingsStore::update_global(cx, |store, cx| {
4692            store.update_user_settings::<AllLanguageSettings>(cx, |file| {
4693                file.defaults.formatter = Some(SelectedFormatter::Auto);
4694                file.defaults.prettier = Some(PrettierSettings {
4695                    allowed: true,
4696                    ..PrettierSettings::default()
4697                });
4698            });
4699        });
4700    });
4701    cx_b.update(|cx| {
4702        SettingsStore::update_global(cx, |store, cx| {
4703            store.update_user_settings::<AllLanguageSettings>(cx, |file| {
4704                file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
4705                    vec![Formatter::LanguageServer { name: None }].into(),
4706                )));
4707                file.defaults.prettier = Some(PrettierSettings {
4708                    allowed: true,
4709                    ..PrettierSettings::default()
4710                });
4711            });
4712        });
4713    });
4714    let fake_language_server = fake_language_servers.next().await.unwrap();
4715    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(|_, _| async move {
4716        panic!(
4717            "Unexpected: prettier should be preferred since it's enabled and language supports it"
4718        )
4719    });
4720
4721    project_b
4722        .update(cx_b, |project, cx| {
4723            project.format(
4724                HashSet::from_iter([buffer_b.clone()]),
4725                LspFormatTarget::Buffers,
4726                true,
4727                FormatTrigger::Save,
4728                cx,
4729            )
4730        })
4731        .await
4732        .unwrap();
4733
4734    executor.run_until_parked();
4735    assert_eq!(
4736        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4737        buffer_text.to_string() + "\n" + prettier_format_suffix,
4738        "Prettier formatting was not applied to client buffer after client's request"
4739    );
4740
4741    project_a
4742        .update(cx_a, |project, cx| {
4743            project.format(
4744                HashSet::from_iter([buffer_a.clone()]),
4745                LspFormatTarget::Buffers,
4746                true,
4747                FormatTrigger::Manual,
4748                cx,
4749            )
4750        })
4751        .await
4752        .unwrap();
4753
4754    executor.run_until_parked();
4755    assert_eq!(
4756        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4757        buffer_text.to_string() + "\n" + prettier_format_suffix + "\n" + prettier_format_suffix,
4758        "Prettier formatting was not applied to client buffer after host's request"
4759    );
4760}
4761
4762#[gpui::test(iterations = 10)]
4763async fn test_definition(
4764    executor: BackgroundExecutor,
4765    cx_a: &mut TestAppContext,
4766    cx_b: &mut TestAppContext,
4767) {
4768    let mut server = TestServer::start(executor.clone()).await;
4769    let client_a = server.create_client(cx_a, "user_a").await;
4770    let client_b = server.create_client(cx_b, "user_b").await;
4771    server
4772        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4773        .await;
4774    let active_call_a = cx_a.read(ActiveCall::global);
4775
4776    let mut fake_language_servers = client_a
4777        .language_registry()
4778        .register_fake_lsp("Rust", Default::default());
4779    client_a.language_registry().add(rust_lang());
4780
4781    client_a
4782        .fs()
4783        .insert_tree(
4784            path!("/root"),
4785            json!({
4786                "dir-1": {
4787                    "a.rs": "const ONE: usize = b::TWO + b::THREE;",
4788                },
4789                "dir-2": {
4790                    "b.rs": "const TWO: c::T2 = 2;\nconst THREE: usize = 3;",
4791                    "c.rs": "type T2 = usize;",
4792                }
4793            }),
4794        )
4795        .await;
4796    let (project_a, worktree_id) = client_a
4797        .build_local_project(path!("/root/dir-1"), cx_a)
4798        .await;
4799    let project_id = active_call_a
4800        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4801        .await
4802        .unwrap();
4803    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4804
4805    // Open the file on client B.
4806    let (buffer_b, _handle) = project_b
4807        .update(cx_b, |p, cx| {
4808            p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
4809        })
4810        .await
4811        .unwrap();
4812
4813    // Request the definition of a symbol as the guest.
4814    let fake_language_server = fake_language_servers.next().await.unwrap();
4815    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
4816        |_, _| async move {
4817            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4818                lsp::Location::new(
4819                    lsp::Url::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
4820                    lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
4821                ),
4822            )))
4823        },
4824    );
4825
4826    let definitions_1 = project_b
4827        .update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
4828        .await
4829        .unwrap();
4830    cx_b.read(|cx| {
4831        assert_eq!(definitions_1.len(), 1);
4832        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
4833        let target_buffer = definitions_1[0].target.buffer.read(cx);
4834        assert_eq!(
4835            target_buffer.text(),
4836            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
4837        );
4838        assert_eq!(
4839            definitions_1[0].target.range.to_point(target_buffer),
4840            Point::new(0, 6)..Point::new(0, 9)
4841        );
4842    });
4843
4844    // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
4845    // the previous call to `definition`.
4846    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
4847        |_, _| async move {
4848            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4849                lsp::Location::new(
4850                    lsp::Url::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
4851                    lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
4852                ),
4853            )))
4854        },
4855    );
4856
4857    let definitions_2 = project_b
4858        .update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
4859        .await
4860        .unwrap();
4861    cx_b.read(|cx| {
4862        assert_eq!(definitions_2.len(), 1);
4863        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
4864        let target_buffer = definitions_2[0].target.buffer.read(cx);
4865        assert_eq!(
4866            target_buffer.text(),
4867            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
4868        );
4869        assert_eq!(
4870            definitions_2[0].target.range.to_point(target_buffer),
4871            Point::new(1, 6)..Point::new(1, 11)
4872        );
4873    });
4874    assert_eq!(
4875        definitions_1[0].target.buffer,
4876        definitions_2[0].target.buffer
4877    );
4878
4879    fake_language_server.set_request_handler::<lsp::request::GotoTypeDefinition, _, _>(
4880        |req, _| async move {
4881            assert_eq!(
4882                req.text_document_position_params.position,
4883                lsp::Position::new(0, 7)
4884            );
4885            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4886                lsp::Location::new(
4887                    lsp::Url::from_file_path(path!("/root/dir-2/c.rs")).unwrap(),
4888                    lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
4889                ),
4890            )))
4891        },
4892    );
4893
4894    let type_definitions = project_b
4895        .update(cx_b, |p, cx| p.type_definition(&buffer_b, 7, cx))
4896        .await
4897        .unwrap();
4898    cx_b.read(|cx| {
4899        assert_eq!(type_definitions.len(), 1);
4900        let target_buffer = type_definitions[0].target.buffer.read(cx);
4901        assert_eq!(target_buffer.text(), "type T2 = usize;");
4902        assert_eq!(
4903            type_definitions[0].target.range.to_point(target_buffer),
4904            Point::new(0, 5)..Point::new(0, 7)
4905        );
4906    });
4907}
4908
4909#[gpui::test(iterations = 10)]
4910async fn test_references(
4911    executor: BackgroundExecutor,
4912    cx_a: &mut TestAppContext,
4913    cx_b: &mut TestAppContext,
4914) {
4915    let mut server = TestServer::start(executor.clone()).await;
4916    let client_a = server.create_client(cx_a, "user_a").await;
4917    let client_b = server.create_client(cx_b, "user_b").await;
4918    server
4919        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4920        .await;
4921    let active_call_a = cx_a.read(ActiveCall::global);
4922
4923    client_a.language_registry().add(rust_lang());
4924    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4925        "Rust",
4926        FakeLspAdapter {
4927            name: "my-fake-lsp-adapter",
4928            capabilities: lsp::ServerCapabilities {
4929                references_provider: Some(lsp::OneOf::Left(true)),
4930                ..Default::default()
4931            },
4932            ..Default::default()
4933        },
4934    );
4935
4936    client_a
4937        .fs()
4938        .insert_tree(
4939            path!("/root"),
4940            json!({
4941                "dir-1": {
4942                    "one.rs": "const ONE: usize = 1;",
4943                    "two.rs": "const TWO: usize = one::ONE + one::ONE;",
4944                },
4945                "dir-2": {
4946                    "three.rs": "const THREE: usize = two::TWO + one::ONE;",
4947                }
4948            }),
4949        )
4950        .await;
4951    let (project_a, worktree_id) = client_a
4952        .build_local_project(path!("/root/dir-1"), cx_a)
4953        .await;
4954    let project_id = active_call_a
4955        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4956        .await
4957        .unwrap();
4958    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4959
4960    // Open the file on client B.
4961    let (buffer_b, _handle) = project_b
4962        .update(cx_b, |p, cx| {
4963            p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
4964        })
4965        .await
4966        .unwrap();
4967
4968    // Request references to a symbol as the guest.
4969    let fake_language_server = fake_language_servers.next().await.unwrap();
4970    let (lsp_response_tx, rx) = mpsc::unbounded::<Result<Option<Vec<lsp::Location>>>>();
4971    fake_language_server.set_request_handler::<lsp::request::References, _, _>({
4972        let rx = Arc::new(Mutex::new(Some(rx)));
4973        move |params, _| {
4974            assert_eq!(
4975                params.text_document_position.text_document.uri.as_str(),
4976                uri!("file:///root/dir-1/one.rs")
4977            );
4978            let rx = rx.clone();
4979            async move {
4980                let mut response_rx = rx.lock().take().unwrap();
4981                let result = response_rx.next().await.unwrap();
4982                *rx.lock() = Some(response_rx);
4983                result
4984            }
4985        }
4986    });
4987
4988    let references = project_b.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx));
4989
4990    // User is informed that a request is pending.
4991    executor.run_until_parked();
4992    project_b.read_with(cx_b, |project, cx| {
4993        let status = project.language_server_statuses(cx).next().unwrap().1;
4994        assert_eq!(status.name, "my-fake-lsp-adapter");
4995        assert_eq!(
4996            status.pending_work.values().next().unwrap().message,
4997            Some("Finding references...".into())
4998        );
4999    });
5000
5001    // Cause the language server to respond.
5002    lsp_response_tx
5003        .unbounded_send(Ok(Some(vec![
5004            lsp::Location {
5005                uri: lsp::Url::from_file_path(path!("/root/dir-1/two.rs")).unwrap(),
5006                range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
5007            },
5008            lsp::Location {
5009                uri: lsp::Url::from_file_path(path!("/root/dir-1/two.rs")).unwrap(),
5010                range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
5011            },
5012            lsp::Location {
5013                uri: lsp::Url::from_file_path(path!("/root/dir-2/three.rs")).unwrap(),
5014                range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
5015            },
5016        ])))
5017        .unwrap();
5018
5019    let references = references.await.unwrap();
5020    executor.run_until_parked();
5021    project_b.read_with(cx_b, |project, cx| {
5022        // User is informed that a request is no longer pending.
5023        let status = project.language_server_statuses(cx).next().unwrap().1;
5024        assert!(status.pending_work.is_empty());
5025
5026        assert_eq!(references.len(), 3);
5027        assert_eq!(project.worktrees(cx).count(), 2);
5028
5029        let two_buffer = references[0].buffer.read(cx);
5030        let three_buffer = references[2].buffer.read(cx);
5031        assert_eq!(
5032            two_buffer.file().unwrap().path().as_ref(),
5033            Path::new("two.rs")
5034        );
5035        assert_eq!(references[1].buffer, references[0].buffer);
5036        assert_eq!(
5037            three_buffer.file().unwrap().full_path(cx),
5038            Path::new(path!("/root/dir-2/three.rs"))
5039        );
5040
5041        assert_eq!(references[0].range.to_offset(two_buffer), 24..27);
5042        assert_eq!(references[1].range.to_offset(two_buffer), 35..38);
5043        assert_eq!(references[2].range.to_offset(three_buffer), 37..40);
5044    });
5045
5046    let references = project_b.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx));
5047
5048    // User is informed that a request is pending.
5049    executor.run_until_parked();
5050    project_b.read_with(cx_b, |project, cx| {
5051        let status = project.language_server_statuses(cx).next().unwrap().1;
5052        assert_eq!(status.name, "my-fake-lsp-adapter");
5053        assert_eq!(
5054            status.pending_work.values().next().unwrap().message,
5055            Some("Finding references...".into())
5056        );
5057    });
5058
5059    // Cause the LSP request to fail.
5060    lsp_response_tx
5061        .unbounded_send(Err(anyhow!("can't find references")))
5062        .unwrap();
5063    references.await.unwrap_err();
5064
5065    // User is informed that the request is no longer pending.
5066    executor.run_until_parked();
5067    project_b.read_with(cx_b, |project, cx| {
5068        let status = project.language_server_statuses(cx).next().unwrap().1;
5069        assert!(status.pending_work.is_empty());
5070    });
5071}
5072
5073#[gpui::test(iterations = 10)]
5074async fn test_project_search(
5075    executor: BackgroundExecutor,
5076    cx_a: &mut TestAppContext,
5077    cx_b: &mut TestAppContext,
5078) {
5079    let mut server = TestServer::start(executor.clone()).await;
5080    let client_a = server.create_client(cx_a, "user_a").await;
5081    let client_b = server.create_client(cx_b, "user_b").await;
5082    server
5083        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5084        .await;
5085    let active_call_a = cx_a.read(ActiveCall::global);
5086
5087    client_a
5088        .fs()
5089        .insert_tree(
5090            "/root",
5091            json!({
5092                "dir-1": {
5093                    "a": "hello world",
5094                    "b": "goodnight moon",
5095                    "c": "a world of goo",
5096                    "d": "world champion of clown world",
5097                },
5098                "dir-2": {
5099                    "e": "disney world is fun",
5100                }
5101            }),
5102        )
5103        .await;
5104    let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
5105    let (worktree_2, _) = project_a
5106        .update(cx_a, |p, cx| {
5107            p.find_or_create_worktree("/root/dir-2", true, cx)
5108        })
5109        .await
5110        .unwrap();
5111    worktree_2
5112        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
5113        .await;
5114    let project_id = active_call_a
5115        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5116        .await
5117        .unwrap();
5118
5119    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5120
5121    // Perform a search as the guest.
5122    let mut results = HashMap::default();
5123    let search_rx = project_b.update(cx_b, |project, cx| {
5124        project.search(
5125            SearchQuery::text(
5126                "world",
5127                false,
5128                false,
5129                false,
5130                Default::default(),
5131                Default::default(),
5132                false,
5133                None,
5134            )
5135            .unwrap(),
5136            cx,
5137        )
5138    });
5139    while let Ok(result) = search_rx.recv().await {
5140        match result {
5141            SearchResult::Buffer { buffer, ranges } => {
5142                results.entry(buffer).or_insert(ranges);
5143            }
5144            SearchResult::LimitReached => {
5145                panic!(
5146                    "Unexpectedly reached search limit in tests. If you do want to assert limit-reached, change this panic call."
5147                )
5148            }
5149        };
5150    }
5151
5152    let mut ranges_by_path = results
5153        .into_iter()
5154        .map(|(buffer, ranges)| {
5155            buffer.read_with(cx_b, |buffer, cx| {
5156                let path = buffer.file().unwrap().full_path(cx);
5157                let offset_ranges = ranges
5158                    .into_iter()
5159                    .map(|range| range.to_offset(buffer))
5160                    .collect::<Vec<_>>();
5161                (path, offset_ranges)
5162            })
5163        })
5164        .collect::<Vec<_>>();
5165    ranges_by_path.sort_by_key(|(path, _)| path.clone());
5166
5167    assert_eq!(
5168        ranges_by_path,
5169        &[
5170            (PathBuf::from("dir-1/a"), vec![6..11]),
5171            (PathBuf::from("dir-1/c"), vec![2..7]),
5172            (PathBuf::from("dir-1/d"), vec![0..5, 24..29]),
5173            (PathBuf::from("dir-2/e"), vec![7..12]),
5174        ]
5175    );
5176}
5177
5178#[gpui::test(iterations = 10)]
5179async fn test_document_highlights(
5180    executor: BackgroundExecutor,
5181    cx_a: &mut TestAppContext,
5182    cx_b: &mut TestAppContext,
5183) {
5184    let mut server = TestServer::start(executor.clone()).await;
5185    let client_a = server.create_client(cx_a, "user_a").await;
5186    let client_b = server.create_client(cx_b, "user_b").await;
5187    server
5188        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5189        .await;
5190    let active_call_a = cx_a.read(ActiveCall::global);
5191
5192    client_a
5193        .fs()
5194        .insert_tree(
5195            path!("/root-1"),
5196            json!({
5197                "main.rs": "fn double(number: i32) -> i32 { number + number }",
5198            }),
5199        )
5200        .await;
5201
5202    let mut fake_language_servers = client_a
5203        .language_registry()
5204        .register_fake_lsp("Rust", Default::default());
5205    client_a.language_registry().add(rust_lang());
5206
5207    let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await;
5208    let project_id = active_call_a
5209        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5210        .await
5211        .unwrap();
5212    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5213
5214    // Open the file on client B.
5215    let (buffer_b, _handle) = project_b
5216        .update(cx_b, |p, cx| {
5217            p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
5218        })
5219        .await
5220        .unwrap();
5221
5222    // Request document highlights as the guest.
5223    let fake_language_server = fake_language_servers.next().await.unwrap();
5224    fake_language_server.set_request_handler::<lsp::request::DocumentHighlightRequest, _, _>(
5225        |params, _| async move {
5226            assert_eq!(
5227                params
5228                    .text_document_position_params
5229                    .text_document
5230                    .uri
5231                    .as_str(),
5232                uri!("file:///root-1/main.rs")
5233            );
5234            assert_eq!(
5235                params.text_document_position_params.position,
5236                lsp::Position::new(0, 34)
5237            );
5238            Ok(Some(vec![
5239                lsp::DocumentHighlight {
5240                    kind: Some(lsp::DocumentHighlightKind::WRITE),
5241                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 16)),
5242                },
5243                lsp::DocumentHighlight {
5244                    kind: Some(lsp::DocumentHighlightKind::READ),
5245                    range: lsp::Range::new(lsp::Position::new(0, 32), lsp::Position::new(0, 38)),
5246                },
5247                lsp::DocumentHighlight {
5248                    kind: Some(lsp::DocumentHighlightKind::READ),
5249                    range: lsp::Range::new(lsp::Position::new(0, 41), lsp::Position::new(0, 47)),
5250                },
5251            ]))
5252        },
5253    );
5254
5255    let highlights = project_b
5256        .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
5257        .await
5258        .unwrap();
5259
5260    buffer_b.read_with(cx_b, |buffer, _| {
5261        let snapshot = buffer.snapshot();
5262
5263        let highlights = highlights
5264            .into_iter()
5265            .map(|highlight| (highlight.kind, highlight.range.to_offset(&snapshot)))
5266            .collect::<Vec<_>>();
5267        assert_eq!(
5268            highlights,
5269            &[
5270                (lsp::DocumentHighlightKind::WRITE, 10..16),
5271                (lsp::DocumentHighlightKind::READ, 32..38),
5272                (lsp::DocumentHighlightKind::READ, 41..47)
5273            ]
5274        )
5275    });
5276}
5277
5278#[gpui::test(iterations = 10)]
5279async fn test_lsp_hover(
5280    executor: BackgroundExecutor,
5281    cx_a: &mut TestAppContext,
5282    cx_b: &mut TestAppContext,
5283) {
5284    let mut server = TestServer::start(executor.clone()).await;
5285    let client_a = server.create_client(cx_a, "user_a").await;
5286    let client_b = server.create_client(cx_b, "user_b").await;
5287    server
5288        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5289        .await;
5290    let active_call_a = cx_a.read(ActiveCall::global);
5291
5292    client_a
5293        .fs()
5294        .insert_tree(
5295            path!("/root-1"),
5296            json!({
5297                "main.rs": "use std::collections::HashMap;",
5298            }),
5299        )
5300        .await;
5301
5302    client_a.language_registry().add(rust_lang());
5303    let language_server_names = ["rust-analyzer", "CrabLang-ls"];
5304    let mut language_servers = [
5305        client_a.language_registry().register_fake_lsp(
5306            "Rust",
5307            FakeLspAdapter {
5308                name: "rust-analyzer",
5309                capabilities: lsp::ServerCapabilities {
5310                    hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
5311                    ..lsp::ServerCapabilities::default()
5312                },
5313                ..FakeLspAdapter::default()
5314            },
5315        ),
5316        client_a.language_registry().register_fake_lsp(
5317            "Rust",
5318            FakeLspAdapter {
5319                name: "CrabLang-ls",
5320                capabilities: lsp::ServerCapabilities {
5321                    hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
5322                    ..lsp::ServerCapabilities::default()
5323                },
5324                ..FakeLspAdapter::default()
5325            },
5326        ),
5327    ];
5328
5329    let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await;
5330    let project_id = active_call_a
5331        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5332        .await
5333        .unwrap();
5334    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5335
5336    // Open the file as the guest
5337    let (buffer_b, _handle) = project_b
5338        .update(cx_b, |p, cx| {
5339            p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
5340        })
5341        .await
5342        .unwrap();
5343
5344    let mut servers_with_hover_requests = HashMap::default();
5345    for i in 0..language_server_names.len() {
5346        let new_server = language_servers[i].next().await.unwrap_or_else(|| {
5347            panic!(
5348                "Failed to get language server #{i} with name {}",
5349                &language_server_names[i]
5350            )
5351        });
5352        let new_server_name = new_server.server.name();
5353        assert!(
5354            !servers_with_hover_requests.contains_key(&new_server_name),
5355            "Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
5356        );
5357        match new_server_name.as_ref() {
5358            "CrabLang-ls" => {
5359                servers_with_hover_requests.insert(
5360                    new_server_name.clone(),
5361                    new_server.set_request_handler::<lsp::request::HoverRequest, _, _>(
5362                        move |params, _| {
5363                            assert_eq!(
5364                                params
5365                                    .text_document_position_params
5366                                    .text_document
5367                                    .uri
5368                                    .as_str(),
5369                                uri!("file:///root-1/main.rs")
5370                            );
5371                            let name = new_server_name.clone();
5372                            async move {
5373                                Ok(Some(lsp::Hover {
5374                                    contents: lsp::HoverContents::Scalar(
5375                                        lsp::MarkedString::String(format!("{name} hover")),
5376                                    ),
5377                                    range: None,
5378                                }))
5379                            }
5380                        },
5381                    ),
5382                );
5383            }
5384            "rust-analyzer" => {
5385                servers_with_hover_requests.insert(
5386                    new_server_name.clone(),
5387                    new_server.set_request_handler::<lsp::request::HoverRequest, _, _>(
5388                        |params, _| async move {
5389                            assert_eq!(
5390                                params
5391                                    .text_document_position_params
5392                                    .text_document
5393                                    .uri
5394                                    .as_str(),
5395                                uri!("file:///root-1/main.rs")
5396                            );
5397                            assert_eq!(
5398                                params.text_document_position_params.position,
5399                                lsp::Position::new(0, 22)
5400                            );
5401                            Ok(Some(lsp::Hover {
5402                                contents: lsp::HoverContents::Array(vec![
5403                                    lsp::MarkedString::String("Test hover content.".to_string()),
5404                                    lsp::MarkedString::LanguageString(lsp::LanguageString {
5405                                        language: "Rust".to_string(),
5406                                        value: "let foo = 42;".to_string(),
5407                                    }),
5408                                ]),
5409                                range: Some(lsp::Range::new(
5410                                    lsp::Position::new(0, 22),
5411                                    lsp::Position::new(0, 29),
5412                                )),
5413                            }))
5414                        },
5415                    ),
5416                );
5417            }
5418            unexpected => panic!("Unexpected server name: {unexpected}"),
5419        }
5420    }
5421
5422    // Request hover information as the guest.
5423    let mut hovers = project_b
5424        .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
5425        .await;
5426    assert_eq!(
5427        hovers.len(),
5428        2,
5429        "Expected two hovers from both language servers, but got: {hovers:?}"
5430    );
5431
5432    let _: Vec<()> = futures::future::join_all(servers_with_hover_requests.into_values().map(
5433        |mut hover_request| async move {
5434            hover_request
5435                .next()
5436                .await
5437                .expect("All hover requests should have been triggered")
5438        },
5439    ))
5440    .await;
5441
5442    hovers.sort_by_key(|hover| hover.contents.len());
5443    let first_hover = hovers.first().cloned().unwrap();
5444    assert_eq!(
5445        first_hover.contents,
5446        vec![project::HoverBlock {
5447            text: "CrabLang-ls hover".to_string(),
5448            kind: HoverBlockKind::Markdown,
5449        },]
5450    );
5451    let second_hover = hovers.last().cloned().unwrap();
5452    assert_eq!(
5453        second_hover.contents,
5454        vec![
5455            project::HoverBlock {
5456                text: "Test hover content.".to_string(),
5457                kind: HoverBlockKind::Markdown,
5458            },
5459            project::HoverBlock {
5460                text: "let foo = 42;".to_string(),
5461                kind: HoverBlockKind::Code {
5462                    language: "Rust".to_string()
5463                },
5464            }
5465        ]
5466    );
5467    buffer_b.read_with(cx_b, |buffer, _| {
5468        let snapshot = buffer.snapshot();
5469        assert_eq!(second_hover.range.unwrap().to_offset(&snapshot), 22..29);
5470    });
5471}
5472
5473#[gpui::test(iterations = 10)]
5474async fn test_project_symbols(
5475    executor: BackgroundExecutor,
5476    cx_a: &mut TestAppContext,
5477    cx_b: &mut TestAppContext,
5478) {
5479    let mut server = TestServer::start(executor.clone()).await;
5480    let client_a = server.create_client(cx_a, "user_a").await;
5481    let client_b = server.create_client(cx_b, "user_b").await;
5482    server
5483        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5484        .await;
5485    let active_call_a = cx_a.read(ActiveCall::global);
5486
5487    client_a.language_registry().add(rust_lang());
5488    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5489        "Rust",
5490        FakeLspAdapter {
5491            capabilities: lsp::ServerCapabilities {
5492                workspace_symbol_provider: Some(OneOf::Left(true)),
5493                ..Default::default()
5494            },
5495            ..Default::default()
5496        },
5497    );
5498
5499    client_a
5500        .fs()
5501        .insert_tree(
5502            path!("/code"),
5503            json!({
5504                "crate-1": {
5505                    "one.rs": "const ONE: usize = 1;",
5506                },
5507                "crate-2": {
5508                    "two.rs": "const TWO: usize = 2; const THREE: usize = 3;",
5509                },
5510                "private": {
5511                    "passwords.txt": "the-password",
5512                }
5513            }),
5514        )
5515        .await;
5516    let (project_a, worktree_id) = client_a
5517        .build_local_project(path!("/code/crate-1"), cx_a)
5518        .await;
5519    let project_id = active_call_a
5520        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5521        .await
5522        .unwrap();
5523    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5524
5525    // Cause the language server to start.
5526    let _buffer = project_b
5527        .update(cx_b, |p, cx| {
5528            p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
5529        })
5530        .await
5531        .unwrap();
5532
5533    let fake_language_server = fake_language_servers.next().await.unwrap();
5534    fake_language_server.set_request_handler::<lsp::WorkspaceSymbolRequest, _, _>(
5535        |_, _| async move {
5536            Ok(Some(lsp::WorkspaceSymbolResponse::Flat(vec![
5537                #[allow(deprecated)]
5538                lsp::SymbolInformation {
5539                    name: "TWO".into(),
5540                    location: lsp::Location {
5541                        uri: lsp::Url::from_file_path(path!("/code/crate-2/two.rs")).unwrap(),
5542                        range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
5543                    },
5544                    kind: lsp::SymbolKind::CONSTANT,
5545                    tags: None,
5546                    container_name: None,
5547                    deprecated: None,
5548                },
5549            ])))
5550        },
5551    );
5552
5553    // Request the definition of a symbol as the guest.
5554    let symbols = project_b
5555        .update(cx_b, |p, cx| p.symbols("two", cx))
5556        .await
5557        .unwrap();
5558    assert_eq!(symbols.len(), 1);
5559    assert_eq!(symbols[0].name, "TWO");
5560
5561    // Open one of the returned symbols.
5562    let buffer_b_2 = project_b
5563        .update(cx_b, |project, cx| {
5564            project.open_buffer_for_symbol(&symbols[0], cx)
5565        })
5566        .await
5567        .unwrap();
5568
5569    buffer_b_2.read_with(cx_b, |buffer, cx| {
5570        assert_eq!(
5571            buffer.file().unwrap().full_path(cx),
5572            Path::new(path!("/code/crate-2/two.rs"))
5573        );
5574    });
5575
5576    // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
5577    let mut fake_symbol = symbols[0].clone();
5578    fake_symbol.path.path = Path::new(path!("/code/secrets")).into();
5579    let error = project_b
5580        .update(cx_b, |project, cx| {
5581            project.open_buffer_for_symbol(&fake_symbol, cx)
5582        })
5583        .await
5584        .unwrap_err();
5585    assert!(error.to_string().contains("invalid symbol signature"));
5586}
5587
5588#[gpui::test(iterations = 10)]
5589async fn test_open_buffer_while_getting_definition_pointing_to_it(
5590    executor: BackgroundExecutor,
5591    cx_a: &mut TestAppContext,
5592    cx_b: &mut TestAppContext,
5593    mut rng: StdRng,
5594) {
5595    let mut server = TestServer::start(executor.clone()).await;
5596    let client_a = server.create_client(cx_a, "user_a").await;
5597    let client_b = server.create_client(cx_b, "user_b").await;
5598    server
5599        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5600        .await;
5601    let active_call_a = cx_a.read(ActiveCall::global);
5602
5603    client_a.language_registry().add(rust_lang());
5604    let mut fake_language_servers = client_a
5605        .language_registry()
5606        .register_fake_lsp("Rust", Default::default());
5607
5608    client_a
5609        .fs()
5610        .insert_tree(
5611            path!("/root"),
5612            json!({
5613                "a.rs": "const ONE: usize = b::TWO;",
5614                "b.rs": "const TWO: usize = 2",
5615            }),
5616        )
5617        .await;
5618    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
5619    let project_id = active_call_a
5620        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5621        .await
5622        .unwrap();
5623    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5624
5625    let (buffer_b1, _lsp) = project_b
5626        .update(cx_b, |p, cx| {
5627            p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
5628        })
5629        .await
5630        .unwrap();
5631
5632    let fake_language_server = fake_language_servers.next().await.unwrap();
5633    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
5634        |_, _| async move {
5635            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
5636                lsp::Location::new(
5637                    lsp::Url::from_file_path(path!("/root/b.rs")).unwrap(),
5638                    lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
5639                ),
5640            )))
5641        },
5642    );
5643
5644    let definitions;
5645    let buffer_b2;
5646    if rng.r#gen() {
5647        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
5648        (buffer_b2, _) = project_b
5649            .update(cx_b, |p, cx| {
5650                p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
5651            })
5652            .await
5653            .unwrap();
5654    } else {
5655        (buffer_b2, _) = project_b
5656            .update(cx_b, |p, cx| {
5657                p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
5658            })
5659            .await
5660            .unwrap();
5661        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
5662    }
5663
5664    let definitions = definitions.await.unwrap();
5665    assert_eq!(definitions.len(), 1);
5666    assert_eq!(definitions[0].target.buffer, buffer_b2);
5667}
5668
5669#[gpui::test(iterations = 10)]
5670async fn test_contacts(
5671    executor: BackgroundExecutor,
5672    cx_a: &mut TestAppContext,
5673    cx_b: &mut TestAppContext,
5674    cx_c: &mut TestAppContext,
5675    cx_d: &mut TestAppContext,
5676) {
5677    let mut server = TestServer::start(executor.clone()).await;
5678    let client_a = server.create_client(cx_a, "user_a").await;
5679    let client_b = server.create_client(cx_b, "user_b").await;
5680    let client_c = server.create_client(cx_c, "user_c").await;
5681    let client_d = server.create_client(cx_d, "user_d").await;
5682    server
5683        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
5684        .await;
5685    let active_call_a = cx_a.read(ActiveCall::global);
5686    let active_call_b = cx_b.read(ActiveCall::global);
5687    let active_call_c = cx_c.read(ActiveCall::global);
5688    let _active_call_d = cx_d.read(ActiveCall::global);
5689
5690    executor.run_until_parked();
5691    assert_eq!(
5692        contacts(&client_a, cx_a),
5693        [
5694            ("user_b".to_string(), "online", "free"),
5695            ("user_c".to_string(), "online", "free")
5696        ]
5697    );
5698    assert_eq!(
5699        contacts(&client_b, cx_b),
5700        [
5701            ("user_a".to_string(), "online", "free"),
5702            ("user_c".to_string(), "online", "free")
5703        ]
5704    );
5705    assert_eq!(
5706        contacts(&client_c, cx_c),
5707        [
5708            ("user_a".to_string(), "online", "free"),
5709            ("user_b".to_string(), "online", "free")
5710        ]
5711    );
5712    assert_eq!(contacts(&client_d, cx_d), []);
5713
5714    server.disconnect_client(client_c.peer_id().unwrap());
5715    server.forbid_connections();
5716    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
5717    assert_eq!(
5718        contacts(&client_a, cx_a),
5719        [
5720            ("user_b".to_string(), "online", "free"),
5721            ("user_c".to_string(), "offline", "free")
5722        ]
5723    );
5724    assert_eq!(
5725        contacts(&client_b, cx_b),
5726        [
5727            ("user_a".to_string(), "online", "free"),
5728            ("user_c".to_string(), "offline", "free")
5729        ]
5730    );
5731    assert_eq!(contacts(&client_c, cx_c), []);
5732    assert_eq!(contacts(&client_d, cx_d), []);
5733
5734    server.allow_connections();
5735    client_c
5736        .authenticate_and_connect(false, &cx_c.to_async())
5737        .await
5738        .into_response()
5739        .unwrap();
5740
5741    executor.run_until_parked();
5742    assert_eq!(
5743        contacts(&client_a, cx_a),
5744        [
5745            ("user_b".to_string(), "online", "free"),
5746            ("user_c".to_string(), "online", "free")
5747        ]
5748    );
5749    assert_eq!(
5750        contacts(&client_b, cx_b),
5751        [
5752            ("user_a".to_string(), "online", "free"),
5753            ("user_c".to_string(), "online", "free")
5754        ]
5755    );
5756    assert_eq!(
5757        contacts(&client_c, cx_c),
5758        [
5759            ("user_a".to_string(), "online", "free"),
5760            ("user_b".to_string(), "online", "free")
5761        ]
5762    );
5763    assert_eq!(contacts(&client_d, cx_d), []);
5764
5765    active_call_a
5766        .update(cx_a, |call, cx| {
5767            call.invite(client_b.user_id().unwrap(), None, cx)
5768        })
5769        .await
5770        .unwrap();
5771    executor.run_until_parked();
5772    assert_eq!(
5773        contacts(&client_a, cx_a),
5774        [
5775            ("user_b".to_string(), "online", "busy"),
5776            ("user_c".to_string(), "online", "free")
5777        ]
5778    );
5779    assert_eq!(
5780        contacts(&client_b, cx_b),
5781        [
5782            ("user_a".to_string(), "online", "busy"),
5783            ("user_c".to_string(), "online", "free")
5784        ]
5785    );
5786    assert_eq!(
5787        contacts(&client_c, cx_c),
5788        [
5789            ("user_a".to_string(), "online", "busy"),
5790            ("user_b".to_string(), "online", "busy")
5791        ]
5792    );
5793    assert_eq!(contacts(&client_d, cx_d), []);
5794
5795    // Client B and client D become contacts while client B is being called.
5796    server
5797        .make_contacts(&mut [(&client_b, cx_b), (&client_d, cx_d)])
5798        .await;
5799    executor.run_until_parked();
5800    assert_eq!(
5801        contacts(&client_a, cx_a),
5802        [
5803            ("user_b".to_string(), "online", "busy"),
5804            ("user_c".to_string(), "online", "free")
5805        ]
5806    );
5807    assert_eq!(
5808        contacts(&client_b, cx_b),
5809        [
5810            ("user_a".to_string(), "online", "busy"),
5811            ("user_c".to_string(), "online", "free"),
5812            ("user_d".to_string(), "online", "free"),
5813        ]
5814    );
5815    assert_eq!(
5816        contacts(&client_c, cx_c),
5817        [
5818            ("user_a".to_string(), "online", "busy"),
5819            ("user_b".to_string(), "online", "busy")
5820        ]
5821    );
5822    assert_eq!(
5823        contacts(&client_d, cx_d),
5824        [("user_b".to_string(), "online", "busy")]
5825    );
5826
5827    active_call_b.update(cx_b, |call, cx| call.decline_incoming(cx).unwrap());
5828    executor.run_until_parked();
5829    assert_eq!(
5830        contacts(&client_a, cx_a),
5831        [
5832            ("user_b".to_string(), "online", "free"),
5833            ("user_c".to_string(), "online", "free")
5834        ]
5835    );
5836    assert_eq!(
5837        contacts(&client_b, cx_b),
5838        [
5839            ("user_a".to_string(), "online", "free"),
5840            ("user_c".to_string(), "online", "free"),
5841            ("user_d".to_string(), "online", "free")
5842        ]
5843    );
5844    assert_eq!(
5845        contacts(&client_c, cx_c),
5846        [
5847            ("user_a".to_string(), "online", "free"),
5848            ("user_b".to_string(), "online", "free")
5849        ]
5850    );
5851    assert_eq!(
5852        contacts(&client_d, cx_d),
5853        [("user_b".to_string(), "online", "free")]
5854    );
5855
5856    active_call_c
5857        .update(cx_c, |call, cx| {
5858            call.invite(client_a.user_id().unwrap(), None, cx)
5859        })
5860        .await
5861        .unwrap();
5862    executor.run_until_parked();
5863    assert_eq!(
5864        contacts(&client_a, cx_a),
5865        [
5866            ("user_b".to_string(), "online", "free"),
5867            ("user_c".to_string(), "online", "busy")
5868        ]
5869    );
5870    assert_eq!(
5871        contacts(&client_b, cx_b),
5872        [
5873            ("user_a".to_string(), "online", "busy"),
5874            ("user_c".to_string(), "online", "busy"),
5875            ("user_d".to_string(), "online", "free")
5876        ]
5877    );
5878    assert_eq!(
5879        contacts(&client_c, cx_c),
5880        [
5881            ("user_a".to_string(), "online", "busy"),
5882            ("user_b".to_string(), "online", "free")
5883        ]
5884    );
5885    assert_eq!(
5886        contacts(&client_d, cx_d),
5887        [("user_b".to_string(), "online", "free")]
5888    );
5889
5890    active_call_a
5891        .update(cx_a, |call, cx| call.accept_incoming(cx))
5892        .await
5893        .unwrap();
5894    executor.run_until_parked();
5895    assert_eq!(
5896        contacts(&client_a, cx_a),
5897        [
5898            ("user_b".to_string(), "online", "free"),
5899            ("user_c".to_string(), "online", "busy")
5900        ]
5901    );
5902    assert_eq!(
5903        contacts(&client_b, cx_b),
5904        [
5905            ("user_a".to_string(), "online", "busy"),
5906            ("user_c".to_string(), "online", "busy"),
5907            ("user_d".to_string(), "online", "free")
5908        ]
5909    );
5910    assert_eq!(
5911        contacts(&client_c, cx_c),
5912        [
5913            ("user_a".to_string(), "online", "busy"),
5914            ("user_b".to_string(), "online", "free")
5915        ]
5916    );
5917    assert_eq!(
5918        contacts(&client_d, cx_d),
5919        [("user_b".to_string(), "online", "free")]
5920    );
5921
5922    active_call_a
5923        .update(cx_a, |call, cx| {
5924            call.invite(client_b.user_id().unwrap(), None, cx)
5925        })
5926        .await
5927        .unwrap();
5928    executor.run_until_parked();
5929    assert_eq!(
5930        contacts(&client_a, cx_a),
5931        [
5932            ("user_b".to_string(), "online", "busy"),
5933            ("user_c".to_string(), "online", "busy")
5934        ]
5935    );
5936    assert_eq!(
5937        contacts(&client_b, cx_b),
5938        [
5939            ("user_a".to_string(), "online", "busy"),
5940            ("user_c".to_string(), "online", "busy"),
5941            ("user_d".to_string(), "online", "free")
5942        ]
5943    );
5944    assert_eq!(
5945        contacts(&client_c, cx_c),
5946        [
5947            ("user_a".to_string(), "online", "busy"),
5948            ("user_b".to_string(), "online", "busy")
5949        ]
5950    );
5951    assert_eq!(
5952        contacts(&client_d, cx_d),
5953        [("user_b".to_string(), "online", "busy")]
5954    );
5955
5956    active_call_a
5957        .update(cx_a, |call, cx| call.hang_up(cx))
5958        .await
5959        .unwrap();
5960    executor.run_until_parked();
5961    assert_eq!(
5962        contacts(&client_a, cx_a),
5963        [
5964            ("user_b".to_string(), "online", "free"),
5965            ("user_c".to_string(), "online", "free")
5966        ]
5967    );
5968    assert_eq!(
5969        contacts(&client_b, cx_b),
5970        [
5971            ("user_a".to_string(), "online", "free"),
5972            ("user_c".to_string(), "online", "free"),
5973            ("user_d".to_string(), "online", "free")
5974        ]
5975    );
5976    assert_eq!(
5977        contacts(&client_c, cx_c),
5978        [
5979            ("user_a".to_string(), "online", "free"),
5980            ("user_b".to_string(), "online", "free")
5981        ]
5982    );
5983    assert_eq!(
5984        contacts(&client_d, cx_d),
5985        [("user_b".to_string(), "online", "free")]
5986    );
5987
5988    active_call_a
5989        .update(cx_a, |call, cx| {
5990            call.invite(client_b.user_id().unwrap(), None, cx)
5991        })
5992        .await
5993        .unwrap();
5994    executor.run_until_parked();
5995    assert_eq!(
5996        contacts(&client_a, cx_a),
5997        [
5998            ("user_b".to_string(), "online", "busy"),
5999            ("user_c".to_string(), "online", "free")
6000        ]
6001    );
6002    assert_eq!(
6003        contacts(&client_b, cx_b),
6004        [
6005            ("user_a".to_string(), "online", "busy"),
6006            ("user_c".to_string(), "online", "free"),
6007            ("user_d".to_string(), "online", "free")
6008        ]
6009    );
6010    assert_eq!(
6011        contacts(&client_c, cx_c),
6012        [
6013            ("user_a".to_string(), "online", "busy"),
6014            ("user_b".to_string(), "online", "busy")
6015        ]
6016    );
6017    assert_eq!(
6018        contacts(&client_d, cx_d),
6019        [("user_b".to_string(), "online", "busy")]
6020    );
6021
6022    server.forbid_connections();
6023    server.disconnect_client(client_a.peer_id().unwrap());
6024    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
6025    assert_eq!(contacts(&client_a, cx_a), []);
6026    assert_eq!(
6027        contacts(&client_b, cx_b),
6028        [
6029            ("user_a".to_string(), "offline", "free"),
6030            ("user_c".to_string(), "online", "free"),
6031            ("user_d".to_string(), "online", "free")
6032        ]
6033    );
6034    assert_eq!(
6035        contacts(&client_c, cx_c),
6036        [
6037            ("user_a".to_string(), "offline", "free"),
6038            ("user_b".to_string(), "online", "free")
6039        ]
6040    );
6041    assert_eq!(
6042        contacts(&client_d, cx_d),
6043        [("user_b".to_string(), "online", "free")]
6044    );
6045
6046    // Test removing a contact
6047    client_b
6048        .user_store()
6049        .update(cx_b, |store, cx| {
6050            store.remove_contact(client_c.user_id().unwrap(), cx)
6051        })
6052        .await
6053        .unwrap();
6054    executor.run_until_parked();
6055    assert_eq!(
6056        contacts(&client_b, cx_b),
6057        [
6058            ("user_a".to_string(), "offline", "free"),
6059            ("user_d".to_string(), "online", "free")
6060        ]
6061    );
6062    assert_eq!(
6063        contacts(&client_c, cx_c),
6064        [("user_a".to_string(), "offline", "free"),]
6065    );
6066
6067    fn contacts(
6068        client: &TestClient,
6069        cx: &TestAppContext,
6070    ) -> Vec<(String, &'static str, &'static str)> {
6071        client.user_store().read_with(cx, |store, _| {
6072            store
6073                .contacts()
6074                .iter()
6075                .map(|contact| {
6076                    (
6077                        contact.user.github_login.clone(),
6078                        if contact.online { "online" } else { "offline" },
6079                        if contact.busy { "busy" } else { "free" },
6080                    )
6081                })
6082                .collect()
6083        })
6084    }
6085}
6086
6087#[gpui::test(iterations = 10)]
6088async fn test_contact_requests(
6089    executor: BackgroundExecutor,
6090    cx_a: &mut TestAppContext,
6091    cx_a2: &mut TestAppContext,
6092    cx_b: &mut TestAppContext,
6093    cx_b2: &mut TestAppContext,
6094    cx_c: &mut TestAppContext,
6095    cx_c2: &mut TestAppContext,
6096) {
6097    // Connect to a server as 3 clients.
6098    let mut server = TestServer::start(executor.clone()).await;
6099    let client_a = server.create_client(cx_a, "user_a").await;
6100    let client_a2 = server.create_client(cx_a2, "user_a").await;
6101    let client_b = server.create_client(cx_b, "user_b").await;
6102    let client_b2 = server.create_client(cx_b2, "user_b").await;
6103    let client_c = server.create_client(cx_c, "user_c").await;
6104    let client_c2 = server.create_client(cx_c2, "user_c").await;
6105
6106    assert_eq!(client_a.user_id().unwrap(), client_a2.user_id().unwrap());
6107    assert_eq!(client_b.user_id().unwrap(), client_b2.user_id().unwrap());
6108    assert_eq!(client_c.user_id().unwrap(), client_c2.user_id().unwrap());
6109
6110    // User A and User C request that user B become their contact.
6111    client_a
6112        .user_store()
6113        .update(cx_a, |store, cx| {
6114            store.request_contact(client_b.user_id().unwrap(), cx)
6115        })
6116        .await
6117        .unwrap();
6118    client_c
6119        .user_store()
6120        .update(cx_c, |store, cx| {
6121            store.request_contact(client_b.user_id().unwrap(), cx)
6122        })
6123        .await
6124        .unwrap();
6125    executor.run_until_parked();
6126
6127    // All users see the pending request appear in all their clients.
6128    assert_eq!(
6129        client_a.summarize_contacts(cx_a).outgoing_requests,
6130        &["user_b"]
6131    );
6132    assert_eq!(
6133        client_a2.summarize_contacts(cx_a2).outgoing_requests,
6134        &["user_b"]
6135    );
6136    assert_eq!(
6137        client_b.summarize_contacts(cx_b).incoming_requests,
6138        &["user_a", "user_c"]
6139    );
6140    assert_eq!(
6141        client_b2.summarize_contacts(cx_b2).incoming_requests,
6142        &["user_a", "user_c"]
6143    );
6144    assert_eq!(
6145        client_c.summarize_contacts(cx_c).outgoing_requests,
6146        &["user_b"]
6147    );
6148    assert_eq!(
6149        client_c2.summarize_contacts(cx_c2).outgoing_requests,
6150        &["user_b"]
6151    );
6152
6153    // Contact requests are present upon connecting (tested here via disconnect/reconnect)
6154    disconnect_and_reconnect(&client_a, cx_a).await;
6155    disconnect_and_reconnect(&client_b, cx_b).await;
6156    disconnect_and_reconnect(&client_c, cx_c).await;
6157    executor.run_until_parked();
6158    assert_eq!(
6159        client_a.summarize_contacts(cx_a).outgoing_requests,
6160        &["user_b"]
6161    );
6162    assert_eq!(
6163        client_b.summarize_contacts(cx_b).incoming_requests,
6164        &["user_a", "user_c"]
6165    );
6166    assert_eq!(
6167        client_c.summarize_contacts(cx_c).outgoing_requests,
6168        &["user_b"]
6169    );
6170
6171    // User B accepts the request from user A.
6172    client_b
6173        .user_store()
6174        .update(cx_b, |store, cx| {
6175            store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
6176        })
6177        .await
6178        .unwrap();
6179
6180    executor.run_until_parked();
6181
6182    // User B sees user A as their contact now in all client, and the incoming request from them is removed.
6183    let contacts_b = client_b.summarize_contacts(cx_b);
6184    assert_eq!(contacts_b.current, &["user_a"]);
6185    assert_eq!(contacts_b.incoming_requests, &["user_c"]);
6186    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
6187    assert_eq!(contacts_b2.current, &["user_a"]);
6188    assert_eq!(contacts_b2.incoming_requests, &["user_c"]);
6189
6190    // User A sees user B as their contact now in all clients, and the outgoing request to them is removed.
6191    let contacts_a = client_a.summarize_contacts(cx_a);
6192    assert_eq!(contacts_a.current, &["user_b"]);
6193    assert!(contacts_a.outgoing_requests.is_empty());
6194    let contacts_a2 = client_a2.summarize_contacts(cx_a2);
6195    assert_eq!(contacts_a2.current, &["user_b"]);
6196    assert!(contacts_a2.outgoing_requests.is_empty());
6197
6198    // Contacts are present upon connecting (tested here via disconnect/reconnect)
6199    disconnect_and_reconnect(&client_a, cx_a).await;
6200    disconnect_and_reconnect(&client_b, cx_b).await;
6201    disconnect_and_reconnect(&client_c, cx_c).await;
6202    executor.run_until_parked();
6203    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
6204    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
6205    assert_eq!(
6206        client_b.summarize_contacts(cx_b).incoming_requests,
6207        &["user_c"]
6208    );
6209    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
6210    assert_eq!(
6211        client_c.summarize_contacts(cx_c).outgoing_requests,
6212        &["user_b"]
6213    );
6214
6215    // User B rejects the request from user C.
6216    client_b
6217        .user_store()
6218        .update(cx_b, |store, cx| {
6219            store.respond_to_contact_request(client_c.user_id().unwrap(), false, cx)
6220        })
6221        .await
6222        .unwrap();
6223
6224    executor.run_until_parked();
6225
6226    // User B doesn't see user C as their contact, and the incoming request from them is removed.
6227    let contacts_b = client_b.summarize_contacts(cx_b);
6228    assert_eq!(contacts_b.current, &["user_a"]);
6229    assert!(contacts_b.incoming_requests.is_empty());
6230    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
6231    assert_eq!(contacts_b2.current, &["user_a"]);
6232    assert!(contacts_b2.incoming_requests.is_empty());
6233
6234    // User C doesn't see user B as their contact, and the outgoing request to them is removed.
6235    let contacts_c = client_c.summarize_contacts(cx_c);
6236    assert!(contacts_c.current.is_empty());
6237    assert!(contacts_c.outgoing_requests.is_empty());
6238    let contacts_c2 = client_c2.summarize_contacts(cx_c2);
6239    assert!(contacts_c2.current.is_empty());
6240    assert!(contacts_c2.outgoing_requests.is_empty());
6241
6242    // Incoming/outgoing requests are not present upon connecting (tested here via disconnect/reconnect)
6243    disconnect_and_reconnect(&client_a, cx_a).await;
6244    disconnect_and_reconnect(&client_b, cx_b).await;
6245    disconnect_and_reconnect(&client_c, cx_c).await;
6246    executor.run_until_parked();
6247    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
6248    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
6249    assert!(
6250        client_b
6251            .summarize_contacts(cx_b)
6252            .incoming_requests
6253            .is_empty()
6254    );
6255    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
6256    assert!(
6257        client_c
6258            .summarize_contacts(cx_c)
6259            .outgoing_requests
6260            .is_empty()
6261    );
6262
6263    async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
6264        client.disconnect(&cx.to_async());
6265        client.clear_contacts(cx).await;
6266        client
6267            .authenticate_and_connect(false, &cx.to_async())
6268            .await
6269            .into_response()
6270            .unwrap();
6271    }
6272}
6273
6274#[gpui::test(iterations = 10)]
6275async fn test_join_call_after_screen_was_shared(
6276    executor: BackgroundExecutor,
6277    cx_a: &mut TestAppContext,
6278    cx_b: &mut TestAppContext,
6279) {
6280    let mut server = TestServer::start(executor.clone()).await;
6281
6282    let client_a = server.create_client(cx_a, "user_a").await;
6283    let client_b = server.create_client(cx_b, "user_b").await;
6284    server
6285        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6286        .await;
6287
6288    let active_call_a = cx_a.read(ActiveCall::global);
6289    let active_call_b = cx_b.read(ActiveCall::global);
6290
6291    // Call users B and C from client A.
6292    active_call_a
6293        .update(cx_a, |call, cx| {
6294            call.invite(client_b.user_id().unwrap(), None, cx)
6295        })
6296        .await
6297        .unwrap();
6298
6299    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
6300    executor.run_until_parked();
6301    assert_eq!(
6302        room_participants(&room_a, cx_a),
6303        RoomParticipants {
6304            remote: Default::default(),
6305            pending: vec!["user_b".to_string()]
6306        }
6307    );
6308
6309    // User B receives the call.
6310
6311    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
6312    let call_b = incoming_call_b.next().await.unwrap().unwrap();
6313    assert_eq!(call_b.calling_user.github_login, "user_a");
6314
6315    // User A shares their screen
6316    let display = gpui::TestScreenCaptureSource::new();
6317    cx_a.set_screen_capture_sources(vec![display]);
6318    active_call_a
6319        .update(cx_a, |call, cx| {
6320            call.room()
6321                .unwrap()
6322                .update(cx, |room, cx| room.share_screen(cx))
6323        })
6324        .await
6325        .unwrap();
6326
6327    client_b.user_store().update(cx_b, |user_store, _| {
6328        user_store.clear_cache();
6329    });
6330
6331    // User B joins the room
6332    active_call_b
6333        .update(cx_b, |call, cx| call.accept_incoming(cx))
6334        .await
6335        .unwrap();
6336
6337    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
6338    assert!(incoming_call_b.next().await.unwrap().is_none());
6339
6340    executor.run_until_parked();
6341    assert_eq!(
6342        room_participants(&room_a, cx_a),
6343        RoomParticipants {
6344            remote: vec!["user_b".to_string()],
6345            pending: vec![],
6346        }
6347    );
6348    assert_eq!(
6349        room_participants(&room_b, cx_b),
6350        RoomParticipants {
6351            remote: vec!["user_a".to_string()],
6352            pending: vec![],
6353        }
6354    );
6355
6356    // Ensure User B sees User A's screenshare.
6357
6358    room_b.read_with(cx_b, |room, _| {
6359        assert_eq!(
6360            room.remote_participants()
6361                .get(&client_a.user_id().unwrap())
6362                .unwrap()
6363                .video_tracks
6364                .len(),
6365            1
6366        );
6367    });
6368}
6369
6370#[gpui::test]
6371async fn test_right_click_menu_behind_collab_panel(cx: &mut TestAppContext) {
6372    let mut server = TestServer::start(cx.executor().clone()).await;
6373    let client_a = server.create_client(cx, "user_a").await;
6374    let (_workspace_a, cx) = client_a.build_test_workspace(cx).await;
6375
6376    cx.simulate_resize(size(px(300.), px(300.)));
6377
6378    cx.simulate_keystrokes("cmd-n cmd-n cmd-n");
6379    cx.update(|window, _cx| window.refresh());
6380
6381    let tab_bounds = cx.debug_bounds("TAB-2").unwrap();
6382    let new_tab_button_bounds = cx.debug_bounds("ICON-Plus").unwrap();
6383
6384    assert!(
6385        tab_bounds.intersects(&new_tab_button_bounds),
6386        "Tab should overlap with the new tab button, if this is failing check if there's been a redesign!"
6387    );
6388
6389    cx.simulate_event(MouseDownEvent {
6390        button: MouseButton::Right,
6391        position: new_tab_button_bounds.center(),
6392        modifiers: Modifiers::default(),
6393        click_count: 1,
6394        first_mouse: false,
6395    });
6396
6397    // regression test that the right click menu for tabs does not open.
6398    assert!(cx.debug_bounds("MENU_ITEM-Close").is_none());
6399
6400    let tab_bounds = cx.debug_bounds("TAB-1").unwrap();
6401    cx.simulate_event(MouseDownEvent {
6402        button: MouseButton::Right,
6403        position: tab_bounds.center(),
6404        modifiers: Modifiers::default(),
6405        click_count: 1,
6406        first_mouse: false,
6407    });
6408    assert!(cx.debug_bounds("MENU_ITEM-Close").is_some());
6409}
6410
6411#[gpui::test]
6412async fn test_pane_split_left(cx: &mut TestAppContext) {
6413    let (_, client) = TestServer::start1(cx).await;
6414    let (workspace, cx) = client.build_test_workspace(cx).await;
6415
6416    cx.simulate_keystrokes("cmd-n");
6417    workspace.update(cx, |workspace, cx| {
6418        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 1);
6419    });
6420    cx.simulate_keystrokes("cmd-k left");
6421    workspace.update(cx, |workspace, cx| {
6422        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
6423    });
6424    cx.simulate_keystrokes("cmd-k");
6425    // sleep for longer than the timeout in keyboard shortcut handling
6426    // to verify that it doesn't fire in this case.
6427    cx.executor().advance_clock(Duration::from_secs(2));
6428    cx.simulate_keystrokes("left");
6429    workspace.update(cx, |workspace, cx| {
6430        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
6431    });
6432}
6433
6434#[gpui::test]
6435async fn test_join_after_restart(cx1: &mut TestAppContext, cx2: &mut TestAppContext) {
6436    let (mut server, client) = TestServer::start1(cx1).await;
6437    let channel1 = server.make_public_channel("channel1", &client, cx1).await;
6438    let channel2 = server.make_public_channel("channel2", &client, cx1).await;
6439
6440    join_channel(channel1, &client, cx1).await.unwrap();
6441    drop(client);
6442
6443    let client2 = server.create_client(cx2, "user_a").await;
6444    join_channel(channel2, &client2, cx2).await.unwrap();
6445}
6446
6447#[gpui::test]
6448async fn test_preview_tabs(cx: &mut TestAppContext) {
6449    let (_server, client) = TestServer::start1(cx).await;
6450    let (workspace, cx) = client.build_test_workspace(cx).await;
6451    let project = workspace.read_with(cx, |workspace, _| workspace.project().clone());
6452
6453    let worktree_id = project.update(cx, |project, cx| {
6454        project.worktrees(cx).next().unwrap().read(cx).id()
6455    });
6456
6457    let path_1 = ProjectPath {
6458        worktree_id,
6459        path: Path::new("1.txt").into(),
6460    };
6461    let path_2 = ProjectPath {
6462        worktree_id,
6463        path: Path::new("2.js").into(),
6464    };
6465    let path_3 = ProjectPath {
6466        worktree_id,
6467        path: Path::new("3.rs").into(),
6468    };
6469
6470    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
6471
6472    let get_path = |pane: &Pane, idx: usize, cx: &App| {
6473        pane.item_for_index(idx).unwrap().project_path(cx).unwrap()
6474    };
6475
6476    // Opening item 3 as a "permanent" tab
6477    workspace
6478        .update_in(cx, |workspace, window, cx| {
6479            workspace.open_path(path_3.clone(), None, false, window, cx)
6480        })
6481        .await
6482        .unwrap();
6483
6484    pane.update(cx, |pane, cx| {
6485        assert_eq!(pane.items_len(), 1);
6486        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6487        assert_eq!(pane.preview_item_id(), None);
6488
6489        assert!(!pane.can_navigate_backward());
6490        assert!(!pane.can_navigate_forward());
6491    });
6492
6493    // Open item 1 as preview
6494    workspace
6495        .update_in(cx, |workspace, window, cx| {
6496            workspace.open_path_preview(path_1.clone(), None, true, true, true, window, cx)
6497        })
6498        .await
6499        .unwrap();
6500
6501    pane.update(cx, |pane, cx| {
6502        assert_eq!(pane.items_len(), 2);
6503        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6504        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6505        assert_eq!(
6506            pane.preview_item_id(),
6507            Some(pane.items().nth(1).unwrap().item_id())
6508        );
6509
6510        assert!(pane.can_navigate_backward());
6511        assert!(!pane.can_navigate_forward());
6512    });
6513
6514    // Open item 2 as preview
6515    workspace
6516        .update_in(cx, |workspace, window, cx| {
6517            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
6518        })
6519        .await
6520        .unwrap();
6521
6522    pane.update(cx, |pane, cx| {
6523        assert_eq!(pane.items_len(), 2);
6524        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6525        assert_eq!(get_path(pane, 1, cx), path_2.clone());
6526        assert_eq!(
6527            pane.preview_item_id(),
6528            Some(pane.items().nth(1).unwrap().item_id())
6529        );
6530
6531        assert!(pane.can_navigate_backward());
6532        assert!(!pane.can_navigate_forward());
6533    });
6534
6535    // Going back should show item 1 as preview
6536    workspace
6537        .update_in(cx, |workspace, window, cx| {
6538            workspace.go_back(pane.downgrade(), window, cx)
6539        })
6540        .await
6541        .unwrap();
6542
6543    pane.update(cx, |pane, cx| {
6544        assert_eq!(pane.items_len(), 2);
6545        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6546        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6547        assert_eq!(
6548            pane.preview_item_id(),
6549            Some(pane.items().nth(1).unwrap().item_id())
6550        );
6551
6552        assert!(pane.can_navigate_backward());
6553        assert!(pane.can_navigate_forward());
6554    });
6555
6556    // Closing item 1
6557    pane.update_in(cx, |pane, window, cx| {
6558        pane.close_item_by_id(
6559            pane.active_item().unwrap().item_id(),
6560            workspace::SaveIntent::Skip,
6561            window,
6562            cx,
6563        )
6564    })
6565    .await
6566    .unwrap();
6567
6568    pane.update(cx, |pane, cx| {
6569        assert_eq!(pane.items_len(), 1);
6570        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6571        assert_eq!(pane.preview_item_id(), None);
6572
6573        assert!(pane.can_navigate_backward());
6574        assert!(!pane.can_navigate_forward());
6575    });
6576
6577    // Going back should show item 1 as preview
6578    workspace
6579        .update_in(cx, |workspace, window, cx| {
6580            workspace.go_back(pane.downgrade(), window, cx)
6581        })
6582        .await
6583        .unwrap();
6584
6585    pane.update(cx, |pane, cx| {
6586        assert_eq!(pane.items_len(), 2);
6587        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6588        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6589        assert_eq!(
6590            pane.preview_item_id(),
6591            Some(pane.items().nth(1).unwrap().item_id())
6592        );
6593
6594        assert!(pane.can_navigate_backward());
6595        assert!(pane.can_navigate_forward());
6596    });
6597
6598    // Close permanent tab
6599    pane.update_in(cx, |pane, window, cx| {
6600        let id = pane.items().next().unwrap().item_id();
6601        pane.close_item_by_id(id, workspace::SaveIntent::Skip, window, cx)
6602    })
6603    .await
6604    .unwrap();
6605
6606    pane.update(cx, |pane, cx| {
6607        assert_eq!(pane.items_len(), 1);
6608        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6609        assert_eq!(
6610            pane.preview_item_id(),
6611            Some(pane.items().next().unwrap().item_id())
6612        );
6613
6614        assert!(pane.can_navigate_backward());
6615        assert!(pane.can_navigate_forward());
6616    });
6617
6618    // Split pane to the right
6619    pane.update(cx, |pane, cx| {
6620        pane.split(workspace::SplitDirection::Right, cx);
6621    });
6622
6623    let right_pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
6624
6625    pane.update(cx, |pane, cx| {
6626        assert_eq!(pane.items_len(), 1);
6627        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6628        assert_eq!(
6629            pane.preview_item_id(),
6630            Some(pane.items().next().unwrap().item_id())
6631        );
6632
6633        assert!(pane.can_navigate_backward());
6634        assert!(pane.can_navigate_forward());
6635    });
6636
6637    right_pane.update(cx, |pane, cx| {
6638        assert_eq!(pane.items_len(), 1);
6639        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6640        assert_eq!(pane.preview_item_id(), None);
6641
6642        assert!(!pane.can_navigate_backward());
6643        assert!(!pane.can_navigate_forward());
6644    });
6645
6646    // Open item 2 as preview in right pane
6647    workspace
6648        .update_in(cx, |workspace, window, cx| {
6649            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
6650        })
6651        .await
6652        .unwrap();
6653
6654    pane.update(cx, |pane, cx| {
6655        assert_eq!(pane.items_len(), 1);
6656        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6657        assert_eq!(
6658            pane.preview_item_id(),
6659            Some(pane.items().next().unwrap().item_id())
6660        );
6661
6662        assert!(pane.can_navigate_backward());
6663        assert!(pane.can_navigate_forward());
6664    });
6665
6666    right_pane.update(cx, |pane, cx| {
6667        assert_eq!(pane.items_len(), 2);
6668        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6669        assert_eq!(get_path(pane, 1, cx), path_2.clone());
6670        assert_eq!(
6671            pane.preview_item_id(),
6672            Some(pane.items().nth(1).unwrap().item_id())
6673        );
6674
6675        assert!(pane.can_navigate_backward());
6676        assert!(!pane.can_navigate_forward());
6677    });
6678
6679    // Focus left pane
6680    workspace.update_in(cx, |workspace, window, cx| {
6681        workspace.activate_pane_in_direction(workspace::SplitDirection::Left, window, cx)
6682    });
6683
6684    // Open item 2 as preview in left pane
6685    workspace
6686        .update_in(cx, |workspace, window, cx| {
6687            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
6688        })
6689        .await
6690        .unwrap();
6691
6692    pane.update(cx, |pane, cx| {
6693        assert_eq!(pane.items_len(), 1);
6694        assert_eq!(get_path(pane, 0, cx), path_2.clone());
6695        assert_eq!(
6696            pane.preview_item_id(),
6697            Some(pane.items().next().unwrap().item_id())
6698        );
6699
6700        assert!(pane.can_navigate_backward());
6701        assert!(!pane.can_navigate_forward());
6702    });
6703
6704    right_pane.update(cx, |pane, cx| {
6705        assert_eq!(pane.items_len(), 2);
6706        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6707        assert_eq!(get_path(pane, 1, cx), path_2.clone());
6708        assert_eq!(
6709            pane.preview_item_id(),
6710            Some(pane.items().nth(1).unwrap().item_id())
6711        );
6712
6713        assert!(pane.can_navigate_backward());
6714        assert!(!pane.can_navigate_forward());
6715    });
6716}
6717
6718#[gpui::test(iterations = 10)]
6719async fn test_context_collaboration_with_reconnect(
6720    executor: BackgroundExecutor,
6721    cx_a: &mut TestAppContext,
6722    cx_b: &mut TestAppContext,
6723) {
6724    let mut server = TestServer::start(executor.clone()).await;
6725    let client_a = server.create_client(cx_a, "user_a").await;
6726    let client_b = server.create_client(cx_b, "user_b").await;
6727    server
6728        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6729        .await;
6730    let active_call_a = cx_a.read(ActiveCall::global);
6731
6732    client_a.fs().insert_tree("/a", Default::default()).await;
6733    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
6734    let project_id = active_call_a
6735        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
6736        .await
6737        .unwrap();
6738    let project_b = client_b.join_remote_project(project_id, cx_b).await;
6739
6740    // Client A sees that a guest has joined.
6741    executor.run_until_parked();
6742
6743    project_a.read_with(cx_a, |project, _| {
6744        assert_eq!(project.collaborators().len(), 1);
6745    });
6746    project_b.read_with(cx_b, |project, _| {
6747        assert_eq!(project.collaborators().len(), 1);
6748    });
6749
6750    let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
6751    let context_store_a = cx_a
6752        .update(|cx| {
6753            ContextStore::new(
6754                project_a.clone(),
6755                prompt_builder.clone(),
6756                Arc::new(SlashCommandWorkingSet::default()),
6757                cx,
6758            )
6759        })
6760        .await
6761        .unwrap();
6762    let context_store_b = cx_b
6763        .update(|cx| {
6764            ContextStore::new(
6765                project_b.clone(),
6766                prompt_builder.clone(),
6767                Arc::new(SlashCommandWorkingSet::default()),
6768                cx,
6769            )
6770        })
6771        .await
6772        .unwrap();
6773
6774    // Client A creates a new chats.
6775    let context_a = context_store_a.update(cx_a, |store, cx| store.create(cx));
6776    executor.run_until_parked();
6777
6778    // Client B retrieves host's contexts and joins one.
6779    let context_b = context_store_b
6780        .update(cx_b, |store, cx| {
6781            let host_contexts = store.host_contexts().to_vec();
6782            assert_eq!(host_contexts.len(), 1);
6783            store.open_remote_context(host_contexts[0].id.clone(), cx)
6784        })
6785        .await
6786        .unwrap();
6787
6788    // Host and guest make changes
6789    context_a.update(cx_a, |context, cx| {
6790        context.buffer().update(cx, |buffer, cx| {
6791            buffer.edit([(0..0, "Host change\n")], None, cx)
6792        })
6793    });
6794    context_b.update(cx_b, |context, cx| {
6795        context.buffer().update(cx, |buffer, cx| {
6796            buffer.edit([(0..0, "Guest change\n")], None, cx)
6797        })
6798    });
6799    executor.run_until_parked();
6800    assert_eq!(
6801        context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
6802        "Guest change\nHost change\n"
6803    );
6804    assert_eq!(
6805        context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
6806        "Guest change\nHost change\n"
6807    );
6808
6809    // Disconnect client A and make some changes while disconnected.
6810    server.disconnect_client(client_a.peer_id().unwrap());
6811    server.forbid_connections();
6812    context_a.update(cx_a, |context, cx| {
6813        context.buffer().update(cx, |buffer, cx| {
6814            buffer.edit([(0..0, "Host offline change\n")], None, cx)
6815        })
6816    });
6817    context_b.update(cx_b, |context, cx| {
6818        context.buffer().update(cx, |buffer, cx| {
6819            buffer.edit([(0..0, "Guest offline change\n")], None, cx)
6820        })
6821    });
6822    executor.run_until_parked();
6823    assert_eq!(
6824        context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
6825        "Host offline change\nGuest change\nHost change\n"
6826    );
6827    assert_eq!(
6828        context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
6829        "Guest offline change\nGuest change\nHost change\n"
6830    );
6831
6832    // Allow client A to reconnect and verify that contexts converge.
6833    server.allow_connections();
6834    executor.advance_clock(RECEIVE_TIMEOUT);
6835    assert_eq!(
6836        context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
6837        "Guest offline change\nHost offline change\nGuest change\nHost change\n"
6838    );
6839    assert_eq!(
6840        context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
6841        "Guest offline change\nHost offline change\nGuest change\nHost change\n"
6842    );
6843
6844    // Client A disconnects without being able to reconnect. Context B becomes readonly.
6845    server.forbid_connections();
6846    server.disconnect_client(client_a.peer_id().unwrap());
6847    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
6848    context_b.read_with(cx_b, |context, cx| {
6849        assert!(context.buffer().read(cx).read_only());
6850    });
6851}
6852
6853#[gpui::test]
6854async fn test_remote_git_branches(
6855    executor: BackgroundExecutor,
6856    cx_a: &mut TestAppContext,
6857    cx_b: &mut TestAppContext,
6858) {
6859    let mut server = TestServer::start(executor.clone()).await;
6860    let client_a = server.create_client(cx_a, "user_a").await;
6861    let client_b = server.create_client(cx_b, "user_b").await;
6862    server
6863        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6864        .await;
6865    let active_call_a = cx_a.read(ActiveCall::global);
6866
6867    client_a
6868        .fs()
6869        .insert_tree("/project", serde_json::json!({ ".git":{} }))
6870        .await;
6871    let branches = ["main", "dev", "feature-1"];
6872    client_a
6873        .fs()
6874        .insert_branches(Path::new("/project/.git"), &branches);
6875    let branches_set = branches
6876        .into_iter()
6877        .map(ToString::to_string)
6878        .collect::<HashSet<_>>();
6879
6880    let (project_a, _) = client_a.build_local_project("/project", cx_a).await;
6881
6882    let project_id = active_call_a
6883        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
6884        .await
6885        .unwrap();
6886    let project_b = client_b.join_remote_project(project_id, cx_b).await;
6887
6888    // Client A sees that a guest has joined and the repo has been populated
6889    executor.run_until_parked();
6890
6891    let repo_b = cx_b.update(|cx| project_b.read(cx).active_repository(cx).unwrap());
6892
6893    let branches_b = cx_b
6894        .update(|cx| repo_b.update(cx, |repository, _| repository.branches()))
6895        .await
6896        .unwrap()
6897        .unwrap();
6898
6899    let new_branch = branches[2];
6900
6901    let branches_b = branches_b
6902        .into_iter()
6903        .map(|branch| branch.name().to_string())
6904        .collect::<HashSet<_>>();
6905
6906    assert_eq!(branches_b, branches_set);
6907
6908    cx_b.update(|cx| {
6909        repo_b.update(cx, |repository, _cx| {
6910            repository.change_branch(new_branch.to_string())
6911        })
6912    })
6913    .await
6914    .unwrap()
6915    .unwrap();
6916
6917    executor.run_until_parked();
6918
6919    let host_branch = cx_a.update(|cx| {
6920        project_a.update(cx, |project, cx| {
6921            project
6922                .repositories(cx)
6923                .values()
6924                .next()
6925                .unwrap()
6926                .read(cx)
6927                .branch
6928                .as_ref()
6929                .unwrap()
6930                .clone()
6931        })
6932    });
6933
6934    assert_eq!(host_branch.name(), branches[2]);
6935
6936    // Also try creating a new branch
6937    cx_b.update(|cx| {
6938        repo_b.update(cx, |repository, _cx| {
6939            repository.create_branch("totally-new-branch".to_string())
6940        })
6941    })
6942    .await
6943    .unwrap()
6944    .unwrap();
6945
6946    cx_b.update(|cx| {
6947        repo_b.update(cx, |repository, _cx| {
6948            repository.change_branch("totally-new-branch".to_string())
6949        })
6950    })
6951    .await
6952    .unwrap()
6953    .unwrap();
6954
6955    executor.run_until_parked();
6956
6957    let host_branch = cx_a.update(|cx| {
6958        project_a.update(cx, |project, cx| {
6959            project
6960                .repositories(cx)
6961                .values()
6962                .next()
6963                .unwrap()
6964                .read(cx)
6965                .branch
6966                .as_ref()
6967                .unwrap()
6968                .clone()
6969        })
6970    });
6971
6972    assert_eq!(host_branch.name(), "totally-new-branch");
6973}