multi_buffer_tests.rs

   1use super::*;
   2use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
   3use gpui::{App, TestAppContext};
   4use indoc::indoc;
   5use language::{Buffer, Rope};
   6use parking_lot::RwLock;
   7use rand::prelude::*;
   8use settings::SettingsStore;
   9use std::env;
  10use std::time::{Duration, Instant};
  11use util::RandomCharIter;
  12use util::rel_path::rel_path;
  13use util::test::sample_text;
  14
  15#[ctor::ctor]
  16fn init_logger() {
  17    zlog::init_test();
  18}
  19
  20#[gpui::test]
  21fn test_empty_singleton(cx: &mut App) {
  22    let buffer = cx.new(|cx| Buffer::local("", cx));
  23    let buffer_id = buffer.read(cx).remote_id();
  24    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
  25    let snapshot = multibuffer.read(cx).snapshot(cx);
  26    assert_eq!(snapshot.text(), "");
  27    assert_eq!(
  28        snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
  29        [RowInfo {
  30            buffer_id: Some(buffer_id),
  31            buffer_row: Some(0),
  32            multibuffer_row: Some(MultiBufferRow(0)),
  33            diff_status: None,
  34            expand_info: None,
  35        }]
  36    );
  37}
  38
  39#[gpui::test]
  40fn test_singleton(cx: &mut App) {
  41    let buffer = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
  42    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
  43
  44    let snapshot = multibuffer.read(cx).snapshot(cx);
  45    assert_eq!(snapshot.text(), buffer.read(cx).text());
  46
  47    assert_eq!(
  48        snapshot
  49            .row_infos(MultiBufferRow(0))
  50            .map(|info| info.buffer_row)
  51            .collect::<Vec<_>>(),
  52        (0..buffer.read(cx).row_count())
  53            .map(Some)
  54            .collect::<Vec<_>>()
  55    );
  56    assert_consistent_line_numbers(&snapshot);
  57
  58    buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx));
  59    let snapshot = multibuffer.read(cx).snapshot(cx);
  60
  61    assert_eq!(snapshot.text(), buffer.read(cx).text());
  62    assert_eq!(
  63        snapshot
  64            .row_infos(MultiBufferRow(0))
  65            .map(|info| info.buffer_row)
  66            .collect::<Vec<_>>(),
  67        (0..buffer.read(cx).row_count())
  68            .map(Some)
  69            .collect::<Vec<_>>()
  70    );
  71    assert_consistent_line_numbers(&snapshot);
  72}
  73
  74#[gpui::test]
  75fn test_remote(cx: &mut App) {
  76    let host_buffer = cx.new(|cx| Buffer::local("a", cx));
  77    let guest_buffer = cx.new(|cx| {
  78        let state = host_buffer.read(cx).to_proto(cx);
  79        let ops = cx
  80            .background_executor()
  81            .block(host_buffer.read(cx).serialize_ops(None, cx));
  82        let mut buffer =
  83            Buffer::from_proto(ReplicaId::REMOTE_SERVER, Capability::ReadWrite, state, None)
  84                .unwrap();
  85        buffer.apply_ops(
  86            ops.into_iter()
  87                .map(|op| language::proto::deserialize_operation(op).unwrap()),
  88            cx,
  89        );
  90        buffer
  91    });
  92    let multibuffer = cx.new(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx));
  93    let snapshot = multibuffer.read(cx).snapshot(cx);
  94    assert_eq!(snapshot.text(), "a");
  95
  96    guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx));
  97    let snapshot = multibuffer.read(cx).snapshot(cx);
  98    assert_eq!(snapshot.text(), "ab");
  99
 100    guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx));
 101    let snapshot = multibuffer.read(cx).snapshot(cx);
 102    assert_eq!(snapshot.text(), "abc");
 103}
 104
 105#[gpui::test]
 106fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
 107    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
 108    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
 109    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 110
 111    let events = Arc::new(RwLock::new(Vec::<Event>::new()));
 112    multibuffer.update(cx, |_, cx| {
 113        let events = events.clone();
 114        cx.subscribe(&multibuffer, move |_, _, event, _| {
 115            if let Event::Edited { .. } = event {
 116                events.write().push(event.clone())
 117            }
 118        })
 119        .detach();
 120    });
 121
 122    let subscription = multibuffer.update(cx, |multibuffer, cx| {
 123        let subscription = multibuffer.subscribe();
 124        multibuffer.push_excerpts(
 125            buffer_1.clone(),
 126            [ExcerptRange::new(Point::new(1, 2)..Point::new(2, 5))],
 127            cx,
 128        );
 129        assert_eq!(
 130            subscription.consume().into_inner(),
 131            [Edit {
 132                old: 0..0,
 133                new: 0..10
 134            }]
 135        );
 136
 137        multibuffer.push_excerpts(
 138            buffer_1.clone(),
 139            [ExcerptRange::new(Point::new(3, 3)..Point::new(4, 4))],
 140            cx,
 141        );
 142        multibuffer.push_excerpts(
 143            buffer_2.clone(),
 144            [ExcerptRange::new(Point::new(3, 1)..Point::new(3, 3))],
 145            cx,
 146        );
 147        assert_eq!(
 148            subscription.consume().into_inner(),
 149            [Edit {
 150                old: 10..10,
 151                new: 10..22
 152            }]
 153        );
 154
 155        subscription
 156    });
 157
 158    // Adding excerpts emits an edited event.
 159    assert_eq!(
 160        events.read().as_slice(),
 161        &[
 162            Event::Edited {
 163                edited_buffer: None,
 164            },
 165            Event::Edited {
 166                edited_buffer: None,
 167            },
 168            Event::Edited {
 169                edited_buffer: None,
 170            }
 171        ]
 172    );
 173
 174    let snapshot = multibuffer.read(cx).snapshot(cx);
 175    assert_eq!(
 176        snapshot.text(),
 177        indoc!(
 178            "
 179            bbbb
 180            ccccc
 181            ddd
 182            eeee
 183            jj"
 184        ),
 185    );
 186    assert_eq!(
 187        snapshot
 188            .row_infos(MultiBufferRow(0))
 189            .map(|info| info.buffer_row)
 190            .collect::<Vec<_>>(),
 191        [Some(1), Some(2), Some(3), Some(4), Some(3)]
 192    );
 193    assert_eq!(
 194        snapshot
 195            .row_infos(MultiBufferRow(2))
 196            .map(|info| info.buffer_row)
 197            .collect::<Vec<_>>(),
 198        [Some(3), Some(4), Some(3)]
 199    );
 200    assert_eq!(
 201        snapshot
 202            .row_infos(MultiBufferRow(4))
 203            .map(|info| info.buffer_row)
 204            .collect::<Vec<_>>(),
 205        [Some(3)]
 206    );
 207    assert!(
 208        snapshot
 209            .row_infos(MultiBufferRow(5))
 210            .map(|info| info.buffer_row)
 211            .collect::<Vec<_>>()
 212            .is_empty()
 213    );
 214
 215    assert_eq!(
 216        boundaries_in_range(Point::new(0, 0)..Point::new(4, 2), &snapshot),
 217        &[
 218            (MultiBufferRow(0), "bbbb\nccccc".to_string(), true),
 219            (MultiBufferRow(2), "ddd\neeee".to_string(), false),
 220            (MultiBufferRow(4), "jj".to_string(), true),
 221        ]
 222    );
 223    assert_eq!(
 224        boundaries_in_range(Point::new(0, 0)..Point::new(2, 0), &snapshot),
 225        &[(MultiBufferRow(0), "bbbb\nccccc".to_string(), true)]
 226    );
 227    assert_eq!(
 228        boundaries_in_range(Point::new(1, 0)..Point::new(1, 5), &snapshot),
 229        &[]
 230    );
 231    assert_eq!(
 232        boundaries_in_range(Point::new(1, 0)..Point::new(2, 0), &snapshot),
 233        &[]
 234    );
 235    assert_eq!(
 236        boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
 237        &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
 238    );
 239    assert_eq!(
 240        boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
 241        &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
 242    );
 243    assert_eq!(
 244        boundaries_in_range(Point::new(2, 0)..Point::new(3, 0), &snapshot),
 245        &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
 246    );
 247    assert_eq!(
 248        boundaries_in_range(Point::new(4, 0)..Point::new(4, 2), &snapshot),
 249        &[(MultiBufferRow(4), "jj".to_string(), true)]
 250    );
 251    assert_eq!(
 252        boundaries_in_range(Point::new(4, 2)..Point::new(4, 2), &snapshot),
 253        &[]
 254    );
 255
 256    buffer_1.update(cx, |buffer, cx| {
 257        let text = "\n";
 258        buffer.edit(
 259            [
 260                (Point::new(0, 0)..Point::new(0, 0), text),
 261                (Point::new(2, 1)..Point::new(2, 3), text),
 262            ],
 263            None,
 264            cx,
 265        );
 266    });
 267
 268    let snapshot = multibuffer.read(cx).snapshot(cx);
 269    assert_eq!(
 270        snapshot.text(),
 271        concat!(
 272            "bbbb\n", // Preserve newlines
 273            "c\n",    //
 274            "cc\n",   //
 275            "ddd\n",  //
 276            "eeee\n", //
 277            "jj"      //
 278        )
 279    );
 280
 281    assert_eq!(
 282        subscription.consume().into_inner(),
 283        [Edit {
 284            old: 6..8,
 285            new: 6..7
 286        }]
 287    );
 288
 289    let snapshot = multibuffer.read(cx).snapshot(cx);
 290    assert_eq!(
 291        snapshot.clip_point(Point::new(0, 5), Bias::Left),
 292        Point::new(0, 4)
 293    );
 294    assert_eq!(
 295        snapshot.clip_point(Point::new(0, 5), Bias::Right),
 296        Point::new(0, 4)
 297    );
 298    assert_eq!(
 299        snapshot.clip_point(Point::new(5, 1), Bias::Right),
 300        Point::new(5, 1)
 301    );
 302    assert_eq!(
 303        snapshot.clip_point(Point::new(5, 2), Bias::Right),
 304        Point::new(5, 2)
 305    );
 306    assert_eq!(
 307        snapshot.clip_point(Point::new(5, 3), Bias::Right),
 308        Point::new(5, 2)
 309    );
 310
 311    let snapshot = multibuffer.update(cx, |multibuffer, cx| {
 312        let (buffer_2_excerpt_id, _) =
 313            multibuffer.excerpts_for_buffer(buffer_2.read(cx).remote_id(), cx)[0].clone();
 314        multibuffer.remove_excerpts([buffer_2_excerpt_id], cx);
 315        multibuffer.snapshot(cx)
 316    });
 317
 318    assert_eq!(
 319        snapshot.text(),
 320        concat!(
 321            "bbbb\n", // Preserve newlines
 322            "c\n",    //
 323            "cc\n",   //
 324            "ddd\n",  //
 325            "eeee",   //
 326        )
 327    );
 328
 329    fn boundaries_in_range(
 330        range: Range<Point>,
 331        snapshot: &MultiBufferSnapshot,
 332    ) -> Vec<(MultiBufferRow, String, bool)> {
 333        snapshot
 334            .excerpt_boundaries_in_range(range)
 335            .map(|boundary| {
 336                let starts_new_buffer = boundary.starts_new_buffer();
 337                (
 338                    boundary.row,
 339                    boundary
 340                        .next
 341                        .buffer
 342                        .text_for_range(boundary.next.range.context)
 343                        .collect::<String>(),
 344                    starts_new_buffer,
 345                )
 346            })
 347            .collect::<Vec<_>>()
 348    }
 349}
 350
 351#[gpui::test]
 352fn test_diff_boundary_anchors(cx: &mut TestAppContext) {
 353    let base_text = "one\ntwo\nthree\n";
 354    let text = "one\nthree\n";
 355    let buffer = cx.new(|cx| Buffer::local(text, cx));
 356    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
 357    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 358    multibuffer.update(cx, |multibuffer, cx| multibuffer.add_diff(diff, cx));
 359
 360    let (before, after) = multibuffer.update(cx, |multibuffer, cx| {
 361        let before = multibuffer.snapshot(cx).anchor_before(Point::new(1, 0));
 362        let after = multibuffer.snapshot(cx).anchor_after(Point::new(1, 0));
 363        multibuffer.set_all_diff_hunks_expanded(cx);
 364        (before, after)
 365    });
 366    cx.run_until_parked();
 367
 368    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 369    let actual_text = snapshot.text();
 370    let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
 371    let actual_diff = format_diff(&actual_text, &actual_row_infos, &Default::default(), None);
 372    pretty_assertions::assert_eq!(
 373        actual_diff,
 374        indoc! {
 375            "  one
 376             - two
 377               three
 378             "
 379        },
 380    );
 381
 382    multibuffer.update(cx, |multibuffer, cx| {
 383        let snapshot = multibuffer.snapshot(cx);
 384        assert_eq!(before.to_point(&snapshot), Point::new(1, 0));
 385        assert_eq!(after.to_point(&snapshot), Point::new(2, 0));
 386        assert_eq!(
 387            vec![Point::new(1, 0), Point::new(2, 0),],
 388            snapshot.summaries_for_anchors::<Point, _>(&[before, after]),
 389        )
 390    })
 391}
 392
 393#[gpui::test]
 394fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
 395    let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n";
 396    let text = "one\nfour\nseven\n";
 397    let buffer = cx.new(|cx| Buffer::local(text, cx));
 398    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
 399    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 400    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
 401        (multibuffer.snapshot(cx), multibuffer.subscribe())
 402    });
 403
 404    multibuffer.update(cx, |multibuffer, cx| {
 405        multibuffer.add_diff(diff, cx);
 406        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
 407    });
 408
 409    assert_new_snapshot(
 410        &multibuffer,
 411        &mut snapshot,
 412        &mut subscription,
 413        cx,
 414        indoc! {
 415            "  one
 416             - two
 417             - three
 418               four
 419             - five
 420             - six
 421               seven
 422             - eight
 423            "
 424        },
 425    );
 426
 427    assert_eq!(
 428        snapshot
 429            .diff_hunks_in_range(Point::new(1, 0)..Point::MAX)
 430            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
 431            .collect::<Vec<_>>(),
 432        vec![1..3, 4..6, 7..8]
 433    );
 434
 435    assert_eq!(snapshot.diff_hunk_before(Point::new(1, 1)), None,);
 436    assert_eq!(
 437        snapshot.diff_hunk_before(Point::new(7, 0)),
 438        Some(MultiBufferRow(4))
 439    );
 440    assert_eq!(
 441        snapshot.diff_hunk_before(Point::new(4, 0)),
 442        Some(MultiBufferRow(1))
 443    );
 444
 445    multibuffer.update(cx, |multibuffer, cx| {
 446        multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
 447    });
 448
 449    assert_new_snapshot(
 450        &multibuffer,
 451        &mut snapshot,
 452        &mut subscription,
 453        cx,
 454        indoc! {
 455            "
 456            one
 457            four
 458            seven
 459            "
 460        },
 461    );
 462
 463    assert_eq!(
 464        snapshot.diff_hunk_before(Point::new(2, 0)),
 465        Some(MultiBufferRow(1)),
 466    );
 467    assert_eq!(
 468        snapshot.diff_hunk_before(Point::new(4, 0)),
 469        Some(MultiBufferRow(2))
 470    );
 471}
 472
 473#[gpui::test]
 474fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) {
 475    let base_text = "one\ntwo\nfour\nfive\nsix\nseven\n";
 476    let text = "one\ntwo\nTHREE\nfour\nfive\nseven\n";
 477    let buffer = cx.new(|cx| Buffer::local(text, cx));
 478    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
 479    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
 480
 481    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
 482        multibuffer.add_diff(diff.clone(), cx);
 483        (multibuffer.snapshot(cx), multibuffer.subscribe())
 484    });
 485
 486    cx.executor().run_until_parked();
 487    multibuffer.update(cx, |multibuffer, cx| {
 488        multibuffer.set_all_diff_hunks_expanded(cx);
 489    });
 490
 491    assert_new_snapshot(
 492        &multibuffer,
 493        &mut snapshot,
 494        &mut subscription,
 495        cx,
 496        indoc! {
 497            "
 498              one
 499              two
 500            + THREE
 501              four
 502              five
 503            - six
 504              seven
 505            "
 506        },
 507    );
 508
 509    // Insert a newline within an insertion hunk
 510    multibuffer.update(cx, |multibuffer, cx| {
 511        multibuffer.edit([(Point::new(2, 0)..Point::new(2, 0), "__\n__")], None, cx);
 512    });
 513    assert_new_snapshot(
 514        &multibuffer,
 515        &mut snapshot,
 516        &mut subscription,
 517        cx,
 518        indoc! {
 519            "
 520              one
 521              two
 522            + __
 523            + __THREE
 524              four
 525              five
 526            - six
 527              seven
 528            "
 529        },
 530    );
 531
 532    // Delete the newline before a deleted hunk.
 533    multibuffer.update(cx, |multibuffer, cx| {
 534        multibuffer.edit([(Point::new(5, 4)..Point::new(6, 0), "")], None, cx);
 535    });
 536    assert_new_snapshot(
 537        &multibuffer,
 538        &mut snapshot,
 539        &mut subscription,
 540        cx,
 541        indoc! {
 542            "
 543              one
 544              two
 545            + __
 546            + __THREE
 547              four
 548              fiveseven
 549            "
 550        },
 551    );
 552
 553    multibuffer.update(cx, |multibuffer, cx| multibuffer.undo(cx));
 554    assert_new_snapshot(
 555        &multibuffer,
 556        &mut snapshot,
 557        &mut subscription,
 558        cx,
 559        indoc! {
 560            "
 561              one
 562              two
 563            + __
 564            + __THREE
 565              four
 566              five
 567            - six
 568              seven
 569            "
 570        },
 571    );
 572
 573    // Cannot (yet) insert at the beginning of a deleted hunk.
 574    // (because it would put the newline in the wrong place)
 575    multibuffer.update(cx, |multibuffer, cx| {
 576        multibuffer.edit([(Point::new(6, 0)..Point::new(6, 0), "\n")], None, cx);
 577    });
 578    assert_new_snapshot(
 579        &multibuffer,
 580        &mut snapshot,
 581        &mut subscription,
 582        cx,
 583        indoc! {
 584            "
 585              one
 586              two
 587            + __
 588            + __THREE
 589              four
 590              five
 591            - six
 592              seven
 593            "
 594        },
 595    );
 596
 597    // Replace a range that ends in a deleted hunk.
 598    multibuffer.update(cx, |multibuffer, cx| {
 599        multibuffer.edit([(Point::new(5, 2)..Point::new(6, 2), "fty-")], None, cx);
 600    });
 601    assert_new_snapshot(
 602        &multibuffer,
 603        &mut snapshot,
 604        &mut subscription,
 605        cx,
 606        indoc! {
 607            "
 608              one
 609              two
 610            + __
 611            + __THREE
 612              four
 613              fifty-seven
 614            "
 615        },
 616    );
 617}
 618
 619/// This test checks for cases where an edit invalidates a diff hunk (by
 620/// invalidating the anchor at the start of the added region), but does not
 621/// cover the whole added region. The multibuffer should handle this by
 622/// extending the edit until the end of the added region.
 623#[gpui::test]
 624async fn test_edits_invalidating_diff_hunks(cx: &mut TestAppContext) {
 625    let base_text = "one\ntwo\nfour\nsix\n";
 626    let text = "one\ntwo\nTHREE\nfour\nFIVE\nsix\n";
 627    let buffer = cx.new(|cx| Buffer::local(text, cx));
 628    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
 629    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
 630
 631    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
 632        multibuffer.add_diff(diff.clone(), cx);
 633        multibuffer.set_all_diff_hunks_expanded(cx);
 634        (multibuffer.snapshot(cx), multibuffer.subscribe())
 635    });
 636
 637    assert_new_snapshot(
 638        &multibuffer,
 639        &mut snapshot,
 640        &mut subscription,
 641        cx,
 642        indoc! {
 643            "
 644              one
 645              two
 646            + THREE
 647              four
 648            + FIVE
 649              six
 650            "
 651        },
 652    );
 653
 654    // Make an edit to the buffer that invalidates the added hunk, but does not cover the whole added region.
 655    multibuffer.update(cx, |multibuffer, cx| {
 656        multibuffer.edit([(Point::new(1, 1)..Point::new(4, 2), "")], None, cx);
 657    });
 658
 659    let edits = assert_new_snapshot(
 660        &multibuffer,
 661        &mut snapshot,
 662        &mut subscription,
 663        cx,
 664        indoc! {
 665            "
 666              one
 667              tVE
 668              six
 669            "
 670        },
 671    );
 672
 673    // The old and new end of the edit are extended to cover the rest of the former diff hunk's added region.
 674    pretty_assertions::assert_eq!(
 675        edits,
 676        vec![Edit {
 677            old: 5..24,
 678            new: 5..8,
 679        }]
 680    );
 681}
 682
 683#[gpui::test]
 684fn test_excerpt_events(cx: &mut App) {
 685    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'a'), cx));
 686    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'm'), cx));
 687
 688    let leader_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 689    let follower_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 690    let follower_edit_event_count = Arc::new(RwLock::new(0));
 691
 692    follower_multibuffer.update(cx, |_, cx| {
 693        let follower_edit_event_count = follower_edit_event_count.clone();
 694        cx.subscribe(
 695            &leader_multibuffer,
 696            move |follower, _, event, cx| match event.clone() {
 697                Event::ExcerptsAdded {
 698                    buffer,
 699                    predecessor,
 700                    excerpts,
 701                } => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx),
 702                Event::ExcerptsRemoved { ids, .. } => follower.remove_excerpts(ids, cx),
 703                Event::Edited { .. } => {
 704                    *follower_edit_event_count.write() += 1;
 705                }
 706                _ => {}
 707            },
 708        )
 709        .detach();
 710    });
 711
 712    leader_multibuffer.update(cx, |leader, cx| {
 713        leader.push_excerpts(
 714            buffer_1.clone(),
 715            [ExcerptRange::new(0..8), ExcerptRange::new(12..16)],
 716            cx,
 717        );
 718        leader.insert_excerpts_after(
 719            leader.excerpt_ids()[0],
 720            buffer_2.clone(),
 721            [ExcerptRange::new(0..5), ExcerptRange::new(10..15)],
 722            cx,
 723        )
 724    });
 725    assert_eq!(
 726        leader_multibuffer.read(cx).snapshot(cx).text(),
 727        follower_multibuffer.read(cx).snapshot(cx).text(),
 728    );
 729    assert_eq!(*follower_edit_event_count.read(), 2);
 730
 731    leader_multibuffer.update(cx, |leader, cx| {
 732        let excerpt_ids = leader.excerpt_ids();
 733        leader.remove_excerpts([excerpt_ids[1], excerpt_ids[3]], cx);
 734    });
 735    assert_eq!(
 736        leader_multibuffer.read(cx).snapshot(cx).text(),
 737        follower_multibuffer.read(cx).snapshot(cx).text(),
 738    );
 739    assert_eq!(*follower_edit_event_count.read(), 3);
 740
 741    // Removing an empty set of excerpts is a noop.
 742    leader_multibuffer.update(cx, |leader, cx| {
 743        leader.remove_excerpts([], cx);
 744    });
 745    assert_eq!(
 746        leader_multibuffer.read(cx).snapshot(cx).text(),
 747        follower_multibuffer.read(cx).snapshot(cx).text(),
 748    );
 749    assert_eq!(*follower_edit_event_count.read(), 3);
 750
 751    // Adding an empty set of excerpts is a noop.
 752    leader_multibuffer.update(cx, |leader, cx| {
 753        leader.push_excerpts::<usize>(buffer_2.clone(), [], cx);
 754    });
 755    assert_eq!(
 756        leader_multibuffer.read(cx).snapshot(cx).text(),
 757        follower_multibuffer.read(cx).snapshot(cx).text(),
 758    );
 759    assert_eq!(*follower_edit_event_count.read(), 3);
 760
 761    leader_multibuffer.update(cx, |leader, cx| {
 762        leader.clear(cx);
 763    });
 764    assert_eq!(
 765        leader_multibuffer.read(cx).snapshot(cx).text(),
 766        follower_multibuffer.read(cx).snapshot(cx).text(),
 767    );
 768    assert_eq!(*follower_edit_event_count.read(), 4);
 769}
 770
 771#[gpui::test]
 772fn test_expand_excerpts(cx: &mut App) {
 773    let buffer = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
 774    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 775
 776    multibuffer.update(cx, |multibuffer, cx| {
 777        multibuffer.set_excerpts_for_path(
 778            PathKey::for_buffer(&buffer, cx),
 779            buffer,
 780            vec![
 781                // Note that in this test, this first excerpt
 782                // does not contain a new line
 783                Point::new(3, 2)..Point::new(3, 3),
 784                Point::new(7, 1)..Point::new(7, 3),
 785                Point::new(15, 0)..Point::new(15, 0),
 786            ],
 787            1,
 788            cx,
 789        )
 790    });
 791
 792    let snapshot = multibuffer.read(cx).snapshot(cx);
 793
 794    assert_eq!(
 795        snapshot.text(),
 796        concat!(
 797            "ccc\n", //
 798            "ddd\n", //
 799            "eee",   //
 800            "\n",    // End of excerpt
 801            "ggg\n", //
 802            "hhh\n", //
 803            "iii",   //
 804            "\n",    // End of excerpt
 805            "ooo\n", //
 806            "ppp\n", //
 807            "qqq",   // End of excerpt
 808        )
 809    );
 810    drop(snapshot);
 811
 812    multibuffer.update(cx, |multibuffer, cx| {
 813        let line_zero = multibuffer.snapshot(cx).anchor_before(Point::new(0, 0));
 814        multibuffer.expand_excerpts(
 815            multibuffer.excerpt_ids(),
 816            1,
 817            ExpandExcerptDirection::UpAndDown,
 818            cx,
 819        );
 820        let snapshot = multibuffer.snapshot(cx);
 821        let line_two = snapshot.anchor_before(Point::new(2, 0));
 822        assert_eq!(line_two.cmp(&line_zero, &snapshot), cmp::Ordering::Greater);
 823    });
 824
 825    let snapshot = multibuffer.read(cx).snapshot(cx);
 826
 827    assert_eq!(
 828        snapshot.text(),
 829        concat!(
 830            "bbb\n", //
 831            "ccc\n", //
 832            "ddd\n", //
 833            "eee\n", //
 834            "fff\n", //
 835            "ggg\n", //
 836            "hhh\n", //
 837            "iii\n", //
 838            "jjj\n", // End of excerpt
 839            "nnn\n", //
 840            "ooo\n", //
 841            "ppp\n", //
 842            "qqq\n", //
 843            "rrr",   // End of excerpt
 844        )
 845    );
 846}
 847
 848#[gpui::test(iterations = 100)]
 849async fn test_set_anchored_excerpts_for_path(cx: &mut TestAppContext) {
 850    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
 851    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(15, 4, 'a'), cx));
 852    let snapshot_1 = buffer_1.update(cx, |buffer, _| buffer.snapshot());
 853    let snapshot_2 = buffer_2.update(cx, |buffer, _| buffer.snapshot());
 854    let ranges_1 = vec![
 855        snapshot_1.anchor_before(Point::new(3, 2))..snapshot_1.anchor_before(Point::new(4, 2)),
 856        snapshot_1.anchor_before(Point::new(7, 1))..snapshot_1.anchor_before(Point::new(7, 3)),
 857        snapshot_1.anchor_before(Point::new(15, 0))..snapshot_1.anchor_before(Point::new(15, 0)),
 858    ];
 859    let ranges_2 = vec![
 860        snapshot_2.anchor_before(Point::new(2, 1))..snapshot_2.anchor_before(Point::new(3, 1)),
 861        snapshot_2.anchor_before(Point::new(10, 0))..snapshot_2.anchor_before(Point::new(10, 2)),
 862    ];
 863
 864    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 865    let anchor_ranges_1 = multibuffer
 866        .update(cx, |multibuffer, cx| {
 867            multibuffer.set_anchored_excerpts_for_path(
 868                PathKey::for_buffer(&buffer_1, cx),
 869                buffer_1.clone(),
 870                ranges_1,
 871                2,
 872                cx,
 873            )
 874        })
 875        .await;
 876    let snapshot_1 = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 877    assert_eq!(
 878        anchor_ranges_1
 879            .iter()
 880            .map(|range| range.to_point(&snapshot_1))
 881            .collect::<Vec<_>>(),
 882        vec![
 883            Point::new(2, 2)..Point::new(3, 2),
 884            Point::new(6, 1)..Point::new(6, 3),
 885            Point::new(11, 0)..Point::new(11, 0),
 886        ]
 887    );
 888    let anchor_ranges_2 = multibuffer
 889        .update(cx, |multibuffer, cx| {
 890            multibuffer.set_anchored_excerpts_for_path(
 891                PathKey::for_buffer(&buffer_2, cx),
 892                buffer_2.clone(),
 893                ranges_2,
 894                2,
 895                cx,
 896            )
 897        })
 898        .await;
 899    let snapshot_2 = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 900    assert_eq!(
 901        anchor_ranges_2
 902            .iter()
 903            .map(|range| range.to_point(&snapshot_2))
 904            .collect::<Vec<_>>(),
 905        vec![
 906            Point::new(16, 1)..Point::new(17, 1),
 907            Point::new(22, 0)..Point::new(22, 2)
 908        ]
 909    );
 910
 911    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 912    assert_eq!(
 913        snapshot.text(),
 914        concat!(
 915            "bbb\n", // buffer_1
 916            "ccc\n", //
 917            "ddd\n", // <-- excerpt 1
 918            "eee\n", // <-- excerpt 1
 919            "fff\n", //
 920            "ggg\n", //
 921            "hhh\n", // <-- excerpt 2
 922            "iii\n", //
 923            "jjj\n", //
 924            //
 925            "nnn\n", //
 926            "ooo\n", //
 927            "ppp\n", // <-- excerpt 3
 928            "qqq\n", //
 929            "rrr\n", //
 930            //
 931            "aaaa\n", // buffer 2
 932            "bbbb\n", //
 933            "cccc\n", // <-- excerpt 4
 934            "dddd\n", // <-- excerpt 4
 935            "eeee\n", //
 936            "ffff\n", //
 937            //
 938            "iiii\n", //
 939            "jjjj\n", //
 940            "kkkk\n", // <-- excerpt 5
 941            "llll\n", //
 942            "mmmm",   //
 943        )
 944    );
 945}
 946
 947#[gpui::test]
 948fn test_empty_multibuffer(cx: &mut App) {
 949    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 950
 951    let snapshot = multibuffer.read(cx).snapshot(cx);
 952    assert_eq!(snapshot.text(), "");
 953    assert_eq!(
 954        snapshot
 955            .row_infos(MultiBufferRow(0))
 956            .map(|info| info.buffer_row)
 957            .collect::<Vec<_>>(),
 958        &[Some(0)]
 959    );
 960    assert!(
 961        snapshot
 962            .row_infos(MultiBufferRow(1))
 963            .map(|info| info.buffer_row)
 964            .collect::<Vec<_>>()
 965            .is_empty(),
 966    );
 967}
 968
 969#[gpui::test]
 970fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
 971    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 972    let buffer = cx.new(|cx| Buffer::local("", cx));
 973    let base_text = "a\nb\nc";
 974
 975    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
 976    multibuffer.update(cx, |multibuffer, cx| {
 977        multibuffer.push_excerpts(buffer.clone(), [ExcerptRange::new(0..0)], cx);
 978        multibuffer.set_all_diff_hunks_expanded(cx);
 979        multibuffer.add_diff(diff.clone(), cx);
 980    });
 981    cx.run_until_parked();
 982
 983    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 984    assert_eq!(snapshot.text(), "a\nb\nc\n");
 985
 986    let hunk = snapshot
 987        .diff_hunks_in_range(Point::new(1, 1)..Point::new(1, 1))
 988        .next()
 989        .unwrap();
 990
 991    assert_eq!(hunk.diff_base_byte_range.start, 0);
 992
 993    let buf2 = cx.new(|cx| Buffer::local("X", cx));
 994    multibuffer.update(cx, |multibuffer, cx| {
 995        multibuffer.push_excerpts(buf2, [ExcerptRange::new(0..1)], cx);
 996    });
 997
 998    buffer.update(cx, |buffer, cx| {
 999        buffer.edit([(0..0, "a\nb\nc")], None, cx);
1000        diff.update(cx, |diff, cx| {
1001            diff.recalculate_diff_sync(buffer.snapshot().text, cx);
1002        });
1003        assert_eq!(buffer.text(), "a\nb\nc")
1004    });
1005    cx.run_until_parked();
1006
1007    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1008    assert_eq!(snapshot.text(), "a\nb\nc\nX");
1009
1010    buffer.update(cx, |buffer, cx| {
1011        buffer.undo(cx);
1012        diff.update(cx, |diff, cx| {
1013            diff.recalculate_diff_sync(buffer.snapshot().text, cx);
1014        });
1015        assert_eq!(buffer.text(), "")
1016    });
1017    cx.run_until_parked();
1018
1019    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1020    assert_eq!(snapshot.text(), "a\nb\nc\n\nX");
1021}
1022
1023#[gpui::test]
1024fn test_singleton_multibuffer_anchors(cx: &mut App) {
1025    let buffer = cx.new(|cx| Buffer::local("abcd", cx));
1026    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
1027    let old_snapshot = multibuffer.read(cx).snapshot(cx);
1028    buffer.update(cx, |buffer, cx| {
1029        buffer.edit([(0..0, "X")], None, cx);
1030        buffer.edit([(5..5, "Y")], None, cx);
1031    });
1032    let new_snapshot = multibuffer.read(cx).snapshot(cx);
1033
1034    assert_eq!(old_snapshot.text(), "abcd");
1035    assert_eq!(new_snapshot.text(), "XabcdY");
1036
1037    assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1038    assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1039    assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5);
1040    assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6);
1041}
1042
1043#[gpui::test]
1044fn test_multibuffer_anchors(cx: &mut App) {
1045    let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1046    let buffer_2 = cx.new(|cx| Buffer::local("efghi", cx));
1047    let multibuffer = cx.new(|cx| {
1048        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1049        multibuffer.push_excerpts(buffer_1.clone(), [ExcerptRange::new(0..4)], cx);
1050        multibuffer.push_excerpts(buffer_2.clone(), [ExcerptRange::new(0..5)], cx);
1051        multibuffer
1052    });
1053    let old_snapshot = multibuffer.read(cx).snapshot(cx);
1054
1055    assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0);
1056    assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0);
1057    assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1058    assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1059    assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1060    assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1061
1062    buffer_1.update(cx, |buffer, cx| {
1063        buffer.edit([(0..0, "W")], None, cx);
1064        buffer.edit([(5..5, "X")], None, cx);
1065    });
1066    buffer_2.update(cx, |buffer, cx| {
1067        buffer.edit([(0..0, "Y")], None, cx);
1068        buffer.edit([(6..6, "Z")], None, cx);
1069    });
1070    let new_snapshot = multibuffer.read(cx).snapshot(cx);
1071
1072    assert_eq!(old_snapshot.text(), "abcd\nefghi");
1073    assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
1074
1075    assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1076    assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1077    assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2);
1078    assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2);
1079    assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3);
1080    assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3);
1081    assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7);
1082    assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8);
1083    assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13);
1084    assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14);
1085}
1086
1087#[gpui::test]
1088fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
1089    let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1090    let buffer_2 = cx.new(|cx| Buffer::local("ABCDEFGHIJKLMNOP", cx));
1091    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1092
1093    // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
1094    // Add an excerpt from buffer 1 that spans this new insertion.
1095    buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx));
1096    let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| {
1097        multibuffer
1098            .push_excerpts(buffer_1.clone(), [ExcerptRange::new(0..7)], cx)
1099            .pop()
1100            .unwrap()
1101    });
1102
1103    let snapshot_1 = multibuffer.read(cx).snapshot(cx);
1104    assert_eq!(snapshot_1.text(), "abcd123");
1105
1106    // Replace the buffer 1 excerpt with new excerpts from buffer 2.
1107    let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
1108        multibuffer.remove_excerpts([excerpt_id_1], cx);
1109        let mut ids = multibuffer
1110            .push_excerpts(
1111                buffer_2.clone(),
1112                [
1113                    ExcerptRange::new(0..4),
1114                    ExcerptRange::new(6..10),
1115                    ExcerptRange::new(12..16),
1116                ],
1117                cx,
1118            )
1119            .into_iter();
1120        (ids.next().unwrap(), ids.next().unwrap())
1121    });
1122    let snapshot_2 = multibuffer.read(cx).snapshot(cx);
1123    assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP");
1124
1125    // The old excerpt id doesn't get reused.
1126    assert_ne!(excerpt_id_2, excerpt_id_1);
1127
1128    // Resolve some anchors from the previous snapshot in the new snapshot.
1129    // The current excerpts are from a different buffer, so we don't attempt to
1130    // resolve the old text anchor in the new buffer.
1131    assert_eq!(
1132        snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)),
1133        0
1134    );
1135    assert_eq!(
1136        snapshot_2.summaries_for_anchors::<usize, _>(&[
1137            snapshot_1.anchor_before(2),
1138            snapshot_1.anchor_after(3)
1139        ]),
1140        vec![0, 0]
1141    );
1142
1143    // Refresh anchors from the old snapshot. The return value indicates that both
1144    // anchors lost their original excerpt.
1145    let refresh =
1146        snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]);
1147    assert_eq!(
1148        refresh,
1149        &[
1150            (0, snapshot_2.anchor_before(0), false),
1151            (1, snapshot_2.anchor_after(0), false),
1152        ]
1153    );
1154
1155    // Replace the middle excerpt with a smaller excerpt in buffer 2,
1156    // that intersects the old excerpt.
1157    let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| {
1158        multibuffer.remove_excerpts([excerpt_id_3], cx);
1159        multibuffer
1160            .insert_excerpts_after(
1161                excerpt_id_2,
1162                buffer_2.clone(),
1163                [ExcerptRange::new(5..8)],
1164                cx,
1165            )
1166            .pop()
1167            .unwrap()
1168    });
1169
1170    let snapshot_3 = multibuffer.read(cx).snapshot(cx);
1171    assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP");
1172    assert_ne!(excerpt_id_5, excerpt_id_3);
1173
1174    // Resolve some anchors from the previous snapshot in the new snapshot.
1175    // The third anchor can't be resolved, since its excerpt has been removed,
1176    // so it resolves to the same position as its predecessor.
1177    let anchors = [
1178        snapshot_2.anchor_before(0),
1179        snapshot_2.anchor_after(2),
1180        snapshot_2.anchor_after(6),
1181        snapshot_2.anchor_after(14),
1182    ];
1183    assert_eq!(
1184        snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
1185        &[0, 2, 9, 13]
1186    );
1187
1188    let new_anchors = snapshot_3.refresh_anchors(&anchors);
1189    assert_eq!(
1190        new_anchors.iter().map(|a| (a.0, a.2)).collect::<Vec<_>>(),
1191        &[(0, true), (1, true), (2, true), (3, true)]
1192    );
1193    assert_eq!(
1194        snapshot_3.summaries_for_anchors::<usize, _>(new_anchors.iter().map(|a| &a.1)),
1195        &[0, 2, 7, 13]
1196    );
1197}
1198
1199#[gpui::test]
1200fn test_basic_diff_hunks(cx: &mut TestAppContext) {
1201    let text = indoc!(
1202        "
1203        ZERO
1204        one
1205        TWO
1206        three
1207        six
1208        "
1209    );
1210    let base_text = indoc!(
1211        "
1212        one
1213        two
1214        three
1215        four
1216        five
1217        six
1218        "
1219    );
1220
1221    let buffer = cx.new(|cx| Buffer::local(text, cx));
1222    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
1223    cx.run_until_parked();
1224
1225    let multibuffer = cx.new(|cx| {
1226        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1227        multibuffer.add_diff(diff.clone(), cx);
1228        multibuffer
1229    });
1230
1231    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1232        (multibuffer.snapshot(cx), multibuffer.subscribe())
1233    });
1234    assert_eq!(
1235        snapshot.text(),
1236        indoc!(
1237            "
1238            ZERO
1239            one
1240            TWO
1241            three
1242            six
1243            "
1244        ),
1245    );
1246
1247    multibuffer.update(cx, |multibuffer, cx| {
1248        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1249    });
1250
1251    assert_new_snapshot(
1252        &multibuffer,
1253        &mut snapshot,
1254        &mut subscription,
1255        cx,
1256        indoc!(
1257            "
1258            + ZERO
1259              one
1260            - two
1261            + TWO
1262              three
1263            - four
1264            - five
1265              six
1266            "
1267        ),
1268    );
1269
1270    assert_eq!(
1271        snapshot
1272            .row_infos(MultiBufferRow(0))
1273            .map(|info| (info.buffer_row, info.diff_status))
1274            .collect::<Vec<_>>(),
1275        vec![
1276            (Some(0), Some(DiffHunkStatus::added_none())),
1277            (Some(1), None),
1278            (Some(1), Some(DiffHunkStatus::deleted_none())),
1279            (Some(2), Some(DiffHunkStatus::added_none())),
1280            (Some(3), None),
1281            (Some(3), Some(DiffHunkStatus::deleted_none())),
1282            (Some(4), Some(DiffHunkStatus::deleted_none())),
1283            (Some(4), None),
1284            (Some(5), None)
1285        ]
1286    );
1287
1288    assert_chunks_in_ranges(&snapshot);
1289    assert_consistent_line_numbers(&snapshot);
1290    assert_position_translation(&snapshot);
1291    assert_line_indents(&snapshot);
1292
1293    multibuffer.update(cx, |multibuffer, cx| {
1294        multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
1295    });
1296    assert_new_snapshot(
1297        &multibuffer,
1298        &mut snapshot,
1299        &mut subscription,
1300        cx,
1301        indoc!(
1302            "
1303            ZERO
1304            one
1305            TWO
1306            three
1307            six
1308            "
1309        ),
1310    );
1311
1312    assert_chunks_in_ranges(&snapshot);
1313    assert_consistent_line_numbers(&snapshot);
1314    assert_position_translation(&snapshot);
1315    assert_line_indents(&snapshot);
1316
1317    // Expand the first diff hunk
1318    multibuffer.update(cx, |multibuffer, cx| {
1319        let position = multibuffer.read(cx).anchor_before(Point::new(2, 2));
1320        multibuffer.expand_diff_hunks(vec![position..position], cx)
1321    });
1322    assert_new_snapshot(
1323        &multibuffer,
1324        &mut snapshot,
1325        &mut subscription,
1326        cx,
1327        indoc!(
1328            "
1329              ZERO
1330              one
1331            - two
1332            + TWO
1333              three
1334              six
1335            "
1336        ),
1337    );
1338
1339    // Expand the second diff hunk
1340    multibuffer.update(cx, |multibuffer, cx| {
1341        let start = multibuffer.read(cx).anchor_before(Point::new(4, 0));
1342        let end = multibuffer.read(cx).anchor_before(Point::new(5, 0));
1343        multibuffer.expand_diff_hunks(vec![start..end], cx)
1344    });
1345    assert_new_snapshot(
1346        &multibuffer,
1347        &mut snapshot,
1348        &mut subscription,
1349        cx,
1350        indoc!(
1351            "
1352              ZERO
1353              one
1354            - two
1355            + TWO
1356              three
1357            - four
1358            - five
1359              six
1360            "
1361        ),
1362    );
1363
1364    assert_chunks_in_ranges(&snapshot);
1365    assert_consistent_line_numbers(&snapshot);
1366    assert_position_translation(&snapshot);
1367    assert_line_indents(&snapshot);
1368
1369    // Edit the buffer before the first hunk
1370    buffer.update(cx, |buffer, cx| {
1371        buffer.edit_via_marked_text(
1372            indoc!(
1373                "
1374                ZERO
1375                one« hundred
1376                  thousand»
1377                TWO
1378                three
1379                six
1380                "
1381            ),
1382            None,
1383            cx,
1384        );
1385    });
1386    assert_new_snapshot(
1387        &multibuffer,
1388        &mut snapshot,
1389        &mut subscription,
1390        cx,
1391        indoc!(
1392            "
1393              ZERO
1394              one hundred
1395                thousand
1396            - two
1397            + TWO
1398              three
1399            - four
1400            - five
1401              six
1402            "
1403        ),
1404    );
1405
1406    assert_chunks_in_ranges(&snapshot);
1407    assert_consistent_line_numbers(&snapshot);
1408    assert_position_translation(&snapshot);
1409    assert_line_indents(&snapshot);
1410
1411    // Recalculate the diff, changing the first diff hunk.
1412    diff.update(cx, |diff, cx| {
1413        diff.recalculate_diff_sync(buffer.read(cx).text_snapshot(), cx);
1414    });
1415    cx.run_until_parked();
1416    assert_new_snapshot(
1417        &multibuffer,
1418        &mut snapshot,
1419        &mut subscription,
1420        cx,
1421        indoc!(
1422            "
1423              ZERO
1424              one hundred
1425                thousand
1426              TWO
1427              three
1428            - four
1429            - five
1430              six
1431            "
1432        ),
1433    );
1434
1435    assert_eq!(
1436        snapshot
1437            .diff_hunks_in_range(0..snapshot.len())
1438            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1439            .collect::<Vec<_>>(),
1440        &[0..4, 5..7]
1441    );
1442}
1443
1444#[gpui::test]
1445fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
1446    let text = indoc!(
1447        "
1448        one
1449        TWO
1450        THREE
1451        four
1452        FIVE
1453        six
1454        "
1455    );
1456    let base_text = indoc!(
1457        "
1458        one
1459        four
1460        five
1461        six
1462        "
1463    );
1464
1465    let buffer = cx.new(|cx| Buffer::local(text, cx));
1466    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
1467    cx.run_until_parked();
1468
1469    let multibuffer = cx.new(|cx| {
1470        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1471        multibuffer.add_diff(diff.clone(), cx);
1472        multibuffer
1473    });
1474
1475    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1476        (multibuffer.snapshot(cx), multibuffer.subscribe())
1477    });
1478
1479    multibuffer.update(cx, |multibuffer, cx| {
1480        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1481    });
1482
1483    assert_new_snapshot(
1484        &multibuffer,
1485        &mut snapshot,
1486        &mut subscription,
1487        cx,
1488        indoc!(
1489            "
1490              one
1491            + TWO
1492            + THREE
1493              four
1494            - five
1495            + FIVE
1496              six
1497            "
1498        ),
1499    );
1500
1501    // Regression test: expanding diff hunks that are already expanded should not change anything.
1502    multibuffer.update(cx, |multibuffer, cx| {
1503        multibuffer.expand_diff_hunks(
1504            vec![
1505                snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_before(Point::new(2, 0)),
1506            ],
1507            cx,
1508        );
1509    });
1510
1511    assert_new_snapshot(
1512        &multibuffer,
1513        &mut snapshot,
1514        &mut subscription,
1515        cx,
1516        indoc!(
1517            "
1518              one
1519            + TWO
1520            + THREE
1521              four
1522            - five
1523            + FIVE
1524              six
1525            "
1526        ),
1527    );
1528
1529    // Now collapse all diff hunks
1530    multibuffer.update(cx, |multibuffer, cx| {
1531        multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1532    });
1533
1534    assert_new_snapshot(
1535        &multibuffer,
1536        &mut snapshot,
1537        &mut subscription,
1538        cx,
1539        indoc!(
1540            "
1541            one
1542            TWO
1543            THREE
1544            four
1545            FIVE
1546            six
1547            "
1548        ),
1549    );
1550
1551    // Expand the hunks again, but this time provide two ranges that are both within the same hunk
1552    // Target the first hunk which is between "one" and "four"
1553    multibuffer.update(cx, |multibuffer, cx| {
1554        multibuffer.expand_diff_hunks(
1555            vec![
1556                snapshot.anchor_before(Point::new(4, 0))..snapshot.anchor_before(Point::new(4, 0)),
1557                snapshot.anchor_before(Point::new(4, 2))..snapshot.anchor_before(Point::new(4, 2)),
1558            ],
1559            cx,
1560        );
1561    });
1562    assert_new_snapshot(
1563        &multibuffer,
1564        &mut snapshot,
1565        &mut subscription,
1566        cx,
1567        indoc!(
1568            "
1569              one
1570              TWO
1571              THREE
1572              four
1573            - five
1574            + FIVE
1575              six
1576            "
1577        ),
1578    );
1579}
1580
1581#[gpui::test]
1582fn test_set_excerpts_for_buffer_ordering(cx: &mut TestAppContext) {
1583    let buf1 = cx.new(|cx| {
1584        Buffer::local(
1585            indoc! {
1586            "zero
1587            one
1588            two
1589            two.five
1590            three
1591            four
1592            five
1593            six
1594            seven
1595            eight
1596            nine
1597            ten
1598            eleven
1599            ",
1600            },
1601            cx,
1602        )
1603    });
1604    let path1: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
1605
1606    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1607    multibuffer.update(cx, |multibuffer, cx| {
1608        multibuffer.set_excerpts_for_path(
1609            path1.clone(),
1610            buf1.clone(),
1611            vec![
1612                Point::row_range(1..2),
1613                Point::row_range(6..7),
1614                Point::row_range(11..12),
1615            ],
1616            1,
1617            cx,
1618        );
1619    });
1620
1621    assert_excerpts_match(
1622        &multibuffer,
1623        cx,
1624        indoc! {
1625            "-----
1626            zero
1627            one
1628            two
1629            two.five
1630            -----
1631            four
1632            five
1633            six
1634            seven
1635            -----
1636            nine
1637            ten
1638            eleven
1639            "
1640        },
1641    );
1642
1643    buf1.update(cx, |buffer, cx| buffer.edit([(0..5, "")], None, cx));
1644
1645    multibuffer.update(cx, |multibuffer, cx| {
1646        multibuffer.set_excerpts_for_path(
1647            path1.clone(),
1648            buf1.clone(),
1649            vec![
1650                Point::row_range(0..3),
1651                Point::row_range(5..7),
1652                Point::row_range(10..11),
1653            ],
1654            1,
1655            cx,
1656        );
1657    });
1658
1659    assert_excerpts_match(
1660        &multibuffer,
1661        cx,
1662        indoc! {
1663            "-----
1664             one
1665             two
1666             two.five
1667             three
1668             four
1669             five
1670             six
1671             seven
1672             eight
1673             nine
1674             ten
1675             eleven
1676            "
1677        },
1678    );
1679}
1680
1681#[gpui::test]
1682fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
1683    let buf1 = cx.new(|cx| {
1684        Buffer::local(
1685            indoc! {
1686            "zero
1687            one
1688            two
1689            three
1690            four
1691            five
1692            six
1693            seven
1694            ",
1695            },
1696            cx,
1697        )
1698    });
1699    let path1: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
1700    let buf2 = cx.new(|cx| {
1701        Buffer::local(
1702            indoc! {
1703            "000
1704            111
1705            222
1706            333
1707            444
1708            555
1709            666
1710            777
1711            888
1712            999
1713            "
1714            },
1715            cx,
1716        )
1717    });
1718    let path2 = PathKey::with_sort_prefix(1, rel_path("root").into_arc());
1719
1720    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1721    multibuffer.update(cx, |multibuffer, cx| {
1722        multibuffer.set_excerpts_for_path(
1723            path1.clone(),
1724            buf1.clone(),
1725            vec![Point::row_range(0..1)],
1726            2,
1727            cx,
1728        );
1729    });
1730
1731    assert_excerpts_match(
1732        &multibuffer,
1733        cx,
1734        indoc! {
1735        "-----
1736        zero
1737        one
1738        two
1739        three
1740        "
1741        },
1742    );
1743
1744    multibuffer.update(cx, |multibuffer, cx| {
1745        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1746    });
1747
1748    assert_excerpts_match(&multibuffer, cx, "");
1749
1750    multibuffer.update(cx, |multibuffer, cx| {
1751        multibuffer.set_excerpts_for_path(
1752            path1.clone(),
1753            buf1.clone(),
1754            vec![Point::row_range(0..1), Point::row_range(7..8)],
1755            2,
1756            cx,
1757        );
1758    });
1759
1760    assert_excerpts_match(
1761        &multibuffer,
1762        cx,
1763        indoc! {"-----
1764                zero
1765                one
1766                two
1767                three
1768                -----
1769                five
1770                six
1771                seven
1772                "},
1773    );
1774
1775    multibuffer.update(cx, |multibuffer, cx| {
1776        multibuffer.set_excerpts_for_path(
1777            path1.clone(),
1778            buf1.clone(),
1779            vec![Point::row_range(0..1), Point::row_range(5..6)],
1780            2,
1781            cx,
1782        );
1783    });
1784
1785    assert_excerpts_match(
1786        &multibuffer,
1787        cx,
1788        indoc! {"-----
1789                    zero
1790                    one
1791                    two
1792                    three
1793                    four
1794                    five
1795                    six
1796                    seven
1797                    "},
1798    );
1799
1800    multibuffer.update(cx, |multibuffer, cx| {
1801        multibuffer.set_excerpts_for_path(
1802            path2.clone(),
1803            buf2.clone(),
1804            vec![Point::row_range(2..3)],
1805            2,
1806            cx,
1807        );
1808    });
1809
1810    assert_excerpts_match(
1811        &multibuffer,
1812        cx,
1813        indoc! {"-----
1814                zero
1815                one
1816                two
1817                three
1818                four
1819                five
1820                six
1821                seven
1822                -----
1823                000
1824                111
1825                222
1826                333
1827                444
1828                555
1829                "},
1830    );
1831
1832    multibuffer.update(cx, |multibuffer, cx| {
1833        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1834    });
1835
1836    multibuffer.update(cx, |multibuffer, cx| {
1837        multibuffer.set_excerpts_for_path(
1838            path1.clone(),
1839            buf1.clone(),
1840            vec![Point::row_range(3..4)],
1841            2,
1842            cx,
1843        );
1844    });
1845
1846    assert_excerpts_match(
1847        &multibuffer,
1848        cx,
1849        indoc! {"-----
1850                one
1851                two
1852                three
1853                four
1854                five
1855                six
1856                -----
1857                000
1858                111
1859                222
1860                333
1861                444
1862                555
1863                "},
1864    );
1865
1866    multibuffer.update(cx, |multibuffer, cx| {
1867        multibuffer.set_excerpts_for_path(
1868            path1.clone(),
1869            buf1.clone(),
1870            vec![Point::row_range(3..4)],
1871            2,
1872            cx,
1873        );
1874    });
1875}
1876
1877#[gpui::test]
1878fn test_set_excerpts_for_buffer_rename(cx: &mut TestAppContext) {
1879    let buf1 = cx.new(|cx| {
1880        Buffer::local(
1881            indoc! {
1882            "zero
1883            one
1884            two
1885            three
1886            four
1887            five
1888            six
1889            seven
1890            ",
1891            },
1892            cx,
1893        )
1894    });
1895    let path: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
1896    let buf2 = cx.new(|cx| {
1897        Buffer::local(
1898            indoc! {
1899            "000
1900            111
1901            222
1902            333
1903            "
1904            },
1905            cx,
1906        )
1907    });
1908
1909    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1910    multibuffer.update(cx, |multibuffer, cx| {
1911        multibuffer.set_excerpts_for_path(
1912            path.clone(),
1913            buf1.clone(),
1914            vec![Point::row_range(1..1), Point::row_range(4..5)],
1915            1,
1916            cx,
1917        );
1918    });
1919
1920    assert_excerpts_match(
1921        &multibuffer,
1922        cx,
1923        indoc! {
1924        "-----
1925        zero
1926        one
1927        two
1928        three
1929        four
1930        five
1931        six
1932        "
1933        },
1934    );
1935
1936    multibuffer.update(cx, |multibuffer, cx| {
1937        multibuffer.set_excerpts_for_path(
1938            path.clone(),
1939            buf2.clone(),
1940            vec![Point::row_range(0..1)],
1941            2,
1942            cx,
1943        );
1944    });
1945
1946    assert_excerpts_match(
1947        &multibuffer,
1948        cx,
1949        indoc! {"-----
1950                000
1951                111
1952                222
1953                333
1954                "},
1955    );
1956}
1957
1958#[gpui::test]
1959fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
1960    let base_text_1 = indoc!(
1961        "
1962        one
1963        two
1964            three
1965        four
1966        five
1967        six
1968        "
1969    );
1970    let text_1 = indoc!(
1971        "
1972        ZERO
1973        one
1974        TWO
1975            three
1976        six
1977        "
1978    );
1979    let base_text_2 = indoc!(
1980        "
1981        seven
1982          eight
1983        nine
1984        ten
1985        eleven
1986        twelve
1987        "
1988    );
1989    let text_2 = indoc!(
1990        "
1991          eight
1992        nine
1993        eleven
1994        THIRTEEN
1995        FOURTEEN
1996        "
1997    );
1998
1999    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
2000    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
2001    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_1, &buffer_1, cx));
2002    let diff_2 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_2, &buffer_2, cx));
2003    cx.run_until_parked();
2004
2005    let multibuffer = cx.new(|cx| {
2006        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
2007        multibuffer.push_excerpts(
2008            buffer_1.clone(),
2009            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
2010            cx,
2011        );
2012        multibuffer.push_excerpts(
2013            buffer_2.clone(),
2014            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
2015            cx,
2016        );
2017        multibuffer.add_diff(diff_1.clone(), cx);
2018        multibuffer.add_diff(diff_2.clone(), cx);
2019        multibuffer
2020    });
2021
2022    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
2023        (multibuffer.snapshot(cx), multibuffer.subscribe())
2024    });
2025    assert_eq!(
2026        snapshot.text(),
2027        indoc!(
2028            "
2029            ZERO
2030            one
2031            TWO
2032                three
2033            six
2034
2035              eight
2036            nine
2037            eleven
2038            THIRTEEN
2039            FOURTEEN
2040            "
2041        ),
2042    );
2043
2044    multibuffer.update(cx, |multibuffer, cx| {
2045        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
2046    });
2047
2048    assert_new_snapshot(
2049        &multibuffer,
2050        &mut snapshot,
2051        &mut subscription,
2052        cx,
2053        indoc!(
2054            "
2055            + ZERO
2056              one
2057            - two
2058            + TWO
2059                  three
2060            - four
2061            - five
2062              six
2063
2064            - seven
2065                eight
2066              nine
2067            - ten
2068              eleven
2069            - twelve
2070            + THIRTEEN
2071            + FOURTEEN
2072            "
2073        ),
2074    );
2075
2076    let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
2077    let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
2078    let base_id_1 = diff_1.read_with(cx, |diff, _| diff.base_text().remote_id());
2079    let base_id_2 = diff_2.read_with(cx, |diff, _| diff.base_text().remote_id());
2080
2081    let buffer_lines = (0..=snapshot.max_row().0)
2082        .map(|row| {
2083            let (buffer, range) = snapshot.buffer_line_for_row(MultiBufferRow(row))?;
2084            Some((
2085                buffer.remote_id(),
2086                buffer.text_for_range(range).collect::<String>(),
2087            ))
2088        })
2089        .collect::<Vec<_>>();
2090    pretty_assertions::assert_eq!(
2091        buffer_lines,
2092        [
2093            Some((id_1, "ZERO".into())),
2094            Some((id_1, "one".into())),
2095            Some((base_id_1, "two".into())),
2096            Some((id_1, "TWO".into())),
2097            Some((id_1, "    three".into())),
2098            Some((base_id_1, "four".into())),
2099            Some((base_id_1, "five".into())),
2100            Some((id_1, "six".into())),
2101            Some((id_1, "".into())),
2102            Some((base_id_2, "seven".into())),
2103            Some((id_2, "  eight".into())),
2104            Some((id_2, "nine".into())),
2105            Some((base_id_2, "ten".into())),
2106            Some((id_2, "eleven".into())),
2107            Some((base_id_2, "twelve".into())),
2108            Some((id_2, "THIRTEEN".into())),
2109            Some((id_2, "FOURTEEN".into())),
2110            Some((id_2, "".into())),
2111        ]
2112    );
2113
2114    let buffer_ids_by_range = [
2115        (Point::new(0, 0)..Point::new(0, 0), &[id_1] as &[_]),
2116        (Point::new(0, 0)..Point::new(2, 0), &[id_1]),
2117        (Point::new(2, 0)..Point::new(2, 0), &[id_1]),
2118        (Point::new(3, 0)..Point::new(3, 0), &[id_1]),
2119        (Point::new(8, 0)..Point::new(9, 0), &[id_1]),
2120        (Point::new(8, 0)..Point::new(10, 0), &[id_1, id_2]),
2121        (Point::new(9, 0)..Point::new(9, 0), &[id_2]),
2122    ];
2123    for (range, buffer_ids) in buffer_ids_by_range {
2124        assert_eq!(
2125            snapshot
2126                .buffer_ids_for_range(range.clone())
2127                .collect::<Vec<_>>(),
2128            buffer_ids,
2129            "buffer_ids_for_range({range:?}"
2130        );
2131    }
2132
2133    assert_position_translation(&snapshot);
2134    assert_line_indents(&snapshot);
2135
2136    assert_eq!(
2137        snapshot
2138            .diff_hunks_in_range(0..snapshot.len())
2139            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
2140            .collect::<Vec<_>>(),
2141        &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
2142    );
2143
2144    buffer_2.update(cx, |buffer, cx| {
2145        buffer.edit_via_marked_text(
2146            indoc!(
2147                "
2148                  eight
2149                «»eleven
2150                THIRTEEN
2151                FOURTEEN
2152                "
2153            ),
2154            None,
2155            cx,
2156        );
2157    });
2158
2159    assert_new_snapshot(
2160        &multibuffer,
2161        &mut snapshot,
2162        &mut subscription,
2163        cx,
2164        indoc!(
2165            "
2166            + ZERO
2167              one
2168            - two
2169            + TWO
2170                  three
2171            - four
2172            - five
2173              six
2174
2175            - seven
2176                eight
2177              eleven
2178            - twelve
2179            + THIRTEEN
2180            + FOURTEEN
2181            "
2182        ),
2183    );
2184
2185    assert_line_indents(&snapshot);
2186}
2187
2188/// A naive implementation of a multi-buffer that does not maintain
2189/// any derived state, used for comparison in a randomized test.
2190#[derive(Default)]
2191struct ReferenceMultibuffer {
2192    excerpts: Vec<ReferenceExcerpt>,
2193    diffs: HashMap<BufferId, Entity<BufferDiff>>,
2194}
2195
2196#[derive(Debug)]
2197struct ReferenceExcerpt {
2198    id: ExcerptId,
2199    buffer: Entity<Buffer>,
2200    range: Range<text::Anchor>,
2201    expanded_diff_hunks: Vec<text::Anchor>,
2202}
2203
2204#[derive(Debug)]
2205struct ReferenceRegion {
2206    buffer_id: Option<BufferId>,
2207    range: Range<usize>,
2208    buffer_start: Option<Point>,
2209    status: Option<DiffHunkStatus>,
2210    excerpt_id: Option<ExcerptId>,
2211}
2212
2213impl ReferenceMultibuffer {
2214    fn expand_excerpts(&mut self, excerpts: &HashSet<ExcerptId>, line_count: u32, cx: &App) {
2215        if line_count == 0 {
2216            return;
2217        }
2218
2219        for id in excerpts {
2220            let excerpt = self.excerpts.iter_mut().find(|e| e.id == *id).unwrap();
2221            let snapshot = excerpt.buffer.read(cx).snapshot();
2222            let mut point_range = excerpt.range.to_point(&snapshot);
2223            point_range.start = Point::new(point_range.start.row.saturating_sub(line_count), 0);
2224            point_range.end =
2225                snapshot.clip_point(Point::new(point_range.end.row + line_count, 0), Bias::Left);
2226            point_range.end.column = snapshot.line_len(point_range.end.row);
2227            excerpt.range =
2228                snapshot.anchor_before(point_range.start)..snapshot.anchor_after(point_range.end);
2229        }
2230    }
2231
2232    fn remove_excerpt(&mut self, id: ExcerptId, cx: &App) {
2233        let ix = self
2234            .excerpts
2235            .iter()
2236            .position(|excerpt| excerpt.id == id)
2237            .unwrap();
2238        let excerpt = self.excerpts.remove(ix);
2239        let buffer = excerpt.buffer.read(cx);
2240        let id = buffer.remote_id();
2241        log::info!(
2242            "Removing excerpt {}: {:?}",
2243            ix,
2244            buffer
2245                .text_for_range(excerpt.range.to_offset(buffer))
2246                .collect::<String>(),
2247        );
2248        if !self
2249            .excerpts
2250            .iter()
2251            .any(|excerpt| excerpt.buffer.read(cx).remote_id() == id)
2252        {
2253            self.diffs.remove(&id);
2254        }
2255    }
2256
2257    fn insert_excerpt_after(
2258        &mut self,
2259        prev_id: ExcerptId,
2260        new_excerpt_id: ExcerptId,
2261        (buffer_handle, anchor_range): (Entity<Buffer>, Range<text::Anchor>),
2262    ) {
2263        let excerpt_ix = if prev_id == ExcerptId::max() {
2264            self.excerpts.len()
2265        } else {
2266            self.excerpts
2267                .iter()
2268                .position(|excerpt| excerpt.id == prev_id)
2269                .unwrap()
2270                + 1
2271        };
2272        self.excerpts.insert(
2273            excerpt_ix,
2274            ReferenceExcerpt {
2275                id: new_excerpt_id,
2276                buffer: buffer_handle,
2277                range: anchor_range,
2278                expanded_diff_hunks: Vec::new(),
2279            },
2280        );
2281    }
2282
2283    fn expand_diff_hunks(&mut self, excerpt_id: ExcerptId, range: Range<text::Anchor>, cx: &App) {
2284        let excerpt = self
2285            .excerpts
2286            .iter_mut()
2287            .find(|e| e.id == excerpt_id)
2288            .unwrap();
2289        let buffer = excerpt.buffer.read(cx).snapshot();
2290        let buffer_id = buffer.remote_id();
2291        let Some(diff) = self.diffs.get(&buffer_id) else {
2292            return;
2293        };
2294        let excerpt_range = excerpt.range.to_offset(&buffer);
2295        for hunk in diff.read(cx).hunks_intersecting_range(range, &buffer, cx) {
2296            let hunk_range = hunk.buffer_range.to_offset(&buffer);
2297            if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end {
2298                continue;
2299            }
2300            if let Err(ix) = excerpt
2301                .expanded_diff_hunks
2302                .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
2303            {
2304                log::info!(
2305                    "expanding diff hunk {:?}. excerpt:{:?}, excerpt range:{:?}",
2306                    hunk_range,
2307                    excerpt_id,
2308                    excerpt_range
2309                );
2310                excerpt
2311                    .expanded_diff_hunks
2312                    .insert(ix, hunk.buffer_range.start);
2313            } else {
2314                log::trace!("hunk {hunk_range:?} already expanded in excerpt {excerpt_id:?}");
2315            }
2316        }
2317    }
2318
2319    fn expected_content(&self, cx: &App) -> (String, Vec<RowInfo>, HashSet<MultiBufferRow>) {
2320        let mut text = String::new();
2321        let mut regions = Vec::<ReferenceRegion>::new();
2322        let mut excerpt_boundary_rows = HashSet::default();
2323        for excerpt in &self.excerpts {
2324            excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
2325            let buffer = excerpt.buffer.read(cx);
2326            let buffer_range = excerpt.range.to_offset(buffer);
2327            let diff = self.diffs.get(&buffer.remote_id()).unwrap().read(cx);
2328            let base_buffer = diff.base_text();
2329
2330            let mut offset = buffer_range.start;
2331            let hunks = diff
2332                .hunks_intersecting_range(excerpt.range.clone(), buffer, cx)
2333                .peekable();
2334
2335            for hunk in hunks {
2336                // Ignore hunks that are outside the excerpt range.
2337                let mut hunk_range = hunk.buffer_range.to_offset(buffer);
2338
2339                hunk_range.end = hunk_range.end.min(buffer_range.end);
2340                if hunk_range.start > buffer_range.end || hunk_range.start < buffer_range.start {
2341                    log::trace!("skipping hunk outside excerpt range");
2342                    continue;
2343                }
2344
2345                if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| {
2346                    expanded_anchor.to_offset(buffer).max(buffer_range.start)
2347                        == hunk_range.start.max(buffer_range.start)
2348                }) {
2349                    log::trace!("skipping a hunk that's not marked as expanded");
2350                    continue;
2351                }
2352
2353                if !hunk.buffer_range.start.is_valid(buffer) {
2354                    log::trace!("skipping hunk with deleted start: {:?}", hunk.range);
2355                    continue;
2356                }
2357
2358                if hunk_range.start >= offset {
2359                    // Add the buffer text before the hunk
2360                    let len = text.len();
2361                    text.extend(buffer.text_for_range(offset..hunk_range.start));
2362                    regions.push(ReferenceRegion {
2363                        buffer_id: Some(buffer.remote_id()),
2364                        range: len..text.len(),
2365                        buffer_start: Some(buffer.offset_to_point(offset)),
2366                        status: None,
2367                        excerpt_id: Some(excerpt.id),
2368                    });
2369
2370                    // Add the deleted text for the hunk.
2371                    if !hunk.diff_base_byte_range.is_empty() {
2372                        let mut base_text = base_buffer
2373                            .text_for_range(hunk.diff_base_byte_range.clone())
2374                            .collect::<String>();
2375                        if !base_text.ends_with('\n') {
2376                            base_text.push('\n');
2377                        }
2378                        let len = text.len();
2379                        text.push_str(&base_text);
2380                        regions.push(ReferenceRegion {
2381                            buffer_id: Some(base_buffer.remote_id()),
2382                            range: len..text.len(),
2383                            buffer_start: Some(
2384                                base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
2385                            ),
2386                            status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
2387                            excerpt_id: Some(excerpt.id),
2388                        });
2389                    }
2390
2391                    offset = hunk_range.start;
2392                }
2393
2394                // Add the inserted text for the hunk.
2395                if hunk_range.end > offset {
2396                    let len = text.len();
2397                    text.extend(buffer.text_for_range(offset..hunk_range.end));
2398                    regions.push(ReferenceRegion {
2399                        buffer_id: Some(buffer.remote_id()),
2400                        range: len..text.len(),
2401                        buffer_start: Some(buffer.offset_to_point(offset)),
2402                        status: Some(DiffHunkStatus::added(hunk.secondary_status)),
2403                        excerpt_id: Some(excerpt.id),
2404                    });
2405                    offset = hunk_range.end;
2406                }
2407            }
2408
2409            // Add the buffer text for the rest of the excerpt.
2410            let len = text.len();
2411            text.extend(buffer.text_for_range(offset..buffer_range.end));
2412            text.push('\n');
2413            regions.push(ReferenceRegion {
2414                buffer_id: Some(buffer.remote_id()),
2415                range: len..text.len(),
2416                buffer_start: Some(buffer.offset_to_point(offset)),
2417                status: None,
2418                excerpt_id: Some(excerpt.id),
2419            });
2420        }
2421
2422        // Remove final trailing newline.
2423        if self.excerpts.is_empty() {
2424            regions.push(ReferenceRegion {
2425                buffer_id: None,
2426                range: 0..1,
2427                buffer_start: Some(Point::new(0, 0)),
2428                status: None,
2429                excerpt_id: None,
2430            });
2431        } else {
2432            text.pop();
2433        }
2434
2435        // Retrieve the row info using the region that contains
2436        // the start of each multi-buffer line.
2437        let mut ix = 0;
2438        let row_infos = text
2439            .split('\n')
2440            .map(|line| {
2441                let row_info = regions
2442                    .iter()
2443                    .position(|region| region.range.contains(&ix))
2444                    .map_or(RowInfo::default(), |region_ix| {
2445                        let region = &regions[region_ix];
2446                        let buffer_row = region.buffer_start.map(|start_point| {
2447                            start_point.row
2448                                + text[region.range.start..ix].matches('\n').count() as u32
2449                        });
2450                        let is_excerpt_start = region_ix == 0
2451                            || &regions[region_ix - 1].excerpt_id != &region.excerpt_id
2452                            || regions[region_ix - 1].range.is_empty();
2453                        let mut is_excerpt_end = region_ix == regions.len() - 1
2454                            || &regions[region_ix + 1].excerpt_id != &region.excerpt_id;
2455                        let is_start = !text[region.range.start..ix].contains('\n');
2456                        let mut is_end = if region.range.end > text.len() {
2457                            !text[ix..].contains('\n')
2458                        } else {
2459                            text[ix..region.range.end.min(text.len())]
2460                                .matches('\n')
2461                                .count()
2462                                == 1
2463                        };
2464                        if region_ix < regions.len() - 1
2465                            && !text[ix..].contains("\n")
2466                            && region.status == Some(DiffHunkStatus::added_none())
2467                            && regions[region_ix + 1].excerpt_id == region.excerpt_id
2468                            && regions[region_ix + 1].range.start == text.len()
2469                        {
2470                            is_end = true;
2471                            is_excerpt_end = true;
2472                        }
2473                        let mut expand_direction = None;
2474                        if let Some(buffer) = &self
2475                            .excerpts
2476                            .iter()
2477                            .find(|e| e.id == region.excerpt_id.unwrap())
2478                            .map(|e| e.buffer.clone())
2479                        {
2480                            let needs_expand_up =
2481                                is_excerpt_start && is_start && buffer_row.unwrap() > 0;
2482                            let needs_expand_down = is_excerpt_end
2483                                && is_end
2484                                && buffer.read(cx).max_point().row > buffer_row.unwrap();
2485                            expand_direction = if needs_expand_up && needs_expand_down {
2486                                Some(ExpandExcerptDirection::UpAndDown)
2487                            } else if needs_expand_up {
2488                                Some(ExpandExcerptDirection::Up)
2489                            } else if needs_expand_down {
2490                                Some(ExpandExcerptDirection::Down)
2491                            } else {
2492                                None
2493                            };
2494                        }
2495                        RowInfo {
2496                            buffer_id: region.buffer_id,
2497                            diff_status: region.status,
2498                            buffer_row,
2499                            multibuffer_row: Some(MultiBufferRow(
2500                                text[..ix].matches('\n').count() as u32
2501                            )),
2502                            expand_info: expand_direction.zip(region.excerpt_id).map(
2503                                |(direction, excerpt_id)| ExpandInfo {
2504                                    direction,
2505                                    excerpt_id,
2506                                },
2507                            ),
2508                        }
2509                    });
2510                ix += line.len() + 1;
2511                row_info
2512            })
2513            .collect();
2514
2515        (text, row_infos, excerpt_boundary_rows)
2516    }
2517
2518    fn diffs_updated(&mut self, cx: &App) {
2519        for excerpt in &mut self.excerpts {
2520            let buffer = excerpt.buffer.read(cx).snapshot();
2521            let excerpt_range = excerpt.range.to_offset(&buffer);
2522            let buffer_id = buffer.remote_id();
2523            let diff = self.diffs.get(&buffer_id).unwrap().read(cx);
2524            let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer, cx).peekable();
2525            excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
2526                if !hunk_anchor.is_valid(&buffer) {
2527                    return false;
2528                }
2529                while let Some(hunk) = hunks.peek() {
2530                    match hunk.buffer_range.start.cmp(hunk_anchor, &buffer) {
2531                        cmp::Ordering::Less => {
2532                            hunks.next();
2533                        }
2534                        cmp::Ordering::Equal => {
2535                            let hunk_range = hunk.buffer_range.to_offset(&buffer);
2536                            return hunk_range.end >= excerpt_range.start
2537                                && hunk_range.start <= excerpt_range.end;
2538                        }
2539                        cmp::Ordering::Greater => break,
2540                    }
2541                }
2542                false
2543            });
2544        }
2545    }
2546
2547    fn add_diff(&mut self, diff: Entity<BufferDiff>, cx: &mut App) {
2548        let buffer_id = diff.read(cx).buffer_id;
2549        self.diffs.insert(buffer_id, diff);
2550    }
2551}
2552
2553#[gpui::test(iterations = 100)]
2554async fn test_random_set_ranges(cx: &mut TestAppContext, mut rng: StdRng) {
2555    let base_text = "a\n".repeat(100);
2556    let buf = cx.update(|cx| cx.new(|cx| Buffer::local(base_text, cx)));
2557    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2558
2559    let operations = env::var("OPERATIONS")
2560        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2561        .unwrap_or(10);
2562
2563    fn row_ranges(ranges: &Vec<Range<Point>>) -> Vec<Range<u32>> {
2564        ranges
2565            .iter()
2566            .map(|range| range.start.row..range.end.row)
2567            .collect()
2568    }
2569
2570    for _ in 0..operations {
2571        let snapshot = buf.update(cx, |buf, _| buf.snapshot());
2572        let num_ranges = rng.random_range(0..=10);
2573        let max_row = snapshot.max_point().row;
2574        let mut ranges = (0..num_ranges)
2575            .map(|_| {
2576                let start = rng.random_range(0..max_row);
2577                let end = rng.random_range(start + 1..max_row + 1);
2578                Point::row_range(start..end)
2579            })
2580            .collect::<Vec<_>>();
2581        ranges.sort_by_key(|range| range.start);
2582        log::info!("Setting ranges: {:?}", row_ranges(&ranges));
2583        let (created, _) = multibuffer.update(cx, |multibuffer, cx| {
2584            multibuffer.set_excerpts_for_path(
2585                PathKey::for_buffer(&buf, cx),
2586                buf.clone(),
2587                ranges.clone(),
2588                2,
2589                cx,
2590            )
2591        });
2592
2593        assert_eq!(created.len(), ranges.len());
2594
2595        let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2596        let mut last_end = None;
2597        let mut seen_ranges = Vec::default();
2598
2599        for (_, buf, range) in snapshot.excerpts() {
2600            let start = range.context.start.to_point(buf);
2601            let end = range.context.end.to_point(buf);
2602            seen_ranges.push(start..end);
2603
2604            if let Some(last_end) = last_end.take() {
2605                assert!(
2606                    start > last_end,
2607                    "multibuffer has out-of-order ranges: {:?}; {:?} <= {:?}",
2608                    row_ranges(&seen_ranges),
2609                    start,
2610                    last_end
2611                )
2612            }
2613
2614            ranges.retain(|range| range.start < start || range.end > end);
2615
2616            last_end = Some(end)
2617        }
2618
2619        assert!(
2620            ranges.is_empty(),
2621            "multibuffer {:?} did not include all ranges: {:?}",
2622            row_ranges(&seen_ranges),
2623            row_ranges(&ranges)
2624        );
2625    }
2626}
2627
2628#[gpui::test(iterations = 100)]
2629async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
2630    let operations = env::var("OPERATIONS")
2631        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2632        .unwrap_or(10);
2633
2634    let mut buffers: Vec<Entity<Buffer>> = Vec::new();
2635    let mut base_texts: HashMap<BufferId, String> = HashMap::default();
2636    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2637    let mut reference = ReferenceMultibuffer::default();
2638    let mut anchors = Vec::new();
2639    let mut old_versions = Vec::new();
2640    let mut needs_diff_calculation = false;
2641
2642    for _ in 0..operations {
2643        match rng.random_range(0..100) {
2644            0..=14 if !buffers.is_empty() => {
2645                let buffer = buffers.choose(&mut rng).unwrap();
2646                buffer.update(cx, |buf, cx| {
2647                    let edit_count = rng.random_range(1..5);
2648                    buf.randomly_edit(&mut rng, edit_count, cx);
2649                    log::info!("buffer text:\n{}", buf.text());
2650                    needs_diff_calculation = true;
2651                });
2652                cx.update(|cx| reference.diffs_updated(cx));
2653            }
2654            15..=19 if !reference.excerpts.is_empty() => {
2655                multibuffer.update(cx, |multibuffer, cx| {
2656                    let ids = multibuffer.excerpt_ids();
2657                    let mut excerpts = HashSet::default();
2658                    for _ in 0..rng.random_range(0..ids.len()) {
2659                        excerpts.extend(ids.choose(&mut rng).copied());
2660                    }
2661
2662                    let line_count = rng.random_range(0..5);
2663
2664                    let excerpt_ixs = excerpts
2665                        .iter()
2666                        .map(|id| reference.excerpts.iter().position(|e| e.id == *id).unwrap())
2667                        .collect::<Vec<_>>();
2668                    log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
2669                    multibuffer.expand_excerpts(
2670                        excerpts.iter().cloned(),
2671                        line_count,
2672                        ExpandExcerptDirection::UpAndDown,
2673                        cx,
2674                    );
2675
2676                    reference.expand_excerpts(&excerpts, line_count, cx);
2677                });
2678            }
2679            20..=29 if !reference.excerpts.is_empty() => {
2680                let mut ids_to_remove = vec![];
2681                for _ in 0..rng.random_range(1..=3) {
2682                    let Some(excerpt) = reference.excerpts.choose(&mut rng) else {
2683                        break;
2684                    };
2685                    let id = excerpt.id;
2686                    cx.update(|cx| reference.remove_excerpt(id, cx));
2687                    ids_to_remove.push(id);
2688                }
2689                let snapshot =
2690                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2691                ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
2692                drop(snapshot);
2693                multibuffer.update(cx, |multibuffer, cx| {
2694                    multibuffer.remove_excerpts(ids_to_remove, cx)
2695                });
2696            }
2697            30..=39 if !reference.excerpts.is_empty() => {
2698                let multibuffer =
2699                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2700                let offset =
2701                    multibuffer.clip_offset(rng.random_range(0..=multibuffer.len()), Bias::Left);
2702                let bias = if rng.random() {
2703                    Bias::Left
2704                } else {
2705                    Bias::Right
2706                };
2707                log::info!("Creating anchor at {} with bias {:?}", offset, bias);
2708                anchors.push(multibuffer.anchor_at(offset, bias));
2709                anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
2710            }
2711            40..=44 if !anchors.is_empty() => {
2712                let multibuffer =
2713                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2714                let prev_len = anchors.len();
2715                anchors = multibuffer
2716                    .refresh_anchors(&anchors)
2717                    .into_iter()
2718                    .map(|a| a.1)
2719                    .collect();
2720
2721                // Ensure the newly-refreshed anchors point to a valid excerpt and don't
2722                // overshoot its boundaries.
2723                assert_eq!(anchors.len(), prev_len);
2724                for anchor in &anchors {
2725                    if anchor.excerpt_id == ExcerptId::min()
2726                        || anchor.excerpt_id == ExcerptId::max()
2727                    {
2728                        continue;
2729                    }
2730
2731                    let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
2732                    assert_eq!(excerpt.id, anchor.excerpt_id);
2733                    assert!(excerpt.contains(anchor));
2734                }
2735            }
2736            45..=55 if !reference.excerpts.is_empty() => {
2737                multibuffer.update(cx, |multibuffer, cx| {
2738                    let snapshot = multibuffer.snapshot(cx);
2739                    let excerpt_ix = rng.random_range(0..reference.excerpts.len());
2740                    let excerpt = &reference.excerpts[excerpt_ix];
2741                    let start = excerpt.range.start;
2742                    let end = excerpt.range.end;
2743                    let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
2744                        ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
2745
2746                    log::info!(
2747                        "expanding diff hunks in range {:?} (excerpt id {:?}, index {excerpt_ix:?}, buffer id {:?})",
2748                        range.to_offset(&snapshot),
2749                        excerpt.id,
2750                        excerpt.buffer.read(cx).remote_id(),
2751                    );
2752                    reference.expand_diff_hunks(excerpt.id, start..end, cx);
2753                    multibuffer.expand_diff_hunks(vec![range], cx);
2754                });
2755            }
2756            56..=85 if needs_diff_calculation => {
2757                multibuffer.update(cx, |multibuffer, cx| {
2758                    for buffer in multibuffer.all_buffers() {
2759                        let snapshot = buffer.read(cx).snapshot();
2760                        multibuffer.diff_for(snapshot.remote_id()).unwrap().update(
2761                            cx,
2762                            |diff, cx| {
2763                                log::info!(
2764                                    "recalculating diff for buffer {:?}",
2765                                    snapshot.remote_id(),
2766                                );
2767                                diff.recalculate_diff_sync(snapshot.text, cx);
2768                            },
2769                        );
2770                    }
2771                    reference.diffs_updated(cx);
2772                    needs_diff_calculation = false;
2773                });
2774            }
2775            _ => {
2776                let buffer_handle = if buffers.is_empty() || rng.random_bool(0.4) {
2777                    let mut base_text = util::RandomCharIter::new(&mut rng)
2778                        .take(256)
2779                        .collect::<String>();
2780
2781                    let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
2782                    text::LineEnding::normalize(&mut base_text);
2783                    base_texts.insert(
2784                        buffer.read_with(cx, |buffer, _| buffer.remote_id()),
2785                        base_text,
2786                    );
2787                    buffers.push(buffer);
2788                    buffers.last().unwrap()
2789                } else {
2790                    buffers.choose(&mut rng).unwrap()
2791                };
2792
2793                let prev_excerpt_ix = rng.random_range(0..=reference.excerpts.len());
2794                let prev_excerpt_id = reference
2795                    .excerpts
2796                    .get(prev_excerpt_ix)
2797                    .map_or(ExcerptId::max(), |e| e.id);
2798                let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
2799
2800                let (range, anchor_range) = buffer_handle.read_with(cx, |buffer, _| {
2801                    let end_row = rng.random_range(0..=buffer.max_point().row);
2802                    let start_row = rng.random_range(0..=end_row);
2803                    let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
2804                    let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
2805                    let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
2806
2807                    log::info!(
2808                        "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
2809                        excerpt_ix,
2810                        reference.excerpts.len(),
2811                        buffer.remote_id(),
2812                        buffer.text(),
2813                        start_ix..end_ix,
2814                        &buffer.text()[start_ix..end_ix]
2815                    );
2816
2817                    (start_ix..end_ix, anchor_range)
2818                });
2819
2820                multibuffer.update(cx, |multibuffer, cx| {
2821                    let id = buffer_handle.read(cx).remote_id();
2822                    if multibuffer.diff_for(id).is_none() {
2823                        let base_text = base_texts.get(&id).unwrap();
2824                        let diff = cx
2825                            .new(|cx| BufferDiff::new_with_base_text(base_text, buffer_handle, cx));
2826                        reference.add_diff(diff.clone(), cx);
2827                        multibuffer.add_diff(diff, cx)
2828                    }
2829                });
2830
2831                let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
2832                    multibuffer
2833                        .insert_excerpts_after(
2834                            prev_excerpt_id,
2835                            buffer_handle.clone(),
2836                            [ExcerptRange::new(range.clone())],
2837                            cx,
2838                        )
2839                        .pop()
2840                        .unwrap()
2841                });
2842
2843                reference.insert_excerpt_after(
2844                    prev_excerpt_id,
2845                    excerpt_id,
2846                    (buffer_handle.clone(), anchor_range),
2847                );
2848            }
2849        }
2850
2851        if rng.random_bool(0.3) {
2852            multibuffer.update(cx, |multibuffer, cx| {
2853                old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
2854            })
2855        }
2856
2857        let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2858        let actual_text = snapshot.text();
2859        let actual_boundary_rows = snapshot
2860            .excerpt_boundaries_in_range(0..)
2861            .map(|b| b.row)
2862            .collect::<HashSet<_>>();
2863        let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2864
2865        let (expected_text, expected_row_infos, expected_boundary_rows) =
2866            cx.update(|cx| reference.expected_content(cx));
2867
2868        let has_diff = actual_row_infos
2869            .iter()
2870            .any(|info| info.diff_status.is_some())
2871            || expected_row_infos
2872                .iter()
2873                .any(|info| info.diff_status.is_some());
2874        let actual_diff = format_diff(
2875            &actual_text,
2876            &actual_row_infos,
2877            &actual_boundary_rows,
2878            Some(has_diff),
2879        );
2880        let expected_diff = format_diff(
2881            &expected_text,
2882            &expected_row_infos,
2883            &expected_boundary_rows,
2884            Some(has_diff),
2885        );
2886
2887        log::info!("Multibuffer content:\n{}", actual_diff);
2888
2889        assert_eq!(
2890            actual_row_infos.len(),
2891            actual_text.split('\n').count(),
2892            "line count: {}",
2893            actual_text.split('\n').count()
2894        );
2895        pretty_assertions::assert_eq!(actual_diff, expected_diff);
2896        pretty_assertions::assert_eq!(actual_text, expected_text);
2897        pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
2898
2899        for _ in 0..5 {
2900            let start_row = rng.random_range(0..=expected_row_infos.len());
2901            assert_eq!(
2902                snapshot
2903                    .row_infos(MultiBufferRow(start_row as u32))
2904                    .collect::<Vec<_>>(),
2905                &expected_row_infos[start_row..],
2906                "buffer_rows({})",
2907                start_row
2908            );
2909        }
2910
2911        assert_eq!(
2912            snapshot.widest_line_number(),
2913            expected_row_infos
2914                .into_iter()
2915                .filter_map(|info| {
2916                    if info.diff_status.is_some_and(|status| status.is_deleted()) {
2917                        None
2918                    } else {
2919                        info.buffer_row
2920                    }
2921                })
2922                .max()
2923                .unwrap()
2924                + 1
2925        );
2926        let reference_ranges = cx.update(|cx| {
2927            reference
2928                .excerpts
2929                .iter()
2930                .map(|excerpt| {
2931                    (
2932                        excerpt.id,
2933                        excerpt.range.to_offset(&excerpt.buffer.read(cx).snapshot()),
2934                    )
2935                })
2936                .collect::<HashMap<_, _>>()
2937        });
2938        for i in 0..snapshot.len() {
2939            let excerpt = snapshot.excerpt_containing(i..i).unwrap();
2940            assert_eq!(excerpt.buffer_range(), reference_ranges[&excerpt.id()]);
2941        }
2942
2943        assert_consistent_line_numbers(&snapshot);
2944        assert_position_translation(&snapshot);
2945
2946        for (row, line) in expected_text.split('\n').enumerate() {
2947            assert_eq!(
2948                snapshot.line_len(MultiBufferRow(row as u32)),
2949                line.len() as u32,
2950                "line_len({}).",
2951                row
2952            );
2953        }
2954
2955        let text_rope = Rope::from(expected_text.as_str());
2956        for _ in 0..10 {
2957            let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
2958            let start_ix = text_rope.clip_offset(rng.random_range(0..=end_ix), Bias::Left);
2959
2960            let text_for_range = snapshot
2961                .text_for_range(start_ix..end_ix)
2962                .collect::<String>();
2963            assert_eq!(
2964                text_for_range,
2965                &expected_text[start_ix..end_ix],
2966                "incorrect text for range {:?}",
2967                start_ix..end_ix
2968            );
2969
2970            let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
2971            assert_eq!(
2972                snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
2973                expected_summary,
2974                "incorrect summary for range {:?}",
2975                start_ix..end_ix
2976            );
2977        }
2978
2979        // Anchor resolution
2980        let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
2981        assert_eq!(anchors.len(), summaries.len());
2982        for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
2983            assert!(resolved_offset <= snapshot.len());
2984            assert_eq!(
2985                snapshot.summary_for_anchor::<usize>(anchor),
2986                resolved_offset,
2987                "anchor: {:?}",
2988                anchor
2989            );
2990        }
2991
2992        for _ in 0..10 {
2993            let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
2994            assert_eq!(
2995                snapshot.reversed_chars_at(end_ix).collect::<String>(),
2996                expected_text[..end_ix].chars().rev().collect::<String>(),
2997            );
2998        }
2999
3000        for _ in 0..10 {
3001            let end_ix = rng.random_range(0..=text_rope.len());
3002            let start_ix = rng.random_range(0..=end_ix);
3003            assert_eq!(
3004                snapshot
3005                    .bytes_in_range(start_ix..end_ix)
3006                    .flatten()
3007                    .copied()
3008                    .collect::<Vec<_>>(),
3009                expected_text.as_bytes()[start_ix..end_ix].to_vec(),
3010                "bytes_in_range({:?})",
3011                start_ix..end_ix,
3012            );
3013        }
3014    }
3015
3016    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3017    for (old_snapshot, subscription) in old_versions {
3018        let edits = subscription.consume().into_inner();
3019
3020        log::info!(
3021            "applying subscription edits to old text: {:?}: {:?}",
3022            old_snapshot.text(),
3023            edits,
3024        );
3025
3026        let mut text = old_snapshot.text();
3027        for edit in edits {
3028            let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
3029            text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
3030        }
3031        assert_eq!(text.to_string(), snapshot.text());
3032    }
3033}
3034
3035#[gpui::test]
3036fn test_history(cx: &mut App) {
3037    let test_settings = SettingsStore::test(cx);
3038    cx.set_global(test_settings);
3039    let group_interval: Duration = Duration::from_millis(1);
3040    let buffer_1 = cx.new(|cx| {
3041        let mut buf = Buffer::local("1234", cx);
3042        buf.set_group_interval(group_interval);
3043        buf
3044    });
3045    let buffer_2 = cx.new(|cx| {
3046        let mut buf = Buffer::local("5678", cx);
3047        buf.set_group_interval(group_interval);
3048        buf
3049    });
3050    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
3051    multibuffer.update(cx, |this, _| {
3052        this.set_group_interval(group_interval);
3053    });
3054    multibuffer.update(cx, |multibuffer, cx| {
3055        multibuffer.push_excerpts(
3056            buffer_1.clone(),
3057            [ExcerptRange::new(0..buffer_1.read(cx).len())],
3058            cx,
3059        );
3060        multibuffer.push_excerpts(
3061            buffer_2.clone(),
3062            [ExcerptRange::new(0..buffer_2.read(cx).len())],
3063            cx,
3064        );
3065    });
3066
3067    let mut now = Instant::now();
3068
3069    multibuffer.update(cx, |multibuffer, cx| {
3070        let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
3071        multibuffer.edit(
3072            [
3073                (Point::new(0, 0)..Point::new(0, 0), "A"),
3074                (Point::new(1, 0)..Point::new(1, 0), "A"),
3075            ],
3076            None,
3077            cx,
3078        );
3079        multibuffer.edit(
3080            [
3081                (Point::new(0, 1)..Point::new(0, 1), "B"),
3082                (Point::new(1, 1)..Point::new(1, 1), "B"),
3083            ],
3084            None,
3085            cx,
3086        );
3087        multibuffer.end_transaction_at(now, cx);
3088        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3089
3090        // Verify edited ranges for transaction 1
3091        assert_eq!(
3092            multibuffer.edited_ranges_for_transaction(transaction_1, cx),
3093            &[
3094                Point::new(0, 0)..Point::new(0, 2),
3095                Point::new(1, 0)..Point::new(1, 2)
3096            ]
3097        );
3098
3099        // Edit buffer 1 through the multibuffer
3100        now += 2 * group_interval;
3101        multibuffer.start_transaction_at(now, cx);
3102        multibuffer.edit([(2..2, "C")], None, cx);
3103        multibuffer.end_transaction_at(now, cx);
3104        assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
3105
3106        // Edit buffer 1 independently
3107        buffer_1.update(cx, |buffer_1, cx| {
3108            buffer_1.start_transaction_at(now);
3109            buffer_1.edit([(3..3, "D")], None, cx);
3110            buffer_1.end_transaction_at(now, cx);
3111
3112            now += 2 * group_interval;
3113            buffer_1.start_transaction_at(now);
3114            buffer_1.edit([(4..4, "E")], None, cx);
3115            buffer_1.end_transaction_at(now, cx);
3116        });
3117        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
3118
3119        // An undo in the multibuffer undoes the multibuffer transaction
3120        // and also any individual buffer edits that have occurred since
3121        // that transaction.
3122        multibuffer.undo(cx);
3123        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3124
3125        multibuffer.undo(cx);
3126        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3127
3128        multibuffer.redo(cx);
3129        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3130
3131        multibuffer.redo(cx);
3132        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
3133
3134        // Undo buffer 2 independently.
3135        buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
3136        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
3137
3138        // An undo in the multibuffer undoes the components of the
3139        // the last multibuffer transaction that are not already undone.
3140        multibuffer.undo(cx);
3141        assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
3142
3143        multibuffer.undo(cx);
3144        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3145
3146        multibuffer.redo(cx);
3147        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3148
3149        buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
3150        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
3151
3152        // Redo stack gets cleared after an edit.
3153        now += 2 * group_interval;
3154        multibuffer.start_transaction_at(now, cx);
3155        multibuffer.edit([(0..0, "X")], None, cx);
3156        multibuffer.end_transaction_at(now, cx);
3157        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3158        multibuffer.redo(cx);
3159        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3160        multibuffer.undo(cx);
3161        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
3162        multibuffer.undo(cx);
3163        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3164
3165        // Transactions can be grouped manually.
3166        multibuffer.redo(cx);
3167        multibuffer.redo(cx);
3168        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3169        multibuffer.group_until_transaction(transaction_1, cx);
3170        multibuffer.undo(cx);
3171        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3172        multibuffer.redo(cx);
3173        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3174    });
3175}
3176
3177#[gpui::test]
3178async fn test_enclosing_indent(cx: &mut TestAppContext) {
3179    async fn enclosing_indent(
3180        text: &str,
3181        buffer_row: u32,
3182        cx: &mut TestAppContext,
3183    ) -> Option<(Range<u32>, LineIndent)> {
3184        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3185        let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
3186        let (range, indent) = snapshot
3187            .enclosing_indent(MultiBufferRow(buffer_row))
3188            .await?;
3189        Some((range.start.0..range.end.0, indent))
3190    }
3191
3192    assert_eq!(
3193        enclosing_indent(
3194            indoc!(
3195                "
3196                fn b() {
3197                    if c {
3198                        let d = 2;
3199                    }
3200                }
3201                "
3202            ),
3203            1,
3204            cx,
3205        )
3206        .await,
3207        Some((
3208            1..2,
3209            LineIndent {
3210                tabs: 0,
3211                spaces: 4,
3212                line_blank: false,
3213            }
3214        ))
3215    );
3216
3217    assert_eq!(
3218        enclosing_indent(
3219            indoc!(
3220                "
3221                fn b() {
3222                    if c {
3223                        let d = 2;
3224                    }
3225                }
3226                "
3227            ),
3228            2,
3229            cx,
3230        )
3231        .await,
3232        Some((
3233            1..2,
3234            LineIndent {
3235                tabs: 0,
3236                spaces: 4,
3237                line_blank: false,
3238            }
3239        ))
3240    );
3241
3242    assert_eq!(
3243        enclosing_indent(
3244            indoc!(
3245                "
3246                fn b() {
3247                    if c {
3248                        let d = 2;
3249
3250                        let e = 5;
3251                    }
3252                }
3253                "
3254            ),
3255            3,
3256            cx,
3257        )
3258        .await,
3259        Some((
3260            1..4,
3261            LineIndent {
3262                tabs: 0,
3263                spaces: 4,
3264                line_blank: false,
3265            }
3266        ))
3267    );
3268}
3269
3270#[gpui::test]
3271fn test_summaries_for_anchors(cx: &mut TestAppContext) {
3272    let base_text_1 = indoc!(
3273        "
3274        bar
3275        "
3276    );
3277    let text_1 = indoc!(
3278        "
3279        BAR
3280        "
3281    );
3282    let base_text_2 = indoc!(
3283        "
3284        foo
3285        "
3286    );
3287    let text_2 = indoc!(
3288        "
3289        FOO
3290        "
3291    );
3292
3293    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3294    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
3295    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_1, &buffer_1, cx));
3296    let diff_2 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_2, &buffer_2, cx));
3297    cx.run_until_parked();
3298
3299    let mut ids = vec![];
3300    let multibuffer = cx.new(|cx| {
3301        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
3302        multibuffer.set_all_diff_hunks_expanded(cx);
3303        ids.extend(multibuffer.push_excerpts(
3304            buffer_1.clone(),
3305            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
3306            cx,
3307        ));
3308        ids.extend(multibuffer.push_excerpts(
3309            buffer_2.clone(),
3310            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
3311            cx,
3312        ));
3313        multibuffer.add_diff(diff_1.clone(), cx);
3314        multibuffer.add_diff(diff_2.clone(), cx);
3315        multibuffer
3316    });
3317
3318    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3319        (multibuffer.snapshot(cx), multibuffer.subscribe())
3320    });
3321
3322    assert_new_snapshot(
3323        &multibuffer,
3324        &mut snapshot,
3325        &mut subscription,
3326        cx,
3327        indoc!(
3328            "
3329            - bar
3330            + BAR
3331
3332            - foo
3333            + FOO
3334            "
3335        ),
3336    );
3337
3338    let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
3339    let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
3340
3341    let anchor_1 = Anchor::in_buffer(ids[0], id_1, text::Anchor::MIN);
3342    let point_1 = snapshot.summaries_for_anchors::<Point, _>([&anchor_1])[0];
3343    assert_eq!(point_1, Point::new(0, 0));
3344
3345    let anchor_2 = Anchor::in_buffer(ids[1], id_2, text::Anchor::MIN);
3346    let point_2 = snapshot.summaries_for_anchors::<Point, _>([&anchor_2])[0];
3347    assert_eq!(point_2, Point::new(3, 0));
3348}
3349
3350#[gpui::test]
3351fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
3352    let base_text_1 = "one\ntwo".to_owned();
3353    let text_1 = "one\n".to_owned();
3354
3355    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3356    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(&base_text_1, &buffer_1, cx));
3357    cx.run_until_parked();
3358
3359    let multibuffer = cx.new(|cx| {
3360        let mut multibuffer = MultiBuffer::singleton(buffer_1.clone(), cx);
3361        multibuffer.add_diff(diff_1.clone(), cx);
3362        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
3363        multibuffer
3364    });
3365
3366    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3367        (multibuffer.snapshot(cx), multibuffer.subscribe())
3368    });
3369
3370    assert_new_snapshot(
3371        &multibuffer,
3372        &mut snapshot,
3373        &mut subscription,
3374        cx,
3375        indoc!(
3376            "
3377              one
3378            - two
3379            "
3380        ),
3381    );
3382
3383    assert_eq!(snapshot.max_point(), Point::new(2, 0));
3384    assert_eq!(snapshot.len(), 8);
3385
3386    assert_eq!(
3387        snapshot
3388            .dimensions_from_points::<Point>([Point::new(2, 0)])
3389            .collect::<Vec<_>>(),
3390        vec![Point::new(2, 0)]
3391    );
3392
3393    let (_, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3394    assert_eq!(translated_offset, "one\n".len());
3395    let (_, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3396    assert_eq!(translated_point, Point::new(1, 0));
3397
3398    // The same, for an excerpt that's not at the end of the multibuffer.
3399
3400    let text_2 = "foo\n".to_owned();
3401    let buffer_2 = cx.new(|cx| Buffer::local(&text_2, cx));
3402    multibuffer.update(cx, |multibuffer, cx| {
3403        multibuffer.push_excerpts(
3404            buffer_2.clone(),
3405            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
3406            cx,
3407        );
3408    });
3409
3410    assert_new_snapshot(
3411        &multibuffer,
3412        &mut snapshot,
3413        &mut subscription,
3414        cx,
3415        indoc!(
3416            "
3417              one
3418            - two
3419
3420              foo
3421            "
3422        ),
3423    );
3424
3425    assert_eq!(
3426        snapshot
3427            .dimensions_from_points::<Point>([Point::new(2, 0)])
3428            .collect::<Vec<_>>(),
3429        vec![Point::new(2, 0)]
3430    );
3431
3432    let buffer_1_id = buffer_1.read_with(cx, |buffer_1, _| buffer_1.remote_id());
3433    let (buffer, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3434    assert_eq!(buffer.remote_id(), buffer_1_id);
3435    assert_eq!(translated_offset, "one\n".len());
3436    let (buffer, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3437    assert_eq!(buffer.remote_id(), buffer_1_id);
3438    assert_eq!(translated_point, Point::new(1, 0));
3439}
3440
3441fn format_diff(
3442    text: &str,
3443    row_infos: &Vec<RowInfo>,
3444    boundary_rows: &HashSet<MultiBufferRow>,
3445    has_diff: Option<bool>,
3446) -> String {
3447    let has_diff =
3448        has_diff.unwrap_or_else(|| row_infos.iter().any(|info| info.diff_status.is_some()));
3449    text.split('\n')
3450        .enumerate()
3451        .zip(row_infos)
3452        .map(|((ix, line), info)| {
3453            let marker = match info.diff_status.map(|status| status.kind) {
3454                Some(DiffHunkStatusKind::Added) => "+ ",
3455                Some(DiffHunkStatusKind::Deleted) => "- ",
3456                Some(DiffHunkStatusKind::Modified) => unreachable!(),
3457                None => {
3458                    if has_diff && !line.is_empty() {
3459                        "  "
3460                    } else {
3461                        ""
3462                    }
3463                }
3464            };
3465            let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
3466                if has_diff {
3467                    "  ----------\n"
3468                } else {
3469                    "---------\n"
3470                }
3471            } else {
3472                ""
3473            };
3474            format!("{boundary_row}{marker}{line}")
3475        })
3476        .collect::<Vec<_>>()
3477        .join("\n")
3478}
3479
3480#[track_caller]
3481fn assert_excerpts_match(
3482    multibuffer: &Entity<MultiBuffer>,
3483    cx: &mut TestAppContext,
3484    expected: &str,
3485) {
3486    let mut output = String::new();
3487    multibuffer.read_with(cx, |multibuffer, cx| {
3488        for (_, buffer, range) in multibuffer.snapshot(cx).excerpts() {
3489            output.push_str("-----\n");
3490            output.extend(buffer.text_for_range(range.context));
3491            if !output.ends_with('\n') {
3492                output.push('\n');
3493            }
3494        }
3495    });
3496    assert_eq!(output, expected);
3497}
3498
3499#[track_caller]
3500fn assert_new_snapshot(
3501    multibuffer: &Entity<MultiBuffer>,
3502    snapshot: &mut MultiBufferSnapshot,
3503    subscription: &mut Subscription,
3504    cx: &mut TestAppContext,
3505    expected_diff: &str,
3506) -> Vec<Edit<usize>> {
3507    let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3508    let actual_text = new_snapshot.text();
3509    let line_infos = new_snapshot
3510        .row_infos(MultiBufferRow(0))
3511        .collect::<Vec<_>>();
3512    let actual_diff = format_diff(&actual_text, &line_infos, &Default::default(), None);
3513    pretty_assertions::assert_eq!(actual_diff, expected_diff);
3514    let edits = subscription.consume().into_inner();
3515    check_edits(snapshot, &new_snapshot, &edits);
3516    *snapshot = new_snapshot;
3517    edits
3518}
3519
3520#[track_caller]
3521fn check_edits(
3522    old_snapshot: &MultiBufferSnapshot,
3523    new_snapshot: &MultiBufferSnapshot,
3524    edits: &[Edit<usize>],
3525) {
3526    let mut text = old_snapshot.text();
3527    let new_text = new_snapshot.text();
3528    for edit in edits.iter().rev() {
3529        if !text.is_char_boundary(edit.old.start)
3530            || !text.is_char_boundary(edit.old.end)
3531            || !new_text.is_char_boundary(edit.new.start)
3532            || !new_text.is_char_boundary(edit.new.end)
3533        {
3534            panic!(
3535                "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
3536                edits, text, new_text
3537            );
3538        }
3539
3540        text.replace_range(
3541            edit.old.start..edit.old.end,
3542            &new_text[edit.new.start..edit.new.end],
3543        );
3544    }
3545
3546    pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
3547}
3548
3549#[track_caller]
3550fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
3551    let full_text = snapshot.text();
3552    for ix in 0..full_text.len() {
3553        let mut chunks = snapshot.chunks(0..snapshot.len(), false);
3554        chunks.seek(ix..snapshot.len());
3555        let tail = chunks.map(|chunk| chunk.text).collect::<String>();
3556        assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
3557    }
3558}
3559
3560#[track_caller]
3561fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
3562    let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3563    for start_row in 1..all_line_numbers.len() {
3564        let line_numbers = snapshot
3565            .row_infos(MultiBufferRow(start_row as u32))
3566            .collect::<Vec<_>>();
3567        assert_eq!(
3568            line_numbers,
3569            all_line_numbers[start_row..],
3570            "start_row: {start_row}"
3571        );
3572    }
3573}
3574
3575#[track_caller]
3576fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
3577    let text = Rope::from(snapshot.text());
3578
3579    let mut left_anchors = Vec::new();
3580    let mut right_anchors = Vec::new();
3581    let mut offsets = Vec::new();
3582    let mut points = Vec::new();
3583    for offset in 0..=text.len() + 1 {
3584        let clipped_left = snapshot.clip_offset(offset, Bias::Left);
3585        let clipped_right = snapshot.clip_offset(offset, Bias::Right);
3586        assert_eq!(
3587            clipped_left,
3588            text.clip_offset(offset, Bias::Left),
3589            "clip_offset({offset:?}, Left)"
3590        );
3591        assert_eq!(
3592            clipped_right,
3593            text.clip_offset(offset, Bias::Right),
3594            "clip_offset({offset:?}, Right)"
3595        );
3596        assert_eq!(
3597            snapshot.offset_to_point(clipped_left),
3598            text.offset_to_point(clipped_left),
3599            "offset_to_point({clipped_left})"
3600        );
3601        assert_eq!(
3602            snapshot.offset_to_point(clipped_right),
3603            text.offset_to_point(clipped_right),
3604            "offset_to_point({clipped_right})"
3605        );
3606        let anchor_after = snapshot.anchor_after(clipped_left);
3607        assert_eq!(
3608            anchor_after.to_offset(snapshot),
3609            clipped_left,
3610            "anchor_after({clipped_left}).to_offset {anchor_after:?}"
3611        );
3612        let anchor_before = snapshot.anchor_before(clipped_left);
3613        assert_eq!(
3614            anchor_before.to_offset(snapshot),
3615            clipped_left,
3616            "anchor_before({clipped_left}).to_offset"
3617        );
3618        left_anchors.push(anchor_before);
3619        right_anchors.push(anchor_after);
3620        offsets.push(clipped_left);
3621        points.push(text.offset_to_point(clipped_left));
3622    }
3623
3624    for row in 0..text.max_point().row {
3625        for column in 0..text.line_len(row) + 1 {
3626            let point = Point { row, column };
3627            let clipped_left = snapshot.clip_point(point, Bias::Left);
3628            let clipped_right = snapshot.clip_point(point, Bias::Right);
3629            assert_eq!(
3630                clipped_left,
3631                text.clip_point(point, Bias::Left),
3632                "clip_point({point:?}, Left)"
3633            );
3634            assert_eq!(
3635                clipped_right,
3636                text.clip_point(point, Bias::Right),
3637                "clip_point({point:?}, Right)"
3638            );
3639            assert_eq!(
3640                snapshot.point_to_offset(clipped_left),
3641                text.point_to_offset(clipped_left),
3642                "point_to_offset({clipped_left:?})"
3643            );
3644            assert_eq!(
3645                snapshot.point_to_offset(clipped_right),
3646                text.point_to_offset(clipped_right),
3647                "point_to_offset({clipped_right:?})"
3648            );
3649        }
3650    }
3651
3652    assert_eq!(
3653        snapshot.summaries_for_anchors::<usize, _>(&left_anchors),
3654        offsets,
3655        "left_anchors <-> offsets"
3656    );
3657    assert_eq!(
3658        snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
3659        points,
3660        "left_anchors <-> points"
3661    );
3662    assert_eq!(
3663        snapshot.summaries_for_anchors::<usize, _>(&right_anchors),
3664        offsets,
3665        "right_anchors <-> offsets"
3666    );
3667    assert_eq!(
3668        snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
3669        points,
3670        "right_anchors <-> points"
3671    );
3672
3673    for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
3674        for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
3675            if ix > 0 && *offset == 252 && offset > &offsets[ix - 1] {
3676                let prev_anchor = left_anchors[ix - 1];
3677                assert!(
3678                    anchor.cmp(&prev_anchor, snapshot).is_gt(),
3679                    "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
3680                    offsets[ix],
3681                    offsets[ix - 1],
3682                );
3683                assert!(
3684                    prev_anchor.cmp(anchor, snapshot).is_lt(),
3685                    "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
3686                    offsets[ix - 1],
3687                    offsets[ix],
3688                );
3689            }
3690        }
3691    }
3692
3693    if let Some((buffer, offset)) = snapshot.point_to_buffer_offset(snapshot.max_point()) {
3694        assert!(offset <= buffer.len());
3695    }
3696    if let Some((buffer, point, _)) = snapshot.point_to_buffer_point(snapshot.max_point()) {
3697        assert!(point <= buffer.max_point());
3698    }
3699}
3700
3701fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
3702    let max_row = snapshot.max_point().row;
3703    let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
3704    let text = text::Buffer::new(ReplicaId::LOCAL, buffer_id, snapshot.text());
3705    let mut line_indents = text
3706        .line_indents_in_row_range(0..max_row + 1)
3707        .collect::<Vec<_>>();
3708    for start_row in 0..snapshot.max_point().row {
3709        pretty_assertions::assert_eq!(
3710            snapshot
3711                .line_indents(MultiBufferRow(start_row), |_| true)
3712                .map(|(row, indent, _)| (row.0, indent))
3713                .collect::<Vec<_>>(),
3714            &line_indents[(start_row as usize)..],
3715            "line_indents({start_row})"
3716        );
3717    }
3718
3719    line_indents.reverse();
3720    pretty_assertions::assert_eq!(
3721        snapshot
3722            .reversed_line_indents(MultiBufferRow(max_row), |_| true)
3723            .map(|(row, indent, _)| (row.0, indent))
3724            .collect::<Vec<_>>(),
3725        &line_indents[..],
3726        "reversed_line_indents({max_row})"
3727    );
3728}
3729
3730#[gpui::test]
3731fn test_new_empty_buffer_uses_untitled_title(cx: &mut App) {
3732    let buffer = cx.new(|cx| Buffer::local("", cx));
3733    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3734
3735    assert_eq!(multibuffer.read(cx).title(cx), "untitled");
3736}
3737
3738#[gpui::test]
3739fn test_new_empty_buffer_uses_untitled_title_when_only_contains_whitespace(cx: &mut App) {
3740    let buffer = cx.new(|cx| Buffer::local("\n ", cx));
3741    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3742
3743    assert_eq!(multibuffer.read(cx).title(cx), "untitled");
3744}
3745
3746#[gpui::test]
3747fn test_new_empty_buffer_takes_first_line_for_title(cx: &mut App) {
3748    let buffer = cx.new(|cx| Buffer::local("Hello World\nSecond line", cx));
3749    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3750
3751    assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
3752}
3753
3754#[gpui::test]
3755fn test_new_empty_buffer_takes_trimmed_first_line_for_title(cx: &mut App) {
3756    let buffer = cx.new(|cx| Buffer::local("\nHello, World ", cx));
3757    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3758
3759    assert_eq!(multibuffer.read(cx).title(cx), "Hello, World");
3760}
3761
3762#[gpui::test]
3763fn test_new_empty_buffer_uses_truncated_first_line_for_title(cx: &mut App) {
3764    let title = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee";
3765    let title_after = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd";
3766    let buffer = cx.new(|cx| Buffer::local(title, cx));
3767    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3768
3769    assert_eq!(multibuffer.read(cx).title(cx), title_after);
3770}
3771
3772#[gpui::test]
3773fn test_new_empty_buffer_uses_truncated_first_line_for_title_after_merging_adjacent_spaces(
3774    cx: &mut App,
3775) {
3776    let title = "aaaaaaaaaabbbbbbbbbb    ccccccccccddddddddddeeeeeeeeee";
3777    let title_after = "aaaaaaaaaabbbbbbbbbb ccccccccccddddddddd";
3778    let buffer = cx.new(|cx| Buffer::local(title, cx));
3779    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3780
3781    assert_eq!(multibuffer.read(cx).title(cx), title_after);
3782}
3783
3784#[gpui::test]
3785fn test_new_empty_buffers_title_can_be_set(cx: &mut App) {
3786    let buffer = cx.new(|cx| Buffer::local("Hello World", cx));
3787    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3788    assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
3789
3790    multibuffer.update(cx, |multibuffer, cx| {
3791        multibuffer.set_title("Hey".into(), cx)
3792    });
3793    assert_eq!(multibuffer.read(cx).title(cx), "Hey");
3794}
3795
3796#[gpui::test(iterations = 100)]
3797fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) {
3798    let multibuffer = if rng.random() {
3799        let len = rng.random_range(0..10000);
3800        let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
3801        let buffer = cx.new(|cx| Buffer::local(text, cx));
3802        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
3803    } else {
3804        MultiBuffer::build_random(&mut rng, cx)
3805    };
3806
3807    let snapshot = multibuffer.read(cx).snapshot(cx);
3808
3809    let chunks = snapshot.chunks(0..snapshot.len(), false);
3810
3811    for chunk in chunks {
3812        let chunk_text = chunk.text;
3813        let chars_bitmap = chunk.chars;
3814        let tabs_bitmap = chunk.tabs;
3815
3816        if chunk_text.is_empty() {
3817            assert_eq!(
3818                chars_bitmap, 0,
3819                "Empty chunk should have empty chars bitmap"
3820            );
3821            assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
3822            continue;
3823        }
3824
3825        assert!(
3826            chunk_text.len() <= 128,
3827            "Chunk text length {} exceeds 128 bytes",
3828            chunk_text.len()
3829        );
3830
3831        // Verify chars bitmap
3832        let char_indices = chunk_text
3833            .char_indices()
3834            .map(|(i, _)| i)
3835            .collect::<Vec<_>>();
3836
3837        for byte_idx in 0..chunk_text.len() {
3838            let should_have_bit = char_indices.contains(&byte_idx);
3839            let has_bit = chars_bitmap & (1 << byte_idx) != 0;
3840
3841            if has_bit != should_have_bit {
3842                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
3843                eprintln!("Char indices: {:?}", char_indices);
3844                eprintln!("Chars bitmap: {:#b}", chars_bitmap);
3845            }
3846
3847            assert_eq!(
3848                has_bit, should_have_bit,
3849                "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
3850                byte_idx, chunk_text, should_have_bit, has_bit
3851            );
3852        }
3853
3854        for (byte_idx, byte) in chunk_text.bytes().enumerate() {
3855            let is_tab = byte == b'\t';
3856            let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
3857
3858            if has_bit != is_tab {
3859                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
3860                eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
3861                assert_eq!(
3862                    has_bit, is_tab,
3863                    "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
3864                    byte_idx, chunk_text, byte as char, is_tab, has_bit
3865                );
3866            }
3867        }
3868    }
3869}
3870
3871#[gpui::test(iterations = 100)]
3872fn test_random_chunk_bitmaps_with_diffs(cx: &mut App, mut rng: StdRng) {
3873    use buffer_diff::BufferDiff;
3874    use util::RandomCharIter;
3875
3876    let multibuffer = if rng.random() {
3877        let len = rng.random_range(100..10000);
3878        let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
3879        let buffer = cx.new(|cx| Buffer::local(text, cx));
3880        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
3881    } else {
3882        MultiBuffer::build_random(&mut rng, cx)
3883    };
3884
3885    let _diff_count = rng.random_range(1..5);
3886    let mut diffs = Vec::new();
3887
3888    multibuffer.update(cx, |multibuffer, cx| {
3889        for buffer_id in multibuffer.excerpt_buffer_ids() {
3890            if rng.random_bool(0.7) {
3891                if let Some(buffer_handle) = multibuffer.buffer(buffer_id) {
3892                    let buffer_text = buffer_handle.read(cx).text();
3893                    let mut base_text = String::new();
3894
3895                    for line in buffer_text.lines() {
3896                        if rng.random_bool(0.3) {
3897                            continue;
3898                        } else if rng.random_bool(0.3) {
3899                            let line_len = rng.random_range(0..50);
3900                            let modified_line = RandomCharIter::new(&mut rng)
3901                                .take(line_len)
3902                                .collect::<String>();
3903                            base_text.push_str(&modified_line);
3904                            base_text.push('\n');
3905                        } else {
3906                            base_text.push_str(line);
3907                            base_text.push('\n');
3908                        }
3909                    }
3910
3911                    if rng.random_bool(0.5) {
3912                        let extra_lines = rng.random_range(1..5);
3913                        for _ in 0..extra_lines {
3914                            let line_len = rng.random_range(0..50);
3915                            let extra_line = RandomCharIter::new(&mut rng)
3916                                .take(line_len)
3917                                .collect::<String>();
3918                            base_text.push_str(&extra_line);
3919                            base_text.push('\n');
3920                        }
3921                    }
3922
3923                    let diff =
3924                        cx.new(|cx| BufferDiff::new_with_base_text(&base_text, &buffer_handle, cx));
3925                    diffs.push(diff.clone());
3926                    multibuffer.add_diff(diff, cx);
3927                }
3928            }
3929        }
3930    });
3931
3932    multibuffer.update(cx, |multibuffer, cx| {
3933        if rng.random_bool(0.5) {
3934            multibuffer.set_all_diff_hunks_expanded(cx);
3935        } else {
3936            let snapshot = multibuffer.snapshot(cx);
3937            let text = snapshot.text();
3938
3939            let mut ranges = Vec::new();
3940            for _ in 0..rng.random_range(1..5) {
3941                if snapshot.len() == 0 {
3942                    break;
3943                }
3944
3945                let diff_size = rng.random_range(5..1000);
3946                let mut start = rng.random_range(0..snapshot.len());
3947
3948                while !text.is_char_boundary(start) {
3949                    start = start.saturating_sub(1);
3950                }
3951
3952                let mut end = rng.random_range(start..snapshot.len().min(start + diff_size));
3953
3954                while !text.is_char_boundary(end) {
3955                    end = end.saturating_add(1);
3956                }
3957                let start_anchor = snapshot.anchor_after(start);
3958                let end_anchor = snapshot.anchor_before(end);
3959                ranges.push(start_anchor..end_anchor);
3960            }
3961            multibuffer.expand_diff_hunks(ranges, cx);
3962        }
3963    });
3964
3965    let snapshot = multibuffer.read(cx).snapshot(cx);
3966
3967    let chunks = snapshot.chunks(0..snapshot.len(), false);
3968
3969    for chunk in chunks {
3970        let chunk_text = chunk.text;
3971        let chars_bitmap = chunk.chars;
3972        let tabs_bitmap = chunk.tabs;
3973
3974        if chunk_text.is_empty() {
3975            assert_eq!(
3976                chars_bitmap, 0,
3977                "Empty chunk should have empty chars bitmap"
3978            );
3979            assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
3980            continue;
3981        }
3982
3983        assert!(
3984            chunk_text.len() <= 128,
3985            "Chunk text length {} exceeds 128 bytes",
3986            chunk_text.len()
3987        );
3988
3989        let char_indices = chunk_text
3990            .char_indices()
3991            .map(|(i, _)| i)
3992            .collect::<Vec<_>>();
3993
3994        for byte_idx in 0..chunk_text.len() {
3995            let should_have_bit = char_indices.contains(&byte_idx);
3996            let has_bit = chars_bitmap & (1 << byte_idx) != 0;
3997
3998            if has_bit != should_have_bit {
3999                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
4000                eprintln!("Char indices: {:?}", char_indices);
4001                eprintln!("Chars bitmap: {:#b}", chars_bitmap);
4002            }
4003
4004            assert_eq!(
4005                has_bit, should_have_bit,
4006                "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
4007                byte_idx, chunk_text, should_have_bit, has_bit
4008            );
4009        }
4010
4011        for (byte_idx, byte) in chunk_text.bytes().enumerate() {
4012            let is_tab = byte == b'\t';
4013            let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
4014
4015            if has_bit != is_tab {
4016                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
4017                eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
4018                assert_eq!(
4019                    has_bit, is_tab,
4020                    "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
4021                    byte_idx, chunk_text, byte as char, is_tab, has_bit
4022                );
4023            }
4024        }
4025    }
4026}