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