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 util::test::sample_text;
  11
  12#[ctor::ctor]
  13fn init_logger() {
  14    if std::env::var("RUST_LOG").is_ok() {
  15        env_logger::init();
  16    }
  17}
  18
  19#[gpui::test]
  20fn test_empty_singleton(cx: &mut App) {
  21    let buffer = cx.new(|cx| Buffer::local("", cx));
  22    let buffer_id = buffer.read(cx).remote_id();
  23    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
  24    let snapshot = multibuffer.read(cx).snapshot(cx);
  25    assert_eq!(snapshot.text(), "");
  26    assert_eq!(
  27        snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
  28        [RowInfo {
  29            buffer_id: Some(buffer_id),
  30            buffer_row: Some(0),
  31            multibuffer_row: Some(MultiBufferRow(0)),
  32            diff_status: None,
  33            expand_info: None,
  34        }]
  35    );
  36}
  37
  38#[gpui::test]
  39fn test_singleton(cx: &mut App) {
  40    let buffer = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
  41    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
  42
  43    let snapshot = multibuffer.read(cx).snapshot(cx);
  44    assert_eq!(snapshot.text(), buffer.read(cx).text());
  45
  46    assert_eq!(
  47        snapshot
  48            .row_infos(MultiBufferRow(0))
  49            .map(|info| info.buffer_row)
  50            .collect::<Vec<_>>(),
  51        (0..buffer.read(cx).row_count())
  52            .map(Some)
  53            .collect::<Vec<_>>()
  54    );
  55    assert_consistent_line_numbers(&snapshot);
  56
  57    buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx));
  58    let snapshot = multibuffer.read(cx).snapshot(cx);
  59
  60    assert_eq!(snapshot.text(), buffer.read(cx).text());
  61    assert_eq!(
  62        snapshot
  63            .row_infos(MultiBufferRow(0))
  64            .map(|info| info.buffer_row)
  65            .collect::<Vec<_>>(),
  66        (0..buffer.read(cx).row_count())
  67            .map(Some)
  68            .collect::<Vec<_>>()
  69    );
  70    assert_consistent_line_numbers(&snapshot);
  71}
  72
  73#[gpui::test]
  74fn test_remote(cx: &mut App) {
  75    let host_buffer = cx.new(|cx| Buffer::local("a", cx));
  76    let guest_buffer = cx.new(|cx| {
  77        let state = host_buffer.read(cx).to_proto(cx);
  78        let ops = cx
  79            .background_executor()
  80            .block(host_buffer.read(cx).serialize_ops(None, cx));
  81        let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
  82        buffer.apply_ops(
  83            ops.into_iter()
  84                .map(|op| language::proto::deserialize_operation(op).unwrap()),
  85            cx,
  86        );
  87        buffer
  88    });
  89    let multibuffer = cx.new(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx));
  90    let snapshot = multibuffer.read(cx).snapshot(cx);
  91    assert_eq!(snapshot.text(), "a");
  92
  93    guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx));
  94    let snapshot = multibuffer.read(cx).snapshot(cx);
  95    assert_eq!(snapshot.text(), "ab");
  96
  97    guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx));
  98    let snapshot = multibuffer.read(cx).snapshot(cx);
  99    assert_eq!(snapshot.text(), "abc");
 100}
 101
 102#[gpui::test]
 103fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
 104    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
 105    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
 106    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 107
 108    let events = Arc::new(RwLock::new(Vec::<Event>::new()));
 109    multibuffer.update(cx, |_, cx| {
 110        let events = events.clone();
 111        cx.subscribe(&multibuffer, move |_, _, event, _| {
 112            if let Event::Edited { .. } = event {
 113                events.write().push(event.clone())
 114            }
 115        })
 116        .detach();
 117    });
 118
 119    let subscription = multibuffer.update(cx, |multibuffer, cx| {
 120        let subscription = multibuffer.subscribe();
 121        multibuffer.push_excerpts(
 122            buffer_1.clone(),
 123            [ExcerptRange::new(Point::new(1, 2)..Point::new(2, 5))],
 124            cx,
 125        );
 126        assert_eq!(
 127            subscription.consume().into_inner(),
 128            [Edit {
 129                old: 0..0,
 130                new: 0..10
 131            }]
 132        );
 133
 134        multibuffer.push_excerpts(
 135            buffer_1.clone(),
 136            [ExcerptRange::new(Point::new(3, 3)..Point::new(4, 4))],
 137            cx,
 138        );
 139        multibuffer.push_excerpts(
 140            buffer_2.clone(),
 141            [ExcerptRange::new(Point::new(3, 1)..Point::new(3, 3))],
 142            cx,
 143        );
 144        assert_eq!(
 145            subscription.consume().into_inner(),
 146            [Edit {
 147                old: 10..10,
 148                new: 10..22
 149            }]
 150        );
 151
 152        subscription
 153    });
 154
 155    // Adding excerpts emits an edited event.
 156    assert_eq!(
 157        events.read().as_slice(),
 158        &[
 159            Event::Edited {
 160                singleton_buffer_edited: false,
 161                edited_buffer: None,
 162            },
 163            Event::Edited {
 164                singleton_buffer_edited: false,
 165                edited_buffer: None,
 166            },
 167            Event::Edited {
 168                singleton_buffer_edited: false,
 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        multibuffer.expand_excerpts(
 750            multibuffer.excerpt_ids(),
 751            1,
 752            ExpandExcerptDirection::UpAndDown,
 753            cx,
 754        )
 755    });
 756
 757    let snapshot = multibuffer.read(cx).snapshot(cx);
 758
 759    // Expanding context lines causes the line containing 'fff' to appear in two different excerpts.
 760    // We don't attempt to merge them, because removing the excerpt could create inconsistency with other layers
 761    // that are tracking excerpt ids.
 762    assert_eq!(
 763        snapshot.text(),
 764        concat!(
 765            "bbb\n", //
 766            "ccc\n", //
 767            "ddd\n", //
 768            "eee\n", //
 769            "fff\n", //
 770            "ggg\n", //
 771            "hhh\n", //
 772            "iii\n", //
 773            "jjj\n", // End of excerpt
 774            "nnn\n", //
 775            "ooo\n", //
 776            "ppp\n", //
 777            "qqq\n", //
 778            "rrr",   // End of excerpt
 779        )
 780    );
 781}
 782
 783#[gpui::test(iterations = 100)]
 784async fn test_push_multiple_excerpts_with_context_lines(cx: &mut TestAppContext) {
 785    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
 786    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(15, 4, 'a'), cx));
 787    let snapshot_1 = buffer_1.update(cx, |buffer, _| buffer.snapshot());
 788    let snapshot_2 = buffer_2.update(cx, |buffer, _| buffer.snapshot());
 789    let ranges_1 = vec![
 790        snapshot_1.anchor_before(Point::new(3, 2))..snapshot_1.anchor_before(Point::new(4, 2)),
 791        snapshot_1.anchor_before(Point::new(7, 1))..snapshot_1.anchor_before(Point::new(7, 3)),
 792        snapshot_1.anchor_before(Point::new(15, 0))..snapshot_1.anchor_before(Point::new(15, 0)),
 793    ];
 794    let ranges_2 = vec![
 795        snapshot_2.anchor_before(Point::new(2, 1))..snapshot_2.anchor_before(Point::new(3, 1)),
 796        snapshot_2.anchor_before(Point::new(10, 0))..snapshot_2.anchor_before(Point::new(10, 2)),
 797    ];
 798
 799    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 800    let anchor_ranges = multibuffer
 801        .update(cx, |multibuffer, cx| {
 802            multibuffer.push_multiple_excerpts_with_context_lines(
 803                vec![(buffer_1.clone(), ranges_1), (buffer_2.clone(), ranges_2)],
 804                2,
 805                cx,
 806            )
 807        })
 808        .await;
 809
 810    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 811    assert_eq!(
 812        snapshot.text(),
 813        concat!(
 814            "bbb\n", // buffer_1
 815            "ccc\n", //
 816            "ddd\n", // <-- excerpt 1
 817            "eee\n", // <-- excerpt 1
 818            "fff\n", //
 819            "ggg\n", //
 820            "hhh\n", // <-- excerpt 2
 821            "iii\n", //
 822            "jjj\n", //
 823            //
 824            "nnn\n", //
 825            "ooo\n", //
 826            "ppp\n", // <-- excerpt 3
 827            "qqq\n", //
 828            "rrr\n", //
 829            //
 830            "aaaa\n", // buffer 2
 831            "bbbb\n", //
 832            "cccc\n", // <-- excerpt 4
 833            "dddd\n", // <-- excerpt 4
 834            "eeee\n", //
 835            "ffff\n", //
 836            //
 837            "iiii\n", //
 838            "jjjj\n", //
 839            "kkkk\n", // <-- excerpt 5
 840            "llll\n", //
 841            "mmmm",   //
 842        )
 843    );
 844
 845    assert_eq!(
 846        anchor_ranges
 847            .iter()
 848            .map(|range| range.to_point(&snapshot))
 849            .collect::<Vec<_>>(),
 850        vec![
 851            Point::new(2, 2)..Point::new(3, 2),
 852            Point::new(6, 1)..Point::new(6, 3),
 853            Point::new(11, 0)..Point::new(11, 0),
 854            Point::new(16, 1)..Point::new(17, 1),
 855            Point::new(22, 0)..Point::new(22, 2)
 856        ]
 857    );
 858}
 859
 860#[gpui::test]
 861fn test_empty_multibuffer(cx: &mut App) {
 862    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 863
 864    let snapshot = multibuffer.read(cx).snapshot(cx);
 865    assert_eq!(snapshot.text(), "");
 866    assert_eq!(
 867        snapshot
 868            .row_infos(MultiBufferRow(0))
 869            .map(|info| info.buffer_row)
 870            .collect::<Vec<_>>(),
 871        &[Some(0)]
 872    );
 873    assert!(
 874        snapshot
 875            .row_infos(MultiBufferRow(1))
 876            .map(|info| info.buffer_row)
 877            .collect::<Vec<_>>()
 878            .is_empty(),
 879    );
 880}
 881
 882#[gpui::test]
 883fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
 884    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 885    let buffer = cx.new(|cx| Buffer::local("", cx));
 886    let base_text = "a\nb\nc";
 887
 888    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
 889    multibuffer.update(cx, |multibuffer, cx| {
 890        multibuffer.push_excerpts(buffer.clone(), [ExcerptRange::new(0..0)], cx);
 891        multibuffer.set_all_diff_hunks_expanded(cx);
 892        multibuffer.add_diff(diff.clone(), cx);
 893    });
 894    cx.run_until_parked();
 895
 896    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 897    assert_eq!(snapshot.text(), "a\nb\nc\n");
 898
 899    let hunk = snapshot
 900        .diff_hunks_in_range(Point::new(1, 1)..Point::new(1, 1))
 901        .next()
 902        .unwrap();
 903
 904    assert_eq!(hunk.diff_base_byte_range.start, 0);
 905
 906    let buf2 = cx.new(|cx| Buffer::local("X", cx));
 907    multibuffer.update(cx, |multibuffer, cx| {
 908        multibuffer.push_excerpts(buf2, [ExcerptRange::new(0..1)], cx);
 909    });
 910
 911    buffer.update(cx, |buffer, cx| {
 912        buffer.edit([(0..0, "a\nb\nc")], None, cx);
 913        diff.update(cx, |diff, cx| {
 914            diff.recalculate_diff_sync(buffer.snapshot().text, cx);
 915        });
 916        assert_eq!(buffer.text(), "a\nb\nc")
 917    });
 918    cx.run_until_parked();
 919
 920    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 921    assert_eq!(snapshot.text(), "a\nb\nc\nX");
 922
 923    buffer.update(cx, |buffer, cx| {
 924        buffer.undo(cx);
 925        diff.update(cx, |diff, cx| {
 926            diff.recalculate_diff_sync(buffer.snapshot().text, cx);
 927        });
 928        assert_eq!(buffer.text(), "")
 929    });
 930    cx.run_until_parked();
 931
 932    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 933    assert_eq!(snapshot.text(), "a\nb\nc\n\nX");
 934}
 935
 936#[gpui::test]
 937fn test_singleton_multibuffer_anchors(cx: &mut App) {
 938    let buffer = cx.new(|cx| Buffer::local("abcd", cx));
 939    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
 940    let old_snapshot = multibuffer.read(cx).snapshot(cx);
 941    buffer.update(cx, |buffer, cx| {
 942        buffer.edit([(0..0, "X")], None, cx);
 943        buffer.edit([(5..5, "Y")], None, cx);
 944    });
 945    let new_snapshot = multibuffer.read(cx).snapshot(cx);
 946
 947    assert_eq!(old_snapshot.text(), "abcd");
 948    assert_eq!(new_snapshot.text(), "XabcdY");
 949
 950    assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
 951    assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
 952    assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5);
 953    assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6);
 954}
 955
 956#[gpui::test]
 957fn test_multibuffer_anchors(cx: &mut App) {
 958    let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
 959    let buffer_2 = cx.new(|cx| Buffer::local("efghi", cx));
 960    let multibuffer = cx.new(|cx| {
 961        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
 962        multibuffer.push_excerpts(buffer_1.clone(), [ExcerptRange::new(0..4)], cx);
 963        multibuffer.push_excerpts(buffer_2.clone(), [ExcerptRange::new(0..5)], cx);
 964        multibuffer
 965    });
 966    let old_snapshot = multibuffer.read(cx).snapshot(cx);
 967
 968    assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0);
 969    assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0);
 970    assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
 971    assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
 972    assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
 973    assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
 974
 975    buffer_1.update(cx, |buffer, cx| {
 976        buffer.edit([(0..0, "W")], None, cx);
 977        buffer.edit([(5..5, "X")], None, cx);
 978    });
 979    buffer_2.update(cx, |buffer, cx| {
 980        buffer.edit([(0..0, "Y")], None, cx);
 981        buffer.edit([(6..6, "Z")], None, cx);
 982    });
 983    let new_snapshot = multibuffer.read(cx).snapshot(cx);
 984
 985    assert_eq!(old_snapshot.text(), "abcd\nefghi");
 986    assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
 987
 988    assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
 989    assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
 990    assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2);
 991    assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2);
 992    assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3);
 993    assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3);
 994    assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7);
 995    assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8);
 996    assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13);
 997    assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14);
 998}
 999
1000#[gpui::test]
1001fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
1002    let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1003    let buffer_2 = cx.new(|cx| Buffer::local("ABCDEFGHIJKLMNOP", cx));
1004    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1005
1006    // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
1007    // Add an excerpt from buffer 1 that spans this new insertion.
1008    buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx));
1009    let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| {
1010        multibuffer
1011            .push_excerpts(buffer_1.clone(), [ExcerptRange::new(0..7)], cx)
1012            .pop()
1013            .unwrap()
1014    });
1015
1016    let snapshot_1 = multibuffer.read(cx).snapshot(cx);
1017    assert_eq!(snapshot_1.text(), "abcd123");
1018
1019    // Replace the buffer 1 excerpt with new excerpts from buffer 2.
1020    let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
1021        multibuffer.remove_excerpts([excerpt_id_1], cx);
1022        let mut ids = multibuffer
1023            .push_excerpts(
1024                buffer_2.clone(),
1025                [
1026                    ExcerptRange::new(0..4),
1027                    ExcerptRange::new(6..10),
1028                    ExcerptRange::new(12..16),
1029                ],
1030                cx,
1031            )
1032            .into_iter();
1033        (ids.next().unwrap(), ids.next().unwrap())
1034    });
1035    let snapshot_2 = multibuffer.read(cx).snapshot(cx);
1036    assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP");
1037
1038    // The old excerpt id doesn't get reused.
1039    assert_ne!(excerpt_id_2, excerpt_id_1);
1040
1041    // Resolve some anchors from the previous snapshot in the new snapshot.
1042    // The current excerpts are from a different buffer, so we don't attempt to
1043    // resolve the old text anchor in the new buffer.
1044    assert_eq!(
1045        snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)),
1046        0
1047    );
1048    assert_eq!(
1049        snapshot_2.summaries_for_anchors::<usize, _>(&[
1050            snapshot_1.anchor_before(2),
1051            snapshot_1.anchor_after(3)
1052        ]),
1053        vec![0, 0]
1054    );
1055
1056    // Refresh anchors from the old snapshot. The return value indicates that both
1057    // anchors lost their original excerpt.
1058    let refresh =
1059        snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]);
1060    assert_eq!(
1061        refresh,
1062        &[
1063            (0, snapshot_2.anchor_before(0), false),
1064            (1, snapshot_2.anchor_after(0), false),
1065        ]
1066    );
1067
1068    // Replace the middle excerpt with a smaller excerpt in buffer 2,
1069    // that intersects the old excerpt.
1070    let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| {
1071        multibuffer.remove_excerpts([excerpt_id_3], cx);
1072        multibuffer
1073            .insert_excerpts_after(
1074                excerpt_id_2,
1075                buffer_2.clone(),
1076                [ExcerptRange::new(5..8)],
1077                cx,
1078            )
1079            .pop()
1080            .unwrap()
1081    });
1082
1083    let snapshot_3 = multibuffer.read(cx).snapshot(cx);
1084    assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP");
1085    assert_ne!(excerpt_id_5, excerpt_id_3);
1086
1087    // Resolve some anchors from the previous snapshot in the new snapshot.
1088    // The third anchor can't be resolved, since its excerpt has been removed,
1089    // so it resolves to the same position as its predecessor.
1090    let anchors = [
1091        snapshot_2.anchor_before(0),
1092        snapshot_2.anchor_after(2),
1093        snapshot_2.anchor_after(6),
1094        snapshot_2.anchor_after(14),
1095    ];
1096    assert_eq!(
1097        snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
1098        &[0, 2, 9, 13]
1099    );
1100
1101    let new_anchors = snapshot_3.refresh_anchors(&anchors);
1102    assert_eq!(
1103        new_anchors.iter().map(|a| (a.0, a.2)).collect::<Vec<_>>(),
1104        &[(0, true), (1, true), (2, true), (3, true)]
1105    );
1106    assert_eq!(
1107        snapshot_3.summaries_for_anchors::<usize, _>(new_anchors.iter().map(|a| &a.1)),
1108        &[0, 2, 7, 13]
1109    );
1110}
1111
1112#[gpui::test]
1113fn test_basic_diff_hunks(cx: &mut TestAppContext) {
1114    let text = indoc!(
1115        "
1116        ZERO
1117        one
1118        TWO
1119        three
1120        six
1121        "
1122    );
1123    let base_text = indoc!(
1124        "
1125        one
1126        two
1127        three
1128        four
1129        five
1130        six
1131        "
1132    );
1133
1134    let buffer = cx.new(|cx| Buffer::local(text, cx));
1135    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
1136    cx.run_until_parked();
1137
1138    let multibuffer = cx.new(|cx| {
1139        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1140        multibuffer.add_diff(diff.clone(), cx);
1141        multibuffer
1142    });
1143
1144    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1145        (multibuffer.snapshot(cx), multibuffer.subscribe())
1146    });
1147    assert_eq!(
1148        snapshot.text(),
1149        indoc!(
1150            "
1151            ZERO
1152            one
1153            TWO
1154            three
1155            six
1156            "
1157        ),
1158    );
1159
1160    multibuffer.update(cx, |multibuffer, cx| {
1161        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1162    });
1163
1164    assert_new_snapshot(
1165        &multibuffer,
1166        &mut snapshot,
1167        &mut subscription,
1168        cx,
1169        indoc!(
1170            "
1171            + ZERO
1172              one
1173            - two
1174            + TWO
1175              three
1176            - four
1177            - five
1178              six
1179            "
1180        ),
1181    );
1182
1183    assert_eq!(
1184        snapshot
1185            .row_infos(MultiBufferRow(0))
1186            .map(|info| (info.buffer_row, info.diff_status))
1187            .collect::<Vec<_>>(),
1188        vec![
1189            (Some(0), Some(DiffHunkStatus::added_none())),
1190            (Some(1), None),
1191            (Some(1), Some(DiffHunkStatus::deleted_none())),
1192            (Some(2), Some(DiffHunkStatus::added_none())),
1193            (Some(3), None),
1194            (Some(3), Some(DiffHunkStatus::deleted_none())),
1195            (Some(4), Some(DiffHunkStatus::deleted_none())),
1196            (Some(4), None),
1197            (Some(5), None)
1198        ]
1199    );
1200
1201    assert_chunks_in_ranges(&snapshot);
1202    assert_consistent_line_numbers(&snapshot);
1203    assert_position_translation(&snapshot);
1204    assert_line_indents(&snapshot);
1205
1206    multibuffer.update(cx, |multibuffer, cx| {
1207        multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
1208    });
1209    assert_new_snapshot(
1210        &multibuffer,
1211        &mut snapshot,
1212        &mut subscription,
1213        cx,
1214        indoc!(
1215            "
1216            ZERO
1217            one
1218            TWO
1219            three
1220            six
1221            "
1222        ),
1223    );
1224
1225    assert_chunks_in_ranges(&snapshot);
1226    assert_consistent_line_numbers(&snapshot);
1227    assert_position_translation(&snapshot);
1228    assert_line_indents(&snapshot);
1229
1230    // Expand the first diff hunk
1231    multibuffer.update(cx, |multibuffer, cx| {
1232        let position = multibuffer.read(cx).anchor_before(Point::new(2, 2));
1233        multibuffer.expand_diff_hunks(vec![position..position], cx)
1234    });
1235    assert_new_snapshot(
1236        &multibuffer,
1237        &mut snapshot,
1238        &mut subscription,
1239        cx,
1240        indoc!(
1241            "
1242              ZERO
1243              one
1244            - two
1245            + TWO
1246              three
1247              six
1248            "
1249        ),
1250    );
1251
1252    // Expand the second diff hunk
1253    multibuffer.update(cx, |multibuffer, cx| {
1254        let start = multibuffer.read(cx).anchor_before(Point::new(4, 0));
1255        let end = multibuffer.read(cx).anchor_before(Point::new(5, 0));
1256        multibuffer.expand_diff_hunks(vec![start..end], 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            - four
1271            - five
1272              six
1273            "
1274        ),
1275    );
1276
1277    assert_chunks_in_ranges(&snapshot);
1278    assert_consistent_line_numbers(&snapshot);
1279    assert_position_translation(&snapshot);
1280    assert_line_indents(&snapshot);
1281
1282    // Edit the buffer before the first hunk
1283    buffer.update(cx, |buffer, cx| {
1284        buffer.edit_via_marked_text(
1285            indoc!(
1286                "
1287                ZERO
1288                one« hundred
1289                  thousand»
1290                TWO
1291                three
1292                six
1293                "
1294            ),
1295            None,
1296            cx,
1297        );
1298    });
1299    assert_new_snapshot(
1300        &multibuffer,
1301        &mut snapshot,
1302        &mut subscription,
1303        cx,
1304        indoc!(
1305            "
1306              ZERO
1307              one hundred
1308                thousand
1309            - two
1310            + TWO
1311              three
1312            - four
1313            - five
1314              six
1315            "
1316        ),
1317    );
1318
1319    assert_chunks_in_ranges(&snapshot);
1320    assert_consistent_line_numbers(&snapshot);
1321    assert_position_translation(&snapshot);
1322    assert_line_indents(&snapshot);
1323
1324    // Recalculate the diff, changing the first diff hunk.
1325    diff.update(cx, |diff, cx| {
1326        diff.recalculate_diff_sync(buffer.read(cx).text_snapshot(), cx);
1327    });
1328    cx.run_until_parked();
1329    assert_new_snapshot(
1330        &multibuffer,
1331        &mut snapshot,
1332        &mut subscription,
1333        cx,
1334        indoc!(
1335            "
1336              ZERO
1337              one hundred
1338                thousand
1339              TWO
1340              three
1341            - four
1342            - five
1343              six
1344            "
1345        ),
1346    );
1347
1348    assert_eq!(
1349        snapshot
1350            .diff_hunks_in_range(0..snapshot.len())
1351            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1352            .collect::<Vec<_>>(),
1353        &[0..4, 5..7]
1354    );
1355}
1356
1357#[gpui::test]
1358fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
1359    let text = indoc!(
1360        "
1361        one
1362        TWO
1363        THREE
1364        four
1365        FIVE
1366        six
1367        "
1368    );
1369    let base_text = indoc!(
1370        "
1371        one
1372        four
1373        five
1374        six
1375        "
1376    );
1377
1378    let buffer = cx.new(|cx| Buffer::local(text, cx));
1379    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
1380    cx.run_until_parked();
1381
1382    let multibuffer = cx.new(|cx| {
1383        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1384        multibuffer.add_diff(diff.clone(), cx);
1385        multibuffer
1386    });
1387
1388    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1389        (multibuffer.snapshot(cx), multibuffer.subscribe())
1390    });
1391
1392    multibuffer.update(cx, |multibuffer, cx| {
1393        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1394    });
1395
1396    assert_new_snapshot(
1397        &multibuffer,
1398        &mut snapshot,
1399        &mut subscription,
1400        cx,
1401        indoc!(
1402            "
1403              one
1404            + TWO
1405            + THREE
1406              four
1407            - five
1408            + FIVE
1409              six
1410            "
1411        ),
1412    );
1413
1414    // Regression test: expanding diff hunks that are already expanded should not change anything.
1415    multibuffer.update(cx, |multibuffer, cx| {
1416        multibuffer.expand_diff_hunks(
1417            vec![
1418                snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_before(Point::new(2, 0)),
1419            ],
1420            cx,
1421        );
1422    });
1423
1424    assert_new_snapshot(
1425        &multibuffer,
1426        &mut snapshot,
1427        &mut subscription,
1428        cx,
1429        indoc!(
1430            "
1431              one
1432            + TWO
1433            + THREE
1434              four
1435            - five
1436            + FIVE
1437              six
1438            "
1439        ),
1440    );
1441
1442    // Now collapse all diff hunks
1443    multibuffer.update(cx, |multibuffer, cx| {
1444        multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1445    });
1446
1447    assert_new_snapshot(
1448        &multibuffer,
1449        &mut snapshot,
1450        &mut subscription,
1451        cx,
1452        indoc!(
1453            "
1454            one
1455            TWO
1456            THREE
1457            four
1458            FIVE
1459            six
1460            "
1461        ),
1462    );
1463
1464    // Expand the hunks again, but this time provide two ranges that are both within the same hunk
1465    // Target the first hunk which is between "one" and "four"
1466    multibuffer.update(cx, |multibuffer, cx| {
1467        multibuffer.expand_diff_hunks(
1468            vec![
1469                snapshot.anchor_before(Point::new(4, 0))..snapshot.anchor_before(Point::new(4, 0)),
1470                snapshot.anchor_before(Point::new(4, 2))..snapshot.anchor_before(Point::new(4, 2)),
1471            ],
1472            cx,
1473        );
1474    });
1475    assert_new_snapshot(
1476        &multibuffer,
1477        &mut snapshot,
1478        &mut subscription,
1479        cx,
1480        indoc!(
1481            "
1482              one
1483              TWO
1484              THREE
1485              four
1486            - five
1487            + FIVE
1488              six
1489            "
1490        ),
1491    );
1492}
1493
1494#[gpui::test]
1495fn test_set_excerpts_for_buffer_ordering(cx: &mut TestAppContext) {
1496    let buf1 = cx.new(|cx| {
1497        Buffer::local(
1498            indoc! {
1499            "zero
1500            one
1501            two
1502            two.five
1503            three
1504            four
1505            five
1506            six
1507            seven
1508            eight
1509            nine
1510            ten
1511            eleven
1512            ",
1513            },
1514            cx,
1515        )
1516    });
1517    let path1: PathKey = PathKey::namespaced(0, Path::new("/").into());
1518
1519    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1520    multibuffer.update(cx, |multibuffer, cx| {
1521        multibuffer.set_excerpts_for_path(
1522            path1.clone(),
1523            buf1.clone(),
1524            vec![
1525                Point::row_range(1..2),
1526                Point::row_range(6..7),
1527                Point::row_range(11..12),
1528            ],
1529            1,
1530            cx,
1531        );
1532    });
1533
1534    assert_excerpts_match(
1535        &multibuffer,
1536        cx,
1537        indoc! {
1538            "-----
1539            zero
1540            one
1541            two
1542            two.five
1543            -----
1544            four
1545            five
1546            six
1547            seven
1548            -----
1549            nine
1550            ten
1551            eleven
1552            "
1553        },
1554    );
1555
1556    buf1.update(cx, |buffer, cx| buffer.edit([(0..5, "")], None, cx));
1557
1558    multibuffer.update(cx, |multibuffer, cx| {
1559        multibuffer.set_excerpts_for_path(
1560            path1.clone(),
1561            buf1.clone(),
1562            vec![
1563                Point::row_range(0..3),
1564                Point::row_range(5..7),
1565                Point::row_range(10..11),
1566            ],
1567            1,
1568            cx,
1569        );
1570    });
1571
1572    assert_excerpts_match(
1573        &multibuffer,
1574        cx,
1575        indoc! {
1576            "-----
1577             one
1578             two
1579             two.five
1580             three
1581             four
1582             five
1583             six
1584             seven
1585             eight
1586             -----
1587             nine
1588             ten
1589             eleven
1590            "
1591        },
1592    );
1593}
1594
1595#[gpui::test]
1596fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
1597    let buf1 = cx.new(|cx| {
1598        Buffer::local(
1599            indoc! {
1600            "zero
1601            one
1602            two
1603            three
1604            four
1605            five
1606            six
1607            seven
1608            ",
1609            },
1610            cx,
1611        )
1612    });
1613    let path1: PathKey = PathKey::namespaced(0, Path::new("/").into());
1614    let buf2 = cx.new(|cx| {
1615        Buffer::local(
1616            indoc! {
1617            "000
1618            111
1619            222
1620            333
1621            444
1622            555
1623            666
1624            777
1625            888
1626            999
1627            "
1628            },
1629            cx,
1630        )
1631    });
1632    let path2 = PathKey::namespaced(1, Path::new("/").into());
1633
1634    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1635    multibuffer.update(cx, |multibuffer, cx| {
1636        multibuffer.set_excerpts_for_path(
1637            path1.clone(),
1638            buf1.clone(),
1639            vec![Point::row_range(0..1)],
1640            2,
1641            cx,
1642        );
1643    });
1644
1645    assert_excerpts_match(
1646        &multibuffer,
1647        cx,
1648        indoc! {
1649        "-----
1650        zero
1651        one
1652        two
1653        three
1654        "
1655        },
1656    );
1657
1658    multibuffer.update(cx, |multibuffer, cx| {
1659        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1660    });
1661
1662    assert_excerpts_match(&multibuffer, cx, "");
1663
1664    multibuffer.update(cx, |multibuffer, cx| {
1665        multibuffer.set_excerpts_for_path(
1666            path1.clone(),
1667            buf1.clone(),
1668            vec![Point::row_range(0..1), Point::row_range(7..8)],
1669            2,
1670            cx,
1671        );
1672    });
1673
1674    assert_excerpts_match(
1675        &multibuffer,
1676        cx,
1677        indoc! {"-----
1678                zero
1679                one
1680                two
1681                three
1682                -----
1683                five
1684                six
1685                seven
1686                "},
1687    );
1688
1689    multibuffer.update(cx, |multibuffer, cx| {
1690        multibuffer.set_excerpts_for_path(
1691            path1.clone(),
1692            buf1.clone(),
1693            vec![Point::row_range(0..1), Point::row_range(5..6)],
1694            2,
1695            cx,
1696        );
1697    });
1698
1699    assert_excerpts_match(
1700        &multibuffer,
1701        cx,
1702        indoc! {"-----
1703                    zero
1704                    one
1705                    two
1706                    three
1707                    four
1708                    five
1709                    six
1710                    seven
1711                    "},
1712    );
1713
1714    multibuffer.update(cx, |multibuffer, cx| {
1715        multibuffer.set_excerpts_for_path(
1716            path2.clone(),
1717            buf2.clone(),
1718            vec![Point::row_range(2..3)],
1719            2,
1720            cx,
1721        );
1722    });
1723
1724    assert_excerpts_match(
1725        &multibuffer,
1726        cx,
1727        indoc! {"-----
1728                zero
1729                one
1730                two
1731                three
1732                four
1733                five
1734                six
1735                seven
1736                -----
1737                000
1738                111
1739                222
1740                333
1741                444
1742                555
1743                "},
1744    );
1745
1746    multibuffer.update(cx, |multibuffer, cx| {
1747        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
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(3..4)],
1755            2,
1756            cx,
1757        );
1758    });
1759
1760    assert_excerpts_match(
1761        &multibuffer,
1762        cx,
1763        indoc! {"-----
1764                one
1765                two
1766                three
1767                four
1768                five
1769                six
1770                -----
1771                000
1772                111
1773                222
1774                333
1775                444
1776                555
1777                "},
1778    );
1779
1780    multibuffer.update(cx, |multibuffer, cx| {
1781        multibuffer.set_excerpts_for_path(
1782            path1.clone(),
1783            buf1.clone(),
1784            vec![Point::row_range(3..4)],
1785            2,
1786            cx,
1787        );
1788    });
1789}
1790
1791#[gpui::test]
1792fn test_set_excerpts_for_buffer_rename(cx: &mut TestAppContext) {
1793    let buf1 = cx.new(|cx| {
1794        Buffer::local(
1795            indoc! {
1796            "zero
1797            one
1798            two
1799            three
1800            four
1801            five
1802            six
1803            seven
1804            ",
1805            },
1806            cx,
1807        )
1808    });
1809    let path: PathKey = PathKey::namespaced(0, Path::new("/").into());
1810    let buf2 = cx.new(|cx| {
1811        Buffer::local(
1812            indoc! {
1813            "000
1814            111
1815            222
1816            333
1817            "
1818            },
1819            cx,
1820        )
1821    });
1822
1823    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1824    multibuffer.update(cx, |multibuffer, cx| {
1825        multibuffer.set_excerpts_for_path(
1826            path.clone(),
1827            buf1.clone(),
1828            vec![Point::row_range(1..1), Point::row_range(4..5)],
1829            1,
1830            cx,
1831        );
1832    });
1833
1834    assert_excerpts_match(
1835        &multibuffer,
1836        cx,
1837        indoc! {
1838        "-----
1839        zero
1840        one
1841        two
1842        -----
1843        three
1844        four
1845        five
1846        six
1847        "
1848        },
1849    );
1850
1851    multibuffer.update(cx, |multibuffer, cx| {
1852        multibuffer.set_excerpts_for_path(
1853            path.clone(),
1854            buf2.clone(),
1855            vec![Point::row_range(0..1)],
1856            2,
1857            cx,
1858        );
1859    });
1860
1861    assert_excerpts_match(
1862        &multibuffer,
1863        cx,
1864        indoc! {"-----
1865                000
1866                111
1867                222
1868                333
1869                "},
1870    );
1871}
1872
1873#[gpui::test]
1874fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
1875    let base_text_1 = indoc!(
1876        "
1877        one
1878        two
1879            three
1880        four
1881        five
1882        six
1883        "
1884    );
1885    let text_1 = indoc!(
1886        "
1887        ZERO
1888        one
1889        TWO
1890            three
1891        six
1892        "
1893    );
1894    let base_text_2 = indoc!(
1895        "
1896        seven
1897          eight
1898        nine
1899        ten
1900        eleven
1901        twelve
1902        "
1903    );
1904    let text_2 = indoc!(
1905        "
1906          eight
1907        nine
1908        eleven
1909        THIRTEEN
1910        FOURTEEN
1911        "
1912    );
1913
1914    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
1915    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
1916    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_1, &buffer_1, cx));
1917    let diff_2 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_2, &buffer_2, cx));
1918    cx.run_until_parked();
1919
1920    let multibuffer = cx.new(|cx| {
1921        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1922        multibuffer.push_excerpts(
1923            buffer_1.clone(),
1924            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
1925            cx,
1926        );
1927        multibuffer.push_excerpts(
1928            buffer_2.clone(),
1929            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
1930            cx,
1931        );
1932        multibuffer.add_diff(diff_1.clone(), cx);
1933        multibuffer.add_diff(diff_2.clone(), cx);
1934        multibuffer
1935    });
1936
1937    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1938        (multibuffer.snapshot(cx), multibuffer.subscribe())
1939    });
1940    assert_eq!(
1941        snapshot.text(),
1942        indoc!(
1943            "
1944            ZERO
1945            one
1946            TWO
1947                three
1948            six
1949
1950              eight
1951            nine
1952            eleven
1953            THIRTEEN
1954            FOURTEEN
1955            "
1956        ),
1957    );
1958
1959    multibuffer.update(cx, |multibuffer, cx| {
1960        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1961    });
1962
1963    assert_new_snapshot(
1964        &multibuffer,
1965        &mut snapshot,
1966        &mut subscription,
1967        cx,
1968        indoc!(
1969            "
1970            + ZERO
1971              one
1972            - two
1973            + TWO
1974                  three
1975            - four
1976            - five
1977              six
1978
1979            - seven
1980                eight
1981              nine
1982            - ten
1983              eleven
1984            - twelve
1985            + THIRTEEN
1986            + FOURTEEN
1987            "
1988        ),
1989    );
1990
1991    let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
1992    let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
1993    let base_id_1 = diff_1.read_with(cx, |diff, _| diff.base_text().remote_id());
1994    let base_id_2 = diff_2.read_with(cx, |diff, _| diff.base_text().remote_id());
1995
1996    let buffer_lines = (0..=snapshot.max_row().0)
1997        .map(|row| {
1998            let (buffer, range) = snapshot.buffer_line_for_row(MultiBufferRow(row))?;
1999            Some((
2000                buffer.remote_id(),
2001                buffer.text_for_range(range).collect::<String>(),
2002            ))
2003        })
2004        .collect::<Vec<_>>();
2005    pretty_assertions::assert_eq!(
2006        buffer_lines,
2007        [
2008            Some((id_1, "ZERO".into())),
2009            Some((id_1, "one".into())),
2010            Some((base_id_1, "two".into())),
2011            Some((id_1, "TWO".into())),
2012            Some((id_1, "    three".into())),
2013            Some((base_id_1, "four".into())),
2014            Some((base_id_1, "five".into())),
2015            Some((id_1, "six".into())),
2016            Some((id_1, "".into())),
2017            Some((base_id_2, "seven".into())),
2018            Some((id_2, "  eight".into())),
2019            Some((id_2, "nine".into())),
2020            Some((base_id_2, "ten".into())),
2021            Some((id_2, "eleven".into())),
2022            Some((base_id_2, "twelve".into())),
2023            Some((id_2, "THIRTEEN".into())),
2024            Some((id_2, "FOURTEEN".into())),
2025            Some((id_2, "".into())),
2026        ]
2027    );
2028
2029    let buffer_ids_by_range = [
2030        (Point::new(0, 0)..Point::new(0, 0), &[id_1] as &[_]),
2031        (Point::new(0, 0)..Point::new(2, 0), &[id_1]),
2032        (Point::new(2, 0)..Point::new(2, 0), &[id_1]),
2033        (Point::new(3, 0)..Point::new(3, 0), &[id_1]),
2034        (Point::new(8, 0)..Point::new(9, 0), &[id_1]),
2035        (Point::new(8, 0)..Point::new(10, 0), &[id_1, id_2]),
2036        (Point::new(9, 0)..Point::new(9, 0), &[id_2]),
2037    ];
2038    for (range, buffer_ids) in buffer_ids_by_range {
2039        assert_eq!(
2040            snapshot
2041                .buffer_ids_for_range(range.clone())
2042                .collect::<Vec<_>>(),
2043            buffer_ids,
2044            "buffer_ids_for_range({range:?}"
2045        );
2046    }
2047
2048    assert_position_translation(&snapshot);
2049    assert_line_indents(&snapshot);
2050
2051    assert_eq!(
2052        snapshot
2053            .diff_hunks_in_range(0..snapshot.len())
2054            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
2055            .collect::<Vec<_>>(),
2056        &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
2057    );
2058
2059    buffer_2.update(cx, |buffer, cx| {
2060        buffer.edit_via_marked_text(
2061            indoc!(
2062                "
2063                  eight
2064                «»eleven
2065                THIRTEEN
2066                FOURTEEN
2067                "
2068            ),
2069            None,
2070            cx,
2071        );
2072    });
2073
2074    assert_new_snapshot(
2075        &multibuffer,
2076        &mut snapshot,
2077        &mut subscription,
2078        cx,
2079        indoc!(
2080            "
2081            + ZERO
2082              one
2083            - two
2084            + TWO
2085                  three
2086            - four
2087            - five
2088              six
2089
2090            - seven
2091                eight
2092              eleven
2093            - twelve
2094            + THIRTEEN
2095            + FOURTEEN
2096            "
2097        ),
2098    );
2099
2100    assert_line_indents(&snapshot);
2101}
2102
2103/// A naive implementation of a multi-buffer that does not maintain
2104/// any derived state, used for comparison in a randomized test.
2105#[derive(Default)]
2106struct ReferenceMultibuffer {
2107    excerpts: Vec<ReferenceExcerpt>,
2108    diffs: HashMap<BufferId, Entity<BufferDiff>>,
2109}
2110
2111#[derive(Debug)]
2112struct ReferenceExcerpt {
2113    id: ExcerptId,
2114    buffer: Entity<Buffer>,
2115    range: Range<text::Anchor>,
2116    expanded_diff_hunks: Vec<text::Anchor>,
2117}
2118
2119#[derive(Debug)]
2120struct ReferenceRegion {
2121    buffer_id: Option<BufferId>,
2122    range: Range<usize>,
2123    buffer_start: Option<Point>,
2124    status: Option<DiffHunkStatus>,
2125    excerpt_id: Option<ExcerptId>,
2126}
2127
2128impl ReferenceMultibuffer {
2129    fn expand_excerpts(&mut self, excerpts: &HashSet<ExcerptId>, line_count: u32, cx: &App) {
2130        if line_count == 0 {
2131            return;
2132        }
2133
2134        for id in excerpts {
2135            let excerpt = self.excerpts.iter_mut().find(|e| e.id == *id).unwrap();
2136            let snapshot = excerpt.buffer.read(cx).snapshot();
2137            let mut point_range = excerpt.range.to_point(&snapshot);
2138            point_range.start = Point::new(point_range.start.row.saturating_sub(line_count), 0);
2139            point_range.end =
2140                snapshot.clip_point(Point::new(point_range.end.row + line_count, 0), Bias::Left);
2141            point_range.end.column = snapshot.line_len(point_range.end.row);
2142            excerpt.range =
2143                snapshot.anchor_before(point_range.start)..snapshot.anchor_after(point_range.end);
2144        }
2145    }
2146
2147    fn remove_excerpt(&mut self, id: ExcerptId, cx: &App) {
2148        let ix = self
2149            .excerpts
2150            .iter()
2151            .position(|excerpt| excerpt.id == id)
2152            .unwrap();
2153        let excerpt = self.excerpts.remove(ix);
2154        let buffer = excerpt.buffer.read(cx);
2155        let id = buffer.remote_id();
2156        log::info!(
2157            "Removing excerpt {}: {:?}",
2158            ix,
2159            buffer
2160                .text_for_range(excerpt.range.to_offset(buffer))
2161                .collect::<String>(),
2162        );
2163        if !self
2164            .excerpts
2165            .iter()
2166            .any(|excerpt| excerpt.buffer.read(cx).remote_id() == id)
2167        {
2168            self.diffs.remove(&id);
2169        }
2170    }
2171
2172    fn insert_excerpt_after(
2173        &mut self,
2174        prev_id: ExcerptId,
2175        new_excerpt_id: ExcerptId,
2176        (buffer_handle, anchor_range): (Entity<Buffer>, Range<text::Anchor>),
2177    ) {
2178        let excerpt_ix = if prev_id == ExcerptId::max() {
2179            self.excerpts.len()
2180        } else {
2181            self.excerpts
2182                .iter()
2183                .position(|excerpt| excerpt.id == prev_id)
2184                .unwrap()
2185                + 1
2186        };
2187        self.excerpts.insert(
2188            excerpt_ix,
2189            ReferenceExcerpt {
2190                id: new_excerpt_id,
2191                buffer: buffer_handle,
2192                range: anchor_range,
2193                expanded_diff_hunks: Vec::new(),
2194            },
2195        );
2196    }
2197
2198    fn expand_diff_hunks(&mut self, excerpt_id: ExcerptId, range: Range<text::Anchor>, cx: &App) {
2199        let excerpt = self
2200            .excerpts
2201            .iter_mut()
2202            .find(|e| e.id == excerpt_id)
2203            .unwrap();
2204        let buffer = excerpt.buffer.read(cx).snapshot();
2205        let buffer_id = buffer.remote_id();
2206        let Some(diff) = self.diffs.get(&buffer_id) else {
2207            return;
2208        };
2209        let excerpt_range = excerpt.range.to_offset(&buffer);
2210        for hunk in diff.read(cx).hunks_intersecting_range(range, &buffer, cx) {
2211            let hunk_range = hunk.buffer_range.to_offset(&buffer);
2212            if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end {
2213                continue;
2214            }
2215            if let Err(ix) = excerpt
2216                .expanded_diff_hunks
2217                .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
2218            {
2219                log::info!(
2220                    "expanding diff hunk {:?}. excerpt:{:?}, excerpt range:{:?}",
2221                    hunk_range,
2222                    excerpt_id,
2223                    excerpt_range
2224                );
2225                excerpt
2226                    .expanded_diff_hunks
2227                    .insert(ix, hunk.buffer_range.start);
2228            } else {
2229                log::trace!("hunk {hunk_range:?} already expanded in excerpt {excerpt_id:?}");
2230            }
2231        }
2232    }
2233
2234    fn expected_content(&self, cx: &App) -> (String, Vec<RowInfo>, HashSet<MultiBufferRow>) {
2235        let mut text = String::new();
2236        let mut regions = Vec::<ReferenceRegion>::new();
2237        let mut excerpt_boundary_rows = HashSet::default();
2238        for excerpt in &self.excerpts {
2239            excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
2240            let buffer = excerpt.buffer.read(cx);
2241            let buffer_range = excerpt.range.to_offset(buffer);
2242            let diff = self.diffs.get(&buffer.remote_id()).unwrap().read(cx);
2243            let base_buffer = diff.base_text();
2244
2245            let mut offset = buffer_range.start;
2246            let mut hunks = diff
2247                .hunks_intersecting_range(excerpt.range.clone(), buffer, cx)
2248                .peekable();
2249
2250            while let Some(hunk) = hunks.next() {
2251                // Ignore hunks that are outside the excerpt range.
2252                let mut hunk_range = hunk.buffer_range.to_offset(buffer);
2253
2254                hunk_range.end = hunk_range.end.min(buffer_range.end);
2255                if hunk_range.start > buffer_range.end || hunk_range.start < buffer_range.start {
2256                    log::trace!("skipping hunk outside excerpt range");
2257                    continue;
2258                }
2259
2260                if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| {
2261                    expanded_anchor.to_offset(&buffer).max(buffer_range.start)
2262                        == hunk_range.start.max(buffer_range.start)
2263                }) {
2264                    log::trace!("skipping a hunk that's not marked as expanded");
2265                    continue;
2266                }
2267
2268                if !hunk.buffer_range.start.is_valid(&buffer) {
2269                    log::trace!("skipping hunk with deleted start: {:?}", hunk.range);
2270                    continue;
2271                }
2272
2273                if hunk_range.start >= offset {
2274                    // Add the buffer text before the hunk
2275                    let len = text.len();
2276                    text.extend(buffer.text_for_range(offset..hunk_range.start));
2277                    regions.push(ReferenceRegion {
2278                        buffer_id: Some(buffer.remote_id()),
2279                        range: len..text.len(),
2280                        buffer_start: Some(buffer.offset_to_point(offset)),
2281                        status: None,
2282                        excerpt_id: Some(excerpt.id),
2283                    });
2284
2285                    // Add the deleted text for the hunk.
2286                    if !hunk.diff_base_byte_range.is_empty() {
2287                        let mut base_text = base_buffer
2288                            .text_for_range(hunk.diff_base_byte_range.clone())
2289                            .collect::<String>();
2290                        if !base_text.ends_with('\n') {
2291                            base_text.push('\n');
2292                        }
2293                        let len = text.len();
2294                        text.push_str(&base_text);
2295                        regions.push(ReferenceRegion {
2296                            buffer_id: Some(base_buffer.remote_id()),
2297                            range: len..text.len(),
2298                            buffer_start: Some(
2299                                base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
2300                            ),
2301                            status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
2302                            excerpt_id: Some(excerpt.id),
2303                        });
2304                    }
2305
2306                    offset = hunk_range.start;
2307                }
2308
2309                // Add the inserted text for the hunk.
2310                if hunk_range.end > offset {
2311                    let len = text.len();
2312                    text.extend(buffer.text_for_range(offset..hunk_range.end));
2313                    regions.push(ReferenceRegion {
2314                        buffer_id: Some(buffer.remote_id()),
2315                        range: len..text.len(),
2316                        buffer_start: Some(buffer.offset_to_point(offset)),
2317                        status: Some(DiffHunkStatus::added(hunk.secondary_status)),
2318                        excerpt_id: Some(excerpt.id),
2319                    });
2320                    offset = hunk_range.end;
2321                }
2322            }
2323
2324            // Add the buffer text for the rest of the excerpt.
2325            let len = text.len();
2326            text.extend(buffer.text_for_range(offset..buffer_range.end));
2327            text.push('\n');
2328            regions.push(ReferenceRegion {
2329                buffer_id: Some(buffer.remote_id()),
2330                range: len..text.len(),
2331                buffer_start: Some(buffer.offset_to_point(offset)),
2332                status: None,
2333                excerpt_id: Some(excerpt.id),
2334            });
2335        }
2336
2337        // Remove final trailing newline.
2338        if self.excerpts.is_empty() {
2339            regions.push(ReferenceRegion {
2340                buffer_id: None,
2341                range: 0..1,
2342                buffer_start: Some(Point::new(0, 0)),
2343                status: None,
2344                excerpt_id: None,
2345            });
2346        } else {
2347            text.pop();
2348        }
2349
2350        // Retrieve the row info using the region that contains
2351        // the start of each multi-buffer line.
2352        let mut ix = 0;
2353        let row_infos = text
2354            .split('\n')
2355            .map(|line| {
2356                let row_info = regions
2357                    .iter()
2358                    .position(|region| region.range.contains(&ix))
2359                    .map_or(RowInfo::default(), |region_ix| {
2360                        let region = &regions[region_ix];
2361                        let buffer_row = region.buffer_start.map(|start_point| {
2362                            start_point.row
2363                                + text[region.range.start..ix].matches('\n').count() as u32
2364                        });
2365                        let is_excerpt_start = region_ix == 0
2366                            || &regions[region_ix - 1].excerpt_id != &region.excerpt_id
2367                            || regions[region_ix - 1].range.is_empty();
2368                        let mut is_excerpt_end = region_ix == regions.len() - 1
2369                            || &regions[region_ix + 1].excerpt_id != &region.excerpt_id;
2370                        let is_start = !text[region.range.start..ix].contains('\n');
2371                        let mut is_end = if region.range.end > text.len() {
2372                            !text[ix..].contains('\n')
2373                        } else {
2374                            text[ix..region.range.end.min(text.len())]
2375                                .matches('\n')
2376                                .count()
2377                                == 1
2378                        };
2379                        if region_ix < regions.len() - 1
2380                            && !text[ix..].contains("\n")
2381                            && region.status == Some(DiffHunkStatus::added_none())
2382                            && regions[region_ix + 1].excerpt_id == region.excerpt_id
2383                            && regions[region_ix + 1].range.start == text.len()
2384                        {
2385                            is_end = true;
2386                            is_excerpt_end = true;
2387                        }
2388                        let mut expand_direction = None;
2389                        if let Some(buffer) = &self
2390                            .excerpts
2391                            .iter()
2392                            .find(|e| e.id == region.excerpt_id.unwrap())
2393                            .map(|e| e.buffer.clone())
2394                        {
2395                            let needs_expand_up =
2396                                is_excerpt_start && is_start && buffer_row.unwrap() > 0;
2397                            let needs_expand_down = is_excerpt_end
2398                                && is_end
2399                                && buffer.read(cx).max_point().row > buffer_row.unwrap();
2400                            expand_direction = if needs_expand_up && needs_expand_down {
2401                                Some(ExpandExcerptDirection::UpAndDown)
2402                            } else if needs_expand_up {
2403                                Some(ExpandExcerptDirection::Up)
2404                            } else if needs_expand_down {
2405                                Some(ExpandExcerptDirection::Down)
2406                            } else {
2407                                None
2408                            };
2409                        }
2410                        RowInfo {
2411                            buffer_id: region.buffer_id,
2412                            diff_status: region.status,
2413                            buffer_row,
2414                            multibuffer_row: Some(MultiBufferRow(
2415                                text[..ix].matches('\n').count() as u32
2416                            )),
2417                            expand_info: expand_direction.zip(region.excerpt_id).map(
2418                                |(direction, excerpt_id)| ExpandInfo {
2419                                    direction,
2420                                    excerpt_id,
2421                                },
2422                            ),
2423                        }
2424                    });
2425                ix += line.len() + 1;
2426                row_info
2427            })
2428            .collect();
2429
2430        (text, row_infos, excerpt_boundary_rows)
2431    }
2432
2433    fn diffs_updated(&mut self, cx: &App) {
2434        for excerpt in &mut self.excerpts {
2435            let buffer = excerpt.buffer.read(cx).snapshot();
2436            let excerpt_range = excerpt.range.to_offset(&buffer);
2437            let buffer_id = buffer.remote_id();
2438            let diff = self.diffs.get(&buffer_id).unwrap().read(cx);
2439            let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer, cx).peekable();
2440            excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
2441                if !hunk_anchor.is_valid(&buffer) {
2442                    return false;
2443                }
2444                while let Some(hunk) = hunks.peek() {
2445                    match hunk.buffer_range.start.cmp(&hunk_anchor, &buffer) {
2446                        cmp::Ordering::Less => {
2447                            hunks.next();
2448                        }
2449                        cmp::Ordering::Equal => {
2450                            let hunk_range = hunk.buffer_range.to_offset(&buffer);
2451                            return hunk_range.end >= excerpt_range.start
2452                                && hunk_range.start <= excerpt_range.end;
2453                        }
2454                        cmp::Ordering::Greater => break,
2455                    }
2456                }
2457                false
2458            });
2459        }
2460    }
2461
2462    fn add_diff(&mut self, diff: Entity<BufferDiff>, cx: &mut App) {
2463        let buffer_id = diff.read(cx).buffer_id;
2464        self.diffs.insert(buffer_id, diff);
2465    }
2466}
2467
2468#[gpui::test(iterations = 100)]
2469async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
2470    let operations = env::var("OPERATIONS")
2471        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2472        .unwrap_or(10);
2473
2474    let mut buffers: Vec<Entity<Buffer>> = Vec::new();
2475    let mut base_texts: HashMap<BufferId, String> = HashMap::default();
2476    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2477    let mut reference = ReferenceMultibuffer::default();
2478    let mut anchors = Vec::new();
2479    let mut old_versions = Vec::new();
2480    let mut needs_diff_calculation = false;
2481
2482    for _ in 0..operations {
2483        match rng.gen_range(0..100) {
2484            0..=14 if !buffers.is_empty() => {
2485                let buffer = buffers.choose(&mut rng).unwrap();
2486                buffer.update(cx, |buf, cx| {
2487                    let edit_count = rng.gen_range(1..5);
2488                    buf.randomly_edit(&mut rng, edit_count, cx);
2489                    log::info!("buffer text:\n{}", buf.text());
2490                    needs_diff_calculation = true;
2491                });
2492                cx.update(|cx| reference.diffs_updated(cx));
2493            }
2494            15..=19 if !reference.excerpts.is_empty() => {
2495                multibuffer.update(cx, |multibuffer, cx| {
2496                    let ids = multibuffer.excerpt_ids();
2497                    let mut excerpts = HashSet::default();
2498                    for _ in 0..rng.gen_range(0..ids.len()) {
2499                        excerpts.extend(ids.choose(&mut rng).copied());
2500                    }
2501
2502                    let line_count = rng.gen_range(0..5);
2503
2504                    let excerpt_ixs = excerpts
2505                        .iter()
2506                        .map(|id| reference.excerpts.iter().position(|e| e.id == *id).unwrap())
2507                        .collect::<Vec<_>>();
2508                    log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
2509                    multibuffer.expand_excerpts(
2510                        excerpts.iter().cloned(),
2511                        line_count,
2512                        ExpandExcerptDirection::UpAndDown,
2513                        cx,
2514                    );
2515
2516                    reference.expand_excerpts(&excerpts, line_count, cx);
2517                });
2518            }
2519            20..=29 if !reference.excerpts.is_empty() => {
2520                let mut ids_to_remove = vec![];
2521                for _ in 0..rng.gen_range(1..=3) {
2522                    let Some(excerpt) = reference.excerpts.choose(&mut rng) else {
2523                        break;
2524                    };
2525                    let id = excerpt.id;
2526                    cx.update(|cx| reference.remove_excerpt(id, cx));
2527                    ids_to_remove.push(id);
2528                }
2529                let snapshot =
2530                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2531                ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
2532                drop(snapshot);
2533                multibuffer.update(cx, |multibuffer, cx| {
2534                    multibuffer.remove_excerpts(ids_to_remove, cx)
2535                });
2536            }
2537            30..=39 if !reference.excerpts.is_empty() => {
2538                let multibuffer =
2539                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2540                let offset =
2541                    multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
2542                let bias = if rng.r#gen() { Bias::Left } else { Bias::Right };
2543                log::info!("Creating anchor at {} with bias {:?}", offset, bias);
2544                anchors.push(multibuffer.anchor_at(offset, bias));
2545                anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
2546            }
2547            40..=44 if !anchors.is_empty() => {
2548                let multibuffer =
2549                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2550                let prev_len = anchors.len();
2551                anchors = multibuffer
2552                    .refresh_anchors(&anchors)
2553                    .into_iter()
2554                    .map(|a| a.1)
2555                    .collect();
2556
2557                // Ensure the newly-refreshed anchors point to a valid excerpt and don't
2558                // overshoot its boundaries.
2559                assert_eq!(anchors.len(), prev_len);
2560                for anchor in &anchors {
2561                    if anchor.excerpt_id == ExcerptId::min()
2562                        || anchor.excerpt_id == ExcerptId::max()
2563                    {
2564                        continue;
2565                    }
2566
2567                    let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
2568                    assert_eq!(excerpt.id, anchor.excerpt_id);
2569                    assert!(excerpt.contains(anchor));
2570                }
2571            }
2572            45..=55 if !reference.excerpts.is_empty() => {
2573                multibuffer.update(cx, |multibuffer, cx| {
2574                    let snapshot = multibuffer.snapshot(cx);
2575                    let excerpt_ix = rng.gen_range(0..reference.excerpts.len());
2576                    let excerpt = &reference.excerpts[excerpt_ix];
2577                    let start = excerpt.range.start;
2578                    let end = excerpt.range.end;
2579                    let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
2580                        ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
2581
2582                    log::info!(
2583                        "expanding diff hunks in range {:?} (excerpt id {:?}, index {excerpt_ix:?}, buffer id {:?})",
2584                        range.to_offset(&snapshot),
2585                        excerpt.id,
2586                        excerpt.buffer.read(cx).remote_id(),
2587                    );
2588                    reference.expand_diff_hunks(excerpt.id, start..end, cx);
2589                    multibuffer.expand_diff_hunks(vec![range], cx);
2590                });
2591            }
2592            56..=85 if needs_diff_calculation => {
2593                multibuffer.update(cx, |multibuffer, cx| {
2594                    for buffer in multibuffer.all_buffers() {
2595                        let snapshot = buffer.read(cx).snapshot();
2596                        multibuffer.diff_for(snapshot.remote_id()).unwrap().update(
2597                            cx,
2598                            |diff, cx| {
2599                                log::info!(
2600                                    "recalculating diff for buffer {:?}",
2601                                    snapshot.remote_id(),
2602                                );
2603                                diff.recalculate_diff_sync(snapshot.text, cx);
2604                            },
2605                        );
2606                    }
2607                    reference.diffs_updated(cx);
2608                    needs_diff_calculation = false;
2609                });
2610            }
2611            _ => {
2612                let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
2613                    let mut base_text = util::RandomCharIter::new(&mut rng)
2614                        .take(256)
2615                        .collect::<String>();
2616
2617                    let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
2618                    text::LineEnding::normalize(&mut base_text);
2619                    base_texts.insert(
2620                        buffer.read_with(cx, |buffer, _| buffer.remote_id()),
2621                        base_text,
2622                    );
2623                    buffers.push(buffer);
2624                    buffers.last().unwrap()
2625                } else {
2626                    buffers.choose(&mut rng).unwrap()
2627                };
2628
2629                let prev_excerpt_ix = rng.gen_range(0..=reference.excerpts.len());
2630                let prev_excerpt_id = reference
2631                    .excerpts
2632                    .get(prev_excerpt_ix)
2633                    .map_or(ExcerptId::max(), |e| e.id);
2634                let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
2635
2636                let (range, anchor_range) = buffer_handle.read_with(cx, |buffer, _| {
2637                    let end_row = rng.gen_range(0..=buffer.max_point().row);
2638                    let start_row = rng.gen_range(0..=end_row);
2639                    let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
2640                    let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
2641                    let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
2642
2643                    log::info!(
2644                        "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
2645                        excerpt_ix,
2646                        reference.excerpts.len(),
2647                        buffer.remote_id(),
2648                        buffer.text(),
2649                        start_ix..end_ix,
2650                        &buffer.text()[start_ix..end_ix]
2651                    );
2652
2653                    (start_ix..end_ix, anchor_range)
2654                });
2655
2656                multibuffer.update(cx, |multibuffer, cx| {
2657                    let id = buffer_handle.read(cx).remote_id();
2658                    if multibuffer.diff_for(id).is_none() {
2659                        let base_text = base_texts.get(&id).unwrap();
2660                        let diff = cx.new(|cx| {
2661                            BufferDiff::new_with_base_text(base_text, &buffer_handle, cx)
2662                        });
2663                        reference.add_diff(diff.clone(), cx);
2664                        multibuffer.add_diff(diff, cx)
2665                    }
2666                });
2667
2668                let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
2669                    multibuffer
2670                        .insert_excerpts_after(
2671                            prev_excerpt_id,
2672                            buffer_handle.clone(),
2673                            [ExcerptRange::new(range.clone())],
2674                            cx,
2675                        )
2676                        .pop()
2677                        .unwrap()
2678                });
2679
2680                reference.insert_excerpt_after(
2681                    prev_excerpt_id,
2682                    excerpt_id,
2683                    (buffer_handle.clone(), anchor_range),
2684                );
2685            }
2686        }
2687
2688        if rng.gen_bool(0.3) {
2689            multibuffer.update(cx, |multibuffer, cx| {
2690                old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
2691            })
2692        }
2693
2694        let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2695        let actual_text = snapshot.text();
2696        let actual_boundary_rows = snapshot
2697            .excerpt_boundaries_in_range(0..)
2698            .map(|b| b.row)
2699            .collect::<HashSet<_>>();
2700        let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2701
2702        let (expected_text, expected_row_infos, expected_boundary_rows) =
2703            cx.update(|cx| reference.expected_content(cx));
2704
2705        let has_diff = actual_row_infos
2706            .iter()
2707            .any(|info| info.diff_status.is_some())
2708            || expected_row_infos
2709                .iter()
2710                .any(|info| info.diff_status.is_some());
2711        let actual_diff = format_diff(
2712            &actual_text,
2713            &actual_row_infos,
2714            &actual_boundary_rows,
2715            Some(has_diff),
2716        );
2717        let expected_diff = format_diff(
2718            &expected_text,
2719            &expected_row_infos,
2720            &expected_boundary_rows,
2721            Some(has_diff),
2722        );
2723
2724        log::info!("Multibuffer content:\n{}", actual_diff);
2725
2726        assert_eq!(
2727            actual_row_infos.len(),
2728            actual_text.split('\n').count(),
2729            "line count: {}",
2730            actual_text.split('\n').count()
2731        );
2732        pretty_assertions::assert_eq!(actual_diff, expected_diff);
2733        pretty_assertions::assert_eq!(actual_text, expected_text);
2734        pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
2735
2736        for _ in 0..5 {
2737            let start_row = rng.gen_range(0..=expected_row_infos.len());
2738            assert_eq!(
2739                snapshot
2740                    .row_infos(MultiBufferRow(start_row as u32))
2741                    .collect::<Vec<_>>(),
2742                &expected_row_infos[start_row..],
2743                "buffer_rows({})",
2744                start_row
2745            );
2746        }
2747
2748        assert_eq!(
2749            snapshot.widest_line_number(),
2750            expected_row_infos
2751                .into_iter()
2752                .filter_map(|info| {
2753                    if info.diff_status.is_some_and(|status| status.is_deleted()) {
2754                        None
2755                    } else {
2756                        info.buffer_row
2757                    }
2758                })
2759                .max()
2760                .unwrap()
2761                + 1
2762        );
2763
2764        assert_consistent_line_numbers(&snapshot);
2765        assert_position_translation(&snapshot);
2766
2767        for (row, line) in expected_text.split('\n').enumerate() {
2768            assert_eq!(
2769                snapshot.line_len(MultiBufferRow(row as u32)),
2770                line.len() as u32,
2771                "line_len({}).",
2772                row
2773            );
2774        }
2775
2776        let text_rope = Rope::from(expected_text.as_str());
2777        for _ in 0..10 {
2778            let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2779            let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
2780
2781            let text_for_range = snapshot
2782                .text_for_range(start_ix..end_ix)
2783                .collect::<String>();
2784            assert_eq!(
2785                text_for_range,
2786                &expected_text[start_ix..end_ix],
2787                "incorrect text for range {:?}",
2788                start_ix..end_ix
2789            );
2790
2791            let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
2792            assert_eq!(
2793                snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
2794                expected_summary,
2795                "incorrect summary for range {:?}",
2796                start_ix..end_ix
2797            );
2798        }
2799
2800        // Anchor resolution
2801        let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
2802        assert_eq!(anchors.len(), summaries.len());
2803        for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
2804            assert!(resolved_offset <= snapshot.len());
2805            assert_eq!(
2806                snapshot.summary_for_anchor::<usize>(anchor),
2807                resolved_offset,
2808                "anchor: {:?}",
2809                anchor
2810            );
2811        }
2812
2813        for _ in 0..10 {
2814            let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2815            assert_eq!(
2816                snapshot.reversed_chars_at(end_ix).collect::<String>(),
2817                expected_text[..end_ix].chars().rev().collect::<String>(),
2818            );
2819        }
2820
2821        for _ in 0..10 {
2822            let end_ix = rng.gen_range(0..=text_rope.len());
2823            let start_ix = rng.gen_range(0..=end_ix);
2824            assert_eq!(
2825                snapshot
2826                    .bytes_in_range(start_ix..end_ix)
2827                    .flatten()
2828                    .copied()
2829                    .collect::<Vec<_>>(),
2830                expected_text.as_bytes()[start_ix..end_ix].to_vec(),
2831                "bytes_in_range({:?})",
2832                start_ix..end_ix,
2833            );
2834        }
2835    }
2836
2837    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2838    for (old_snapshot, subscription) in old_versions {
2839        let edits = subscription.consume().into_inner();
2840
2841        log::info!(
2842            "applying subscription edits to old text: {:?}: {:?}",
2843            old_snapshot.text(),
2844            edits,
2845        );
2846
2847        let mut text = old_snapshot.text();
2848        for edit in edits {
2849            let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
2850            text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
2851        }
2852        assert_eq!(text.to_string(), snapshot.text());
2853    }
2854}
2855
2856#[gpui::test]
2857fn test_history(cx: &mut App) {
2858    let test_settings = SettingsStore::test(cx);
2859    cx.set_global(test_settings);
2860    let group_interval: Duration = Duration::from_millis(1);
2861    let buffer_1 = cx.new(|cx| {
2862        let mut buf = Buffer::local("1234", cx);
2863        buf.set_group_interval(group_interval);
2864        buf
2865    });
2866    let buffer_2 = cx.new(|cx| {
2867        let mut buf = Buffer::local("5678", cx);
2868        buf.set_group_interval(group_interval);
2869        buf
2870    });
2871    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2872    multibuffer.update(cx, |this, _| {
2873        this.history.group_interval = group_interval;
2874    });
2875    multibuffer.update(cx, |multibuffer, cx| {
2876        multibuffer.push_excerpts(
2877            buffer_1.clone(),
2878            [ExcerptRange::new(0..buffer_1.read(cx).len())],
2879            cx,
2880        );
2881        multibuffer.push_excerpts(
2882            buffer_2.clone(),
2883            [ExcerptRange::new(0..buffer_2.read(cx).len())],
2884            cx,
2885        );
2886    });
2887
2888    let mut now = Instant::now();
2889
2890    multibuffer.update(cx, |multibuffer, cx| {
2891        let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
2892        multibuffer.edit(
2893            [
2894                (Point::new(0, 0)..Point::new(0, 0), "A"),
2895                (Point::new(1, 0)..Point::new(1, 0), "A"),
2896            ],
2897            None,
2898            cx,
2899        );
2900        multibuffer.edit(
2901            [
2902                (Point::new(0, 1)..Point::new(0, 1), "B"),
2903                (Point::new(1, 1)..Point::new(1, 1), "B"),
2904            ],
2905            None,
2906            cx,
2907        );
2908        multibuffer.end_transaction_at(now, cx);
2909        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2910
2911        // Verify edited ranges for transaction 1
2912        assert_eq!(
2913            multibuffer.edited_ranges_for_transaction(transaction_1, cx),
2914            &[
2915                Point::new(0, 0)..Point::new(0, 2),
2916                Point::new(1, 0)..Point::new(1, 2)
2917            ]
2918        );
2919
2920        // Edit buffer 1 through the multibuffer
2921        now += 2 * group_interval;
2922        multibuffer.start_transaction_at(now, cx);
2923        multibuffer.edit([(2..2, "C")], None, cx);
2924        multibuffer.end_transaction_at(now, cx);
2925        assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
2926
2927        // Edit buffer 1 independently
2928        buffer_1.update(cx, |buffer_1, cx| {
2929            buffer_1.start_transaction_at(now);
2930            buffer_1.edit([(3..3, "D")], None, cx);
2931            buffer_1.end_transaction_at(now, cx);
2932
2933            now += 2 * group_interval;
2934            buffer_1.start_transaction_at(now);
2935            buffer_1.edit([(4..4, "E")], None, cx);
2936            buffer_1.end_transaction_at(now, cx);
2937        });
2938        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2939
2940        // An undo in the multibuffer undoes the multibuffer transaction
2941        // and also any individual buffer edits that have occurred since
2942        // that transaction.
2943        multibuffer.undo(cx);
2944        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2945
2946        multibuffer.undo(cx);
2947        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2948
2949        multibuffer.redo(cx);
2950        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2951
2952        multibuffer.redo(cx);
2953        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2954
2955        // Undo buffer 2 independently.
2956        buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
2957        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
2958
2959        // An undo in the multibuffer undoes the components of the
2960        // the last multibuffer transaction that are not already undone.
2961        multibuffer.undo(cx);
2962        assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
2963
2964        multibuffer.undo(cx);
2965        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2966
2967        multibuffer.redo(cx);
2968        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2969
2970        buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
2971        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2972
2973        // Redo stack gets cleared after an edit.
2974        now += 2 * group_interval;
2975        multibuffer.start_transaction_at(now, cx);
2976        multibuffer.edit([(0..0, "X")], None, cx);
2977        multibuffer.end_transaction_at(now, cx);
2978        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2979        multibuffer.redo(cx);
2980        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2981        multibuffer.undo(cx);
2982        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2983        multibuffer.undo(cx);
2984        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2985
2986        // Transactions can be grouped manually.
2987        multibuffer.redo(cx);
2988        multibuffer.redo(cx);
2989        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2990        multibuffer.group_until_transaction(transaction_1, cx);
2991        multibuffer.undo(cx);
2992        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2993        multibuffer.redo(cx);
2994        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2995    });
2996}
2997
2998#[gpui::test]
2999async fn test_enclosing_indent(cx: &mut TestAppContext) {
3000    async fn enclosing_indent(
3001        text: &str,
3002        buffer_row: u32,
3003        cx: &mut TestAppContext,
3004    ) -> Option<(Range<u32>, LineIndent)> {
3005        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3006        let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
3007        let (range, indent) = snapshot
3008            .enclosing_indent(MultiBufferRow(buffer_row))
3009            .await?;
3010        Some((range.start.0..range.end.0, indent))
3011    }
3012
3013    assert_eq!(
3014        enclosing_indent(
3015            indoc!(
3016                "
3017                fn b() {
3018                    if c {
3019                        let d = 2;
3020                    }
3021                }
3022                "
3023            ),
3024            1,
3025            cx,
3026        )
3027        .await,
3028        Some((
3029            1..2,
3030            LineIndent {
3031                tabs: 0,
3032                spaces: 4,
3033                line_blank: false,
3034            }
3035        ))
3036    );
3037
3038    assert_eq!(
3039        enclosing_indent(
3040            indoc!(
3041                "
3042                fn b() {
3043                    if c {
3044                        let d = 2;
3045                    }
3046                }
3047                "
3048            ),
3049            2,
3050            cx,
3051        )
3052        .await,
3053        Some((
3054            1..2,
3055            LineIndent {
3056                tabs: 0,
3057                spaces: 4,
3058                line_blank: false,
3059            }
3060        ))
3061    );
3062
3063    assert_eq!(
3064        enclosing_indent(
3065            indoc!(
3066                "
3067                fn b() {
3068                    if c {
3069                        let d = 2;
3070
3071                        let e = 5;
3072                    }
3073                }
3074                "
3075            ),
3076            3,
3077            cx,
3078        )
3079        .await,
3080        Some((
3081            1..4,
3082            LineIndent {
3083                tabs: 0,
3084                spaces: 4,
3085                line_blank: false,
3086            }
3087        ))
3088    );
3089}
3090
3091#[gpui::test]
3092fn test_summaries_for_anchors(cx: &mut TestAppContext) {
3093    let base_text_1 = indoc!(
3094        "
3095        bar
3096        "
3097    );
3098    let text_1 = indoc!(
3099        "
3100        BAR
3101        "
3102    );
3103    let base_text_2 = indoc!(
3104        "
3105        foo
3106        "
3107    );
3108    let text_2 = indoc!(
3109        "
3110        FOO
3111        "
3112    );
3113
3114    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3115    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
3116    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_1, &buffer_1, cx));
3117    let diff_2 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_2, &buffer_2, cx));
3118    cx.run_until_parked();
3119
3120    let mut ids = vec![];
3121    let multibuffer = cx.new(|cx| {
3122        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
3123        multibuffer.set_all_diff_hunks_expanded(cx);
3124        ids.extend(multibuffer.push_excerpts(
3125            buffer_1.clone(),
3126            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
3127            cx,
3128        ));
3129        ids.extend(multibuffer.push_excerpts(
3130            buffer_2.clone(),
3131            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
3132            cx,
3133        ));
3134        multibuffer.add_diff(diff_1.clone(), cx);
3135        multibuffer.add_diff(diff_2.clone(), cx);
3136        multibuffer
3137    });
3138
3139    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3140        (multibuffer.snapshot(cx), multibuffer.subscribe())
3141    });
3142
3143    assert_new_snapshot(
3144        &multibuffer,
3145        &mut snapshot,
3146        &mut subscription,
3147        cx,
3148        indoc!(
3149            "
3150            - bar
3151            + BAR
3152
3153            - foo
3154            + FOO
3155            "
3156        ),
3157    );
3158
3159    let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
3160    let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
3161
3162    let anchor_1 = Anchor::in_buffer(ids[0], id_1, text::Anchor::MIN);
3163    let point_1 = snapshot.summaries_for_anchors::<Point, _>([&anchor_1])[0];
3164    assert_eq!(point_1, Point::new(0, 0));
3165
3166    let anchor_2 = Anchor::in_buffer(ids[1], id_2, text::Anchor::MIN);
3167    let point_2 = snapshot.summaries_for_anchors::<Point, _>([&anchor_2])[0];
3168    assert_eq!(point_2, Point::new(3, 0));
3169}
3170
3171#[gpui::test]
3172fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
3173    let base_text_1 = "one\ntwo".to_owned();
3174    let text_1 = "one\n".to_owned();
3175
3176    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3177    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(&base_text_1, &buffer_1, cx));
3178    cx.run_until_parked();
3179
3180    let multibuffer = cx.new(|cx| {
3181        let mut multibuffer = MultiBuffer::singleton(buffer_1.clone(), cx);
3182        multibuffer.add_diff(diff_1.clone(), cx);
3183        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
3184        multibuffer
3185    });
3186
3187    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3188        (multibuffer.snapshot(cx), multibuffer.subscribe())
3189    });
3190
3191    assert_new_snapshot(
3192        &multibuffer,
3193        &mut snapshot,
3194        &mut subscription,
3195        cx,
3196        indoc!(
3197            "
3198              one
3199            - two
3200            "
3201        ),
3202    );
3203
3204    assert_eq!(snapshot.max_point(), Point::new(2, 0));
3205    assert_eq!(snapshot.len(), 8);
3206
3207    assert_eq!(
3208        snapshot
3209            .dimensions_from_points::<Point>([Point::new(2, 0)])
3210            .collect::<Vec<_>>(),
3211        vec![Point::new(2, 0)]
3212    );
3213
3214    let (_, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3215    assert_eq!(translated_offset, "one\n".len());
3216    let (_, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3217    assert_eq!(translated_point, Point::new(1, 0));
3218
3219    // The same, for an excerpt that's not at the end of the multibuffer.
3220
3221    let text_2 = "foo\n".to_owned();
3222    let buffer_2 = cx.new(|cx| Buffer::local(&text_2, cx));
3223    multibuffer.update(cx, |multibuffer, cx| {
3224        multibuffer.push_excerpts(
3225            buffer_2.clone(),
3226            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
3227            cx,
3228        );
3229    });
3230
3231    assert_new_snapshot(
3232        &multibuffer,
3233        &mut snapshot,
3234        &mut subscription,
3235        cx,
3236        indoc!(
3237            "
3238              one
3239            - two
3240
3241              foo
3242            "
3243        ),
3244    );
3245
3246    assert_eq!(
3247        snapshot
3248            .dimensions_from_points::<Point>([Point::new(2, 0)])
3249            .collect::<Vec<_>>(),
3250        vec![Point::new(2, 0)]
3251    );
3252
3253    let buffer_1_id = buffer_1.read_with(cx, |buffer_1, _| buffer_1.remote_id());
3254    let (buffer, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3255    assert_eq!(buffer.remote_id(), buffer_1_id);
3256    assert_eq!(translated_offset, "one\n".len());
3257    let (buffer, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3258    assert_eq!(buffer.remote_id(), buffer_1_id);
3259    assert_eq!(translated_point, Point::new(1, 0));
3260}
3261
3262fn format_diff(
3263    text: &str,
3264    row_infos: &Vec<RowInfo>,
3265    boundary_rows: &HashSet<MultiBufferRow>,
3266    has_diff: Option<bool>,
3267) -> String {
3268    let has_diff =
3269        has_diff.unwrap_or_else(|| row_infos.iter().any(|info| info.diff_status.is_some()));
3270    text.split('\n')
3271        .enumerate()
3272        .zip(row_infos)
3273        .map(|((ix, line), info)| {
3274            let marker = match info.diff_status.map(|status| status.kind) {
3275                Some(DiffHunkStatusKind::Added) => "+ ",
3276                Some(DiffHunkStatusKind::Deleted) => "- ",
3277                Some(DiffHunkStatusKind::Modified) => unreachable!(),
3278                None => {
3279                    if has_diff && !line.is_empty() {
3280                        "  "
3281                    } else {
3282                        ""
3283                    }
3284                }
3285            };
3286            let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
3287                if has_diff {
3288                    "  ----------\n"
3289                } else {
3290                    "---------\n"
3291                }
3292            } else {
3293                ""
3294            };
3295            format!("{boundary_row}{marker}{line}")
3296        })
3297        .collect::<Vec<_>>()
3298        .join("\n")
3299}
3300
3301#[track_caller]
3302fn assert_excerpts_match(
3303    multibuffer: &Entity<MultiBuffer>,
3304    cx: &mut TestAppContext,
3305    expected: &str,
3306) {
3307    let mut output = String::new();
3308    multibuffer.read_with(cx, |multibuffer, cx| {
3309        for (_, buffer, range) in multibuffer.snapshot(cx).excerpts() {
3310            output.push_str("-----\n");
3311            output.extend(buffer.text_for_range(range.context));
3312            if !output.ends_with('\n') {
3313                output.push('\n');
3314            }
3315        }
3316    });
3317    assert_eq!(output, expected);
3318}
3319
3320#[track_caller]
3321fn assert_new_snapshot(
3322    multibuffer: &Entity<MultiBuffer>,
3323    snapshot: &mut MultiBufferSnapshot,
3324    subscription: &mut Subscription,
3325    cx: &mut TestAppContext,
3326    expected_diff: &str,
3327) {
3328    let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3329    let actual_text = new_snapshot.text();
3330    let line_infos = new_snapshot
3331        .row_infos(MultiBufferRow(0))
3332        .collect::<Vec<_>>();
3333    let actual_diff = format_diff(&actual_text, &line_infos, &Default::default(), None);
3334    pretty_assertions::assert_eq!(actual_diff, expected_diff);
3335    check_edits(
3336        snapshot,
3337        &new_snapshot,
3338        &subscription.consume().into_inner(),
3339    );
3340    *snapshot = new_snapshot;
3341}
3342
3343#[track_caller]
3344fn check_edits(
3345    old_snapshot: &MultiBufferSnapshot,
3346    new_snapshot: &MultiBufferSnapshot,
3347    edits: &[Edit<usize>],
3348) {
3349    let mut text = old_snapshot.text();
3350    let new_text = new_snapshot.text();
3351    for edit in edits.iter().rev() {
3352        if !text.is_char_boundary(edit.old.start)
3353            || !text.is_char_boundary(edit.old.end)
3354            || !new_text.is_char_boundary(edit.new.start)
3355            || !new_text.is_char_boundary(edit.new.end)
3356        {
3357            panic!(
3358                "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
3359                edits, text, new_text
3360            );
3361        }
3362
3363        text.replace_range(
3364            edit.old.start..edit.old.end,
3365            &new_text[edit.new.start..edit.new.end],
3366        );
3367    }
3368
3369    pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
3370}
3371
3372#[track_caller]
3373fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
3374    let full_text = snapshot.text();
3375    for ix in 0..full_text.len() {
3376        let mut chunks = snapshot.chunks(0..snapshot.len(), false);
3377        chunks.seek(ix..snapshot.len());
3378        let tail = chunks.map(|chunk| chunk.text).collect::<String>();
3379        assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
3380    }
3381}
3382
3383#[track_caller]
3384fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
3385    let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3386    for start_row in 1..all_line_numbers.len() {
3387        let line_numbers = snapshot
3388            .row_infos(MultiBufferRow(start_row as u32))
3389            .collect::<Vec<_>>();
3390        assert_eq!(
3391            line_numbers,
3392            all_line_numbers[start_row..],
3393            "start_row: {start_row}"
3394        );
3395    }
3396}
3397
3398#[track_caller]
3399fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
3400    let text = Rope::from(snapshot.text());
3401
3402    let mut left_anchors = Vec::new();
3403    let mut right_anchors = Vec::new();
3404    let mut offsets = Vec::new();
3405    let mut points = Vec::new();
3406    for offset in 0..=text.len() + 1 {
3407        let clipped_left = snapshot.clip_offset(offset, Bias::Left);
3408        let clipped_right = snapshot.clip_offset(offset, Bias::Right);
3409        assert_eq!(
3410            clipped_left,
3411            text.clip_offset(offset, Bias::Left),
3412            "clip_offset({offset:?}, Left)"
3413        );
3414        assert_eq!(
3415            clipped_right,
3416            text.clip_offset(offset, Bias::Right),
3417            "clip_offset({offset:?}, Right)"
3418        );
3419        assert_eq!(
3420            snapshot.offset_to_point(clipped_left),
3421            text.offset_to_point(clipped_left),
3422            "offset_to_point({clipped_left})"
3423        );
3424        assert_eq!(
3425            snapshot.offset_to_point(clipped_right),
3426            text.offset_to_point(clipped_right),
3427            "offset_to_point({clipped_right})"
3428        );
3429        let anchor_after = snapshot.anchor_after(clipped_left);
3430        assert_eq!(
3431            anchor_after.to_offset(snapshot),
3432            clipped_left,
3433            "anchor_after({clipped_left}).to_offset {anchor_after:?}"
3434        );
3435        let anchor_before = snapshot.anchor_before(clipped_left);
3436        assert_eq!(
3437            anchor_before.to_offset(snapshot),
3438            clipped_left,
3439            "anchor_before({clipped_left}).to_offset"
3440        );
3441        left_anchors.push(anchor_before);
3442        right_anchors.push(anchor_after);
3443        offsets.push(clipped_left);
3444        points.push(text.offset_to_point(clipped_left));
3445    }
3446
3447    for row in 0..text.max_point().row {
3448        for column in 0..text.line_len(row) + 1 {
3449            let point = Point { row, column };
3450            let clipped_left = snapshot.clip_point(point, Bias::Left);
3451            let clipped_right = snapshot.clip_point(point, Bias::Right);
3452            assert_eq!(
3453                clipped_left,
3454                text.clip_point(point, Bias::Left),
3455                "clip_point({point:?}, Left)"
3456            );
3457            assert_eq!(
3458                clipped_right,
3459                text.clip_point(point, Bias::Right),
3460                "clip_point({point:?}, Right)"
3461            );
3462            assert_eq!(
3463                snapshot.point_to_offset(clipped_left),
3464                text.point_to_offset(clipped_left),
3465                "point_to_offset({clipped_left:?})"
3466            );
3467            assert_eq!(
3468                snapshot.point_to_offset(clipped_right),
3469                text.point_to_offset(clipped_right),
3470                "point_to_offset({clipped_right:?})"
3471            );
3472        }
3473    }
3474
3475    assert_eq!(
3476        snapshot.summaries_for_anchors::<usize, _>(&left_anchors),
3477        offsets,
3478        "left_anchors <-> offsets"
3479    );
3480    assert_eq!(
3481        snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
3482        points,
3483        "left_anchors <-> points"
3484    );
3485    assert_eq!(
3486        snapshot.summaries_for_anchors::<usize, _>(&right_anchors),
3487        offsets,
3488        "right_anchors <-> offsets"
3489    );
3490    assert_eq!(
3491        snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
3492        points,
3493        "right_anchors <-> points"
3494    );
3495
3496    for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
3497        for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
3498            if ix > 0 {
3499                if *offset == 252 {
3500                    if offset > &offsets[ix - 1] {
3501                        let prev_anchor = left_anchors[ix - 1];
3502                        assert!(
3503                            anchor.cmp(&prev_anchor, snapshot).is_gt(),
3504                            "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
3505                            offsets[ix],
3506                            offsets[ix - 1],
3507                        );
3508                        assert!(
3509                            prev_anchor.cmp(&anchor, snapshot).is_lt(),
3510                            "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
3511                            offsets[ix - 1],
3512                            offsets[ix],
3513                        );
3514                    }
3515                }
3516            }
3517        }
3518    }
3519
3520    if let Some((buffer, offset)) = snapshot.point_to_buffer_offset(snapshot.max_point()) {
3521        assert!(offset <= buffer.len());
3522    }
3523    if let Some((buffer, point, _)) = snapshot.point_to_buffer_point(snapshot.max_point()) {
3524        assert!(point <= buffer.max_point());
3525    }
3526}
3527
3528fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
3529    let max_row = snapshot.max_point().row;
3530    let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
3531    let text = text::Buffer::new(0, buffer_id, snapshot.text());
3532    let mut line_indents = text
3533        .line_indents_in_row_range(0..max_row + 1)
3534        .collect::<Vec<_>>();
3535    for start_row in 0..snapshot.max_point().row {
3536        pretty_assertions::assert_eq!(
3537            snapshot
3538                .line_indents(MultiBufferRow(start_row), |_| true)
3539                .map(|(row, indent, _)| (row.0, indent))
3540                .collect::<Vec<_>>(),
3541            &line_indents[(start_row as usize)..],
3542            "line_indents({start_row})"
3543        );
3544    }
3545
3546    line_indents.reverse();
3547    pretty_assertions::assert_eq!(
3548        snapshot
3549            .reversed_line_indents(MultiBufferRow(max_row), |_| true)
3550            .map(|(row, indent, _)| (row.0, indent))
3551            .collect::<Vec<_>>(),
3552        &line_indents[..],
3553        "reversed_line_indents({max_row})"
3554    );
3555}