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