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