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