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