multi_buffer_tests.rs

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