integration_tests.rs

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