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