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             -----
1596             nine
1597             ten
1598             eleven
1599            "
1600        },
1601    );
1602}
1603
1604#[gpui::test]
1605fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
1606    let buf1 = cx.new(|cx| {
1607        Buffer::local(
1608            indoc! {
1609            "zero
1610            one
1611            two
1612            three
1613            four
1614            five
1615            six
1616            seven
1617            ",
1618            },
1619            cx,
1620        )
1621    });
1622    let path1: PathKey = PathKey::namespaced(0, Path::new("/").into());
1623    let buf2 = cx.new(|cx| {
1624        Buffer::local(
1625            indoc! {
1626            "000
1627            111
1628            222
1629            333
1630            444
1631            555
1632            666
1633            777
1634            888
1635            999
1636            "
1637            },
1638            cx,
1639        )
1640    });
1641    let path2 = PathKey::namespaced(1, Path::new("/").into());
1642
1643    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1644    multibuffer.update(cx, |multibuffer, cx| {
1645        multibuffer.set_excerpts_for_path(
1646            path1.clone(),
1647            buf1.clone(),
1648            vec![Point::row_range(0..1)],
1649            2,
1650            cx,
1651        );
1652    });
1653
1654    assert_excerpts_match(
1655        &multibuffer,
1656        cx,
1657        indoc! {
1658        "-----
1659        zero
1660        one
1661        two
1662        three
1663        "
1664        },
1665    );
1666
1667    multibuffer.update(cx, |multibuffer, cx| {
1668        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1669    });
1670
1671    assert_excerpts_match(&multibuffer, cx, "");
1672
1673    multibuffer.update(cx, |multibuffer, cx| {
1674        multibuffer.set_excerpts_for_path(
1675            path1.clone(),
1676            buf1.clone(),
1677            vec![Point::row_range(0..1), Point::row_range(7..8)],
1678            2,
1679            cx,
1680        );
1681    });
1682
1683    assert_excerpts_match(
1684        &multibuffer,
1685        cx,
1686        indoc! {"-----
1687                zero
1688                one
1689                two
1690                three
1691                -----
1692                five
1693                six
1694                seven
1695                "},
1696    );
1697
1698    multibuffer.update(cx, |multibuffer, cx| {
1699        multibuffer.set_excerpts_for_path(
1700            path1.clone(),
1701            buf1.clone(),
1702            vec![Point::row_range(0..1), Point::row_range(5..6)],
1703            2,
1704            cx,
1705        );
1706    });
1707
1708    assert_excerpts_match(
1709        &multibuffer,
1710        cx,
1711        indoc! {"-----
1712                    zero
1713                    one
1714                    two
1715                    three
1716                    four
1717                    five
1718                    six
1719                    seven
1720                    "},
1721    );
1722
1723    multibuffer.update(cx, |multibuffer, cx| {
1724        multibuffer.set_excerpts_for_path(
1725            path2.clone(),
1726            buf2.clone(),
1727            vec![Point::row_range(2..3)],
1728            2,
1729            cx,
1730        );
1731    });
1732
1733    assert_excerpts_match(
1734        &multibuffer,
1735        cx,
1736        indoc! {"-----
1737                zero
1738                one
1739                two
1740                three
1741                four
1742                five
1743                six
1744                seven
1745                -----
1746                000
1747                111
1748                222
1749                333
1750                444
1751                555
1752                "},
1753    );
1754
1755    multibuffer.update(cx, |multibuffer, cx| {
1756        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1757    });
1758
1759    multibuffer.update(cx, |multibuffer, cx| {
1760        multibuffer.set_excerpts_for_path(
1761            path1.clone(),
1762            buf1.clone(),
1763            vec![Point::row_range(3..4)],
1764            2,
1765            cx,
1766        );
1767    });
1768
1769    assert_excerpts_match(
1770        &multibuffer,
1771        cx,
1772        indoc! {"-----
1773                one
1774                two
1775                three
1776                four
1777                five
1778                six
1779                -----
1780                000
1781                111
1782                222
1783                333
1784                444
1785                555
1786                "},
1787    );
1788
1789    multibuffer.update(cx, |multibuffer, cx| {
1790        multibuffer.set_excerpts_for_path(
1791            path1.clone(),
1792            buf1.clone(),
1793            vec![Point::row_range(3..4)],
1794            2,
1795            cx,
1796        );
1797    });
1798}
1799
1800#[gpui::test]
1801fn test_set_excerpts_for_buffer_rename(cx: &mut TestAppContext) {
1802    let buf1 = cx.new(|cx| {
1803        Buffer::local(
1804            indoc! {
1805            "zero
1806            one
1807            two
1808            three
1809            four
1810            five
1811            six
1812            seven
1813            ",
1814            },
1815            cx,
1816        )
1817    });
1818    let path: PathKey = PathKey::namespaced(0, Path::new("/").into());
1819    let buf2 = cx.new(|cx| {
1820        Buffer::local(
1821            indoc! {
1822            "000
1823            111
1824            222
1825            333
1826            "
1827            },
1828            cx,
1829        )
1830    });
1831
1832    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1833    multibuffer.update(cx, |multibuffer, cx| {
1834        multibuffer.set_excerpts_for_path(
1835            path.clone(),
1836            buf1.clone(),
1837            vec![Point::row_range(1..1), Point::row_range(4..5)],
1838            1,
1839            cx,
1840        );
1841    });
1842
1843    assert_excerpts_match(
1844        &multibuffer,
1845        cx,
1846        indoc! {
1847        "-----
1848        zero
1849        one
1850        two
1851        -----
1852        three
1853        four
1854        five
1855        six
1856        "
1857        },
1858    );
1859
1860    multibuffer.update(cx, |multibuffer, cx| {
1861        multibuffer.set_excerpts_for_path(
1862            path.clone(),
1863            buf2.clone(),
1864            vec![Point::row_range(0..1)],
1865            2,
1866            cx,
1867        );
1868    });
1869
1870    assert_excerpts_match(
1871        &multibuffer,
1872        cx,
1873        indoc! {"-----
1874                000
1875                111
1876                222
1877                333
1878                "},
1879    );
1880}
1881
1882#[gpui::test]
1883fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
1884    let base_text_1 = indoc!(
1885        "
1886        one
1887        two
1888            three
1889        four
1890        five
1891        six
1892        "
1893    );
1894    let text_1 = indoc!(
1895        "
1896        ZERO
1897        one
1898        TWO
1899            three
1900        six
1901        "
1902    );
1903    let base_text_2 = indoc!(
1904        "
1905        seven
1906          eight
1907        nine
1908        ten
1909        eleven
1910        twelve
1911        "
1912    );
1913    let text_2 = indoc!(
1914        "
1915          eight
1916        nine
1917        eleven
1918        THIRTEEN
1919        FOURTEEN
1920        "
1921    );
1922
1923    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
1924    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
1925    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_1, &buffer_1, cx));
1926    let diff_2 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_2, &buffer_2, cx));
1927    cx.run_until_parked();
1928
1929    let multibuffer = cx.new(|cx| {
1930        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1931        multibuffer.push_excerpts(
1932            buffer_1.clone(),
1933            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
1934            cx,
1935        );
1936        multibuffer.push_excerpts(
1937            buffer_2.clone(),
1938            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
1939            cx,
1940        );
1941        multibuffer.add_diff(diff_1.clone(), cx);
1942        multibuffer.add_diff(diff_2.clone(), cx);
1943        multibuffer
1944    });
1945
1946    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1947        (multibuffer.snapshot(cx), multibuffer.subscribe())
1948    });
1949    assert_eq!(
1950        snapshot.text(),
1951        indoc!(
1952            "
1953            ZERO
1954            one
1955            TWO
1956                three
1957            six
1958
1959              eight
1960            nine
1961            eleven
1962            THIRTEEN
1963            FOURTEEN
1964            "
1965        ),
1966    );
1967
1968    multibuffer.update(cx, |multibuffer, cx| {
1969        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1970    });
1971
1972    assert_new_snapshot(
1973        &multibuffer,
1974        &mut snapshot,
1975        &mut subscription,
1976        cx,
1977        indoc!(
1978            "
1979            + ZERO
1980              one
1981            - two
1982            + TWO
1983                  three
1984            - four
1985            - five
1986              six
1987
1988            - seven
1989                eight
1990              nine
1991            - ten
1992              eleven
1993            - twelve
1994            + THIRTEEN
1995            + FOURTEEN
1996            "
1997        ),
1998    );
1999
2000    let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
2001    let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
2002    let base_id_1 = diff_1.read_with(cx, |diff, _| diff.base_text().remote_id());
2003    let base_id_2 = diff_2.read_with(cx, |diff, _| diff.base_text().remote_id());
2004
2005    let buffer_lines = (0..=snapshot.max_row().0)
2006        .map(|row| {
2007            let (buffer, range) = snapshot.buffer_line_for_row(MultiBufferRow(row))?;
2008            Some((
2009                buffer.remote_id(),
2010                buffer.text_for_range(range).collect::<String>(),
2011            ))
2012        })
2013        .collect::<Vec<_>>();
2014    pretty_assertions::assert_eq!(
2015        buffer_lines,
2016        [
2017            Some((id_1, "ZERO".into())),
2018            Some((id_1, "one".into())),
2019            Some((base_id_1, "two".into())),
2020            Some((id_1, "TWO".into())),
2021            Some((id_1, "    three".into())),
2022            Some((base_id_1, "four".into())),
2023            Some((base_id_1, "five".into())),
2024            Some((id_1, "six".into())),
2025            Some((id_1, "".into())),
2026            Some((base_id_2, "seven".into())),
2027            Some((id_2, "  eight".into())),
2028            Some((id_2, "nine".into())),
2029            Some((base_id_2, "ten".into())),
2030            Some((id_2, "eleven".into())),
2031            Some((base_id_2, "twelve".into())),
2032            Some((id_2, "THIRTEEN".into())),
2033            Some((id_2, "FOURTEEN".into())),
2034            Some((id_2, "".into())),
2035        ]
2036    );
2037
2038    let buffer_ids_by_range = [
2039        (Point::new(0, 0)..Point::new(0, 0), &[id_1] as &[_]),
2040        (Point::new(0, 0)..Point::new(2, 0), &[id_1]),
2041        (Point::new(2, 0)..Point::new(2, 0), &[id_1]),
2042        (Point::new(3, 0)..Point::new(3, 0), &[id_1]),
2043        (Point::new(8, 0)..Point::new(9, 0), &[id_1]),
2044        (Point::new(8, 0)..Point::new(10, 0), &[id_1, id_2]),
2045        (Point::new(9, 0)..Point::new(9, 0), &[id_2]),
2046    ];
2047    for (range, buffer_ids) in buffer_ids_by_range {
2048        assert_eq!(
2049            snapshot
2050                .buffer_ids_for_range(range.clone())
2051                .collect::<Vec<_>>(),
2052            buffer_ids,
2053            "buffer_ids_for_range({range:?}"
2054        );
2055    }
2056
2057    assert_position_translation(&snapshot);
2058    assert_line_indents(&snapshot);
2059
2060    assert_eq!(
2061        snapshot
2062            .diff_hunks_in_range(0..snapshot.len())
2063            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
2064            .collect::<Vec<_>>(),
2065        &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
2066    );
2067
2068    buffer_2.update(cx, |buffer, cx| {
2069        buffer.edit_via_marked_text(
2070            indoc!(
2071                "
2072                  eight
2073                «»eleven
2074                THIRTEEN
2075                FOURTEEN
2076                "
2077            ),
2078            None,
2079            cx,
2080        );
2081    });
2082
2083    assert_new_snapshot(
2084        &multibuffer,
2085        &mut snapshot,
2086        &mut subscription,
2087        cx,
2088        indoc!(
2089            "
2090            + ZERO
2091              one
2092            - two
2093            + TWO
2094                  three
2095            - four
2096            - five
2097              six
2098
2099            - seven
2100                eight
2101              eleven
2102            - twelve
2103            + THIRTEEN
2104            + FOURTEEN
2105            "
2106        ),
2107    );
2108
2109    assert_line_indents(&snapshot);
2110}
2111
2112/// A naive implementation of a multi-buffer that does not maintain
2113/// any derived state, used for comparison in a randomized test.
2114#[derive(Default)]
2115struct ReferenceMultibuffer {
2116    excerpts: Vec<ReferenceExcerpt>,
2117    diffs: HashMap<BufferId, Entity<BufferDiff>>,
2118}
2119
2120#[derive(Debug)]
2121struct ReferenceExcerpt {
2122    id: ExcerptId,
2123    buffer: Entity<Buffer>,
2124    range: Range<text::Anchor>,
2125    expanded_diff_hunks: Vec<text::Anchor>,
2126}
2127
2128#[derive(Debug)]
2129struct ReferenceRegion {
2130    buffer_id: Option<BufferId>,
2131    range: Range<usize>,
2132    buffer_start: Option<Point>,
2133    status: Option<DiffHunkStatus>,
2134    excerpt_id: Option<ExcerptId>,
2135}
2136
2137impl ReferenceMultibuffer {
2138    fn expand_excerpts(&mut self, excerpts: &HashSet<ExcerptId>, line_count: u32, cx: &App) {
2139        if line_count == 0 {
2140            return;
2141        }
2142
2143        for id in excerpts {
2144            let excerpt = self.excerpts.iter_mut().find(|e| e.id == *id).unwrap();
2145            let snapshot = excerpt.buffer.read(cx).snapshot();
2146            let mut point_range = excerpt.range.to_point(&snapshot);
2147            point_range.start = Point::new(point_range.start.row.saturating_sub(line_count), 0);
2148            point_range.end =
2149                snapshot.clip_point(Point::new(point_range.end.row + line_count, 0), Bias::Left);
2150            point_range.end.column = snapshot.line_len(point_range.end.row);
2151            excerpt.range =
2152                snapshot.anchor_before(point_range.start)..snapshot.anchor_after(point_range.end);
2153        }
2154    }
2155
2156    fn remove_excerpt(&mut self, id: ExcerptId, cx: &App) {
2157        let ix = self
2158            .excerpts
2159            .iter()
2160            .position(|excerpt| excerpt.id == id)
2161            .unwrap();
2162        let excerpt = self.excerpts.remove(ix);
2163        let buffer = excerpt.buffer.read(cx);
2164        let id = buffer.remote_id();
2165        log::info!(
2166            "Removing excerpt {}: {:?}",
2167            ix,
2168            buffer
2169                .text_for_range(excerpt.range.to_offset(buffer))
2170                .collect::<String>(),
2171        );
2172        if !self
2173            .excerpts
2174            .iter()
2175            .any(|excerpt| excerpt.buffer.read(cx).remote_id() == id)
2176        {
2177            self.diffs.remove(&id);
2178        }
2179    }
2180
2181    fn insert_excerpt_after(
2182        &mut self,
2183        prev_id: ExcerptId,
2184        new_excerpt_id: ExcerptId,
2185        (buffer_handle, anchor_range): (Entity<Buffer>, Range<text::Anchor>),
2186    ) {
2187        let excerpt_ix = if prev_id == ExcerptId::max() {
2188            self.excerpts.len()
2189        } else {
2190            self.excerpts
2191                .iter()
2192                .position(|excerpt| excerpt.id == prev_id)
2193                .unwrap()
2194                + 1
2195        };
2196        self.excerpts.insert(
2197            excerpt_ix,
2198            ReferenceExcerpt {
2199                id: new_excerpt_id,
2200                buffer: buffer_handle,
2201                range: anchor_range,
2202                expanded_diff_hunks: Vec::new(),
2203            },
2204        );
2205    }
2206
2207    fn expand_diff_hunks(&mut self, excerpt_id: ExcerptId, range: Range<text::Anchor>, cx: &App) {
2208        let excerpt = self
2209            .excerpts
2210            .iter_mut()
2211            .find(|e| e.id == excerpt_id)
2212            .unwrap();
2213        let buffer = excerpt.buffer.read(cx).snapshot();
2214        let buffer_id = buffer.remote_id();
2215        let Some(diff) = self.diffs.get(&buffer_id) else {
2216            return;
2217        };
2218        let excerpt_range = excerpt.range.to_offset(&buffer);
2219        for hunk in diff.read(cx).hunks_intersecting_range(range, &buffer, cx) {
2220            let hunk_range = hunk.buffer_range.to_offset(&buffer);
2221            if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end {
2222                continue;
2223            }
2224            if let Err(ix) = excerpt
2225                .expanded_diff_hunks
2226                .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
2227            {
2228                log::info!(
2229                    "expanding diff hunk {:?}. excerpt:{:?}, excerpt range:{:?}",
2230                    hunk_range,
2231                    excerpt_id,
2232                    excerpt_range
2233                );
2234                excerpt
2235                    .expanded_diff_hunks
2236                    .insert(ix, hunk.buffer_range.start);
2237            } else {
2238                log::trace!("hunk {hunk_range:?} already expanded in excerpt {excerpt_id:?}");
2239            }
2240        }
2241    }
2242
2243    fn expected_content(&self, cx: &App) -> (String, Vec<RowInfo>, HashSet<MultiBufferRow>) {
2244        let mut text = String::new();
2245        let mut regions = Vec::<ReferenceRegion>::new();
2246        let mut excerpt_boundary_rows = HashSet::default();
2247        for excerpt in &self.excerpts {
2248            excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
2249            let buffer = excerpt.buffer.read(cx);
2250            let buffer_range = excerpt.range.to_offset(buffer);
2251            let diff = self.diffs.get(&buffer.remote_id()).unwrap().read(cx);
2252            let base_buffer = diff.base_text();
2253
2254            let mut offset = buffer_range.start;
2255            let mut hunks = diff
2256                .hunks_intersecting_range(excerpt.range.clone(), buffer, cx)
2257                .peekable();
2258
2259            while let Some(hunk) = hunks.next() {
2260                // Ignore hunks that are outside the excerpt range.
2261                let mut hunk_range = hunk.buffer_range.to_offset(buffer);
2262
2263                hunk_range.end = hunk_range.end.min(buffer_range.end);
2264                if hunk_range.start > buffer_range.end || hunk_range.start < buffer_range.start {
2265                    log::trace!("skipping hunk outside excerpt range");
2266                    continue;
2267                }
2268
2269                if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| {
2270                    expanded_anchor.to_offset(&buffer).max(buffer_range.start)
2271                        == hunk_range.start.max(buffer_range.start)
2272                }) {
2273                    log::trace!("skipping a hunk that's not marked as expanded");
2274                    continue;
2275                }
2276
2277                if !hunk.buffer_range.start.is_valid(&buffer) {
2278                    log::trace!("skipping hunk with deleted start: {:?}", hunk.range);
2279                    continue;
2280                }
2281
2282                if hunk_range.start >= offset {
2283                    // Add the buffer text before the hunk
2284                    let len = text.len();
2285                    text.extend(buffer.text_for_range(offset..hunk_range.start));
2286                    regions.push(ReferenceRegion {
2287                        buffer_id: Some(buffer.remote_id()),
2288                        range: len..text.len(),
2289                        buffer_start: Some(buffer.offset_to_point(offset)),
2290                        status: None,
2291                        excerpt_id: Some(excerpt.id),
2292                    });
2293
2294                    // Add the deleted text for the hunk.
2295                    if !hunk.diff_base_byte_range.is_empty() {
2296                        let mut base_text = base_buffer
2297                            .text_for_range(hunk.diff_base_byte_range.clone())
2298                            .collect::<String>();
2299                        if !base_text.ends_with('\n') {
2300                            base_text.push('\n');
2301                        }
2302                        let len = text.len();
2303                        text.push_str(&base_text);
2304                        regions.push(ReferenceRegion {
2305                            buffer_id: Some(base_buffer.remote_id()),
2306                            range: len..text.len(),
2307                            buffer_start: Some(
2308                                base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
2309                            ),
2310                            status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
2311                            excerpt_id: Some(excerpt.id),
2312                        });
2313                    }
2314
2315                    offset = hunk_range.start;
2316                }
2317
2318                // Add the inserted text for the hunk.
2319                if hunk_range.end > offset {
2320                    let len = text.len();
2321                    text.extend(buffer.text_for_range(offset..hunk_range.end));
2322                    regions.push(ReferenceRegion {
2323                        buffer_id: Some(buffer.remote_id()),
2324                        range: len..text.len(),
2325                        buffer_start: Some(buffer.offset_to_point(offset)),
2326                        status: Some(DiffHunkStatus::added(hunk.secondary_status)),
2327                        excerpt_id: Some(excerpt.id),
2328                    });
2329                    offset = hunk_range.end;
2330                }
2331            }
2332
2333            // Add the buffer text for the rest of the excerpt.
2334            let len = text.len();
2335            text.extend(buffer.text_for_range(offset..buffer_range.end));
2336            text.push('\n');
2337            regions.push(ReferenceRegion {
2338                buffer_id: Some(buffer.remote_id()),
2339                range: len..text.len(),
2340                buffer_start: Some(buffer.offset_to_point(offset)),
2341                status: None,
2342                excerpt_id: Some(excerpt.id),
2343            });
2344        }
2345
2346        // Remove final trailing newline.
2347        if self.excerpts.is_empty() {
2348            regions.push(ReferenceRegion {
2349                buffer_id: None,
2350                range: 0..1,
2351                buffer_start: Some(Point::new(0, 0)),
2352                status: None,
2353                excerpt_id: None,
2354            });
2355        } else {
2356            text.pop();
2357        }
2358
2359        // Retrieve the row info using the region that contains
2360        // the start of each multi-buffer line.
2361        let mut ix = 0;
2362        let row_infos = text
2363            .split('\n')
2364            .map(|line| {
2365                let row_info = regions
2366                    .iter()
2367                    .position(|region| region.range.contains(&ix))
2368                    .map_or(RowInfo::default(), |region_ix| {
2369                        let region = &regions[region_ix];
2370                        let buffer_row = region.buffer_start.map(|start_point| {
2371                            start_point.row
2372                                + text[region.range.start..ix].matches('\n').count() as u32
2373                        });
2374                        let is_excerpt_start = region_ix == 0
2375                            || &regions[region_ix - 1].excerpt_id != &region.excerpt_id
2376                            || regions[region_ix - 1].range.is_empty();
2377                        let mut is_excerpt_end = region_ix == regions.len() - 1
2378                            || &regions[region_ix + 1].excerpt_id != &region.excerpt_id;
2379                        let is_start = !text[region.range.start..ix].contains('\n');
2380                        let mut is_end = if region.range.end > text.len() {
2381                            !text[ix..].contains('\n')
2382                        } else {
2383                            text[ix..region.range.end.min(text.len())]
2384                                .matches('\n')
2385                                .count()
2386                                == 1
2387                        };
2388                        if region_ix < regions.len() - 1
2389                            && !text[ix..].contains("\n")
2390                            && region.status == Some(DiffHunkStatus::added_none())
2391                            && regions[region_ix + 1].excerpt_id == region.excerpt_id
2392                            && regions[region_ix + 1].range.start == text.len()
2393                        {
2394                            is_end = true;
2395                            is_excerpt_end = true;
2396                        }
2397                        let mut expand_direction = None;
2398                        if let Some(buffer) = &self
2399                            .excerpts
2400                            .iter()
2401                            .find(|e| e.id == region.excerpt_id.unwrap())
2402                            .map(|e| e.buffer.clone())
2403                        {
2404                            let needs_expand_up =
2405                                is_excerpt_start && is_start && buffer_row.unwrap() > 0;
2406                            let needs_expand_down = is_excerpt_end
2407                                && is_end
2408                                && buffer.read(cx).max_point().row > buffer_row.unwrap();
2409                            expand_direction = if needs_expand_up && needs_expand_down {
2410                                Some(ExpandExcerptDirection::UpAndDown)
2411                            } else if needs_expand_up {
2412                                Some(ExpandExcerptDirection::Up)
2413                            } else if needs_expand_down {
2414                                Some(ExpandExcerptDirection::Down)
2415                            } else {
2416                                None
2417                            };
2418                        }
2419                        RowInfo {
2420                            buffer_id: region.buffer_id,
2421                            diff_status: region.status,
2422                            buffer_row,
2423                            multibuffer_row: Some(MultiBufferRow(
2424                                text[..ix].matches('\n').count() as u32
2425                            )),
2426                            expand_info: expand_direction.zip(region.excerpt_id).map(
2427                                |(direction, excerpt_id)| ExpandInfo {
2428                                    direction,
2429                                    excerpt_id,
2430                                },
2431                            ),
2432                        }
2433                    });
2434                ix += line.len() + 1;
2435                row_info
2436            })
2437            .collect();
2438
2439        (text, row_infos, excerpt_boundary_rows)
2440    }
2441
2442    fn diffs_updated(&mut self, cx: &App) {
2443        for excerpt in &mut self.excerpts {
2444            let buffer = excerpt.buffer.read(cx).snapshot();
2445            let excerpt_range = excerpt.range.to_offset(&buffer);
2446            let buffer_id = buffer.remote_id();
2447            let diff = self.diffs.get(&buffer_id).unwrap().read(cx);
2448            let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer, cx).peekable();
2449            excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
2450                if !hunk_anchor.is_valid(&buffer) {
2451                    return false;
2452                }
2453                while let Some(hunk) = hunks.peek() {
2454                    match hunk.buffer_range.start.cmp(&hunk_anchor, &buffer) {
2455                        cmp::Ordering::Less => {
2456                            hunks.next();
2457                        }
2458                        cmp::Ordering::Equal => {
2459                            let hunk_range = hunk.buffer_range.to_offset(&buffer);
2460                            return hunk_range.end >= excerpt_range.start
2461                                && hunk_range.start <= excerpt_range.end;
2462                        }
2463                        cmp::Ordering::Greater => break,
2464                    }
2465                }
2466                false
2467            });
2468        }
2469    }
2470
2471    fn add_diff(&mut self, diff: Entity<BufferDiff>, cx: &mut App) {
2472        let buffer_id = diff.read(cx).buffer_id;
2473        self.diffs.insert(buffer_id, diff);
2474    }
2475}
2476
2477#[gpui::test(iterations = 100)]
2478async fn test_random_set_ranges(cx: &mut TestAppContext, mut rng: StdRng) {
2479    let base_text = "a\n".repeat(100);
2480    let buf = cx.update(|cx| cx.new(|cx| Buffer::local(base_text, cx)));
2481    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2482
2483    let operations = env::var("OPERATIONS")
2484        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2485        .unwrap_or(10);
2486
2487    fn row_ranges(ranges: &Vec<Range<Point>>) -> Vec<Range<u32>> {
2488        ranges
2489            .iter()
2490            .map(|range| range.start.row..range.end.row)
2491            .collect()
2492    }
2493
2494    for _ in 0..operations {
2495        let snapshot = buf.update(cx, |buf, _| buf.snapshot());
2496        let num_ranges = rng.gen_range(0..=10);
2497        let max_row = snapshot.max_point().row;
2498        let mut ranges = (0..num_ranges)
2499            .map(|_| {
2500                let start = rng.gen_range(0..max_row);
2501                let end = rng.gen_range(start + 1..max_row + 1);
2502                Point::row_range(start..end)
2503            })
2504            .collect::<Vec<_>>();
2505        ranges.sort_by_key(|range| range.start);
2506        log::info!("Setting ranges: {:?}", row_ranges(&ranges));
2507        let (created, _) = multibuffer.update(cx, |multibuffer, cx| {
2508            multibuffer.set_excerpts_for_path(
2509                PathKey::for_buffer(&buf, cx),
2510                buf.clone(),
2511                ranges.clone(),
2512                2,
2513                cx,
2514            )
2515        });
2516
2517        assert_eq!(created.len(), ranges.len());
2518
2519        let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2520        let mut last_end = None;
2521        let mut seen_ranges = Vec::default();
2522
2523        for (_, buf, range) in snapshot.excerpts() {
2524            let start = range.context.start.to_point(&buf);
2525            let end = range.context.end.to_point(&buf);
2526            seen_ranges.push(start..end);
2527
2528            if let Some(last_end) = last_end.take() {
2529                assert!(
2530                    start > last_end,
2531                    "multibuffer has out-of-order ranges: {:?}; {:?} <= {:?}",
2532                    row_ranges(&seen_ranges),
2533                    start,
2534                    last_end
2535                )
2536            }
2537
2538            ranges.retain(|range| range.start < start || range.end > end);
2539
2540            last_end = Some(end)
2541        }
2542
2543        assert!(
2544            ranges.is_empty(),
2545            "multibuffer {:?} did not include all ranges: {:?}",
2546            row_ranges(&seen_ranges),
2547            row_ranges(&ranges)
2548        );
2549    }
2550}
2551
2552#[gpui::test(iterations = 100)]
2553async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
2554    let operations = env::var("OPERATIONS")
2555        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2556        .unwrap_or(10);
2557
2558    let mut buffers: Vec<Entity<Buffer>> = Vec::new();
2559    let mut base_texts: HashMap<BufferId, String> = HashMap::default();
2560    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2561    let mut reference = ReferenceMultibuffer::default();
2562    let mut anchors = Vec::new();
2563    let mut old_versions = Vec::new();
2564    let mut needs_diff_calculation = false;
2565
2566    for _ in 0..operations {
2567        match rng.gen_range(0..100) {
2568            0..=14 if !buffers.is_empty() => {
2569                let buffer = buffers.choose(&mut rng).unwrap();
2570                buffer.update(cx, |buf, cx| {
2571                    let edit_count = rng.gen_range(1..5);
2572                    buf.randomly_edit(&mut rng, edit_count, cx);
2573                    log::info!("buffer text:\n{}", buf.text());
2574                    needs_diff_calculation = true;
2575                });
2576                cx.update(|cx| reference.diffs_updated(cx));
2577            }
2578            15..=19 if !reference.excerpts.is_empty() => {
2579                multibuffer.update(cx, |multibuffer, cx| {
2580                    let ids = multibuffer.excerpt_ids();
2581                    let mut excerpts = HashSet::default();
2582                    for _ in 0..rng.gen_range(0..ids.len()) {
2583                        excerpts.extend(ids.choose(&mut rng).copied());
2584                    }
2585
2586                    let line_count = rng.gen_range(0..5);
2587
2588                    let excerpt_ixs = excerpts
2589                        .iter()
2590                        .map(|id| reference.excerpts.iter().position(|e| e.id == *id).unwrap())
2591                        .collect::<Vec<_>>();
2592                    log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
2593                    multibuffer.expand_excerpts(
2594                        excerpts.iter().cloned(),
2595                        line_count,
2596                        ExpandExcerptDirection::UpAndDown,
2597                        cx,
2598                    );
2599
2600                    reference.expand_excerpts(&excerpts, line_count, cx);
2601                });
2602            }
2603            20..=29 if !reference.excerpts.is_empty() => {
2604                let mut ids_to_remove = vec![];
2605                for _ in 0..rng.gen_range(1..=3) {
2606                    let Some(excerpt) = reference.excerpts.choose(&mut rng) else {
2607                        break;
2608                    };
2609                    let id = excerpt.id;
2610                    cx.update(|cx| reference.remove_excerpt(id, cx));
2611                    ids_to_remove.push(id);
2612                }
2613                let snapshot =
2614                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2615                ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
2616                drop(snapshot);
2617                multibuffer.update(cx, |multibuffer, cx| {
2618                    multibuffer.remove_excerpts(ids_to_remove, cx)
2619                });
2620            }
2621            30..=39 if !reference.excerpts.is_empty() => {
2622                let multibuffer =
2623                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2624                let offset =
2625                    multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
2626                let bias = if rng.r#gen() { Bias::Left } else { Bias::Right };
2627                log::info!("Creating anchor at {} with bias {:?}", offset, bias);
2628                anchors.push(multibuffer.anchor_at(offset, bias));
2629                anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
2630            }
2631            40..=44 if !anchors.is_empty() => {
2632                let multibuffer =
2633                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2634                let prev_len = anchors.len();
2635                anchors = multibuffer
2636                    .refresh_anchors(&anchors)
2637                    .into_iter()
2638                    .map(|a| a.1)
2639                    .collect();
2640
2641                // Ensure the newly-refreshed anchors point to a valid excerpt and don't
2642                // overshoot its boundaries.
2643                assert_eq!(anchors.len(), prev_len);
2644                for anchor in &anchors {
2645                    if anchor.excerpt_id == ExcerptId::min()
2646                        || anchor.excerpt_id == ExcerptId::max()
2647                    {
2648                        continue;
2649                    }
2650
2651                    let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
2652                    assert_eq!(excerpt.id, anchor.excerpt_id);
2653                    assert!(excerpt.contains(anchor));
2654                }
2655            }
2656            45..=55 if !reference.excerpts.is_empty() => {
2657                multibuffer.update(cx, |multibuffer, cx| {
2658                    let snapshot = multibuffer.snapshot(cx);
2659                    let excerpt_ix = rng.gen_range(0..reference.excerpts.len());
2660                    let excerpt = &reference.excerpts[excerpt_ix];
2661                    let start = excerpt.range.start;
2662                    let end = excerpt.range.end;
2663                    let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
2664                        ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
2665
2666                    log::info!(
2667                        "expanding diff hunks in range {:?} (excerpt id {:?}, index {excerpt_ix:?}, buffer id {:?})",
2668                        range.to_offset(&snapshot),
2669                        excerpt.id,
2670                        excerpt.buffer.read(cx).remote_id(),
2671                    );
2672                    reference.expand_diff_hunks(excerpt.id, start..end, cx);
2673                    multibuffer.expand_diff_hunks(vec![range], cx);
2674                });
2675            }
2676            56..=85 if needs_diff_calculation => {
2677                multibuffer.update(cx, |multibuffer, cx| {
2678                    for buffer in multibuffer.all_buffers() {
2679                        let snapshot = buffer.read(cx).snapshot();
2680                        multibuffer.diff_for(snapshot.remote_id()).unwrap().update(
2681                            cx,
2682                            |diff, cx| {
2683                                log::info!(
2684                                    "recalculating diff for buffer {:?}",
2685                                    snapshot.remote_id(),
2686                                );
2687                                diff.recalculate_diff_sync(snapshot.text, cx);
2688                            },
2689                        );
2690                    }
2691                    reference.diffs_updated(cx);
2692                    needs_diff_calculation = false;
2693                });
2694            }
2695            _ => {
2696                let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
2697                    let mut base_text = util::RandomCharIter::new(&mut rng)
2698                        .take(256)
2699                        .collect::<String>();
2700
2701                    let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
2702                    text::LineEnding::normalize(&mut base_text);
2703                    base_texts.insert(
2704                        buffer.read_with(cx, |buffer, _| buffer.remote_id()),
2705                        base_text,
2706                    );
2707                    buffers.push(buffer);
2708                    buffers.last().unwrap()
2709                } else {
2710                    buffers.choose(&mut rng).unwrap()
2711                };
2712
2713                let prev_excerpt_ix = rng.gen_range(0..=reference.excerpts.len());
2714                let prev_excerpt_id = reference
2715                    .excerpts
2716                    .get(prev_excerpt_ix)
2717                    .map_or(ExcerptId::max(), |e| e.id);
2718                let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
2719
2720                let (range, anchor_range) = buffer_handle.read_with(cx, |buffer, _| {
2721                    let end_row = rng.gen_range(0..=buffer.max_point().row);
2722                    let start_row = rng.gen_range(0..=end_row);
2723                    let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
2724                    let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
2725                    let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
2726
2727                    log::info!(
2728                        "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
2729                        excerpt_ix,
2730                        reference.excerpts.len(),
2731                        buffer.remote_id(),
2732                        buffer.text(),
2733                        start_ix..end_ix,
2734                        &buffer.text()[start_ix..end_ix]
2735                    );
2736
2737                    (start_ix..end_ix, anchor_range)
2738                });
2739
2740                multibuffer.update(cx, |multibuffer, cx| {
2741                    let id = buffer_handle.read(cx).remote_id();
2742                    if multibuffer.diff_for(id).is_none() {
2743                        let base_text = base_texts.get(&id).unwrap();
2744                        let diff = cx.new(|cx| {
2745                            BufferDiff::new_with_base_text(base_text, &buffer_handle, cx)
2746                        });
2747                        reference.add_diff(diff.clone(), cx);
2748                        multibuffer.add_diff(diff, cx)
2749                    }
2750                });
2751
2752                let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
2753                    multibuffer
2754                        .insert_excerpts_after(
2755                            prev_excerpt_id,
2756                            buffer_handle.clone(),
2757                            [ExcerptRange::new(range.clone())],
2758                            cx,
2759                        )
2760                        .pop()
2761                        .unwrap()
2762                });
2763
2764                reference.insert_excerpt_after(
2765                    prev_excerpt_id,
2766                    excerpt_id,
2767                    (buffer_handle.clone(), anchor_range),
2768                );
2769            }
2770        }
2771
2772        if rng.gen_bool(0.3) {
2773            multibuffer.update(cx, |multibuffer, cx| {
2774                old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
2775            })
2776        }
2777
2778        let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2779        let actual_text = snapshot.text();
2780        let actual_boundary_rows = snapshot
2781            .excerpt_boundaries_in_range(0..)
2782            .map(|b| b.row)
2783            .collect::<HashSet<_>>();
2784        let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2785
2786        let (expected_text, expected_row_infos, expected_boundary_rows) =
2787            cx.update(|cx| reference.expected_content(cx));
2788
2789        let has_diff = actual_row_infos
2790            .iter()
2791            .any(|info| info.diff_status.is_some())
2792            || expected_row_infos
2793                .iter()
2794                .any(|info| info.diff_status.is_some());
2795        let actual_diff = format_diff(
2796            &actual_text,
2797            &actual_row_infos,
2798            &actual_boundary_rows,
2799            Some(has_diff),
2800        );
2801        let expected_diff = format_diff(
2802            &expected_text,
2803            &expected_row_infos,
2804            &expected_boundary_rows,
2805            Some(has_diff),
2806        );
2807
2808        log::info!("Multibuffer content:\n{}", actual_diff);
2809
2810        assert_eq!(
2811            actual_row_infos.len(),
2812            actual_text.split('\n').count(),
2813            "line count: {}",
2814            actual_text.split('\n').count()
2815        );
2816        pretty_assertions::assert_eq!(actual_diff, expected_diff);
2817        pretty_assertions::assert_eq!(actual_text, expected_text);
2818        pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
2819
2820        for _ in 0..5 {
2821            let start_row = rng.gen_range(0..=expected_row_infos.len());
2822            assert_eq!(
2823                snapshot
2824                    .row_infos(MultiBufferRow(start_row as u32))
2825                    .collect::<Vec<_>>(),
2826                &expected_row_infos[start_row..],
2827                "buffer_rows({})",
2828                start_row
2829            );
2830        }
2831
2832        assert_eq!(
2833            snapshot.widest_line_number(),
2834            expected_row_infos
2835                .into_iter()
2836                .filter_map(|info| {
2837                    if info.diff_status.is_some_and(|status| status.is_deleted()) {
2838                        None
2839                    } else {
2840                        info.buffer_row
2841                    }
2842                })
2843                .max()
2844                .unwrap()
2845                + 1
2846        );
2847
2848        assert_consistent_line_numbers(&snapshot);
2849        assert_position_translation(&snapshot);
2850
2851        for (row, line) in expected_text.split('\n').enumerate() {
2852            assert_eq!(
2853                snapshot.line_len(MultiBufferRow(row as u32)),
2854                line.len() as u32,
2855                "line_len({}).",
2856                row
2857            );
2858        }
2859
2860        let text_rope = Rope::from(expected_text.as_str());
2861        for _ in 0..10 {
2862            let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2863            let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
2864
2865            let text_for_range = snapshot
2866                .text_for_range(start_ix..end_ix)
2867                .collect::<String>();
2868            assert_eq!(
2869                text_for_range,
2870                &expected_text[start_ix..end_ix],
2871                "incorrect text for range {:?}",
2872                start_ix..end_ix
2873            );
2874
2875            let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
2876            assert_eq!(
2877                snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
2878                expected_summary,
2879                "incorrect summary for range {:?}",
2880                start_ix..end_ix
2881            );
2882        }
2883
2884        // Anchor resolution
2885        let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
2886        assert_eq!(anchors.len(), summaries.len());
2887        for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
2888            assert!(resolved_offset <= snapshot.len());
2889            assert_eq!(
2890                snapshot.summary_for_anchor::<usize>(anchor),
2891                resolved_offset,
2892                "anchor: {:?}",
2893                anchor
2894            );
2895        }
2896
2897        for _ in 0..10 {
2898            let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2899            assert_eq!(
2900                snapshot.reversed_chars_at(end_ix).collect::<String>(),
2901                expected_text[..end_ix].chars().rev().collect::<String>(),
2902            );
2903        }
2904
2905        for _ in 0..10 {
2906            let end_ix = rng.gen_range(0..=text_rope.len());
2907            let start_ix = rng.gen_range(0..=end_ix);
2908            assert_eq!(
2909                snapshot
2910                    .bytes_in_range(start_ix..end_ix)
2911                    .flatten()
2912                    .copied()
2913                    .collect::<Vec<_>>(),
2914                expected_text.as_bytes()[start_ix..end_ix].to_vec(),
2915                "bytes_in_range({:?})",
2916                start_ix..end_ix,
2917            );
2918        }
2919    }
2920
2921    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2922    for (old_snapshot, subscription) in old_versions {
2923        let edits = subscription.consume().into_inner();
2924
2925        log::info!(
2926            "applying subscription edits to old text: {:?}: {:?}",
2927            old_snapshot.text(),
2928            edits,
2929        );
2930
2931        let mut text = old_snapshot.text();
2932        for edit in edits {
2933            let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
2934            text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
2935        }
2936        assert_eq!(text.to_string(), snapshot.text());
2937    }
2938}
2939
2940#[gpui::test]
2941fn test_history(cx: &mut App) {
2942    let test_settings = SettingsStore::test(cx);
2943    cx.set_global(test_settings);
2944    let group_interval: Duration = Duration::from_millis(1);
2945    let buffer_1 = cx.new(|cx| {
2946        let mut buf = Buffer::local("1234", cx);
2947        buf.set_group_interval(group_interval);
2948        buf
2949    });
2950    let buffer_2 = cx.new(|cx| {
2951        let mut buf = Buffer::local("5678", cx);
2952        buf.set_group_interval(group_interval);
2953        buf
2954    });
2955    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2956    multibuffer.update(cx, |this, _| {
2957        this.history.group_interval = group_interval;
2958    });
2959    multibuffer.update(cx, |multibuffer, cx| {
2960        multibuffer.push_excerpts(
2961            buffer_1.clone(),
2962            [ExcerptRange::new(0..buffer_1.read(cx).len())],
2963            cx,
2964        );
2965        multibuffer.push_excerpts(
2966            buffer_2.clone(),
2967            [ExcerptRange::new(0..buffer_2.read(cx).len())],
2968            cx,
2969        );
2970    });
2971
2972    let mut now = Instant::now();
2973
2974    multibuffer.update(cx, |multibuffer, cx| {
2975        let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
2976        multibuffer.edit(
2977            [
2978                (Point::new(0, 0)..Point::new(0, 0), "A"),
2979                (Point::new(1, 0)..Point::new(1, 0), "A"),
2980            ],
2981            None,
2982            cx,
2983        );
2984        multibuffer.edit(
2985            [
2986                (Point::new(0, 1)..Point::new(0, 1), "B"),
2987                (Point::new(1, 1)..Point::new(1, 1), "B"),
2988            ],
2989            None,
2990            cx,
2991        );
2992        multibuffer.end_transaction_at(now, cx);
2993        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2994
2995        // Verify edited ranges for transaction 1
2996        assert_eq!(
2997            multibuffer.edited_ranges_for_transaction(transaction_1, cx),
2998            &[
2999                Point::new(0, 0)..Point::new(0, 2),
3000                Point::new(1, 0)..Point::new(1, 2)
3001            ]
3002        );
3003
3004        // Edit buffer 1 through the multibuffer
3005        now += 2 * group_interval;
3006        multibuffer.start_transaction_at(now, cx);
3007        multibuffer.edit([(2..2, "C")], None, cx);
3008        multibuffer.end_transaction_at(now, cx);
3009        assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
3010
3011        // Edit buffer 1 independently
3012        buffer_1.update(cx, |buffer_1, cx| {
3013            buffer_1.start_transaction_at(now);
3014            buffer_1.edit([(3..3, "D")], None, cx);
3015            buffer_1.end_transaction_at(now, cx);
3016
3017            now += 2 * group_interval;
3018            buffer_1.start_transaction_at(now);
3019            buffer_1.edit([(4..4, "E")], None, cx);
3020            buffer_1.end_transaction_at(now, cx);
3021        });
3022        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
3023
3024        // An undo in the multibuffer undoes the multibuffer transaction
3025        // and also any individual buffer edits that have occurred since
3026        // that transaction.
3027        multibuffer.undo(cx);
3028        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3029
3030        multibuffer.undo(cx);
3031        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3032
3033        multibuffer.redo(cx);
3034        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3035
3036        multibuffer.redo(cx);
3037        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
3038
3039        // Undo buffer 2 independently.
3040        buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
3041        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
3042
3043        // An undo in the multibuffer undoes the components of the
3044        // the last multibuffer transaction that are not already undone.
3045        multibuffer.undo(cx);
3046        assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
3047
3048        multibuffer.undo(cx);
3049        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3050
3051        multibuffer.redo(cx);
3052        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3053
3054        buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
3055        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
3056
3057        // Redo stack gets cleared after an edit.
3058        now += 2 * group_interval;
3059        multibuffer.start_transaction_at(now, cx);
3060        multibuffer.edit([(0..0, "X")], None, cx);
3061        multibuffer.end_transaction_at(now, cx);
3062        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3063        multibuffer.redo(cx);
3064        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3065        multibuffer.undo(cx);
3066        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
3067        multibuffer.undo(cx);
3068        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3069
3070        // Transactions can be grouped manually.
3071        multibuffer.redo(cx);
3072        multibuffer.redo(cx);
3073        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3074        multibuffer.group_until_transaction(transaction_1, cx);
3075        multibuffer.undo(cx);
3076        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3077        multibuffer.redo(cx);
3078        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3079    });
3080}
3081
3082#[gpui::test]
3083async fn test_enclosing_indent(cx: &mut TestAppContext) {
3084    async fn enclosing_indent(
3085        text: &str,
3086        buffer_row: u32,
3087        cx: &mut TestAppContext,
3088    ) -> Option<(Range<u32>, LineIndent)> {
3089        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3090        let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
3091        let (range, indent) = snapshot
3092            .enclosing_indent(MultiBufferRow(buffer_row))
3093            .await?;
3094        Some((range.start.0..range.end.0, indent))
3095    }
3096
3097    assert_eq!(
3098        enclosing_indent(
3099            indoc!(
3100                "
3101                fn b() {
3102                    if c {
3103                        let d = 2;
3104                    }
3105                }
3106                "
3107            ),
3108            1,
3109            cx,
3110        )
3111        .await,
3112        Some((
3113            1..2,
3114            LineIndent {
3115                tabs: 0,
3116                spaces: 4,
3117                line_blank: false,
3118            }
3119        ))
3120    );
3121
3122    assert_eq!(
3123        enclosing_indent(
3124            indoc!(
3125                "
3126                fn b() {
3127                    if c {
3128                        let d = 2;
3129                    }
3130                }
3131                "
3132            ),
3133            2,
3134            cx,
3135        )
3136        .await,
3137        Some((
3138            1..2,
3139            LineIndent {
3140                tabs: 0,
3141                spaces: 4,
3142                line_blank: false,
3143            }
3144        ))
3145    );
3146
3147    assert_eq!(
3148        enclosing_indent(
3149            indoc!(
3150                "
3151                fn b() {
3152                    if c {
3153                        let d = 2;
3154
3155                        let e = 5;
3156                    }
3157                }
3158                "
3159            ),
3160            3,
3161            cx,
3162        )
3163        .await,
3164        Some((
3165            1..4,
3166            LineIndent {
3167                tabs: 0,
3168                spaces: 4,
3169                line_blank: false,
3170            }
3171        ))
3172    );
3173}
3174
3175#[gpui::test]
3176fn test_summaries_for_anchors(cx: &mut TestAppContext) {
3177    let base_text_1 = indoc!(
3178        "
3179        bar
3180        "
3181    );
3182    let text_1 = indoc!(
3183        "
3184        BAR
3185        "
3186    );
3187    let base_text_2 = indoc!(
3188        "
3189        foo
3190        "
3191    );
3192    let text_2 = indoc!(
3193        "
3194        FOO
3195        "
3196    );
3197
3198    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3199    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
3200    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_1, &buffer_1, cx));
3201    let diff_2 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_2, &buffer_2, cx));
3202    cx.run_until_parked();
3203
3204    let mut ids = vec![];
3205    let multibuffer = cx.new(|cx| {
3206        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
3207        multibuffer.set_all_diff_hunks_expanded(cx);
3208        ids.extend(multibuffer.push_excerpts(
3209            buffer_1.clone(),
3210            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
3211            cx,
3212        ));
3213        ids.extend(multibuffer.push_excerpts(
3214            buffer_2.clone(),
3215            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
3216            cx,
3217        ));
3218        multibuffer.add_diff(diff_1.clone(), cx);
3219        multibuffer.add_diff(diff_2.clone(), cx);
3220        multibuffer
3221    });
3222
3223    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3224        (multibuffer.snapshot(cx), multibuffer.subscribe())
3225    });
3226
3227    assert_new_snapshot(
3228        &multibuffer,
3229        &mut snapshot,
3230        &mut subscription,
3231        cx,
3232        indoc!(
3233            "
3234            - bar
3235            + BAR
3236
3237            - foo
3238            + FOO
3239            "
3240        ),
3241    );
3242
3243    let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
3244    let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
3245
3246    let anchor_1 = Anchor::in_buffer(ids[0], id_1, text::Anchor::MIN);
3247    let point_1 = snapshot.summaries_for_anchors::<Point, _>([&anchor_1])[0];
3248    assert_eq!(point_1, Point::new(0, 0));
3249
3250    let anchor_2 = Anchor::in_buffer(ids[1], id_2, text::Anchor::MIN);
3251    let point_2 = snapshot.summaries_for_anchors::<Point, _>([&anchor_2])[0];
3252    assert_eq!(point_2, Point::new(3, 0));
3253}
3254
3255#[gpui::test]
3256fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
3257    let base_text_1 = "one\ntwo".to_owned();
3258    let text_1 = "one\n".to_owned();
3259
3260    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3261    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(&base_text_1, &buffer_1, cx));
3262    cx.run_until_parked();
3263
3264    let multibuffer = cx.new(|cx| {
3265        let mut multibuffer = MultiBuffer::singleton(buffer_1.clone(), cx);
3266        multibuffer.add_diff(diff_1.clone(), cx);
3267        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
3268        multibuffer
3269    });
3270
3271    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3272        (multibuffer.snapshot(cx), multibuffer.subscribe())
3273    });
3274
3275    assert_new_snapshot(
3276        &multibuffer,
3277        &mut snapshot,
3278        &mut subscription,
3279        cx,
3280        indoc!(
3281            "
3282              one
3283            - two
3284            "
3285        ),
3286    );
3287
3288    assert_eq!(snapshot.max_point(), Point::new(2, 0));
3289    assert_eq!(snapshot.len(), 8);
3290
3291    assert_eq!(
3292        snapshot
3293            .dimensions_from_points::<Point>([Point::new(2, 0)])
3294            .collect::<Vec<_>>(),
3295        vec![Point::new(2, 0)]
3296    );
3297
3298    let (_, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3299    assert_eq!(translated_offset, "one\n".len());
3300    let (_, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3301    assert_eq!(translated_point, Point::new(1, 0));
3302
3303    // The same, for an excerpt that's not at the end of the multibuffer.
3304
3305    let text_2 = "foo\n".to_owned();
3306    let buffer_2 = cx.new(|cx| Buffer::local(&text_2, cx));
3307    multibuffer.update(cx, |multibuffer, cx| {
3308        multibuffer.push_excerpts(
3309            buffer_2.clone(),
3310            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
3311            cx,
3312        );
3313    });
3314
3315    assert_new_snapshot(
3316        &multibuffer,
3317        &mut snapshot,
3318        &mut subscription,
3319        cx,
3320        indoc!(
3321            "
3322              one
3323            - two
3324
3325              foo
3326            "
3327        ),
3328    );
3329
3330    assert_eq!(
3331        snapshot
3332            .dimensions_from_points::<Point>([Point::new(2, 0)])
3333            .collect::<Vec<_>>(),
3334        vec![Point::new(2, 0)]
3335    );
3336
3337    let buffer_1_id = buffer_1.read_with(cx, |buffer_1, _| buffer_1.remote_id());
3338    let (buffer, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3339    assert_eq!(buffer.remote_id(), buffer_1_id);
3340    assert_eq!(translated_offset, "one\n".len());
3341    let (buffer, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3342    assert_eq!(buffer.remote_id(), buffer_1_id);
3343    assert_eq!(translated_point, Point::new(1, 0));
3344}
3345
3346fn format_diff(
3347    text: &str,
3348    row_infos: &Vec<RowInfo>,
3349    boundary_rows: &HashSet<MultiBufferRow>,
3350    has_diff: Option<bool>,
3351) -> String {
3352    let has_diff =
3353        has_diff.unwrap_or_else(|| row_infos.iter().any(|info| info.diff_status.is_some()));
3354    text.split('\n')
3355        .enumerate()
3356        .zip(row_infos)
3357        .map(|((ix, line), info)| {
3358            let marker = match info.diff_status.map(|status| status.kind) {
3359                Some(DiffHunkStatusKind::Added) => "+ ",
3360                Some(DiffHunkStatusKind::Deleted) => "- ",
3361                Some(DiffHunkStatusKind::Modified) => unreachable!(),
3362                None => {
3363                    if has_diff && !line.is_empty() {
3364                        "  "
3365                    } else {
3366                        ""
3367                    }
3368                }
3369            };
3370            let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
3371                if has_diff {
3372                    "  ----------\n"
3373                } else {
3374                    "---------\n"
3375                }
3376            } else {
3377                ""
3378            };
3379            format!("{boundary_row}{marker}{line}")
3380        })
3381        .collect::<Vec<_>>()
3382        .join("\n")
3383}
3384
3385#[track_caller]
3386fn assert_excerpts_match(
3387    multibuffer: &Entity<MultiBuffer>,
3388    cx: &mut TestAppContext,
3389    expected: &str,
3390) {
3391    let mut output = String::new();
3392    multibuffer.read_with(cx, |multibuffer, cx| {
3393        for (_, buffer, range) in multibuffer.snapshot(cx).excerpts() {
3394            output.push_str("-----\n");
3395            output.extend(buffer.text_for_range(range.context));
3396            if !output.ends_with('\n') {
3397                output.push('\n');
3398            }
3399        }
3400    });
3401    assert_eq!(output, expected);
3402}
3403
3404#[track_caller]
3405fn assert_new_snapshot(
3406    multibuffer: &Entity<MultiBuffer>,
3407    snapshot: &mut MultiBufferSnapshot,
3408    subscription: &mut Subscription,
3409    cx: &mut TestAppContext,
3410    expected_diff: &str,
3411) {
3412    let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3413    let actual_text = new_snapshot.text();
3414    let line_infos = new_snapshot
3415        .row_infos(MultiBufferRow(0))
3416        .collect::<Vec<_>>();
3417    let actual_diff = format_diff(&actual_text, &line_infos, &Default::default(), None);
3418    pretty_assertions::assert_eq!(actual_diff, expected_diff);
3419    check_edits(
3420        snapshot,
3421        &new_snapshot,
3422        &subscription.consume().into_inner(),
3423    );
3424    *snapshot = new_snapshot;
3425}
3426
3427#[track_caller]
3428fn check_edits(
3429    old_snapshot: &MultiBufferSnapshot,
3430    new_snapshot: &MultiBufferSnapshot,
3431    edits: &[Edit<usize>],
3432) {
3433    let mut text = old_snapshot.text();
3434    let new_text = new_snapshot.text();
3435    for edit in edits.iter().rev() {
3436        if !text.is_char_boundary(edit.old.start)
3437            || !text.is_char_boundary(edit.old.end)
3438            || !new_text.is_char_boundary(edit.new.start)
3439            || !new_text.is_char_boundary(edit.new.end)
3440        {
3441            panic!(
3442                "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
3443                edits, text, new_text
3444            );
3445        }
3446
3447        text.replace_range(
3448            edit.old.start..edit.old.end,
3449            &new_text[edit.new.start..edit.new.end],
3450        );
3451    }
3452
3453    pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
3454}
3455
3456#[track_caller]
3457fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
3458    let full_text = snapshot.text();
3459    for ix in 0..full_text.len() {
3460        let mut chunks = snapshot.chunks(0..snapshot.len(), false);
3461        chunks.seek(ix..snapshot.len());
3462        let tail = chunks.map(|chunk| chunk.text).collect::<String>();
3463        assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
3464    }
3465}
3466
3467#[track_caller]
3468fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
3469    let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3470    for start_row in 1..all_line_numbers.len() {
3471        let line_numbers = snapshot
3472            .row_infos(MultiBufferRow(start_row as u32))
3473            .collect::<Vec<_>>();
3474        assert_eq!(
3475            line_numbers,
3476            all_line_numbers[start_row..],
3477            "start_row: {start_row}"
3478        );
3479    }
3480}
3481
3482#[track_caller]
3483fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
3484    let text = Rope::from(snapshot.text());
3485
3486    let mut left_anchors = Vec::new();
3487    let mut right_anchors = Vec::new();
3488    let mut offsets = Vec::new();
3489    let mut points = Vec::new();
3490    for offset in 0..=text.len() + 1 {
3491        let clipped_left = snapshot.clip_offset(offset, Bias::Left);
3492        let clipped_right = snapshot.clip_offset(offset, Bias::Right);
3493        assert_eq!(
3494            clipped_left,
3495            text.clip_offset(offset, Bias::Left),
3496            "clip_offset({offset:?}, Left)"
3497        );
3498        assert_eq!(
3499            clipped_right,
3500            text.clip_offset(offset, Bias::Right),
3501            "clip_offset({offset:?}, Right)"
3502        );
3503        assert_eq!(
3504            snapshot.offset_to_point(clipped_left),
3505            text.offset_to_point(clipped_left),
3506            "offset_to_point({clipped_left})"
3507        );
3508        assert_eq!(
3509            snapshot.offset_to_point(clipped_right),
3510            text.offset_to_point(clipped_right),
3511            "offset_to_point({clipped_right})"
3512        );
3513        let anchor_after = snapshot.anchor_after(clipped_left);
3514        assert_eq!(
3515            anchor_after.to_offset(snapshot),
3516            clipped_left,
3517            "anchor_after({clipped_left}).to_offset {anchor_after:?}"
3518        );
3519        let anchor_before = snapshot.anchor_before(clipped_left);
3520        assert_eq!(
3521            anchor_before.to_offset(snapshot),
3522            clipped_left,
3523            "anchor_before({clipped_left}).to_offset"
3524        );
3525        left_anchors.push(anchor_before);
3526        right_anchors.push(anchor_after);
3527        offsets.push(clipped_left);
3528        points.push(text.offset_to_point(clipped_left));
3529    }
3530
3531    for row in 0..text.max_point().row {
3532        for column in 0..text.line_len(row) + 1 {
3533            let point = Point { row, column };
3534            let clipped_left = snapshot.clip_point(point, Bias::Left);
3535            let clipped_right = snapshot.clip_point(point, Bias::Right);
3536            assert_eq!(
3537                clipped_left,
3538                text.clip_point(point, Bias::Left),
3539                "clip_point({point:?}, Left)"
3540            );
3541            assert_eq!(
3542                clipped_right,
3543                text.clip_point(point, Bias::Right),
3544                "clip_point({point:?}, Right)"
3545            );
3546            assert_eq!(
3547                snapshot.point_to_offset(clipped_left),
3548                text.point_to_offset(clipped_left),
3549                "point_to_offset({clipped_left:?})"
3550            );
3551            assert_eq!(
3552                snapshot.point_to_offset(clipped_right),
3553                text.point_to_offset(clipped_right),
3554                "point_to_offset({clipped_right:?})"
3555            );
3556        }
3557    }
3558
3559    assert_eq!(
3560        snapshot.summaries_for_anchors::<usize, _>(&left_anchors),
3561        offsets,
3562        "left_anchors <-> offsets"
3563    );
3564    assert_eq!(
3565        snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
3566        points,
3567        "left_anchors <-> points"
3568    );
3569    assert_eq!(
3570        snapshot.summaries_for_anchors::<usize, _>(&right_anchors),
3571        offsets,
3572        "right_anchors <-> offsets"
3573    );
3574    assert_eq!(
3575        snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
3576        points,
3577        "right_anchors <-> points"
3578    );
3579
3580    for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
3581        for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
3582            if ix > 0 {
3583                if *offset == 252 {
3584                    if offset > &offsets[ix - 1] {
3585                        let prev_anchor = left_anchors[ix - 1];
3586                        assert!(
3587                            anchor.cmp(&prev_anchor, snapshot).is_gt(),
3588                            "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
3589                            offsets[ix],
3590                            offsets[ix - 1],
3591                        );
3592                        assert!(
3593                            prev_anchor.cmp(&anchor, snapshot).is_lt(),
3594                            "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
3595                            offsets[ix - 1],
3596                            offsets[ix],
3597                        );
3598                    }
3599                }
3600            }
3601        }
3602    }
3603
3604    if let Some((buffer, offset)) = snapshot.point_to_buffer_offset(snapshot.max_point()) {
3605        assert!(offset <= buffer.len());
3606    }
3607    if let Some((buffer, point, _)) = snapshot.point_to_buffer_point(snapshot.max_point()) {
3608        assert!(point <= buffer.max_point());
3609    }
3610}
3611
3612fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
3613    let max_row = snapshot.max_point().row;
3614    let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
3615    let text = text::Buffer::new(0, buffer_id, snapshot.text());
3616    let mut line_indents = text
3617        .line_indents_in_row_range(0..max_row + 1)
3618        .collect::<Vec<_>>();
3619    for start_row in 0..snapshot.max_point().row {
3620        pretty_assertions::assert_eq!(
3621            snapshot
3622                .line_indents(MultiBufferRow(start_row), |_| true)
3623                .map(|(row, indent, _)| (row.0, indent))
3624                .collect::<Vec<_>>(),
3625            &line_indents[(start_row as usize)..],
3626            "line_indents({start_row})"
3627        );
3628    }
3629
3630    line_indents.reverse();
3631    pretty_assertions::assert_eq!(
3632        snapshot
3633            .reversed_line_indents(MultiBufferRow(max_row), |_| true)
3634            .map(|(row, indent, _)| (row.0, indent))
3635            .collect::<Vec<_>>(),
3636        &line_indents[..],
3637        "reversed_line_indents({max_row})"
3638    );
3639}