multi_buffer_tests.rs

   1use super::*;
   2use git::diff::DiffHunkStatus;
   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    if std::env::var("RUST_LOG").is_ok() {
  15        env_logger::init();
  16    }
  17}
  18
  19#[gpui::test]
  20fn test_empty_singleton(cx: &mut App) {
  21    let buffer = cx.new(|cx| Buffer::local("", cx));
  22    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
  23    let snapshot = multibuffer.read(cx).snapshot(cx);
  24    assert_eq!(snapshot.text(), "");
  25    assert_eq!(
  26        snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
  27        [RowInfo {
  28            buffer_row: Some(0),
  29            multibuffer_row: Some(MultiBufferRow(0)),
  30            diff_status: None
  31        }]
  32    );
  33}
  34
  35#[gpui::test]
  36fn test_singleton(cx: &mut App) {
  37    let buffer = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
  38    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
  39
  40    let snapshot = multibuffer.read(cx).snapshot(cx);
  41    assert_eq!(snapshot.text(), buffer.read(cx).text());
  42
  43    assert_eq!(
  44        snapshot
  45            .row_infos(MultiBufferRow(0))
  46            .map(|info| info.buffer_row)
  47            .collect::<Vec<_>>(),
  48        (0..buffer.read(cx).row_count())
  49            .map(Some)
  50            .collect::<Vec<_>>()
  51    );
  52    assert_consistent_line_numbers(&snapshot);
  53
  54    buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx));
  55    let snapshot = multibuffer.read(cx).snapshot(cx);
  56
  57    assert_eq!(snapshot.text(), buffer.read(cx).text());
  58    assert_eq!(
  59        snapshot
  60            .row_infos(MultiBufferRow(0))
  61            .map(|info| info.buffer_row)
  62            .collect::<Vec<_>>(),
  63        (0..buffer.read(cx).row_count())
  64            .map(Some)
  65            .collect::<Vec<_>>()
  66    );
  67    assert_consistent_line_numbers(&snapshot);
  68}
  69
  70#[gpui::test]
  71fn test_remote(cx: &mut App) {
  72    let host_buffer = cx.new(|cx| Buffer::local("a", cx));
  73    let guest_buffer = cx.new(|cx| {
  74        let state = host_buffer.read(cx).to_proto(cx);
  75        let ops = cx
  76            .background_executor()
  77            .block(host_buffer.read(cx).serialize_ops(None, cx));
  78        let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
  79        buffer.apply_ops(
  80            ops.into_iter()
  81                .map(|op| language::proto::deserialize_operation(op).unwrap()),
  82            cx,
  83        );
  84        buffer
  85    });
  86    let multibuffer = cx.new(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx));
  87    let snapshot = multibuffer.read(cx).snapshot(cx);
  88    assert_eq!(snapshot.text(), "a");
  89
  90    guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx));
  91    let snapshot = multibuffer.read(cx).snapshot(cx);
  92    assert_eq!(snapshot.text(), "ab");
  93
  94    guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx));
  95    let snapshot = multibuffer.read(cx).snapshot(cx);
  96    assert_eq!(snapshot.text(), "abc");
  97}
  98
  99#[gpui::test]
 100fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
 101    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
 102    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
 103    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 104
 105    let events = Arc::new(RwLock::new(Vec::<Event>::new()));
 106    multibuffer.update(cx, |_, cx| {
 107        let events = events.clone();
 108        cx.subscribe(&multibuffer, move |_, _, event, _| {
 109            if let Event::Edited { .. } = event {
 110                events.write().push(event.clone())
 111            }
 112        })
 113        .detach();
 114    });
 115
 116    let subscription = multibuffer.update(cx, |multibuffer, cx| {
 117        let subscription = multibuffer.subscribe();
 118        multibuffer.push_excerpts(
 119            buffer_1.clone(),
 120            [ExcerptRange {
 121                context: Point::new(1, 2)..Point::new(2, 5),
 122                primary: None,
 123            }],
 124            cx,
 125        );
 126        assert_eq!(
 127            subscription.consume().into_inner(),
 128            [Edit {
 129                old: 0..0,
 130                new: 0..10
 131            }]
 132        );
 133
 134        multibuffer.push_excerpts(
 135            buffer_1.clone(),
 136            [ExcerptRange {
 137                context: Point::new(3, 3)..Point::new(4, 4),
 138                primary: None,
 139            }],
 140            cx,
 141        );
 142        multibuffer.push_excerpts(
 143            buffer_2.clone(),
 144            [ExcerptRange {
 145                context: Point::new(3, 1)..Point::new(3, 3),
 146                primary: None,
 147            }],
 148            cx,
 149        );
 150        assert_eq!(
 151            subscription.consume().into_inner(),
 152            [Edit {
 153                old: 10..10,
 154                new: 10..22
 155            }]
 156        );
 157
 158        subscription
 159    });
 160
 161    // Adding excerpts emits an edited event.
 162    assert_eq!(
 163        events.read().as_slice(),
 164        &[
 165            Event::Edited {
 166                singleton_buffer_edited: false,
 167                edited_buffer: None,
 168            },
 169            Event::Edited {
 170                singleton_buffer_edited: false,
 171                edited_buffer: None,
 172            },
 173            Event::Edited {
 174                singleton_buffer_edited: false,
 175                edited_buffer: None,
 176            }
 177        ]
 178    );
 179
 180    let snapshot = multibuffer.read(cx).snapshot(cx);
 181    assert_eq!(
 182        snapshot.text(),
 183        indoc!(
 184            "
 185            bbbb
 186            ccccc
 187            ddd
 188            eeee
 189            jj"
 190        ),
 191    );
 192    assert_eq!(
 193        snapshot
 194            .row_infos(MultiBufferRow(0))
 195            .map(|info| info.buffer_row)
 196            .collect::<Vec<_>>(),
 197        [Some(1), Some(2), Some(3), Some(4), Some(3)]
 198    );
 199    assert_eq!(
 200        snapshot
 201            .row_infos(MultiBufferRow(2))
 202            .map(|info| info.buffer_row)
 203            .collect::<Vec<_>>(),
 204        [Some(3), Some(4), Some(3)]
 205    );
 206    assert_eq!(
 207        snapshot
 208            .row_infos(MultiBufferRow(4))
 209            .map(|info| info.buffer_row)
 210            .collect::<Vec<_>>(),
 211        [Some(3)]
 212    );
 213    assert_eq!(
 214        snapshot
 215            .row_infos(MultiBufferRow(5))
 216            .map(|info| info.buffer_row)
 217            .collect::<Vec<_>>(),
 218        []
 219    );
 220
 221    assert_eq!(
 222        boundaries_in_range(Point::new(0, 0)..Point::new(4, 2), &snapshot),
 223        &[
 224            (MultiBufferRow(0), "bbbb\nccccc".to_string(), true),
 225            (MultiBufferRow(2), "ddd\neeee".to_string(), false),
 226            (MultiBufferRow(4), "jj".to_string(), true),
 227        ]
 228    );
 229    assert_eq!(
 230        boundaries_in_range(Point::new(0, 0)..Point::new(2, 0), &snapshot),
 231        &[(MultiBufferRow(0), "bbbb\nccccc".to_string(), true)]
 232    );
 233    assert_eq!(
 234        boundaries_in_range(Point::new(1, 0)..Point::new(1, 5), &snapshot),
 235        &[]
 236    );
 237    assert_eq!(
 238        boundaries_in_range(Point::new(1, 0)..Point::new(2, 0), &snapshot),
 239        &[]
 240    );
 241    assert_eq!(
 242        boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
 243        &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
 244    );
 245    assert_eq!(
 246        boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
 247        &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
 248    );
 249    assert_eq!(
 250        boundaries_in_range(Point::new(2, 0)..Point::new(3, 0), &snapshot),
 251        &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
 252    );
 253    assert_eq!(
 254        boundaries_in_range(Point::new(4, 0)..Point::new(4, 2), &snapshot),
 255        &[(MultiBufferRow(4), "jj".to_string(), true)]
 256    );
 257    assert_eq!(
 258        boundaries_in_range(Point::new(4, 2)..Point::new(4, 2), &snapshot),
 259        &[]
 260    );
 261
 262    buffer_1.update(cx, |buffer, cx| {
 263        let text = "\n";
 264        buffer.edit(
 265            [
 266                (Point::new(0, 0)..Point::new(0, 0), text),
 267                (Point::new(2, 1)..Point::new(2, 3), text),
 268            ],
 269            None,
 270            cx,
 271        );
 272    });
 273
 274    let snapshot = multibuffer.read(cx).snapshot(cx);
 275    assert_eq!(
 276        snapshot.text(),
 277        concat!(
 278            "bbbb\n", // Preserve newlines
 279            "c\n",    //
 280            "cc\n",   //
 281            "ddd\n",  //
 282            "eeee\n", //
 283            "jj"      //
 284        )
 285    );
 286
 287    assert_eq!(
 288        subscription.consume().into_inner(),
 289        [Edit {
 290            old: 6..8,
 291            new: 6..7
 292        }]
 293    );
 294
 295    let snapshot = multibuffer.read(cx).snapshot(cx);
 296    assert_eq!(
 297        snapshot.clip_point(Point::new(0, 5), Bias::Left),
 298        Point::new(0, 4)
 299    );
 300    assert_eq!(
 301        snapshot.clip_point(Point::new(0, 5), Bias::Right),
 302        Point::new(0, 4)
 303    );
 304    assert_eq!(
 305        snapshot.clip_point(Point::new(5, 1), Bias::Right),
 306        Point::new(5, 1)
 307    );
 308    assert_eq!(
 309        snapshot.clip_point(Point::new(5, 2), Bias::Right),
 310        Point::new(5, 2)
 311    );
 312    assert_eq!(
 313        snapshot.clip_point(Point::new(5, 3), Bias::Right),
 314        Point::new(5, 2)
 315    );
 316
 317    let snapshot = multibuffer.update(cx, |multibuffer, cx| {
 318        let (buffer_2_excerpt_id, _) = multibuffer.excerpts_for_buffer(&buffer_2, cx)[0].clone();
 319        multibuffer.remove_excerpts([buffer_2_excerpt_id], cx);
 320        multibuffer.snapshot(cx)
 321    });
 322
 323    assert_eq!(
 324        snapshot.text(),
 325        concat!(
 326            "bbbb\n", // Preserve newlines
 327            "c\n",    //
 328            "cc\n",   //
 329            "ddd\n",  //
 330            "eeee",   //
 331        )
 332    );
 333
 334    fn boundaries_in_range(
 335        range: Range<Point>,
 336        snapshot: &MultiBufferSnapshot,
 337    ) -> Vec<(MultiBufferRow, String, bool)> {
 338        snapshot
 339            .excerpt_boundaries_in_range(range)
 340            .filter_map(|boundary| {
 341                let starts_new_buffer = boundary.starts_new_buffer();
 342                boundary.next.map(|next| {
 343                    (
 344                        boundary.row,
 345                        next.buffer
 346                            .text_for_range(next.range.context)
 347                            .collect::<String>(),
 348                        starts_new_buffer,
 349                    )
 350                })
 351            })
 352            .collect::<Vec<_>>()
 353    }
 354}
 355
 356#[gpui::test]
 357fn test_diff_boundary_anchors(cx: &mut App) {
 358    let base_text = "one\ntwo\nthree\n";
 359    let text = "one\nthree\n";
 360    let buffer = cx.new(|cx| Buffer::local(text, cx));
 361    let snapshot = buffer.read(cx).snapshot();
 362    let change_set = cx.new(|cx| {
 363        let mut change_set = BufferChangeSet::new(&buffer, cx);
 364        change_set.recalculate_diff_sync(base_text.into(), snapshot.text, true, cx);
 365        change_set
 366    });
 367    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 368    multibuffer.update(cx, |multibuffer, cx| {
 369        multibuffer.add_change_set(change_set, cx)
 370    });
 371
 372    let (before, after) = multibuffer.update(cx, |multibuffer, cx| {
 373        let before = multibuffer.snapshot(cx).anchor_before(Point::new(1, 0));
 374        let after = multibuffer.snapshot(cx).anchor_after(Point::new(1, 0));
 375        multibuffer.set_all_diff_hunks_expanded(cx);
 376        (before, after)
 377    });
 378    cx.background_executor().run_until_parked();
 379
 380    let snapshot = multibuffer.read(cx).snapshot(cx);
 381    let actual_text = snapshot.text();
 382    let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
 383    let actual_diff = format_diff(&actual_text, &actual_row_infos, &Default::default());
 384    pretty_assertions::assert_eq!(
 385        actual_diff,
 386        indoc! {
 387            "  one
 388             - two
 389               three
 390             "
 391        },
 392    );
 393
 394    multibuffer.update(cx, |multibuffer, cx| {
 395        let snapshot = multibuffer.snapshot(cx);
 396        assert_eq!(before.to_point(&snapshot), Point::new(1, 0));
 397        assert_eq!(after.to_point(&snapshot), Point::new(2, 0));
 398        assert_eq!(
 399            vec![Point::new(1, 0), Point::new(2, 0),],
 400            snapshot.summaries_for_anchors::<Point, _>(&[before, after]),
 401        )
 402    })
 403}
 404
 405#[gpui::test]
 406fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
 407    let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n";
 408    let text = "one\nfour\nseven\n";
 409    let buffer = cx.new(|cx| Buffer::local(text, cx));
 410    let change_set = cx.new(|cx| {
 411        let mut change_set = BufferChangeSet::new(&buffer, cx);
 412        let snapshot = buffer.read(cx).snapshot();
 413        change_set.recalculate_diff_sync(base_text.into(), snapshot.text, true, cx);
 414        change_set
 415    });
 416    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 417    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
 418        (multibuffer.snapshot(cx), multibuffer.subscribe())
 419    });
 420
 421    multibuffer.update(cx, |multibuffer, cx| {
 422        multibuffer.add_change_set(change_set, cx);
 423        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
 424    });
 425
 426    assert_new_snapshot(
 427        &multibuffer,
 428        &mut snapshot,
 429        &mut subscription,
 430        cx,
 431        indoc! {
 432            "  one
 433             - two
 434             - three
 435               four
 436             - five
 437             - six
 438               seven
 439             - eight
 440            "
 441        },
 442    );
 443
 444    assert_eq!(
 445        snapshot
 446            .diff_hunks_in_range(Point::new(1, 0)..Point::MAX)
 447            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
 448            .collect::<Vec<_>>(),
 449        vec![1..3, 4..6, 7..8]
 450    );
 451
 452    assert_eq!(
 453        snapshot
 454            .diff_hunk_before(Point::new(1, 1))
 455            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
 456        None,
 457    );
 458    assert_eq!(
 459        snapshot
 460            .diff_hunk_before(Point::new(7, 0))
 461            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
 462        Some(4..6)
 463    );
 464    assert_eq!(
 465        snapshot
 466            .diff_hunk_before(Point::new(4, 0))
 467            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
 468        Some(1..3)
 469    );
 470
 471    multibuffer.update(cx, |multibuffer, cx| {
 472        multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
 473    });
 474
 475    assert_new_snapshot(
 476        &multibuffer,
 477        &mut snapshot,
 478        &mut subscription,
 479        cx,
 480        indoc! {
 481            "
 482            one
 483            four
 484            seven
 485            "
 486        },
 487    );
 488
 489    assert_eq!(
 490        snapshot
 491            .diff_hunk_before(Point::new(2, 0))
 492            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
 493        Some(1..1),
 494    );
 495    assert_eq!(
 496        snapshot
 497            .diff_hunk_before(Point::new(4, 0))
 498            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
 499        Some(2..2)
 500    );
 501}
 502
 503#[gpui::test]
 504fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) {
 505    let base_text = "one\ntwo\nfour\nfive\nsix\nseven\n";
 506    let text = "one\ntwo\nTHREE\nfour\nfive\nseven\n";
 507    let buffer = cx.new(|cx| Buffer::local(text, cx));
 508    let change_set = cx.new(|cx| {
 509        let mut change_set = BufferChangeSet::new(&buffer, cx);
 510        let snapshot = buffer.read(cx).snapshot();
 511        change_set.recalculate_diff_sync(base_text.into(), snapshot.text, true, cx);
 512        change_set
 513    });
 514    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
 515
 516    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
 517        multibuffer.add_change_set(change_set.clone(), cx);
 518        (multibuffer.snapshot(cx), multibuffer.subscribe())
 519    });
 520
 521    cx.executor().run_until_parked();
 522    multibuffer.update(cx, |multibuffer, cx| {
 523        multibuffer.set_all_diff_hunks_expanded(cx);
 524    });
 525
 526    assert_new_snapshot(
 527        &multibuffer,
 528        &mut snapshot,
 529        &mut subscription,
 530        cx,
 531        indoc! {
 532            "
 533              one
 534              two
 535            + THREE
 536              four
 537              five
 538            - six
 539              seven
 540            "
 541        },
 542    );
 543
 544    // Insert a newline within an insertion hunk
 545    multibuffer.update(cx, |multibuffer, cx| {
 546        multibuffer.edit([(Point::new(2, 0)..Point::new(2, 0), "__\n__")], None, cx);
 547    });
 548    assert_new_snapshot(
 549        &multibuffer,
 550        &mut snapshot,
 551        &mut subscription,
 552        cx,
 553        indoc! {
 554            "
 555              one
 556              two
 557            + __
 558            + __THREE
 559              four
 560              five
 561            - six
 562              seven
 563            "
 564        },
 565    );
 566
 567    // Delete the newline before a deleted hunk.
 568    multibuffer.update(cx, |multibuffer, cx| {
 569        multibuffer.edit([(Point::new(5, 4)..Point::new(6, 0), "")], None, cx);
 570    });
 571    assert_new_snapshot(
 572        &multibuffer,
 573        &mut snapshot,
 574        &mut subscription,
 575        cx,
 576        indoc! {
 577            "
 578              one
 579              two
 580            + __
 581            + __THREE
 582              four
 583              fiveseven
 584            "
 585        },
 586    );
 587
 588    multibuffer.update(cx, |multibuffer, cx| multibuffer.undo(cx));
 589    change_set.update(cx, |change_set, cx| {
 590        change_set.recalculate_diff_sync(
 591            base_text.into(),
 592            buffer.read(cx).text_snapshot(),
 593            true,
 594            cx,
 595        );
 596    });
 597    assert_new_snapshot(
 598        &multibuffer,
 599        &mut snapshot,
 600        &mut subscription,
 601        cx,
 602        indoc! {
 603            "
 604              one
 605              two
 606            + __
 607            + __THREE
 608              four
 609              five
 610            - six
 611              seven
 612            "
 613        },
 614    );
 615
 616    // Cannot (yet) insert at the beginning of a deleted hunk.
 617    // (because it would put the newline in the wrong place)
 618    multibuffer.update(cx, |multibuffer, cx| {
 619        multibuffer.edit([(Point::new(6, 0)..Point::new(6, 0), "\n")], None, cx);
 620    });
 621    assert_new_snapshot(
 622        &multibuffer,
 623        &mut snapshot,
 624        &mut subscription,
 625        cx,
 626        indoc! {
 627            "
 628              one
 629              two
 630            + __
 631            + __THREE
 632              four
 633              five
 634            - six
 635              seven
 636            "
 637        },
 638    );
 639
 640    // Replace a range that ends in a deleted hunk.
 641    multibuffer.update(cx, |multibuffer, cx| {
 642        multibuffer.edit([(Point::new(5, 2)..Point::new(6, 2), "fty-")], None, cx);
 643    });
 644    assert_new_snapshot(
 645        &multibuffer,
 646        &mut snapshot,
 647        &mut subscription,
 648        cx,
 649        indoc! {
 650            "
 651              one
 652              two
 653            + __
 654            + __THREE
 655              four
 656              fifty-seven
 657            "
 658        },
 659    );
 660}
 661
 662#[gpui::test]
 663fn test_excerpt_events(cx: &mut App) {
 664    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'a'), cx));
 665    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'm'), cx));
 666
 667    let leader_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 668    let follower_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 669    let follower_edit_event_count = Arc::new(RwLock::new(0));
 670
 671    follower_multibuffer.update(cx, |_, cx| {
 672        let follower_edit_event_count = follower_edit_event_count.clone();
 673        cx.subscribe(
 674            &leader_multibuffer,
 675            move |follower, _, event, cx| match event.clone() {
 676                Event::ExcerptsAdded {
 677                    buffer,
 678                    predecessor,
 679                    excerpts,
 680                } => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx),
 681                Event::ExcerptsRemoved { ids } => follower.remove_excerpts(ids, cx),
 682                Event::Edited { .. } => {
 683                    *follower_edit_event_count.write() += 1;
 684                }
 685                _ => {}
 686            },
 687        )
 688        .detach();
 689    });
 690
 691    leader_multibuffer.update(cx, |leader, cx| {
 692        leader.push_excerpts(
 693            buffer_1.clone(),
 694            [
 695                ExcerptRange {
 696                    context: 0..8,
 697                    primary: None,
 698                },
 699                ExcerptRange {
 700                    context: 12..16,
 701                    primary: None,
 702                },
 703            ],
 704            cx,
 705        );
 706        leader.insert_excerpts_after(
 707            leader.excerpt_ids()[0],
 708            buffer_2.clone(),
 709            [
 710                ExcerptRange {
 711                    context: 0..5,
 712                    primary: None,
 713                },
 714                ExcerptRange {
 715                    context: 10..15,
 716                    primary: None,
 717                },
 718            ],
 719            cx,
 720        )
 721    });
 722    assert_eq!(
 723        leader_multibuffer.read(cx).snapshot(cx).text(),
 724        follower_multibuffer.read(cx).snapshot(cx).text(),
 725    );
 726    assert_eq!(*follower_edit_event_count.read(), 2);
 727
 728    leader_multibuffer.update(cx, |leader, cx| {
 729        let excerpt_ids = leader.excerpt_ids();
 730        leader.remove_excerpts([excerpt_ids[1], excerpt_ids[3]], cx);
 731    });
 732    assert_eq!(
 733        leader_multibuffer.read(cx).snapshot(cx).text(),
 734        follower_multibuffer.read(cx).snapshot(cx).text(),
 735    );
 736    assert_eq!(*follower_edit_event_count.read(), 3);
 737
 738    // Removing an empty set of excerpts is a noop.
 739    leader_multibuffer.update(cx, |leader, cx| {
 740        leader.remove_excerpts([], cx);
 741    });
 742    assert_eq!(
 743        leader_multibuffer.read(cx).snapshot(cx).text(),
 744        follower_multibuffer.read(cx).snapshot(cx).text(),
 745    );
 746    assert_eq!(*follower_edit_event_count.read(), 3);
 747
 748    // Adding an empty set of excerpts is a noop.
 749    leader_multibuffer.update(cx, |leader, cx| {
 750        leader.push_excerpts::<usize>(buffer_2.clone(), [], cx);
 751    });
 752    assert_eq!(
 753        leader_multibuffer.read(cx).snapshot(cx).text(),
 754        follower_multibuffer.read(cx).snapshot(cx).text(),
 755    );
 756    assert_eq!(*follower_edit_event_count.read(), 3);
 757
 758    leader_multibuffer.update(cx, |leader, cx| {
 759        leader.clear(cx);
 760    });
 761    assert_eq!(
 762        leader_multibuffer.read(cx).snapshot(cx).text(),
 763        follower_multibuffer.read(cx).snapshot(cx).text(),
 764    );
 765    assert_eq!(*follower_edit_event_count.read(), 4);
 766}
 767
 768#[gpui::test]
 769fn test_expand_excerpts(cx: &mut App) {
 770    let buffer = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
 771    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 772
 773    multibuffer.update(cx, |multibuffer, cx| {
 774        multibuffer.push_excerpts_with_context_lines(
 775            buffer.clone(),
 776            vec![
 777                // Note that in this test, this first excerpt
 778                // does not contain a new line
 779                Point::new(3, 2)..Point::new(3, 3),
 780                Point::new(7, 1)..Point::new(7, 3),
 781                Point::new(15, 0)..Point::new(15, 0),
 782            ],
 783            1,
 784            cx,
 785        )
 786    });
 787
 788    let snapshot = multibuffer.read(cx).snapshot(cx);
 789
 790    assert_eq!(
 791        snapshot.text(),
 792        concat!(
 793            "ccc\n", //
 794            "ddd\n", //
 795            "eee",   //
 796            "\n",    // End of excerpt
 797            "ggg\n", //
 798            "hhh\n", //
 799            "iii",   //
 800            "\n",    // End of excerpt
 801            "ooo\n", //
 802            "ppp\n", //
 803            "qqq",   // End of excerpt
 804        )
 805    );
 806    drop(snapshot);
 807
 808    multibuffer.update(cx, |multibuffer, cx| {
 809        multibuffer.expand_excerpts(
 810            multibuffer.excerpt_ids(),
 811            1,
 812            ExpandExcerptDirection::UpAndDown,
 813            cx,
 814        )
 815    });
 816
 817    let snapshot = multibuffer.read(cx).snapshot(cx);
 818
 819    // Expanding context lines causes the line containing 'fff' to appear in two different excerpts.
 820    // We don't attempt to merge them, because removing the excerpt could create inconsistency with other layers
 821    // that are tracking excerpt ids.
 822    assert_eq!(
 823        snapshot.text(),
 824        concat!(
 825            "bbb\n", //
 826            "ccc\n", //
 827            "ddd\n", //
 828            "eee\n", //
 829            "fff\n", // End of excerpt
 830            "fff\n", //
 831            "ggg\n", //
 832            "hhh\n", //
 833            "iii\n", //
 834            "jjj\n", // End of excerpt
 835            "nnn\n", //
 836            "ooo\n", //
 837            "ppp\n", //
 838            "qqq\n", //
 839            "rrr",   // End of excerpt
 840        )
 841    );
 842}
 843
 844#[gpui::test]
 845fn test_push_excerpts_with_context_lines(cx: &mut App) {
 846    let buffer = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
 847    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 848    let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
 849        multibuffer.push_excerpts_with_context_lines(
 850            buffer.clone(),
 851            vec![
 852                // Note that in this test, this first excerpt
 853                // does contain a new line
 854                Point::new(3, 2)..Point::new(4, 2),
 855                Point::new(7, 1)..Point::new(7, 3),
 856                Point::new(15, 0)..Point::new(15, 0),
 857            ],
 858            2,
 859            cx,
 860        )
 861    });
 862
 863    let snapshot = multibuffer.read(cx).snapshot(cx);
 864    assert_eq!(
 865        snapshot.text(),
 866        concat!(
 867            "bbb\n", // Preserve newlines
 868            "ccc\n", //
 869            "ddd\n", //
 870            "eee\n", //
 871            "fff\n", //
 872            "ggg\n", //
 873            "hhh\n", //
 874            "iii\n", //
 875            "jjj\n", //
 876            "nnn\n", //
 877            "ooo\n", //
 878            "ppp\n", //
 879            "qqq\n", //
 880            "rrr",   //
 881        )
 882    );
 883
 884    assert_eq!(
 885        anchor_ranges
 886            .iter()
 887            .map(|range| range.to_point(&snapshot))
 888            .collect::<Vec<_>>(),
 889        vec![
 890            Point::new(2, 2)..Point::new(3, 2),
 891            Point::new(6, 1)..Point::new(6, 3),
 892            Point::new(11, 0)..Point::new(11, 0)
 893        ]
 894    );
 895}
 896
 897#[gpui::test(iterations = 100)]
 898async fn test_push_multiple_excerpts_with_context_lines(cx: &mut TestAppContext) {
 899    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
 900    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(15, 4, 'a'), cx));
 901    let snapshot_1 = buffer_1.update(cx, |buffer, _| buffer.snapshot());
 902    let snapshot_2 = buffer_2.update(cx, |buffer, _| buffer.snapshot());
 903    let ranges_1 = vec![
 904        snapshot_1.anchor_before(Point::new(3, 2))..snapshot_1.anchor_before(Point::new(4, 2)),
 905        snapshot_1.anchor_before(Point::new(7, 1))..snapshot_1.anchor_before(Point::new(7, 3)),
 906        snapshot_1.anchor_before(Point::new(15, 0))..snapshot_1.anchor_before(Point::new(15, 0)),
 907    ];
 908    let ranges_2 = vec![
 909        snapshot_2.anchor_before(Point::new(2, 1))..snapshot_2.anchor_before(Point::new(3, 1)),
 910        snapshot_2.anchor_before(Point::new(10, 0))..snapshot_2.anchor_before(Point::new(10, 2)),
 911    ];
 912
 913    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 914    let anchor_ranges = multibuffer
 915        .update(cx, |multibuffer, cx| {
 916            multibuffer.push_multiple_excerpts_with_context_lines(
 917                vec![(buffer_1.clone(), ranges_1), (buffer_2.clone(), ranges_2)],
 918                2,
 919                cx,
 920            )
 921        })
 922        .await;
 923
 924    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 925    assert_eq!(
 926        snapshot.text(),
 927        concat!(
 928            "bbb\n", // buffer_1
 929            "ccc\n", //
 930            "ddd\n", // <-- excerpt 1
 931            "eee\n", // <-- excerpt 1
 932            "fff\n", //
 933            "ggg\n", //
 934            "hhh\n", // <-- excerpt 2
 935            "iii\n", //
 936            "jjj\n", //
 937            //
 938            "nnn\n", //
 939            "ooo\n", //
 940            "ppp\n", // <-- excerpt 3
 941            "qqq\n", //
 942            "rrr\n", //
 943            //
 944            "aaaa\n", // buffer 2
 945            "bbbb\n", //
 946            "cccc\n", // <-- excerpt 4
 947            "dddd\n", // <-- excerpt 4
 948            "eeee\n", //
 949            "ffff\n", //
 950            //
 951            "iiii\n", //
 952            "jjjj\n", //
 953            "kkkk\n", // <-- excerpt 5
 954            "llll\n", //
 955            "mmmm",   //
 956        )
 957    );
 958
 959    assert_eq!(
 960        anchor_ranges
 961            .iter()
 962            .map(|range| range.to_point(&snapshot))
 963            .collect::<Vec<_>>(),
 964        vec![
 965            Point::new(2, 2)..Point::new(3, 2),
 966            Point::new(6, 1)..Point::new(6, 3),
 967            Point::new(11, 0)..Point::new(11, 0),
 968            Point::new(16, 1)..Point::new(17, 1),
 969            Point::new(22, 0)..Point::new(22, 2)
 970        ]
 971    );
 972}
 973
 974#[gpui::test]
 975fn test_empty_multibuffer(cx: &mut App) {
 976    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 977
 978    let snapshot = multibuffer.read(cx).snapshot(cx);
 979    assert_eq!(snapshot.text(), "");
 980    assert_eq!(
 981        snapshot
 982            .row_infos(MultiBufferRow(0))
 983            .map(|info| info.buffer_row)
 984            .collect::<Vec<_>>(),
 985        &[Some(0)]
 986    );
 987    assert_eq!(
 988        snapshot
 989            .row_infos(MultiBufferRow(1))
 990            .map(|info| info.buffer_row)
 991            .collect::<Vec<_>>(),
 992        &[]
 993    );
 994}
 995
 996#[gpui::test]
 997fn test_singleton_multibuffer_anchors(cx: &mut App) {
 998    let buffer = cx.new(|cx| Buffer::local("abcd", cx));
 999    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
1000    let old_snapshot = multibuffer.read(cx).snapshot(cx);
1001    buffer.update(cx, |buffer, cx| {
1002        buffer.edit([(0..0, "X")], None, cx);
1003        buffer.edit([(5..5, "Y")], None, cx);
1004    });
1005    let new_snapshot = multibuffer.read(cx).snapshot(cx);
1006
1007    assert_eq!(old_snapshot.text(), "abcd");
1008    assert_eq!(new_snapshot.text(), "XabcdY");
1009
1010    assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1011    assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1012    assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5);
1013    assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6);
1014}
1015
1016#[gpui::test]
1017fn test_multibuffer_anchors(cx: &mut App) {
1018    let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1019    let buffer_2 = cx.new(|cx| Buffer::local("efghi", cx));
1020    let multibuffer = cx.new(|cx| {
1021        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1022        multibuffer.push_excerpts(
1023            buffer_1.clone(),
1024            [ExcerptRange {
1025                context: 0..4,
1026                primary: None,
1027            }],
1028            cx,
1029        );
1030        multibuffer.push_excerpts(
1031            buffer_2.clone(),
1032            [ExcerptRange {
1033                context: 0..5,
1034                primary: None,
1035            }],
1036            cx,
1037        );
1038        multibuffer
1039    });
1040    let old_snapshot = multibuffer.read(cx).snapshot(cx);
1041
1042    assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0);
1043    assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0);
1044    assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1045    assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1046    assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1047    assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1048
1049    buffer_1.update(cx, |buffer, cx| {
1050        buffer.edit([(0..0, "W")], None, cx);
1051        buffer.edit([(5..5, "X")], None, cx);
1052    });
1053    buffer_2.update(cx, |buffer, cx| {
1054        buffer.edit([(0..0, "Y")], None, cx);
1055        buffer.edit([(6..6, "Z")], None, cx);
1056    });
1057    let new_snapshot = multibuffer.read(cx).snapshot(cx);
1058
1059    assert_eq!(old_snapshot.text(), "abcd\nefghi");
1060    assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
1061
1062    assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1063    assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1064    assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2);
1065    assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2);
1066    assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3);
1067    assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3);
1068    assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7);
1069    assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8);
1070    assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13);
1071    assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14);
1072}
1073
1074#[gpui::test]
1075fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
1076    let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1077    let buffer_2 = cx.new(|cx| Buffer::local("ABCDEFGHIJKLMNOP", cx));
1078    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1079
1080    // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
1081    // Add an excerpt from buffer 1 that spans this new insertion.
1082    buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx));
1083    let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| {
1084        multibuffer
1085            .push_excerpts(
1086                buffer_1.clone(),
1087                [ExcerptRange {
1088                    context: 0..7,
1089                    primary: None,
1090                }],
1091                cx,
1092            )
1093            .pop()
1094            .unwrap()
1095    });
1096
1097    let snapshot_1 = multibuffer.read(cx).snapshot(cx);
1098    assert_eq!(snapshot_1.text(), "abcd123");
1099
1100    // Replace the buffer 1 excerpt with new excerpts from buffer 2.
1101    let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
1102        multibuffer.remove_excerpts([excerpt_id_1], cx);
1103        let mut ids = multibuffer
1104            .push_excerpts(
1105                buffer_2.clone(),
1106                [
1107                    ExcerptRange {
1108                        context: 0..4,
1109                        primary: None,
1110                    },
1111                    ExcerptRange {
1112                        context: 6..10,
1113                        primary: None,
1114                    },
1115                    ExcerptRange {
1116                        context: 12..16,
1117                        primary: None,
1118                    },
1119                ],
1120                cx,
1121            )
1122            .into_iter();
1123        (ids.next().unwrap(), ids.next().unwrap())
1124    });
1125    let snapshot_2 = multibuffer.read(cx).snapshot(cx);
1126    assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP");
1127
1128    // The old excerpt id doesn't get reused.
1129    assert_ne!(excerpt_id_2, excerpt_id_1);
1130
1131    // Resolve some anchors from the previous snapshot in the new snapshot.
1132    // The current excerpts are from a different buffer, so we don't attempt to
1133    // resolve the old text anchor in the new buffer.
1134    assert_eq!(
1135        snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)),
1136        0
1137    );
1138    assert_eq!(
1139        snapshot_2.summaries_for_anchors::<usize, _>(&[
1140            snapshot_1.anchor_before(2),
1141            snapshot_1.anchor_after(3)
1142        ]),
1143        vec![0, 0]
1144    );
1145
1146    // Refresh anchors from the old snapshot. The return value indicates that both
1147    // anchors lost their original excerpt.
1148    let refresh =
1149        snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]);
1150    assert_eq!(
1151        refresh,
1152        &[
1153            (0, snapshot_2.anchor_before(0), false),
1154            (1, snapshot_2.anchor_after(0), false),
1155        ]
1156    );
1157
1158    // Replace the middle excerpt with a smaller excerpt in buffer 2,
1159    // that intersects the old excerpt.
1160    let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| {
1161        multibuffer.remove_excerpts([excerpt_id_3], cx);
1162        multibuffer
1163            .insert_excerpts_after(
1164                excerpt_id_2,
1165                buffer_2.clone(),
1166                [ExcerptRange {
1167                    context: 5..8,
1168                    primary: None,
1169                }],
1170                cx,
1171            )
1172            .pop()
1173            .unwrap()
1174    });
1175
1176    let snapshot_3 = multibuffer.read(cx).snapshot(cx);
1177    assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP");
1178    assert_ne!(excerpt_id_5, excerpt_id_3);
1179
1180    // Resolve some anchors from the previous snapshot in the new snapshot.
1181    // The third anchor can't be resolved, since its excerpt has been removed,
1182    // so it resolves to the same position as its predecessor.
1183    let anchors = [
1184        snapshot_2.anchor_before(0),
1185        snapshot_2.anchor_after(2),
1186        snapshot_2.anchor_after(6),
1187        snapshot_2.anchor_after(14),
1188    ];
1189    assert_eq!(
1190        snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
1191        &[0, 2, 9, 13]
1192    );
1193
1194    let new_anchors = snapshot_3.refresh_anchors(&anchors);
1195    assert_eq!(
1196        new_anchors.iter().map(|a| (a.0, a.2)).collect::<Vec<_>>(),
1197        &[(0, true), (1, true), (2, true), (3, true)]
1198    );
1199    assert_eq!(
1200        snapshot_3.summaries_for_anchors::<usize, _>(new_anchors.iter().map(|a| &a.1)),
1201        &[0, 2, 7, 13]
1202    );
1203}
1204
1205#[gpui::test]
1206fn test_basic_diff_hunks(cx: &mut TestAppContext) {
1207    let text = indoc!(
1208        "
1209        ZERO
1210        one
1211        TWO
1212        three
1213        six
1214        "
1215    );
1216    let base_text = indoc!(
1217        "
1218        one
1219        two
1220        three
1221        four
1222        five
1223        six
1224        "
1225    );
1226
1227    let buffer = cx.new(|cx| Buffer::local(text, cx));
1228    let change_set =
1229        cx.new(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
1230    cx.run_until_parked();
1231
1232    let multibuffer = cx.new(|cx| {
1233        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1234        multibuffer.add_change_set(change_set.clone(), cx);
1235        multibuffer
1236    });
1237
1238    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1239        (multibuffer.snapshot(cx), multibuffer.subscribe())
1240    });
1241    assert_eq!(
1242        snapshot.text(),
1243        indoc!(
1244            "
1245            ZERO
1246            one
1247            TWO
1248            three
1249            six
1250            "
1251        ),
1252    );
1253
1254    multibuffer.update(cx, |multibuffer, cx| {
1255        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1256    });
1257
1258    assert_new_snapshot(
1259        &multibuffer,
1260        &mut snapshot,
1261        &mut subscription,
1262        cx,
1263        indoc!(
1264            "
1265            + ZERO
1266              one
1267            - two
1268            + TWO
1269              three
1270            - four
1271            - five
1272              six
1273            "
1274        ),
1275    );
1276
1277    assert_eq!(
1278        snapshot
1279            .row_infos(MultiBufferRow(0))
1280            .map(|info| (info.buffer_row, info.diff_status))
1281            .collect::<Vec<_>>(),
1282        vec![
1283            (Some(0), Some(DiffHunkStatus::Added)),
1284            (Some(1), None),
1285            (Some(1), Some(DiffHunkStatus::Removed)),
1286            (Some(2), Some(DiffHunkStatus::Added)),
1287            (Some(3), None),
1288            (Some(3), Some(DiffHunkStatus::Removed)),
1289            (Some(4), Some(DiffHunkStatus::Removed)),
1290            (Some(4), None),
1291            (Some(5), None)
1292        ]
1293    );
1294
1295    assert_chunks_in_ranges(&snapshot);
1296    assert_consistent_line_numbers(&snapshot);
1297    assert_position_translation(&snapshot);
1298    assert_line_indents(&snapshot);
1299
1300    multibuffer.update(cx, |multibuffer, cx| {
1301        multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
1302    });
1303    assert_new_snapshot(
1304        &multibuffer,
1305        &mut snapshot,
1306        &mut subscription,
1307        cx,
1308        indoc!(
1309            "
1310            ZERO
1311            one
1312            TWO
1313            three
1314            six
1315            "
1316        ),
1317    );
1318
1319    assert_chunks_in_ranges(&snapshot);
1320    assert_consistent_line_numbers(&snapshot);
1321    assert_position_translation(&snapshot);
1322    assert_line_indents(&snapshot);
1323
1324    // Expand the first diff hunk
1325    multibuffer.update(cx, |multibuffer, cx| {
1326        let position = multibuffer.read(cx).anchor_before(Point::new(2, 2));
1327        multibuffer.expand_diff_hunks(vec![position..position], cx)
1328    });
1329    assert_new_snapshot(
1330        &multibuffer,
1331        &mut snapshot,
1332        &mut subscription,
1333        cx,
1334        indoc!(
1335            "
1336              ZERO
1337              one
1338            - two
1339            + TWO
1340              three
1341              six
1342            "
1343        ),
1344    );
1345
1346    // Expand the second diff hunk
1347    multibuffer.update(cx, |multibuffer, cx| {
1348        let start = multibuffer.read(cx).anchor_before(Point::new(4, 0));
1349        let end = multibuffer.read(cx).anchor_before(Point::new(5, 0));
1350        multibuffer.expand_diff_hunks(vec![start..end], cx)
1351    });
1352    assert_new_snapshot(
1353        &multibuffer,
1354        &mut snapshot,
1355        &mut subscription,
1356        cx,
1357        indoc!(
1358            "
1359              ZERO
1360              one
1361            - two
1362            + TWO
1363              three
1364            - four
1365            - five
1366              six
1367            "
1368        ),
1369    );
1370
1371    assert_chunks_in_ranges(&snapshot);
1372    assert_consistent_line_numbers(&snapshot);
1373    assert_position_translation(&snapshot);
1374    assert_line_indents(&snapshot);
1375
1376    // Edit the buffer before the first hunk
1377    buffer.update(cx, |buffer, cx| {
1378        buffer.edit_via_marked_text(
1379            indoc!(
1380                "
1381                ZERO
1382                one« hundred
1383                  thousand»
1384                TWO
1385                three
1386                six
1387                "
1388            ),
1389            None,
1390            cx,
1391        );
1392    });
1393    assert_new_snapshot(
1394        &multibuffer,
1395        &mut snapshot,
1396        &mut subscription,
1397        cx,
1398        indoc!(
1399            "
1400              ZERO
1401              one hundred
1402                thousand
1403            - two
1404            + TWO
1405              three
1406            - four
1407            - five
1408              six
1409            "
1410        ),
1411    );
1412
1413    assert_chunks_in_ranges(&snapshot);
1414    assert_consistent_line_numbers(&snapshot);
1415    assert_position_translation(&snapshot);
1416    assert_line_indents(&snapshot);
1417
1418    // Recalculate the diff, changing the first diff hunk.
1419    let _ = change_set.update(cx, |change_set, cx| {
1420        change_set.recalculate_diff(buffer.read(cx).text_snapshot(), cx)
1421    });
1422    cx.run_until_parked();
1423    assert_new_snapshot(
1424        &multibuffer,
1425        &mut snapshot,
1426        &mut subscription,
1427        cx,
1428        indoc!(
1429            "
1430              ZERO
1431              one hundred
1432                thousand
1433              TWO
1434              three
1435            - four
1436            - five
1437              six
1438            "
1439        ),
1440    );
1441
1442    assert_eq!(
1443        snapshot
1444            .diff_hunks_in_range(0..snapshot.len())
1445            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1446            .collect::<Vec<_>>(),
1447        &[0..4, 5..7]
1448    );
1449}
1450
1451#[gpui::test]
1452fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
1453    let text = indoc!(
1454        "
1455        one
1456        TWO
1457        THREE
1458        four
1459        FIVE
1460        six
1461        "
1462    );
1463    let base_text = indoc!(
1464        "
1465        one
1466        four
1467        six
1468        "
1469    );
1470
1471    let buffer = cx.new(|cx| Buffer::local(text, cx));
1472    let change_set =
1473        cx.new(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
1474    cx.run_until_parked();
1475
1476    let multibuffer = cx.new(|cx| {
1477        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1478        multibuffer.add_change_set(change_set.clone(), cx);
1479        multibuffer
1480    });
1481
1482    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1483        (multibuffer.snapshot(cx), multibuffer.subscribe())
1484    });
1485
1486    multibuffer.update(cx, |multibuffer, cx| {
1487        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1488    });
1489
1490    assert_new_snapshot(
1491        &multibuffer,
1492        &mut snapshot,
1493        &mut subscription,
1494        cx,
1495        indoc!(
1496            "
1497              one
1498            + TWO
1499            + THREE
1500              four
1501            + FIVE
1502              six
1503            "
1504        ),
1505    );
1506
1507    // Regression test: expanding diff hunks that are already expanded should not change anything.
1508    multibuffer.update(cx, |multibuffer, cx| {
1509        multibuffer.expand_diff_hunks(
1510            vec![
1511                snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_before(Point::new(2, 0)),
1512            ],
1513            cx,
1514        );
1515    });
1516
1517    assert_new_snapshot(
1518        &multibuffer,
1519        &mut snapshot,
1520        &mut subscription,
1521        cx,
1522        indoc!(
1523            "
1524              one
1525            + TWO
1526            + THREE
1527              four
1528            + FIVE
1529              six
1530            "
1531        ),
1532    );
1533}
1534
1535#[gpui::test]
1536fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
1537    let base_text_1 = indoc!(
1538        "
1539        one
1540        two
1541            three
1542        four
1543        five
1544        six
1545        "
1546    );
1547    let text_1 = indoc!(
1548        "
1549        ZERO
1550        one
1551        TWO
1552            three
1553        six
1554        "
1555    );
1556    let base_text_2 = indoc!(
1557        "
1558        seven
1559          eight
1560        nine
1561        ten
1562        eleven
1563        twelve
1564        "
1565    );
1566    let text_2 = indoc!(
1567        "
1568          eight
1569        nine
1570        eleven
1571        THIRTEEN
1572        FOURTEEN
1573        "
1574    );
1575
1576    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
1577    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
1578    let change_set_1 =
1579        cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_1.to_string(), &buffer_1, cx));
1580    let change_set_2 =
1581        cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_2.to_string(), &buffer_2, cx));
1582    cx.run_until_parked();
1583
1584    let multibuffer = cx.new(|cx| {
1585        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1586        multibuffer.push_excerpts(
1587            buffer_1.clone(),
1588            [ExcerptRange {
1589                context: text::Anchor::MIN..text::Anchor::MAX,
1590                primary: None,
1591            }],
1592            cx,
1593        );
1594        multibuffer.push_excerpts(
1595            buffer_2.clone(),
1596            [ExcerptRange {
1597                context: text::Anchor::MIN..text::Anchor::MAX,
1598                primary: None,
1599            }],
1600            cx,
1601        );
1602        multibuffer.add_change_set(change_set_1.clone(), cx);
1603        multibuffer.add_change_set(change_set_2.clone(), cx);
1604        multibuffer
1605    });
1606
1607    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1608        (multibuffer.snapshot(cx), multibuffer.subscribe())
1609    });
1610    assert_eq!(
1611        snapshot.text(),
1612        indoc!(
1613            "
1614            ZERO
1615            one
1616            TWO
1617                three
1618            six
1619
1620              eight
1621            nine
1622            eleven
1623            THIRTEEN
1624            FOURTEEN
1625            "
1626        ),
1627    );
1628
1629    multibuffer.update(cx, |multibuffer, cx| {
1630        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1631    });
1632
1633    assert_new_snapshot(
1634        &multibuffer,
1635        &mut snapshot,
1636        &mut subscription,
1637        cx,
1638        indoc!(
1639            "
1640            + ZERO
1641              one
1642            - two
1643            + TWO
1644                  three
1645            - four
1646            - five
1647              six
1648
1649            - seven
1650                eight
1651              nine
1652            - ten
1653              eleven
1654            - twelve
1655            + THIRTEEN
1656            + FOURTEEN
1657            "
1658        ),
1659    );
1660
1661    let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
1662    let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
1663    let base_id_1 = change_set_1.read_with(cx, |change_set, _| {
1664        change_set.base_text.as_ref().unwrap().remote_id()
1665    });
1666    let base_id_2 = change_set_2.read_with(cx, |change_set, _| {
1667        change_set.base_text.as_ref().unwrap().remote_id()
1668    });
1669
1670    let buffer_lines = (0..=snapshot.max_row().0)
1671        .map(|row| {
1672            let (buffer, range) = snapshot.buffer_line_for_row(MultiBufferRow(row))?;
1673            Some((
1674                buffer.remote_id(),
1675                buffer.text_for_range(range).collect::<String>(),
1676            ))
1677        })
1678        .collect::<Vec<_>>();
1679    pretty_assertions::assert_eq!(
1680        buffer_lines,
1681        [
1682            Some((id_1, "ZERO".into())),
1683            Some((id_1, "one".into())),
1684            Some((base_id_1, "two".into())),
1685            Some((id_1, "TWO".into())),
1686            Some((id_1, "    three".into())),
1687            Some((base_id_1, "four".into())),
1688            Some((base_id_1, "five".into())),
1689            Some((id_1, "six".into())),
1690            Some((id_1, "".into())),
1691            Some((base_id_2, "seven".into())),
1692            Some((id_2, "  eight".into())),
1693            Some((id_2, "nine".into())),
1694            Some((base_id_2, "ten".into())),
1695            Some((id_2, "eleven".into())),
1696            Some((base_id_2, "twelve".into())),
1697            Some((id_2, "THIRTEEN".into())),
1698            Some((id_2, "FOURTEEN".into())),
1699            Some((id_2, "".into())),
1700        ]
1701    );
1702
1703    assert_position_translation(&snapshot);
1704    assert_line_indents(&snapshot);
1705
1706    assert_eq!(
1707        snapshot
1708            .diff_hunks_in_range(0..snapshot.len())
1709            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1710            .collect::<Vec<_>>(),
1711        &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
1712    );
1713
1714    buffer_2.update(cx, |buffer, cx| {
1715        buffer.edit_via_marked_text(
1716            indoc!(
1717                "
1718                  eight
1719                «»eleven
1720                THIRTEEN
1721                FOURTEEN
1722                "
1723            ),
1724            None,
1725            cx,
1726        );
1727    });
1728
1729    assert_new_snapshot(
1730        &multibuffer,
1731        &mut snapshot,
1732        &mut subscription,
1733        cx,
1734        indoc!(
1735            "
1736            + ZERO
1737              one
1738            - two
1739            + TWO
1740                  three
1741            - four
1742            - five
1743              six
1744
1745            - seven
1746                eight
1747              eleven
1748            - twelve
1749            + THIRTEEN
1750            + FOURTEEN
1751            "
1752        ),
1753    );
1754
1755    assert_line_indents(&snapshot);
1756}
1757
1758/// A naive implementation of a multi-buffer that does not maintain
1759/// any derived state, used for comparison in a randomized test.
1760#[derive(Default)]
1761struct ReferenceMultibuffer {
1762    excerpts: Vec<ReferenceExcerpt>,
1763    change_sets: HashMap<BufferId, Entity<BufferChangeSet>>,
1764}
1765
1766struct ReferenceExcerpt {
1767    id: ExcerptId,
1768    buffer: Entity<Buffer>,
1769    range: Range<text::Anchor>,
1770    expanded_diff_hunks: Vec<text::Anchor>,
1771}
1772
1773#[derive(Debug)]
1774struct ReferenceRegion {
1775    range: Range<usize>,
1776    buffer_start: Option<Point>,
1777    status: Option<DiffHunkStatus>,
1778}
1779
1780impl ReferenceMultibuffer {
1781    fn expand_excerpts(&mut self, excerpts: &HashSet<ExcerptId>, line_count: u32, cx: &App) {
1782        if line_count == 0 {
1783            return;
1784        }
1785
1786        for id in excerpts {
1787            let excerpt = self.excerpts.iter_mut().find(|e| e.id == *id).unwrap();
1788            let snapshot = excerpt.buffer.read(cx).snapshot();
1789            let mut point_range = excerpt.range.to_point(&snapshot);
1790            point_range.start = Point::new(point_range.start.row.saturating_sub(line_count), 0);
1791            point_range.end =
1792                snapshot.clip_point(Point::new(point_range.end.row + line_count, 0), Bias::Left);
1793            point_range.end.column = snapshot.line_len(point_range.end.row);
1794            excerpt.range =
1795                snapshot.anchor_before(point_range.start)..snapshot.anchor_after(point_range.end);
1796        }
1797    }
1798
1799    fn remove_excerpt(&mut self, id: ExcerptId, cx: &App) {
1800        let ix = self
1801            .excerpts
1802            .iter()
1803            .position(|excerpt| excerpt.id == id)
1804            .unwrap();
1805        let excerpt = self.excerpts.remove(ix);
1806        let buffer = excerpt.buffer.read(cx);
1807        log::info!(
1808            "Removing excerpt {}: {:?}",
1809            ix,
1810            buffer
1811                .text_for_range(excerpt.range.to_offset(buffer))
1812                .collect::<String>(),
1813        );
1814    }
1815
1816    fn insert_excerpt_after(
1817        &mut self,
1818        prev_id: ExcerptId,
1819        new_excerpt_id: ExcerptId,
1820        (buffer_handle, anchor_range): (Entity<Buffer>, Range<text::Anchor>),
1821    ) {
1822        let excerpt_ix = if prev_id == ExcerptId::max() {
1823            self.excerpts.len()
1824        } else {
1825            self.excerpts
1826                .iter()
1827                .position(|excerpt| excerpt.id == prev_id)
1828                .unwrap()
1829                + 1
1830        };
1831        self.excerpts.insert(
1832            excerpt_ix,
1833            ReferenceExcerpt {
1834                id: new_excerpt_id,
1835                buffer: buffer_handle,
1836                range: anchor_range,
1837                expanded_diff_hunks: Vec::new(),
1838            },
1839        );
1840    }
1841
1842    fn expand_diff_hunks(&mut self, excerpt_id: ExcerptId, range: Range<text::Anchor>, cx: &App) {
1843        let excerpt = self
1844            .excerpts
1845            .iter_mut()
1846            .find(|e| e.id == excerpt_id)
1847            .unwrap();
1848        let buffer = excerpt.buffer.read(cx).snapshot();
1849        let buffer_id = buffer.remote_id();
1850        let Some(change_set) = self.change_sets.get(&buffer_id) else {
1851            return;
1852        };
1853        let diff = change_set.read(cx).diff_to_buffer.clone();
1854        let excerpt_range = excerpt.range.to_offset(&buffer);
1855        if excerpt_range.is_empty() {
1856            return;
1857        }
1858        for hunk in diff.hunks_intersecting_range(range, &buffer) {
1859            let hunk_range = hunk.buffer_range.to_offset(&buffer);
1860            let hunk_precedes_excerpt = hunk
1861                .buffer_range
1862                .end
1863                .cmp(&excerpt.range.start, &buffer)
1864                .is_le();
1865            let hunk_follows_excerpt = hunk
1866                .buffer_range
1867                .start
1868                .cmp(&excerpt.range.end, &buffer)
1869                .is_ge();
1870            if hunk_precedes_excerpt || hunk_follows_excerpt {
1871                continue;
1872            }
1873
1874            if let Err(ix) = excerpt
1875                .expanded_diff_hunks
1876                .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
1877            {
1878                log::info!(
1879                    "expanding diff hunk {:?}. excerpt: {:?}",
1880                    hunk_range,
1881                    excerpt_range
1882                );
1883                excerpt
1884                    .expanded_diff_hunks
1885                    .insert(ix, hunk.buffer_range.start);
1886            }
1887        }
1888    }
1889
1890    fn expected_content(&self, cx: &App) -> (String, Vec<RowInfo>, HashSet<MultiBufferRow>) {
1891        let mut text = String::new();
1892        let mut regions = Vec::<ReferenceRegion>::new();
1893        let mut excerpt_boundary_rows = HashSet::default();
1894        for excerpt in &self.excerpts {
1895            excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
1896            let buffer = excerpt.buffer.read(cx);
1897            let buffer_range = excerpt.range.to_offset(buffer);
1898            let change_set = self.change_sets.get(&buffer.remote_id()).unwrap().read(cx);
1899            let diff = change_set.diff_to_buffer.clone();
1900            let base_buffer = change_set.base_text.as_ref().unwrap();
1901
1902            let mut offset = buffer_range.start;
1903            let mut hunks = diff
1904                .hunks_intersecting_range(excerpt.range.clone(), buffer)
1905                .peekable();
1906
1907            while let Some(hunk) = hunks.next() {
1908                if !hunk.buffer_range.start.is_valid(&buffer) {
1909                    continue;
1910                }
1911
1912                // Ignore hunks that are outside the excerpt range.
1913                let mut hunk_range = hunk.buffer_range.to_offset(buffer);
1914                hunk_range.end = hunk_range.end.min(buffer_range.end);
1915                if hunk_range.start > buffer_range.end
1916                    || hunk_range.end < buffer_range.start
1917                    || buffer_range.is_empty()
1918                {
1919                    continue;
1920                }
1921
1922                if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| {
1923                    expanded_anchor.to_offset(&buffer).max(buffer_range.start)
1924                        == hunk_range.start.max(buffer_range.start)
1925                }) {
1926                    continue;
1927                }
1928
1929                if hunk_range.start >= offset {
1930                    // Add the buffer text before the hunk
1931                    let len = text.len();
1932                    text.extend(buffer.text_for_range(offset..hunk_range.start));
1933                    regions.push(ReferenceRegion {
1934                        range: len..text.len(),
1935                        buffer_start: Some(buffer.offset_to_point(offset)),
1936                        status: None,
1937                    });
1938
1939                    // Add the deleted text for the hunk.
1940                    if !hunk.diff_base_byte_range.is_empty() {
1941                        let mut base_text = base_buffer
1942                            .text_for_range(hunk.diff_base_byte_range.clone())
1943                            .collect::<String>();
1944                        if !base_text.ends_with('\n') {
1945                            base_text.push('\n');
1946                        }
1947                        let len = text.len();
1948                        text.push_str(&base_text);
1949                        regions.push(ReferenceRegion {
1950                            range: len..text.len(),
1951                            buffer_start: Some(
1952                                base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
1953                            ),
1954                            status: Some(DiffHunkStatus::Removed),
1955                        });
1956                    }
1957
1958                    offset = hunk_range.start;
1959                }
1960
1961                // Add the inserted text for the hunk.
1962                if hunk_range.end > offset {
1963                    let len = text.len();
1964                    text.extend(buffer.text_for_range(offset..hunk_range.end));
1965                    regions.push(ReferenceRegion {
1966                        range: len..text.len(),
1967                        buffer_start: Some(buffer.offset_to_point(offset)),
1968                        status: Some(DiffHunkStatus::Added),
1969                    });
1970                    offset = hunk_range.end;
1971                }
1972            }
1973
1974            // Add the buffer text for the rest of the excerpt.
1975            let len = text.len();
1976            text.extend(buffer.text_for_range(offset..buffer_range.end));
1977            text.push('\n');
1978            regions.push(ReferenceRegion {
1979                range: len..text.len(),
1980                buffer_start: Some(buffer.offset_to_point(offset)),
1981                status: None,
1982            });
1983        }
1984
1985        // Remove final trailing newline.
1986        if self.excerpts.is_empty() {
1987            regions.push(ReferenceRegion {
1988                range: 0..1,
1989                buffer_start: Some(Point::new(0, 0)),
1990                status: None,
1991            });
1992        } else {
1993            text.pop();
1994        }
1995
1996        // Retrieve the row info using the region that contains
1997        // the start of each multi-buffer line.
1998        let mut ix = 0;
1999        let row_infos = text
2000            .split('\n')
2001            .map(|line| {
2002                let row_info = regions
2003                    .iter()
2004                    .find(|region| region.range.contains(&ix))
2005                    .map_or(RowInfo::default(), |region| {
2006                        let buffer_row = region.buffer_start.map(|start_point| {
2007                            start_point.row
2008                                + text[region.range.start..ix].matches('\n').count() as u32
2009                        });
2010                        RowInfo {
2011                            diff_status: region.status,
2012                            buffer_row,
2013                            multibuffer_row: Some(MultiBufferRow(
2014                                text[..ix].matches('\n').count() as u32
2015                            )),
2016                        }
2017                    });
2018                ix += line.len() + 1;
2019                row_info
2020            })
2021            .collect();
2022
2023        (text, row_infos, excerpt_boundary_rows)
2024    }
2025
2026    fn diffs_updated(&mut self, cx: &App) {
2027        for excerpt in &mut self.excerpts {
2028            let buffer = excerpt.buffer.read(cx).snapshot();
2029            let excerpt_range = excerpt.range.to_offset(&buffer);
2030            let buffer_id = buffer.remote_id();
2031            let diff = &self
2032                .change_sets
2033                .get(&buffer_id)
2034                .unwrap()
2035                .read(cx)
2036                .diff_to_buffer;
2037            let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer).peekable();
2038            excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
2039                if !hunk_anchor.is_valid(&buffer) {
2040                    return false;
2041                }
2042                while let Some(hunk) = hunks.peek() {
2043                    match hunk.buffer_range.start.cmp(&hunk_anchor, &buffer) {
2044                        cmp::Ordering::Less => {
2045                            hunks.next();
2046                        }
2047                        cmp::Ordering::Equal => {
2048                            let hunk_range = hunk.buffer_range.to_offset(&buffer);
2049                            return hunk_range.end >= excerpt_range.start
2050                                && hunk_range.start <= excerpt_range.end;
2051                        }
2052                        cmp::Ordering::Greater => break,
2053                    }
2054                }
2055                false
2056            });
2057        }
2058    }
2059
2060    fn add_change_set(&mut self, change_set: Entity<BufferChangeSet>, cx: &mut App) {
2061        let buffer_id = change_set.read(cx).buffer_id;
2062        self.change_sets.insert(buffer_id, change_set);
2063    }
2064}
2065
2066#[gpui::test(iterations = 100)]
2067fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
2068    let operations = env::var("OPERATIONS")
2069        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2070        .unwrap_or(10);
2071
2072    let mut buffers: Vec<Entity<Buffer>> = Vec::new();
2073    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2074    let mut reference = ReferenceMultibuffer::default();
2075    let mut anchors = Vec::new();
2076    let mut old_versions = Vec::new();
2077    let mut needs_diff_calculation = false;
2078
2079    for _ in 0..operations {
2080        match rng.gen_range(0..100) {
2081            0..=14 if !buffers.is_empty() => {
2082                let buffer = buffers.choose(&mut rng).unwrap();
2083                buffer.update(cx, |buf, cx| {
2084                    let edit_count = rng.gen_range(1..5);
2085                    buf.randomly_edit(&mut rng, edit_count, cx);
2086                    needs_diff_calculation = true;
2087                });
2088                reference.diffs_updated(cx);
2089            }
2090            15..=19 if !reference.excerpts.is_empty() => {
2091                multibuffer.update(cx, |multibuffer, cx| {
2092                    let ids = multibuffer.excerpt_ids();
2093                    let mut excerpts = HashSet::default();
2094                    for _ in 0..rng.gen_range(0..ids.len()) {
2095                        excerpts.extend(ids.choose(&mut rng).copied());
2096                    }
2097
2098                    let line_count = rng.gen_range(0..5);
2099
2100                    let excerpt_ixs = excerpts
2101                        .iter()
2102                        .map(|id| reference.excerpts.iter().position(|e| e.id == *id).unwrap())
2103                        .collect::<Vec<_>>();
2104                    log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
2105                    multibuffer.expand_excerpts(
2106                        excerpts.iter().cloned(),
2107                        line_count,
2108                        ExpandExcerptDirection::UpAndDown,
2109                        cx,
2110                    );
2111
2112                    reference.expand_excerpts(&excerpts, line_count, cx);
2113                });
2114            }
2115            20..=29 if !reference.excerpts.is_empty() => {
2116                let mut ids_to_remove = vec![];
2117                for _ in 0..rng.gen_range(1..=3) {
2118                    let Some(excerpt) = reference.excerpts.choose(&mut rng) else {
2119                        break;
2120                    };
2121                    let id = excerpt.id;
2122                    reference.remove_excerpt(id, cx);
2123                    ids_to_remove.push(id);
2124                }
2125                let snapshot = multibuffer.read(cx).read(cx);
2126                ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
2127                drop(snapshot);
2128                multibuffer.update(cx, |multibuffer, cx| {
2129                    multibuffer.remove_excerpts(ids_to_remove, cx)
2130                });
2131            }
2132            30..=39 if !reference.excerpts.is_empty() => {
2133                let multibuffer = multibuffer.read(cx).read(cx);
2134                let offset =
2135                    multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
2136                let bias = if rng.gen() { Bias::Left } else { Bias::Right };
2137                log::info!("Creating anchor at {} with bias {:?}", offset, bias);
2138                anchors.push(multibuffer.anchor_at(offset, bias));
2139                anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
2140            }
2141            40..=44 if !anchors.is_empty() => {
2142                let multibuffer = multibuffer.read(cx).read(cx);
2143                let prev_len = anchors.len();
2144                anchors = multibuffer
2145                    .refresh_anchors(&anchors)
2146                    .into_iter()
2147                    .map(|a| a.1)
2148                    .collect();
2149
2150                // Ensure the newly-refreshed anchors point to a valid excerpt and don't
2151                // overshoot its boundaries.
2152                assert_eq!(anchors.len(), prev_len);
2153                for anchor in &anchors {
2154                    if anchor.excerpt_id == ExcerptId::min()
2155                        || anchor.excerpt_id == ExcerptId::max()
2156                    {
2157                        continue;
2158                    }
2159
2160                    let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
2161                    assert_eq!(excerpt.id, anchor.excerpt_id);
2162                    assert!(excerpt.contains(anchor));
2163                }
2164            }
2165            45..=55 if !reference.excerpts.is_empty() => {
2166                multibuffer.update(cx, |multibuffer, cx| {
2167                    let snapshot = multibuffer.snapshot(cx);
2168                    let excerpt_ix = rng.gen_range(0..reference.excerpts.len());
2169                    let excerpt = &reference.excerpts[excerpt_ix];
2170                    let start = excerpt.range.start;
2171                    let end = excerpt.range.end;
2172                    let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
2173                        ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
2174
2175                    log::info!("expanding diff hunks for excerpt {:?}", excerpt_ix);
2176                    reference.expand_diff_hunks(excerpt.id, start..end, cx);
2177                    multibuffer.expand_diff_hunks(vec![range], cx);
2178                });
2179            }
2180            56..=85 if needs_diff_calculation => {
2181                multibuffer.update(cx, |multibuffer, cx| {
2182                    for buffer in multibuffer.all_buffers() {
2183                        let snapshot = buffer.read(cx).snapshot();
2184                        let _ = multibuffer
2185                            .change_set_for(snapshot.remote_id())
2186                            .unwrap()
2187                            .update(cx, |change_set, cx| {
2188                                log::info!(
2189                                    "recalculating diff for buffer {:?}",
2190                                    snapshot.remote_id(),
2191                                );
2192                                change_set.recalculate_diff_sync(
2193                                    change_set.base_text.clone().unwrap().text(),
2194                                    snapshot.text,
2195                                    false,
2196                                    cx,
2197                                )
2198                            });
2199                    }
2200                    reference.diffs_updated(cx);
2201                    needs_diff_calculation = false;
2202                });
2203            }
2204            _ => {
2205                let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
2206                    let base_text = util::RandomCharIter::new(&mut rng)
2207                        .take(256)
2208                        .collect::<String>();
2209
2210                    let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
2211                    let snapshot = buffer.read(cx).snapshot();
2212                    let change_set = cx.new(|cx| {
2213                        let mut change_set = BufferChangeSet::new(&buffer, cx);
2214                        change_set.recalculate_diff_sync(base_text, snapshot.text, true, cx);
2215                        change_set
2216                    });
2217
2218                    reference.add_change_set(change_set.clone(), cx);
2219                    multibuffer.update(cx, |multibuffer, cx| {
2220                        multibuffer.add_change_set(change_set, cx)
2221                    });
2222                    buffers.push(buffer);
2223                    buffers.last().unwrap()
2224                } else {
2225                    buffers.choose(&mut rng).unwrap()
2226                };
2227
2228                let buffer = buffer_handle.read(cx);
2229                let end_row = rng.gen_range(0..=buffer.max_point().row);
2230                let start_row = rng.gen_range(0..=end_row);
2231                let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
2232                let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
2233                let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
2234                let prev_excerpt_ix = rng.gen_range(0..=reference.excerpts.len());
2235                let prev_excerpt_id = reference
2236                    .excerpts
2237                    .get(prev_excerpt_ix)
2238                    .map_or(ExcerptId::max(), |e| e.id);
2239                let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
2240
2241                log::info!(
2242                    "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
2243                    excerpt_ix,
2244                    reference.excerpts.len(),
2245                    buffer_handle.read(cx).remote_id(),
2246                    buffer.text(),
2247                    start_ix..end_ix,
2248                    &buffer.text()[start_ix..end_ix]
2249                );
2250
2251                let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
2252                    multibuffer
2253                        .insert_excerpts_after(
2254                            prev_excerpt_id,
2255                            buffer_handle.clone(),
2256                            [ExcerptRange {
2257                                context: start_ix..end_ix,
2258                                primary: None,
2259                            }],
2260                            cx,
2261                        )
2262                        .pop()
2263                        .unwrap()
2264                });
2265
2266                reference.insert_excerpt_after(
2267                    prev_excerpt_id,
2268                    excerpt_id,
2269                    (buffer_handle.clone(), anchor_range),
2270                );
2271            }
2272        }
2273
2274        if rng.gen_bool(0.3) {
2275            multibuffer.update(cx, |multibuffer, cx| {
2276                old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
2277            })
2278        }
2279
2280        let snapshot = multibuffer.read(cx).snapshot(cx);
2281        let actual_text = snapshot.text();
2282        let actual_boundary_rows = snapshot
2283            .excerpt_boundaries_in_range(0..)
2284            .filter_map(|b| if b.next.is_some() { Some(b.row) } else { None })
2285            .collect::<HashSet<_>>();
2286        let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2287        let actual_diff = format_diff(&actual_text, &actual_row_infos, &actual_boundary_rows);
2288
2289        let (expected_text, expected_row_infos, expected_boundary_rows) =
2290            reference.expected_content(cx);
2291        let expected_diff =
2292            format_diff(&expected_text, &expected_row_infos, &expected_boundary_rows);
2293
2294        log::info!("Multibuffer content:\n{}", actual_diff);
2295
2296        assert_eq!(
2297            actual_row_infos.len(),
2298            actual_text.split('\n').count(),
2299            "line count: {}",
2300            actual_text.split('\n').count()
2301        );
2302        pretty_assertions::assert_eq!(actual_diff, expected_diff);
2303        pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
2304        pretty_assertions::assert_eq!(actual_text, expected_text);
2305
2306        for _ in 0..5 {
2307            let start_row = rng.gen_range(0..=expected_row_infos.len());
2308            assert_eq!(
2309                snapshot
2310                    .row_infos(MultiBufferRow(start_row as u32))
2311                    .collect::<Vec<_>>(),
2312                &expected_row_infos[start_row..],
2313                "buffer_rows({})",
2314                start_row
2315            );
2316        }
2317
2318        assert_eq!(
2319            snapshot.widest_line_number(),
2320            expected_row_infos
2321                .into_iter()
2322                .filter_map(
2323                    |info| if info.diff_status == Some(DiffHunkStatus::Removed) {
2324                        None
2325                    } else {
2326                        info.buffer_row
2327                    }
2328                )
2329                .max()
2330                .unwrap()
2331                + 1
2332        );
2333
2334        assert_consistent_line_numbers(&snapshot);
2335        assert_position_translation(&snapshot);
2336
2337        for (row, line) in expected_text.split('\n').enumerate() {
2338            assert_eq!(
2339                snapshot.line_len(MultiBufferRow(row as u32)),
2340                line.len() as u32,
2341                "line_len({}).",
2342                row
2343            );
2344        }
2345
2346        let text_rope = Rope::from(expected_text.as_str());
2347        for _ in 0..10 {
2348            let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2349            let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
2350
2351            let text_for_range = snapshot
2352                .text_for_range(start_ix..end_ix)
2353                .collect::<String>();
2354            assert_eq!(
2355                text_for_range,
2356                &expected_text[start_ix..end_ix],
2357                "incorrect text for range {:?}",
2358                start_ix..end_ix
2359            );
2360
2361            let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
2362            assert_eq!(
2363                snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
2364                expected_summary,
2365                "incorrect summary for range {:?}",
2366                start_ix..end_ix
2367            );
2368        }
2369
2370        // Anchor resolution
2371        let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
2372        assert_eq!(anchors.len(), summaries.len());
2373        for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
2374            assert!(resolved_offset <= snapshot.len());
2375            assert_eq!(
2376                snapshot.summary_for_anchor::<usize>(anchor),
2377                resolved_offset,
2378                "anchor: {:?}",
2379                anchor
2380            );
2381        }
2382
2383        for _ in 0..10 {
2384            let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2385            assert_eq!(
2386                snapshot.reversed_chars_at(end_ix).collect::<String>(),
2387                expected_text[..end_ix].chars().rev().collect::<String>(),
2388            );
2389        }
2390
2391        for _ in 0..10 {
2392            let end_ix = rng.gen_range(0..=text_rope.len());
2393            let start_ix = rng.gen_range(0..=end_ix);
2394            assert_eq!(
2395                snapshot
2396                    .bytes_in_range(start_ix..end_ix)
2397                    .flatten()
2398                    .copied()
2399                    .collect::<Vec<_>>(),
2400                expected_text.as_bytes()[start_ix..end_ix].to_vec(),
2401                "bytes_in_range({:?})",
2402                start_ix..end_ix,
2403            );
2404        }
2405    }
2406
2407    let snapshot = multibuffer.read(cx).snapshot(cx);
2408    for (old_snapshot, subscription) in old_versions {
2409        let edits = subscription.consume().into_inner();
2410
2411        log::info!(
2412            "applying subscription edits to old text: {:?}: {:?}",
2413            old_snapshot.text(),
2414            edits,
2415        );
2416
2417        let mut text = old_snapshot.text();
2418        for edit in edits {
2419            let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
2420            text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
2421        }
2422        assert_eq!(text.to_string(), snapshot.text());
2423    }
2424}
2425
2426#[gpui::test]
2427fn test_history(cx: &mut App) {
2428    let test_settings = SettingsStore::test(cx);
2429    cx.set_global(test_settings);
2430    let group_interval: Duration = Duration::from_millis(1);
2431    let buffer_1 = cx.new(|cx| {
2432        let mut buf = Buffer::local("1234", cx);
2433        buf.set_group_interval(group_interval);
2434        buf
2435    });
2436    let buffer_2 = cx.new(|cx| {
2437        let mut buf = Buffer::local("5678", cx);
2438        buf.set_group_interval(group_interval);
2439        buf
2440    });
2441    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2442    multibuffer.update(cx, |this, _| {
2443        this.history.group_interval = group_interval;
2444    });
2445    multibuffer.update(cx, |multibuffer, cx| {
2446        multibuffer.push_excerpts(
2447            buffer_1.clone(),
2448            [ExcerptRange {
2449                context: 0..buffer_1.read(cx).len(),
2450                primary: None,
2451            }],
2452            cx,
2453        );
2454        multibuffer.push_excerpts(
2455            buffer_2.clone(),
2456            [ExcerptRange {
2457                context: 0..buffer_2.read(cx).len(),
2458                primary: None,
2459            }],
2460            cx,
2461        );
2462    });
2463
2464    let mut now = Instant::now();
2465
2466    multibuffer.update(cx, |multibuffer, cx| {
2467        let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
2468        multibuffer.edit(
2469            [
2470                (Point::new(0, 0)..Point::new(0, 0), "A"),
2471                (Point::new(1, 0)..Point::new(1, 0), "A"),
2472            ],
2473            None,
2474            cx,
2475        );
2476        multibuffer.edit(
2477            [
2478                (Point::new(0, 1)..Point::new(0, 1), "B"),
2479                (Point::new(1, 1)..Point::new(1, 1), "B"),
2480            ],
2481            None,
2482            cx,
2483        );
2484        multibuffer.end_transaction_at(now, cx);
2485        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2486
2487        // Verify edited ranges for transaction 1
2488        assert_eq!(
2489            multibuffer.edited_ranges_for_transaction(transaction_1, cx),
2490            &[
2491                Point::new(0, 0)..Point::new(0, 2),
2492                Point::new(1, 0)..Point::new(1, 2)
2493            ]
2494        );
2495
2496        // Edit buffer 1 through the multibuffer
2497        now += 2 * group_interval;
2498        multibuffer.start_transaction_at(now, cx);
2499        multibuffer.edit([(2..2, "C")], None, cx);
2500        multibuffer.end_transaction_at(now, cx);
2501        assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
2502
2503        // Edit buffer 1 independently
2504        buffer_1.update(cx, |buffer_1, cx| {
2505            buffer_1.start_transaction_at(now);
2506            buffer_1.edit([(3..3, "D")], None, cx);
2507            buffer_1.end_transaction_at(now, cx);
2508
2509            now += 2 * group_interval;
2510            buffer_1.start_transaction_at(now);
2511            buffer_1.edit([(4..4, "E")], None, cx);
2512            buffer_1.end_transaction_at(now, cx);
2513        });
2514        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2515
2516        // An undo in the multibuffer undoes the multibuffer transaction
2517        // and also any individual buffer edits that have occurred since
2518        // that transaction.
2519        multibuffer.undo(cx);
2520        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2521
2522        multibuffer.undo(cx);
2523        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2524
2525        multibuffer.redo(cx);
2526        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2527
2528        multibuffer.redo(cx);
2529        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2530
2531        // Undo buffer 2 independently.
2532        buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
2533        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
2534
2535        // An undo in the multibuffer undoes the components of the
2536        // the last multibuffer transaction that are not already undone.
2537        multibuffer.undo(cx);
2538        assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
2539
2540        multibuffer.undo(cx);
2541        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2542
2543        multibuffer.redo(cx);
2544        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2545
2546        buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
2547        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2548
2549        // Redo stack gets cleared after an edit.
2550        now += 2 * group_interval;
2551        multibuffer.start_transaction_at(now, cx);
2552        multibuffer.edit([(0..0, "X")], None, cx);
2553        multibuffer.end_transaction_at(now, cx);
2554        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2555        multibuffer.redo(cx);
2556        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2557        multibuffer.undo(cx);
2558        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2559        multibuffer.undo(cx);
2560        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2561
2562        // Transactions can be grouped manually.
2563        multibuffer.redo(cx);
2564        multibuffer.redo(cx);
2565        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2566        multibuffer.group_until_transaction(transaction_1, cx);
2567        multibuffer.undo(cx);
2568        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2569        multibuffer.redo(cx);
2570        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2571    });
2572}
2573
2574#[gpui::test]
2575async fn test_enclosing_indent(cx: &mut TestAppContext) {
2576    async fn enclosing_indent(
2577        text: &str,
2578        buffer_row: u32,
2579        cx: &mut TestAppContext,
2580    ) -> Option<(Range<u32>, LineIndent)> {
2581        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2582        let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
2583        let (range, indent) = snapshot
2584            .enclosing_indent(MultiBufferRow(buffer_row))
2585            .await?;
2586        Some((range.start.0..range.end.0, indent))
2587    }
2588
2589    assert_eq!(
2590        enclosing_indent(
2591            indoc!(
2592                "
2593                fn b() {
2594                    if c {
2595                        let d = 2;
2596                    }
2597                }
2598                "
2599            ),
2600            1,
2601            cx,
2602        )
2603        .await,
2604        Some((
2605            1..2,
2606            LineIndent {
2607                tabs: 0,
2608                spaces: 4,
2609                line_blank: false,
2610            }
2611        ))
2612    );
2613
2614    assert_eq!(
2615        enclosing_indent(
2616            indoc!(
2617                "
2618                fn b() {
2619                    if c {
2620                        let d = 2;
2621                    }
2622                }
2623                "
2624            ),
2625            2,
2626            cx,
2627        )
2628        .await,
2629        Some((
2630            1..2,
2631            LineIndent {
2632                tabs: 0,
2633                spaces: 4,
2634                line_blank: false,
2635            }
2636        ))
2637    );
2638
2639    assert_eq!(
2640        enclosing_indent(
2641            indoc!(
2642                "
2643                fn b() {
2644                    if c {
2645                        let d = 2;
2646
2647                        let e = 5;
2648                    }
2649                }
2650                "
2651            ),
2652            3,
2653            cx,
2654        )
2655        .await,
2656        Some((
2657            1..4,
2658            LineIndent {
2659                tabs: 0,
2660                spaces: 4,
2661                line_blank: false,
2662            }
2663        ))
2664    );
2665}
2666
2667fn format_diff(
2668    text: &str,
2669    row_infos: &Vec<RowInfo>,
2670    boundary_rows: &HashSet<MultiBufferRow>,
2671) -> String {
2672    let has_diff = row_infos.iter().any(|info| info.diff_status.is_some());
2673    text.split('\n')
2674        .enumerate()
2675        .zip(row_infos)
2676        .map(|((ix, line), info)| {
2677            let marker = match info.diff_status {
2678                Some(DiffHunkStatus::Added) => "+ ",
2679                Some(DiffHunkStatus::Removed) => "- ",
2680                Some(DiffHunkStatus::Modified) => unreachable!(),
2681                None => {
2682                    if has_diff && !line.is_empty() {
2683                        "  "
2684                    } else {
2685                        ""
2686                    }
2687                }
2688            };
2689            let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
2690                if has_diff {
2691                    "  ----------\n"
2692                } else {
2693                    "---------\n"
2694                }
2695            } else {
2696                ""
2697            };
2698            format!("{boundary_row}{marker}{line}")
2699        })
2700        .collect::<Vec<_>>()
2701        .join("\n")
2702}
2703
2704#[track_caller]
2705fn assert_new_snapshot(
2706    multibuffer: &Entity<MultiBuffer>,
2707    snapshot: &mut MultiBufferSnapshot,
2708    subscription: &mut Subscription,
2709    cx: &mut TestAppContext,
2710    expected_diff: &str,
2711) {
2712    let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2713    let actual_text = new_snapshot.text();
2714    let line_infos = new_snapshot
2715        .row_infos(MultiBufferRow(0))
2716        .collect::<Vec<_>>();
2717    let actual_diff = format_diff(&actual_text, &line_infos, &Default::default());
2718    pretty_assertions::assert_eq!(actual_diff, expected_diff);
2719    check_edits(
2720        snapshot,
2721        &new_snapshot,
2722        &subscription.consume().into_inner(),
2723    );
2724    *snapshot = new_snapshot;
2725}
2726
2727#[track_caller]
2728fn check_edits(
2729    old_snapshot: &MultiBufferSnapshot,
2730    new_snapshot: &MultiBufferSnapshot,
2731    edits: &[Edit<usize>],
2732) {
2733    let mut text = old_snapshot.text();
2734    let new_text = new_snapshot.text();
2735    for edit in edits.iter().rev() {
2736        if !text.is_char_boundary(edit.old.start)
2737            || !text.is_char_boundary(edit.old.end)
2738            || !new_text.is_char_boundary(edit.new.start)
2739            || !new_text.is_char_boundary(edit.new.end)
2740        {
2741            panic!(
2742                "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
2743                edits, text, new_text
2744            );
2745        }
2746
2747        text.replace_range(
2748            edit.old.start..edit.old.end,
2749            &new_text[edit.new.start..edit.new.end],
2750        );
2751    }
2752
2753    pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
2754}
2755
2756#[track_caller]
2757fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
2758    let full_text = snapshot.text();
2759    for ix in 0..full_text.len() {
2760        let mut chunks = snapshot.chunks(0..snapshot.len(), false);
2761        chunks.seek(ix..snapshot.len());
2762        let tail = chunks.map(|chunk| chunk.text).collect::<String>();
2763        assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
2764    }
2765}
2766
2767#[track_caller]
2768fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
2769    let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2770    for start_row in 1..all_line_numbers.len() {
2771        let line_numbers = snapshot
2772            .row_infos(MultiBufferRow(start_row as u32))
2773            .collect::<Vec<_>>();
2774        assert_eq!(
2775            line_numbers,
2776            all_line_numbers[start_row..],
2777            "start_row: {start_row}"
2778        );
2779    }
2780}
2781
2782#[track_caller]
2783fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
2784    let text = Rope::from(snapshot.text());
2785
2786    let mut left_anchors = Vec::new();
2787    let mut right_anchors = Vec::new();
2788    let mut offsets = Vec::new();
2789    let mut points = Vec::new();
2790    for offset in 0..=text.len() + 1 {
2791        let clipped_left = snapshot.clip_offset(offset, Bias::Left);
2792        let clipped_right = snapshot.clip_offset(offset, Bias::Right);
2793        assert_eq!(
2794            clipped_left,
2795            text.clip_offset(offset, Bias::Left),
2796            "clip_offset({offset:?}, Left)"
2797        );
2798        assert_eq!(
2799            clipped_right,
2800            text.clip_offset(offset, Bias::Right),
2801            "clip_offset({offset:?}, Right)"
2802        );
2803        assert_eq!(
2804            snapshot.offset_to_point(clipped_left),
2805            text.offset_to_point(clipped_left),
2806            "offset_to_point({clipped_left})"
2807        );
2808        assert_eq!(
2809            snapshot.offset_to_point(clipped_right),
2810            text.offset_to_point(clipped_right),
2811            "offset_to_point({clipped_right})"
2812        );
2813        let anchor_after = snapshot.anchor_after(clipped_left);
2814        assert_eq!(
2815            anchor_after.to_offset(snapshot),
2816            clipped_left,
2817            "anchor_after({clipped_left}).to_offset {anchor_after:?}"
2818        );
2819        let anchor_before = snapshot.anchor_before(clipped_left);
2820        assert_eq!(
2821            anchor_before.to_offset(snapshot),
2822            clipped_left,
2823            "anchor_before({clipped_left}).to_offset"
2824        );
2825        left_anchors.push(anchor_before);
2826        right_anchors.push(anchor_after);
2827        offsets.push(clipped_left);
2828        points.push(text.offset_to_point(clipped_left));
2829    }
2830
2831    for row in 0..text.max_point().row {
2832        for column in 0..text.line_len(row) + 1 {
2833            let point = Point { row, column };
2834            let clipped_left = snapshot.clip_point(point, Bias::Left);
2835            let clipped_right = snapshot.clip_point(point, Bias::Right);
2836            assert_eq!(
2837                clipped_left,
2838                text.clip_point(point, Bias::Left),
2839                "clip_point({point:?}, Left)"
2840            );
2841            assert_eq!(
2842                clipped_right,
2843                text.clip_point(point, Bias::Right),
2844                "clip_point({point:?}, Right)"
2845            );
2846            assert_eq!(
2847                snapshot.point_to_offset(clipped_left),
2848                text.point_to_offset(clipped_left),
2849                "point_to_offset({clipped_left:?})"
2850            );
2851            assert_eq!(
2852                snapshot.point_to_offset(clipped_right),
2853                text.point_to_offset(clipped_right),
2854                "point_to_offset({clipped_right:?})"
2855            );
2856        }
2857    }
2858
2859    assert_eq!(
2860        snapshot.summaries_for_anchors::<usize, _>(&left_anchors),
2861        offsets,
2862        "left_anchors <-> offsets"
2863    );
2864    assert_eq!(
2865        snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
2866        points,
2867        "left_anchors <-> points"
2868    );
2869    assert_eq!(
2870        snapshot.summaries_for_anchors::<usize, _>(&right_anchors),
2871        offsets,
2872        "right_anchors <-> offsets"
2873    );
2874    assert_eq!(
2875        snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
2876        points,
2877        "right_anchors <-> points"
2878    );
2879
2880    for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
2881        for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
2882            if ix > 0 {
2883                if *offset == 252 {
2884                    if offset > &offsets[ix - 1] {
2885                        let prev_anchor = left_anchors[ix - 1];
2886                        assert!(
2887                            anchor.cmp(&prev_anchor, snapshot).is_gt(),
2888                            "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
2889                            offsets[ix],
2890                            offsets[ix - 1],
2891                        );
2892                        assert!(
2893                            prev_anchor.cmp(&anchor, snapshot).is_lt(),
2894                            "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
2895                            offsets[ix - 1],
2896                            offsets[ix],
2897                        );
2898                    }
2899                }
2900            }
2901        }
2902    }
2903}
2904
2905fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
2906    let max_row = snapshot.max_point().row;
2907    let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
2908    let text = text::Buffer::new(0, buffer_id, snapshot.text());
2909    let mut line_indents = text
2910        .line_indents_in_row_range(0..max_row + 1)
2911        .collect::<Vec<_>>();
2912    for start_row in 0..snapshot.max_point().row {
2913        pretty_assertions::assert_eq!(
2914            snapshot
2915                .line_indents(MultiBufferRow(start_row), |_| true)
2916                .map(|(row, indent, _)| (row.0, indent))
2917                .collect::<Vec<_>>(),
2918            &line_indents[(start_row as usize)..],
2919            "line_indents({start_row})"
2920        );
2921    }
2922
2923    line_indents.reverse();
2924    pretty_assertions::assert_eq!(
2925        snapshot
2926            .reversed_line_indents(MultiBufferRow(max_row), |_| true)
2927            .map(|(row, indent, _)| (row.0, indent))
2928            .collect::<Vec<_>>(),
2929        &line_indents[..],
2930        "reversed_line_indents({max_row})"
2931    );
2932}