multi_buffer_tests.rs

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