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