multi_buffer_tests.rs

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