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.gen_range(0..=10);
2495        let max_row = snapshot.max_point().row;
2496        let mut ranges = (0..num_ranges)
2497            .map(|_| {
2498                let start = rng.gen_range(0..max_row);
2499                let end = rng.gen_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.gen_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.gen_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.gen_range(0..ids.len()) {
2581                        excerpts.extend(ids.choose(&mut rng).copied());
2582                    }
2583
2584                    let line_count = rng.gen_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.gen_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.gen_range(0..=multibuffer.len()), Bias::Left);
2624                let bias = if rng.r#gen() { Bias::Left } else { Bias::Right };
2625                log::info!("Creating anchor at {} with bias {:?}", offset, bias);
2626                anchors.push(multibuffer.anchor_at(offset, bias));
2627                anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
2628            }
2629            40..=44 if !anchors.is_empty() => {
2630                let multibuffer =
2631                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2632                let prev_len = anchors.len();
2633                anchors = multibuffer
2634                    .refresh_anchors(&anchors)
2635                    .into_iter()
2636                    .map(|a| a.1)
2637                    .collect();
2638
2639                // Ensure the newly-refreshed anchors point to a valid excerpt and don't
2640                // overshoot its boundaries.
2641                assert_eq!(anchors.len(), prev_len);
2642                for anchor in &anchors {
2643                    if anchor.excerpt_id == ExcerptId::min()
2644                        || anchor.excerpt_id == ExcerptId::max()
2645                    {
2646                        continue;
2647                    }
2648
2649                    let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
2650                    assert_eq!(excerpt.id, anchor.excerpt_id);
2651                    assert!(excerpt.contains(anchor));
2652                }
2653            }
2654            45..=55 if !reference.excerpts.is_empty() => {
2655                multibuffer.update(cx, |multibuffer, cx| {
2656                    let snapshot = multibuffer.snapshot(cx);
2657                    let excerpt_ix = rng.gen_range(0..reference.excerpts.len());
2658                    let excerpt = &reference.excerpts[excerpt_ix];
2659                    let start = excerpt.range.start;
2660                    let end = excerpt.range.end;
2661                    let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
2662                        ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
2663
2664                    log::info!(
2665                        "expanding diff hunks in range {:?} (excerpt id {:?}, index {excerpt_ix:?}, buffer id {:?})",
2666                        range.to_offset(&snapshot),
2667                        excerpt.id,
2668                        excerpt.buffer.read(cx).remote_id(),
2669                    );
2670                    reference.expand_diff_hunks(excerpt.id, start..end, cx);
2671                    multibuffer.expand_diff_hunks(vec![range], cx);
2672                });
2673            }
2674            56..=85 if needs_diff_calculation => {
2675                multibuffer.update(cx, |multibuffer, cx| {
2676                    for buffer in multibuffer.all_buffers() {
2677                        let snapshot = buffer.read(cx).snapshot();
2678                        multibuffer.diff_for(snapshot.remote_id()).unwrap().update(
2679                            cx,
2680                            |diff, cx| {
2681                                log::info!(
2682                                    "recalculating diff for buffer {:?}",
2683                                    snapshot.remote_id(),
2684                                );
2685                                diff.recalculate_diff_sync(snapshot.text, cx);
2686                            },
2687                        );
2688                    }
2689                    reference.diffs_updated(cx);
2690                    needs_diff_calculation = false;
2691                });
2692            }
2693            _ => {
2694                let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
2695                    let mut base_text = util::RandomCharIter::new(&mut rng)
2696                        .take(256)
2697                        .collect::<String>();
2698
2699                    let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
2700                    text::LineEnding::normalize(&mut base_text);
2701                    base_texts.insert(
2702                        buffer.read_with(cx, |buffer, _| buffer.remote_id()),
2703                        base_text,
2704                    );
2705                    buffers.push(buffer);
2706                    buffers.last().unwrap()
2707                } else {
2708                    buffers.choose(&mut rng).unwrap()
2709                };
2710
2711                let prev_excerpt_ix = rng.gen_range(0..=reference.excerpts.len());
2712                let prev_excerpt_id = reference
2713                    .excerpts
2714                    .get(prev_excerpt_ix)
2715                    .map_or(ExcerptId::max(), |e| e.id);
2716                let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
2717
2718                let (range, anchor_range) = buffer_handle.read_with(cx, |buffer, _| {
2719                    let end_row = rng.gen_range(0..=buffer.max_point().row);
2720                    let start_row = rng.gen_range(0..=end_row);
2721                    let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
2722                    let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
2723                    let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
2724
2725                    log::info!(
2726                        "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
2727                        excerpt_ix,
2728                        reference.excerpts.len(),
2729                        buffer.remote_id(),
2730                        buffer.text(),
2731                        start_ix..end_ix,
2732                        &buffer.text()[start_ix..end_ix]
2733                    );
2734
2735                    (start_ix..end_ix, anchor_range)
2736                });
2737
2738                multibuffer.update(cx, |multibuffer, cx| {
2739                    let id = buffer_handle.read(cx).remote_id();
2740                    if multibuffer.diff_for(id).is_none() {
2741                        let base_text = base_texts.get(&id).unwrap();
2742                        let diff = cx
2743                            .new(|cx| BufferDiff::new_with_base_text(base_text, buffer_handle, cx));
2744                        reference.add_diff(diff.clone(), cx);
2745                        multibuffer.add_diff(diff, cx)
2746                    }
2747                });
2748
2749                let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
2750                    multibuffer
2751                        .insert_excerpts_after(
2752                            prev_excerpt_id,
2753                            buffer_handle.clone(),
2754                            [ExcerptRange::new(range.clone())],
2755                            cx,
2756                        )
2757                        .pop()
2758                        .unwrap()
2759                });
2760
2761                reference.insert_excerpt_after(
2762                    prev_excerpt_id,
2763                    excerpt_id,
2764                    (buffer_handle.clone(), anchor_range),
2765                );
2766            }
2767        }
2768
2769        if rng.gen_bool(0.3) {
2770            multibuffer.update(cx, |multibuffer, cx| {
2771                old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
2772            })
2773        }
2774
2775        let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2776        let actual_text = snapshot.text();
2777        let actual_boundary_rows = snapshot
2778            .excerpt_boundaries_in_range(0..)
2779            .map(|b| b.row)
2780            .collect::<HashSet<_>>();
2781        let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2782
2783        let (expected_text, expected_row_infos, expected_boundary_rows) =
2784            cx.update(|cx| reference.expected_content(cx));
2785
2786        let has_diff = actual_row_infos
2787            .iter()
2788            .any(|info| info.diff_status.is_some())
2789            || expected_row_infos
2790                .iter()
2791                .any(|info| info.diff_status.is_some());
2792        let actual_diff = format_diff(
2793            &actual_text,
2794            &actual_row_infos,
2795            &actual_boundary_rows,
2796            Some(has_diff),
2797        );
2798        let expected_diff = format_diff(
2799            &expected_text,
2800            &expected_row_infos,
2801            &expected_boundary_rows,
2802            Some(has_diff),
2803        );
2804
2805        log::info!("Multibuffer content:\n{}", actual_diff);
2806
2807        assert_eq!(
2808            actual_row_infos.len(),
2809            actual_text.split('\n').count(),
2810            "line count: {}",
2811            actual_text.split('\n').count()
2812        );
2813        pretty_assertions::assert_eq!(actual_diff, expected_diff);
2814        pretty_assertions::assert_eq!(actual_text, expected_text);
2815        pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
2816
2817        for _ in 0..5 {
2818            let start_row = rng.gen_range(0..=expected_row_infos.len());
2819            assert_eq!(
2820                snapshot
2821                    .row_infos(MultiBufferRow(start_row as u32))
2822                    .collect::<Vec<_>>(),
2823                &expected_row_infos[start_row..],
2824                "buffer_rows({})",
2825                start_row
2826            );
2827        }
2828
2829        assert_eq!(
2830            snapshot.widest_line_number(),
2831            expected_row_infos
2832                .into_iter()
2833                .filter_map(|info| {
2834                    if info.diff_status.is_some_and(|status| status.is_deleted()) {
2835                        None
2836                    } else {
2837                        info.buffer_row
2838                    }
2839                })
2840                .max()
2841                .unwrap()
2842                + 1
2843        );
2844        let reference_ranges = cx.update(|cx| {
2845            reference
2846                .excerpts
2847                .iter()
2848                .map(|excerpt| {
2849                    (
2850                        excerpt.id,
2851                        excerpt.range.to_offset(&excerpt.buffer.read(cx).snapshot()),
2852                    )
2853                })
2854                .collect::<HashMap<_, _>>()
2855        });
2856        for i in 0..snapshot.len() {
2857            let excerpt = snapshot.excerpt_containing(i..i).unwrap();
2858            assert_eq!(excerpt.buffer_range(), reference_ranges[&excerpt.id()]);
2859        }
2860
2861        assert_consistent_line_numbers(&snapshot);
2862        assert_position_translation(&snapshot);
2863
2864        for (row, line) in expected_text.split('\n').enumerate() {
2865            assert_eq!(
2866                snapshot.line_len(MultiBufferRow(row as u32)),
2867                line.len() as u32,
2868                "line_len({}).",
2869                row
2870            );
2871        }
2872
2873        let text_rope = Rope::from(expected_text.as_str());
2874        for _ in 0..10 {
2875            let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2876            let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
2877
2878            let text_for_range = snapshot
2879                .text_for_range(start_ix..end_ix)
2880                .collect::<String>();
2881            assert_eq!(
2882                text_for_range,
2883                &expected_text[start_ix..end_ix],
2884                "incorrect text for range {:?}",
2885                start_ix..end_ix
2886            );
2887
2888            let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
2889            assert_eq!(
2890                snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
2891                expected_summary,
2892                "incorrect summary for range {:?}",
2893                start_ix..end_ix
2894            );
2895        }
2896
2897        // Anchor resolution
2898        let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
2899        assert_eq!(anchors.len(), summaries.len());
2900        for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
2901            assert!(resolved_offset <= snapshot.len());
2902            assert_eq!(
2903                snapshot.summary_for_anchor::<usize>(anchor),
2904                resolved_offset,
2905                "anchor: {:?}",
2906                anchor
2907            );
2908        }
2909
2910        for _ in 0..10 {
2911            let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2912            assert_eq!(
2913                snapshot.reversed_chars_at(end_ix).collect::<String>(),
2914                expected_text[..end_ix].chars().rev().collect::<String>(),
2915            );
2916        }
2917
2918        for _ in 0..10 {
2919            let end_ix = rng.gen_range(0..=text_rope.len());
2920            let start_ix = rng.gen_range(0..=end_ix);
2921            assert_eq!(
2922                snapshot
2923                    .bytes_in_range(start_ix..end_ix)
2924                    .flatten()
2925                    .copied()
2926                    .collect::<Vec<_>>(),
2927                expected_text.as_bytes()[start_ix..end_ix].to_vec(),
2928                "bytes_in_range({:?})",
2929                start_ix..end_ix,
2930            );
2931        }
2932    }
2933
2934    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2935    for (old_snapshot, subscription) in old_versions {
2936        let edits = subscription.consume().into_inner();
2937
2938        log::info!(
2939            "applying subscription edits to old text: {:?}: {:?}",
2940            old_snapshot.text(),
2941            edits,
2942        );
2943
2944        let mut text = old_snapshot.text();
2945        for edit in edits {
2946            let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
2947            text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
2948        }
2949        assert_eq!(text.to_string(), snapshot.text());
2950    }
2951}
2952
2953#[gpui::test]
2954fn test_history(cx: &mut App) {
2955    let test_settings = SettingsStore::test(cx);
2956    cx.set_global(test_settings);
2957    let group_interval: Duration = Duration::from_millis(1);
2958    let buffer_1 = cx.new(|cx| {
2959        let mut buf = Buffer::local("1234", cx);
2960        buf.set_group_interval(group_interval);
2961        buf
2962    });
2963    let buffer_2 = cx.new(|cx| {
2964        let mut buf = Buffer::local("5678", cx);
2965        buf.set_group_interval(group_interval);
2966        buf
2967    });
2968    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2969    multibuffer.update(cx, |this, _| {
2970        this.history.group_interval = group_interval;
2971    });
2972    multibuffer.update(cx, |multibuffer, cx| {
2973        multibuffer.push_excerpts(
2974            buffer_1.clone(),
2975            [ExcerptRange::new(0..buffer_1.read(cx).len())],
2976            cx,
2977        );
2978        multibuffer.push_excerpts(
2979            buffer_2.clone(),
2980            [ExcerptRange::new(0..buffer_2.read(cx).len())],
2981            cx,
2982        );
2983    });
2984
2985    let mut now = Instant::now();
2986
2987    multibuffer.update(cx, |multibuffer, cx| {
2988        let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
2989        multibuffer.edit(
2990            [
2991                (Point::new(0, 0)..Point::new(0, 0), "A"),
2992                (Point::new(1, 0)..Point::new(1, 0), "A"),
2993            ],
2994            None,
2995            cx,
2996        );
2997        multibuffer.edit(
2998            [
2999                (Point::new(0, 1)..Point::new(0, 1), "B"),
3000                (Point::new(1, 1)..Point::new(1, 1), "B"),
3001            ],
3002            None,
3003            cx,
3004        );
3005        multibuffer.end_transaction_at(now, cx);
3006        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3007
3008        // Verify edited ranges for transaction 1
3009        assert_eq!(
3010            multibuffer.edited_ranges_for_transaction(transaction_1, cx),
3011            &[
3012                Point::new(0, 0)..Point::new(0, 2),
3013                Point::new(1, 0)..Point::new(1, 2)
3014            ]
3015        );
3016
3017        // Edit buffer 1 through the multibuffer
3018        now += 2 * group_interval;
3019        multibuffer.start_transaction_at(now, cx);
3020        multibuffer.edit([(2..2, "C")], None, cx);
3021        multibuffer.end_transaction_at(now, cx);
3022        assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
3023
3024        // Edit buffer 1 independently
3025        buffer_1.update(cx, |buffer_1, cx| {
3026            buffer_1.start_transaction_at(now);
3027            buffer_1.edit([(3..3, "D")], None, cx);
3028            buffer_1.end_transaction_at(now, cx);
3029
3030            now += 2 * group_interval;
3031            buffer_1.start_transaction_at(now);
3032            buffer_1.edit([(4..4, "E")], None, cx);
3033            buffer_1.end_transaction_at(now, cx);
3034        });
3035        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
3036
3037        // An undo in the multibuffer undoes the multibuffer transaction
3038        // and also any individual buffer edits that have occurred since
3039        // that transaction.
3040        multibuffer.undo(cx);
3041        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3042
3043        multibuffer.undo(cx);
3044        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3045
3046        multibuffer.redo(cx);
3047        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3048
3049        multibuffer.redo(cx);
3050        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
3051
3052        // Undo buffer 2 independently.
3053        buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
3054        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
3055
3056        // An undo in the multibuffer undoes the components of the
3057        // the last multibuffer transaction that are not already undone.
3058        multibuffer.undo(cx);
3059        assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
3060
3061        multibuffer.undo(cx);
3062        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3063
3064        multibuffer.redo(cx);
3065        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3066
3067        buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
3068        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
3069
3070        // Redo stack gets cleared after an edit.
3071        now += 2 * group_interval;
3072        multibuffer.start_transaction_at(now, cx);
3073        multibuffer.edit([(0..0, "X")], None, cx);
3074        multibuffer.end_transaction_at(now, cx);
3075        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3076        multibuffer.redo(cx);
3077        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3078        multibuffer.undo(cx);
3079        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
3080        multibuffer.undo(cx);
3081        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3082
3083        // Transactions can be grouped manually.
3084        multibuffer.redo(cx);
3085        multibuffer.redo(cx);
3086        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3087        multibuffer.group_until_transaction(transaction_1, cx);
3088        multibuffer.undo(cx);
3089        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3090        multibuffer.redo(cx);
3091        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3092    });
3093}
3094
3095#[gpui::test]
3096async fn test_enclosing_indent(cx: &mut TestAppContext) {
3097    async fn enclosing_indent(
3098        text: &str,
3099        buffer_row: u32,
3100        cx: &mut TestAppContext,
3101    ) -> Option<(Range<u32>, LineIndent)> {
3102        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3103        let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
3104        let (range, indent) = snapshot
3105            .enclosing_indent(MultiBufferRow(buffer_row))
3106            .await?;
3107        Some((range.start.0..range.end.0, indent))
3108    }
3109
3110    assert_eq!(
3111        enclosing_indent(
3112            indoc!(
3113                "
3114                fn b() {
3115                    if c {
3116                        let d = 2;
3117                    }
3118                }
3119                "
3120            ),
3121            1,
3122            cx,
3123        )
3124        .await,
3125        Some((
3126            1..2,
3127            LineIndent {
3128                tabs: 0,
3129                spaces: 4,
3130                line_blank: false,
3131            }
3132        ))
3133    );
3134
3135    assert_eq!(
3136        enclosing_indent(
3137            indoc!(
3138                "
3139                fn b() {
3140                    if c {
3141                        let d = 2;
3142                    }
3143                }
3144                "
3145            ),
3146            2,
3147            cx,
3148        )
3149        .await,
3150        Some((
3151            1..2,
3152            LineIndent {
3153                tabs: 0,
3154                spaces: 4,
3155                line_blank: false,
3156            }
3157        ))
3158    );
3159
3160    assert_eq!(
3161        enclosing_indent(
3162            indoc!(
3163                "
3164                fn b() {
3165                    if c {
3166                        let d = 2;
3167
3168                        let e = 5;
3169                    }
3170                }
3171                "
3172            ),
3173            3,
3174            cx,
3175        )
3176        .await,
3177        Some((
3178            1..4,
3179            LineIndent {
3180                tabs: 0,
3181                spaces: 4,
3182                line_blank: false,
3183            }
3184        ))
3185    );
3186}
3187
3188#[gpui::test]
3189fn test_summaries_for_anchors(cx: &mut TestAppContext) {
3190    let base_text_1 = indoc!(
3191        "
3192        bar
3193        "
3194    );
3195    let text_1 = indoc!(
3196        "
3197        BAR
3198        "
3199    );
3200    let base_text_2 = indoc!(
3201        "
3202        foo
3203        "
3204    );
3205    let text_2 = indoc!(
3206        "
3207        FOO
3208        "
3209    );
3210
3211    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3212    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
3213    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_1, &buffer_1, cx));
3214    let diff_2 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_2, &buffer_2, cx));
3215    cx.run_until_parked();
3216
3217    let mut ids = vec![];
3218    let multibuffer = cx.new(|cx| {
3219        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
3220        multibuffer.set_all_diff_hunks_expanded(cx);
3221        ids.extend(multibuffer.push_excerpts(
3222            buffer_1.clone(),
3223            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
3224            cx,
3225        ));
3226        ids.extend(multibuffer.push_excerpts(
3227            buffer_2.clone(),
3228            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
3229            cx,
3230        ));
3231        multibuffer.add_diff(diff_1.clone(), cx);
3232        multibuffer.add_diff(diff_2.clone(), cx);
3233        multibuffer
3234    });
3235
3236    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3237        (multibuffer.snapshot(cx), multibuffer.subscribe())
3238    });
3239
3240    assert_new_snapshot(
3241        &multibuffer,
3242        &mut snapshot,
3243        &mut subscription,
3244        cx,
3245        indoc!(
3246            "
3247            - bar
3248            + BAR
3249
3250            - foo
3251            + FOO
3252            "
3253        ),
3254    );
3255
3256    let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
3257    let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
3258
3259    let anchor_1 = Anchor::in_buffer(ids[0], id_1, text::Anchor::MIN);
3260    let point_1 = snapshot.summaries_for_anchors::<Point, _>([&anchor_1])[0];
3261    assert_eq!(point_1, Point::new(0, 0));
3262
3263    let anchor_2 = Anchor::in_buffer(ids[1], id_2, text::Anchor::MIN);
3264    let point_2 = snapshot.summaries_for_anchors::<Point, _>([&anchor_2])[0];
3265    assert_eq!(point_2, Point::new(3, 0));
3266}
3267
3268#[gpui::test]
3269fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
3270    let base_text_1 = "one\ntwo".to_owned();
3271    let text_1 = "one\n".to_owned();
3272
3273    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3274    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(&base_text_1, &buffer_1, cx));
3275    cx.run_until_parked();
3276
3277    let multibuffer = cx.new(|cx| {
3278        let mut multibuffer = MultiBuffer::singleton(buffer_1.clone(), cx);
3279        multibuffer.add_diff(diff_1.clone(), cx);
3280        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
3281        multibuffer
3282    });
3283
3284    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3285        (multibuffer.snapshot(cx), multibuffer.subscribe())
3286    });
3287
3288    assert_new_snapshot(
3289        &multibuffer,
3290        &mut snapshot,
3291        &mut subscription,
3292        cx,
3293        indoc!(
3294            "
3295              one
3296            - two
3297            "
3298        ),
3299    );
3300
3301    assert_eq!(snapshot.max_point(), Point::new(2, 0));
3302    assert_eq!(snapshot.len(), 8);
3303
3304    assert_eq!(
3305        snapshot
3306            .dimensions_from_points::<Point>([Point::new(2, 0)])
3307            .collect::<Vec<_>>(),
3308        vec![Point::new(2, 0)]
3309    );
3310
3311    let (_, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3312    assert_eq!(translated_offset, "one\n".len());
3313    let (_, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3314    assert_eq!(translated_point, Point::new(1, 0));
3315
3316    // The same, for an excerpt that's not at the end of the multibuffer.
3317
3318    let text_2 = "foo\n".to_owned();
3319    let buffer_2 = cx.new(|cx| Buffer::local(&text_2, cx));
3320    multibuffer.update(cx, |multibuffer, cx| {
3321        multibuffer.push_excerpts(
3322            buffer_2.clone(),
3323            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
3324            cx,
3325        );
3326    });
3327
3328    assert_new_snapshot(
3329        &multibuffer,
3330        &mut snapshot,
3331        &mut subscription,
3332        cx,
3333        indoc!(
3334            "
3335              one
3336            - two
3337
3338              foo
3339            "
3340        ),
3341    );
3342
3343    assert_eq!(
3344        snapshot
3345            .dimensions_from_points::<Point>([Point::new(2, 0)])
3346            .collect::<Vec<_>>(),
3347        vec![Point::new(2, 0)]
3348    );
3349
3350    let buffer_1_id = buffer_1.read_with(cx, |buffer_1, _| buffer_1.remote_id());
3351    let (buffer, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3352    assert_eq!(buffer.remote_id(), buffer_1_id);
3353    assert_eq!(translated_offset, "one\n".len());
3354    let (buffer, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3355    assert_eq!(buffer.remote_id(), buffer_1_id);
3356    assert_eq!(translated_point, Point::new(1, 0));
3357}
3358
3359fn format_diff(
3360    text: &str,
3361    row_infos: &Vec<RowInfo>,
3362    boundary_rows: &HashSet<MultiBufferRow>,
3363    has_diff: Option<bool>,
3364) -> String {
3365    let has_diff =
3366        has_diff.unwrap_or_else(|| row_infos.iter().any(|info| info.diff_status.is_some()));
3367    text.split('\n')
3368        .enumerate()
3369        .zip(row_infos)
3370        .map(|((ix, line), info)| {
3371            let marker = match info.diff_status.map(|status| status.kind) {
3372                Some(DiffHunkStatusKind::Added) => "+ ",
3373                Some(DiffHunkStatusKind::Deleted) => "- ",
3374                Some(DiffHunkStatusKind::Modified) => unreachable!(),
3375                None => {
3376                    if has_diff && !line.is_empty() {
3377                        "  "
3378                    } else {
3379                        ""
3380                    }
3381                }
3382            };
3383            let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
3384                if has_diff {
3385                    "  ----------\n"
3386                } else {
3387                    "---------\n"
3388                }
3389            } else {
3390                ""
3391            };
3392            format!("{boundary_row}{marker}{line}")
3393        })
3394        .collect::<Vec<_>>()
3395        .join("\n")
3396}
3397
3398#[track_caller]
3399fn assert_excerpts_match(
3400    multibuffer: &Entity<MultiBuffer>,
3401    cx: &mut TestAppContext,
3402    expected: &str,
3403) {
3404    let mut output = String::new();
3405    multibuffer.read_with(cx, |multibuffer, cx| {
3406        for (_, buffer, range) in multibuffer.snapshot(cx).excerpts() {
3407            output.push_str("-----\n");
3408            output.extend(buffer.text_for_range(range.context));
3409            if !output.ends_with('\n') {
3410                output.push('\n');
3411            }
3412        }
3413    });
3414    assert_eq!(output, expected);
3415}
3416
3417#[track_caller]
3418fn assert_new_snapshot(
3419    multibuffer: &Entity<MultiBuffer>,
3420    snapshot: &mut MultiBufferSnapshot,
3421    subscription: &mut Subscription,
3422    cx: &mut TestAppContext,
3423    expected_diff: &str,
3424) {
3425    let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3426    let actual_text = new_snapshot.text();
3427    let line_infos = new_snapshot
3428        .row_infos(MultiBufferRow(0))
3429        .collect::<Vec<_>>();
3430    let actual_diff = format_diff(&actual_text, &line_infos, &Default::default(), None);
3431    pretty_assertions::assert_eq!(actual_diff, expected_diff);
3432    check_edits(
3433        snapshot,
3434        &new_snapshot,
3435        &subscription.consume().into_inner(),
3436    );
3437    *snapshot = new_snapshot;
3438}
3439
3440#[track_caller]
3441fn check_edits(
3442    old_snapshot: &MultiBufferSnapshot,
3443    new_snapshot: &MultiBufferSnapshot,
3444    edits: &[Edit<usize>],
3445) {
3446    let mut text = old_snapshot.text();
3447    let new_text = new_snapshot.text();
3448    for edit in edits.iter().rev() {
3449        if !text.is_char_boundary(edit.old.start)
3450            || !text.is_char_boundary(edit.old.end)
3451            || !new_text.is_char_boundary(edit.new.start)
3452            || !new_text.is_char_boundary(edit.new.end)
3453        {
3454            panic!(
3455                "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
3456                edits, text, new_text
3457            );
3458        }
3459
3460        text.replace_range(
3461            edit.old.start..edit.old.end,
3462            &new_text[edit.new.start..edit.new.end],
3463        );
3464    }
3465
3466    pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
3467}
3468
3469#[track_caller]
3470fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
3471    let full_text = snapshot.text();
3472    for ix in 0..full_text.len() {
3473        let mut chunks = snapshot.chunks(0..snapshot.len(), false);
3474        chunks.seek(ix..snapshot.len());
3475        let tail = chunks.map(|chunk| chunk.text).collect::<String>();
3476        assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
3477    }
3478}
3479
3480#[track_caller]
3481fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
3482    let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3483    for start_row in 1..all_line_numbers.len() {
3484        let line_numbers = snapshot
3485            .row_infos(MultiBufferRow(start_row as u32))
3486            .collect::<Vec<_>>();
3487        assert_eq!(
3488            line_numbers,
3489            all_line_numbers[start_row..],
3490            "start_row: {start_row}"
3491        );
3492    }
3493}
3494
3495#[track_caller]
3496fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
3497    let text = Rope::from(snapshot.text());
3498
3499    let mut left_anchors = Vec::new();
3500    let mut right_anchors = Vec::new();
3501    let mut offsets = Vec::new();
3502    let mut points = Vec::new();
3503    for offset in 0..=text.len() + 1 {
3504        let clipped_left = snapshot.clip_offset(offset, Bias::Left);
3505        let clipped_right = snapshot.clip_offset(offset, Bias::Right);
3506        assert_eq!(
3507            clipped_left,
3508            text.clip_offset(offset, Bias::Left),
3509            "clip_offset({offset:?}, Left)"
3510        );
3511        assert_eq!(
3512            clipped_right,
3513            text.clip_offset(offset, Bias::Right),
3514            "clip_offset({offset:?}, Right)"
3515        );
3516        assert_eq!(
3517            snapshot.offset_to_point(clipped_left),
3518            text.offset_to_point(clipped_left),
3519            "offset_to_point({clipped_left})"
3520        );
3521        assert_eq!(
3522            snapshot.offset_to_point(clipped_right),
3523            text.offset_to_point(clipped_right),
3524            "offset_to_point({clipped_right})"
3525        );
3526        let anchor_after = snapshot.anchor_after(clipped_left);
3527        assert_eq!(
3528            anchor_after.to_offset(snapshot),
3529            clipped_left,
3530            "anchor_after({clipped_left}).to_offset {anchor_after:?}"
3531        );
3532        let anchor_before = snapshot.anchor_before(clipped_left);
3533        assert_eq!(
3534            anchor_before.to_offset(snapshot),
3535            clipped_left,
3536            "anchor_before({clipped_left}).to_offset"
3537        );
3538        left_anchors.push(anchor_before);
3539        right_anchors.push(anchor_after);
3540        offsets.push(clipped_left);
3541        points.push(text.offset_to_point(clipped_left));
3542    }
3543
3544    for row in 0..text.max_point().row {
3545        for column in 0..text.line_len(row) + 1 {
3546            let point = Point { row, column };
3547            let clipped_left = snapshot.clip_point(point, Bias::Left);
3548            let clipped_right = snapshot.clip_point(point, Bias::Right);
3549            assert_eq!(
3550                clipped_left,
3551                text.clip_point(point, Bias::Left),
3552                "clip_point({point:?}, Left)"
3553            );
3554            assert_eq!(
3555                clipped_right,
3556                text.clip_point(point, Bias::Right),
3557                "clip_point({point:?}, Right)"
3558            );
3559            assert_eq!(
3560                snapshot.point_to_offset(clipped_left),
3561                text.point_to_offset(clipped_left),
3562                "point_to_offset({clipped_left:?})"
3563            );
3564            assert_eq!(
3565                snapshot.point_to_offset(clipped_right),
3566                text.point_to_offset(clipped_right),
3567                "point_to_offset({clipped_right:?})"
3568            );
3569        }
3570    }
3571
3572    assert_eq!(
3573        snapshot.summaries_for_anchors::<usize, _>(&left_anchors),
3574        offsets,
3575        "left_anchors <-> offsets"
3576    );
3577    assert_eq!(
3578        snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
3579        points,
3580        "left_anchors <-> points"
3581    );
3582    assert_eq!(
3583        snapshot.summaries_for_anchors::<usize, _>(&right_anchors),
3584        offsets,
3585        "right_anchors <-> offsets"
3586    );
3587    assert_eq!(
3588        snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
3589        points,
3590        "right_anchors <-> points"
3591    );
3592
3593    for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
3594        for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
3595            if ix > 0 && *offset == 252 && offset > &offsets[ix - 1] {
3596                let prev_anchor = left_anchors[ix - 1];
3597                assert!(
3598                    anchor.cmp(&prev_anchor, snapshot).is_gt(),
3599                    "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
3600                    offsets[ix],
3601                    offsets[ix - 1],
3602                );
3603                assert!(
3604                    prev_anchor.cmp(anchor, snapshot).is_lt(),
3605                    "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
3606                    offsets[ix - 1],
3607                    offsets[ix],
3608                );
3609            }
3610        }
3611    }
3612
3613    if let Some((buffer, offset)) = snapshot.point_to_buffer_offset(snapshot.max_point()) {
3614        assert!(offset <= buffer.len());
3615    }
3616    if let Some((buffer, point, _)) = snapshot.point_to_buffer_point(snapshot.max_point()) {
3617        assert!(point <= buffer.max_point());
3618    }
3619}
3620
3621fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
3622    let max_row = snapshot.max_point().row;
3623    let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
3624    let text = text::Buffer::new(0, buffer_id, snapshot.text());
3625    let mut line_indents = text
3626        .line_indents_in_row_range(0..max_row + 1)
3627        .collect::<Vec<_>>();
3628    for start_row in 0..snapshot.max_point().row {
3629        pretty_assertions::assert_eq!(
3630            snapshot
3631                .line_indents(MultiBufferRow(start_row), |_| true)
3632                .map(|(row, indent, _)| (row.0, indent))
3633                .collect::<Vec<_>>(),
3634            &line_indents[(start_row as usize)..],
3635            "line_indents({start_row})"
3636        );
3637    }
3638
3639    line_indents.reverse();
3640    pretty_assertions::assert_eq!(
3641        snapshot
3642            .reversed_line_indents(MultiBufferRow(max_row), |_| true)
3643            .map(|(row, indent, _)| (row.0, indent))
3644            .collect::<Vec<_>>(),
3645        &line_indents[..],
3646        "reversed_line_indents({max_row})"
3647    );
3648}
3649
3650#[gpui::test]
3651fn test_new_empty_buffer_uses_untitled_title(cx: &mut App) {
3652    let buffer = cx.new(|cx| Buffer::local("", cx));
3653    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3654
3655    assert_eq!(multibuffer.read(cx).title(cx), "untitled");
3656}
3657
3658#[gpui::test]
3659fn test_new_empty_buffer_uses_untitled_title_when_only_contains_whitespace(cx: &mut App) {
3660    let buffer = cx.new(|cx| Buffer::local("\n ", cx));
3661    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3662
3663    assert_eq!(multibuffer.read(cx).title(cx), "untitled");
3664}
3665
3666#[gpui::test]
3667fn test_new_empty_buffer_takes_first_line_for_title(cx: &mut App) {
3668    let buffer = cx.new(|cx| Buffer::local("Hello World\nSecond line", cx));
3669    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3670
3671    assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
3672}
3673
3674#[gpui::test]
3675fn test_new_empty_buffer_takes_trimmed_first_line_for_title(cx: &mut App) {
3676    let buffer = cx.new(|cx| Buffer::local("\nHello, World ", cx));
3677    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3678
3679    assert_eq!(multibuffer.read(cx).title(cx), "Hello, World");
3680}
3681
3682#[gpui::test]
3683fn test_new_empty_buffer_uses_truncated_first_line_for_title(cx: &mut App) {
3684    let title = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee";
3685    let title_after = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd";
3686    let buffer = cx.new(|cx| Buffer::local(title, cx));
3687    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3688
3689    assert_eq!(multibuffer.read(cx).title(cx), title_after);
3690}
3691
3692#[gpui::test]
3693fn test_new_empty_buffer_uses_truncated_first_line_for_title_after_merging_adjacent_spaces(
3694    cx: &mut App,
3695) {
3696    let title = "aaaaaaaaaabbbbbbbbbb    ccccccccccddddddddddeeeeeeeeee";
3697    let title_after = "aaaaaaaaaabbbbbbbbbb ccccccccccddddddddd";
3698    let buffer = cx.new(|cx| Buffer::local(title, cx));
3699    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3700
3701    assert_eq!(multibuffer.read(cx).title(cx), title_after);
3702}
3703
3704#[gpui::test]
3705fn test_new_empty_buffers_title_can_be_set(cx: &mut App) {
3706    let buffer = cx.new(|cx| Buffer::local("Hello World", cx));
3707    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3708    assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
3709
3710    multibuffer.update(cx, |multibuffer, cx| {
3711        multibuffer.set_title("Hey".into(), cx)
3712    });
3713    assert_eq!(multibuffer.read(cx).title(cx), "Hey");
3714}