multi_buffer_tests.rs

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