multi_buffer_tests.rs

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