multi_buffer_tests.rs

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