multi_buffer_tests.rs

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