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