multi_buffer_tests.rs

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