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