multi_buffer_tests.rs

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