multi_buffer_tests.rs

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