integration_tests.rs

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