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_empty_diff_excerpt(cx: &mut TestAppContext) {
 994    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 995    let buffer = cx.new(|cx| Buffer::local("", cx));
 996    let base_text = "a\nb\nc";
 997
 998    let change_set = cx.new(|cx| {
 999        let snapshot = buffer.read(cx).snapshot();
1000        let mut change_set = BufferChangeSet::new(&buffer, cx);
1001        let _ = change_set.set_base_text(base_text.into(), snapshot.text, cx);
1002        change_set
1003    });
1004    multibuffer.update(cx, |multibuffer, cx| {
1005        multibuffer.set_all_diff_hunks_expanded(cx);
1006        multibuffer.add_change_set(change_set.clone(), cx);
1007        multibuffer.push_excerpts(
1008            buffer.clone(),
1009            [ExcerptRange {
1010                context: 0..0,
1011                primary: None,
1012            }],
1013            cx,
1014        );
1015    });
1016    cx.run_until_parked();
1017
1018    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1019    assert_eq!(snapshot.text(), "a\nb\nc\n");
1020
1021    let hunk = snapshot
1022        .diff_hunks_in_range(Point::new(1, 1)..Point::new(1, 1))
1023        .next()
1024        .unwrap();
1025
1026    assert_eq!(hunk.diff_base_byte_range.start, 0);
1027
1028    let buf2 = cx.new(|cx| Buffer::local("X", cx));
1029    multibuffer.update(cx, |multibuffer, cx| {
1030        multibuffer.push_excerpts(
1031            buf2,
1032            [ExcerptRange {
1033                context: 0..1,
1034                primary: None,
1035            }],
1036            cx,
1037        );
1038    });
1039
1040    buffer.update(cx, |buffer, cx| {
1041        buffer.edit([(0..0, "a\nb\nc")], None, cx);
1042        change_set.update(cx, |change_set, cx| {
1043            let _ = change_set.recalculate_diff(buffer.snapshot().text, cx);
1044        });
1045        assert_eq!(buffer.text(), "a\nb\nc")
1046    });
1047    cx.run_until_parked();
1048
1049    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1050    assert_eq!(snapshot.text(), "a\nb\nc\nX");
1051
1052    buffer.update(cx, |buffer, cx| {
1053        buffer.undo(cx);
1054        change_set.update(cx, |change_set, cx| {
1055            let _ = change_set.recalculate_diff(buffer.snapshot().text, cx);
1056        });
1057        assert_eq!(buffer.text(), "")
1058    });
1059    cx.run_until_parked();
1060
1061    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1062    assert_eq!(snapshot.text(), "a\nb\nc\n\nX");
1063}
1064
1065#[gpui::test]
1066fn test_singleton_multibuffer_anchors(cx: &mut App) {
1067    let buffer = cx.new(|cx| Buffer::local("abcd", cx));
1068    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
1069    let old_snapshot = multibuffer.read(cx).snapshot(cx);
1070    buffer.update(cx, |buffer, cx| {
1071        buffer.edit([(0..0, "X")], None, cx);
1072        buffer.edit([(5..5, "Y")], None, cx);
1073    });
1074    let new_snapshot = multibuffer.read(cx).snapshot(cx);
1075
1076    assert_eq!(old_snapshot.text(), "abcd");
1077    assert_eq!(new_snapshot.text(), "XabcdY");
1078
1079    assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1080    assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1081    assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5);
1082    assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6);
1083}
1084
1085#[gpui::test]
1086fn test_multibuffer_anchors(cx: &mut App) {
1087    let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1088    let buffer_2 = cx.new(|cx| Buffer::local("efghi", cx));
1089    let multibuffer = cx.new(|cx| {
1090        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1091        multibuffer.push_excerpts(
1092            buffer_1.clone(),
1093            [ExcerptRange {
1094                context: 0..4,
1095                primary: None,
1096            }],
1097            cx,
1098        );
1099        multibuffer.push_excerpts(
1100            buffer_2.clone(),
1101            [ExcerptRange {
1102                context: 0..5,
1103                primary: None,
1104            }],
1105            cx,
1106        );
1107        multibuffer
1108    });
1109    let old_snapshot = multibuffer.read(cx).snapshot(cx);
1110
1111    assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0);
1112    assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0);
1113    assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1114    assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1115    assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1116    assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1117
1118    buffer_1.update(cx, |buffer, cx| {
1119        buffer.edit([(0..0, "W")], None, cx);
1120        buffer.edit([(5..5, "X")], None, cx);
1121    });
1122    buffer_2.update(cx, |buffer, cx| {
1123        buffer.edit([(0..0, "Y")], None, cx);
1124        buffer.edit([(6..6, "Z")], None, cx);
1125    });
1126    let new_snapshot = multibuffer.read(cx).snapshot(cx);
1127
1128    assert_eq!(old_snapshot.text(), "abcd\nefghi");
1129    assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
1130
1131    assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1132    assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1133    assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2);
1134    assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2);
1135    assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3);
1136    assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3);
1137    assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7);
1138    assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8);
1139    assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13);
1140    assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14);
1141}
1142
1143#[gpui::test]
1144fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
1145    let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1146    let buffer_2 = cx.new(|cx| Buffer::local("ABCDEFGHIJKLMNOP", cx));
1147    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1148
1149    // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
1150    // Add an excerpt from buffer 1 that spans this new insertion.
1151    buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx));
1152    let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| {
1153        multibuffer
1154            .push_excerpts(
1155                buffer_1.clone(),
1156                [ExcerptRange {
1157                    context: 0..7,
1158                    primary: None,
1159                }],
1160                cx,
1161            )
1162            .pop()
1163            .unwrap()
1164    });
1165
1166    let snapshot_1 = multibuffer.read(cx).snapshot(cx);
1167    assert_eq!(snapshot_1.text(), "abcd123");
1168
1169    // Replace the buffer 1 excerpt with new excerpts from buffer 2.
1170    let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
1171        multibuffer.remove_excerpts([excerpt_id_1], cx);
1172        let mut ids = multibuffer
1173            .push_excerpts(
1174                buffer_2.clone(),
1175                [
1176                    ExcerptRange {
1177                        context: 0..4,
1178                        primary: None,
1179                    },
1180                    ExcerptRange {
1181                        context: 6..10,
1182                        primary: None,
1183                    },
1184                    ExcerptRange {
1185                        context: 12..16,
1186                        primary: None,
1187                    },
1188                ],
1189                cx,
1190            )
1191            .into_iter();
1192        (ids.next().unwrap(), ids.next().unwrap())
1193    });
1194    let snapshot_2 = multibuffer.read(cx).snapshot(cx);
1195    assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP");
1196
1197    // The old excerpt id doesn't get reused.
1198    assert_ne!(excerpt_id_2, excerpt_id_1);
1199
1200    // Resolve some anchors from the previous snapshot in the new snapshot.
1201    // The current excerpts are from a different buffer, so we don't attempt to
1202    // resolve the old text anchor in the new buffer.
1203    assert_eq!(
1204        snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)),
1205        0
1206    );
1207    assert_eq!(
1208        snapshot_2.summaries_for_anchors::<usize, _>(&[
1209            snapshot_1.anchor_before(2),
1210            snapshot_1.anchor_after(3)
1211        ]),
1212        vec![0, 0]
1213    );
1214
1215    // Refresh anchors from the old snapshot. The return value indicates that both
1216    // anchors lost their original excerpt.
1217    let refresh =
1218        snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]);
1219    assert_eq!(
1220        refresh,
1221        &[
1222            (0, snapshot_2.anchor_before(0), false),
1223            (1, snapshot_2.anchor_after(0), false),
1224        ]
1225    );
1226
1227    // Replace the middle excerpt with a smaller excerpt in buffer 2,
1228    // that intersects the old excerpt.
1229    let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| {
1230        multibuffer.remove_excerpts([excerpt_id_3], cx);
1231        multibuffer
1232            .insert_excerpts_after(
1233                excerpt_id_2,
1234                buffer_2.clone(),
1235                [ExcerptRange {
1236                    context: 5..8,
1237                    primary: None,
1238                }],
1239                cx,
1240            )
1241            .pop()
1242            .unwrap()
1243    });
1244
1245    let snapshot_3 = multibuffer.read(cx).snapshot(cx);
1246    assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP");
1247    assert_ne!(excerpt_id_5, excerpt_id_3);
1248
1249    // Resolve some anchors from the previous snapshot in the new snapshot.
1250    // The third anchor can't be resolved, since its excerpt has been removed,
1251    // so it resolves to the same position as its predecessor.
1252    let anchors = [
1253        snapshot_2.anchor_before(0),
1254        snapshot_2.anchor_after(2),
1255        snapshot_2.anchor_after(6),
1256        snapshot_2.anchor_after(14),
1257    ];
1258    assert_eq!(
1259        snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
1260        &[0, 2, 9, 13]
1261    );
1262
1263    let new_anchors = snapshot_3.refresh_anchors(&anchors);
1264    assert_eq!(
1265        new_anchors.iter().map(|a| (a.0, a.2)).collect::<Vec<_>>(),
1266        &[(0, true), (1, true), (2, true), (3, true)]
1267    );
1268    assert_eq!(
1269        snapshot_3.summaries_for_anchors::<usize, _>(new_anchors.iter().map(|a| &a.1)),
1270        &[0, 2, 7, 13]
1271    );
1272}
1273
1274#[gpui::test]
1275fn test_basic_diff_hunks(cx: &mut TestAppContext) {
1276    let text = indoc!(
1277        "
1278        ZERO
1279        one
1280        TWO
1281        three
1282        six
1283        "
1284    );
1285    let base_text = indoc!(
1286        "
1287        one
1288        two
1289        three
1290        four
1291        five
1292        six
1293        "
1294    );
1295
1296    let buffer = cx.new(|cx| Buffer::local(text, cx));
1297    let change_set =
1298        cx.new(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
1299    cx.run_until_parked();
1300
1301    let multibuffer = cx.new(|cx| {
1302        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1303        multibuffer.add_change_set(change_set.clone(), cx);
1304        multibuffer
1305    });
1306
1307    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1308        (multibuffer.snapshot(cx), multibuffer.subscribe())
1309    });
1310    assert_eq!(
1311        snapshot.text(),
1312        indoc!(
1313            "
1314            ZERO
1315            one
1316            TWO
1317            three
1318            six
1319            "
1320        ),
1321    );
1322
1323    multibuffer.update(cx, |multibuffer, cx| {
1324        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1325    });
1326
1327    assert_new_snapshot(
1328        &multibuffer,
1329        &mut snapshot,
1330        &mut subscription,
1331        cx,
1332        indoc!(
1333            "
1334            + ZERO
1335              one
1336            - two
1337            + TWO
1338              three
1339            - four
1340            - five
1341              six
1342            "
1343        ),
1344    );
1345
1346    assert_eq!(
1347        snapshot
1348            .row_infos(MultiBufferRow(0))
1349            .map(|info| (info.buffer_row, info.diff_status))
1350            .collect::<Vec<_>>(),
1351        vec![
1352            (Some(0), Some(DiffHunkStatus::Added)),
1353            (Some(1), None),
1354            (Some(1), Some(DiffHunkStatus::Removed)),
1355            (Some(2), Some(DiffHunkStatus::Added)),
1356            (Some(3), None),
1357            (Some(3), Some(DiffHunkStatus::Removed)),
1358            (Some(4), Some(DiffHunkStatus::Removed)),
1359            (Some(4), None),
1360            (Some(5), None)
1361        ]
1362    );
1363
1364    assert_chunks_in_ranges(&snapshot);
1365    assert_consistent_line_numbers(&snapshot);
1366    assert_position_translation(&snapshot);
1367    assert_line_indents(&snapshot);
1368
1369    multibuffer.update(cx, |multibuffer, cx| {
1370        multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
1371    });
1372    assert_new_snapshot(
1373        &multibuffer,
1374        &mut snapshot,
1375        &mut subscription,
1376        cx,
1377        indoc!(
1378            "
1379            ZERO
1380            one
1381            TWO
1382            three
1383            six
1384            "
1385        ),
1386    );
1387
1388    assert_chunks_in_ranges(&snapshot);
1389    assert_consistent_line_numbers(&snapshot);
1390    assert_position_translation(&snapshot);
1391    assert_line_indents(&snapshot);
1392
1393    // Expand the first diff hunk
1394    multibuffer.update(cx, |multibuffer, cx| {
1395        let position = multibuffer.read(cx).anchor_before(Point::new(2, 2));
1396        multibuffer.expand_diff_hunks(vec![position..position], cx)
1397    });
1398    assert_new_snapshot(
1399        &multibuffer,
1400        &mut snapshot,
1401        &mut subscription,
1402        cx,
1403        indoc!(
1404            "
1405              ZERO
1406              one
1407            - two
1408            + TWO
1409              three
1410              six
1411            "
1412        ),
1413    );
1414
1415    // Expand the second diff hunk
1416    multibuffer.update(cx, |multibuffer, cx| {
1417        let start = multibuffer.read(cx).anchor_before(Point::new(4, 0));
1418        let end = multibuffer.read(cx).anchor_before(Point::new(5, 0));
1419        multibuffer.expand_diff_hunks(vec![start..end], cx)
1420    });
1421    assert_new_snapshot(
1422        &multibuffer,
1423        &mut snapshot,
1424        &mut subscription,
1425        cx,
1426        indoc!(
1427            "
1428              ZERO
1429              one
1430            - two
1431            + TWO
1432              three
1433            - four
1434            - five
1435              six
1436            "
1437        ),
1438    );
1439
1440    assert_chunks_in_ranges(&snapshot);
1441    assert_consistent_line_numbers(&snapshot);
1442    assert_position_translation(&snapshot);
1443    assert_line_indents(&snapshot);
1444
1445    // Edit the buffer before the first hunk
1446    buffer.update(cx, |buffer, cx| {
1447        buffer.edit_via_marked_text(
1448            indoc!(
1449                "
1450                ZERO
1451                one« hundred
1452                  thousand»
1453                TWO
1454                three
1455                six
1456                "
1457            ),
1458            None,
1459            cx,
1460        );
1461    });
1462    assert_new_snapshot(
1463        &multibuffer,
1464        &mut snapshot,
1465        &mut subscription,
1466        cx,
1467        indoc!(
1468            "
1469              ZERO
1470              one hundred
1471                thousand
1472            - two
1473            + TWO
1474              three
1475            - four
1476            - five
1477              six
1478            "
1479        ),
1480    );
1481
1482    assert_chunks_in_ranges(&snapshot);
1483    assert_consistent_line_numbers(&snapshot);
1484    assert_position_translation(&snapshot);
1485    assert_line_indents(&snapshot);
1486
1487    // Recalculate the diff, changing the first diff hunk.
1488    let _ = change_set.update(cx, |change_set, cx| {
1489        change_set.recalculate_diff(buffer.read(cx).text_snapshot(), cx)
1490    });
1491    cx.run_until_parked();
1492    assert_new_snapshot(
1493        &multibuffer,
1494        &mut snapshot,
1495        &mut subscription,
1496        cx,
1497        indoc!(
1498            "
1499              ZERO
1500              one hundred
1501                thousand
1502              TWO
1503              three
1504            - four
1505            - five
1506              six
1507            "
1508        ),
1509    );
1510
1511    assert_eq!(
1512        snapshot
1513            .diff_hunks_in_range(0..snapshot.len())
1514            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1515            .collect::<Vec<_>>(),
1516        &[0..4, 5..7]
1517    );
1518}
1519
1520#[gpui::test]
1521fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
1522    let text = indoc!(
1523        "
1524        one
1525        TWO
1526        THREE
1527        four
1528        FIVE
1529        six
1530        "
1531    );
1532    let base_text = indoc!(
1533        "
1534        one
1535        four
1536        six
1537        "
1538    );
1539
1540    let buffer = cx.new(|cx| Buffer::local(text, cx));
1541    let change_set =
1542        cx.new(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
1543    cx.run_until_parked();
1544
1545    let multibuffer = cx.new(|cx| {
1546        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1547        multibuffer.add_change_set(change_set.clone(), cx);
1548        multibuffer
1549    });
1550
1551    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1552        (multibuffer.snapshot(cx), multibuffer.subscribe())
1553    });
1554
1555    multibuffer.update(cx, |multibuffer, cx| {
1556        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1557    });
1558
1559    assert_new_snapshot(
1560        &multibuffer,
1561        &mut snapshot,
1562        &mut subscription,
1563        cx,
1564        indoc!(
1565            "
1566              one
1567            + TWO
1568            + THREE
1569              four
1570            + FIVE
1571              six
1572            "
1573        ),
1574    );
1575
1576    // Regression test: expanding diff hunks that are already expanded should not change anything.
1577    multibuffer.update(cx, |multibuffer, cx| {
1578        multibuffer.expand_diff_hunks(
1579            vec![
1580                snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_before(Point::new(2, 0)),
1581            ],
1582            cx,
1583        );
1584    });
1585
1586    assert_new_snapshot(
1587        &multibuffer,
1588        &mut snapshot,
1589        &mut subscription,
1590        cx,
1591        indoc!(
1592            "
1593              one
1594            + TWO
1595            + THREE
1596              four
1597            + FIVE
1598              six
1599            "
1600        ),
1601    );
1602}
1603
1604#[gpui::test]
1605fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
1606    let buf1 = cx.new(|cx| {
1607        Buffer::local(
1608            indoc! {
1609            "zero
1610            one
1611            two
1612            three
1613            four
1614            five
1615            six
1616            seven
1617            ",
1618            },
1619            cx,
1620        )
1621    });
1622    let path1: Arc<Path> = Arc::from(PathBuf::from("path1"));
1623    let buf2 = cx.new(|cx| {
1624        Buffer::local(
1625            indoc! {
1626            "000
1627            111
1628            222
1629            333
1630            444
1631            555
1632            666
1633            777
1634            888
1635            999
1636            "
1637            },
1638            cx,
1639        )
1640    });
1641    let path2: Arc<Path> = Arc::from(PathBuf::from("path2"));
1642
1643    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1644    multibuffer.update(cx, |multibuffer, cx| {
1645        multibuffer.set_excerpts_for_path(
1646            path1.clone(),
1647            buf1.clone(),
1648            vec![Point::row_range(0..1)],
1649            2,
1650            cx,
1651        );
1652    });
1653
1654    assert_excerpts_match(
1655        &multibuffer,
1656        cx,
1657        indoc! {
1658        "-----
1659        zero
1660        one
1661        two
1662        three
1663        "
1664        },
1665    );
1666
1667    multibuffer.update(cx, |multibuffer, cx| {
1668        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1669    });
1670
1671    assert_excerpts_match(&multibuffer, cx, "");
1672
1673    multibuffer.update(cx, |multibuffer, cx| {
1674        multibuffer.set_excerpts_for_path(
1675            path1.clone(),
1676            buf1.clone(),
1677            vec![Point::row_range(0..1), Point::row_range(7..8)],
1678            2,
1679            cx,
1680        );
1681    });
1682
1683    assert_excerpts_match(
1684        &multibuffer,
1685        cx,
1686        indoc! {"-----
1687                zero
1688                one
1689                two
1690                three
1691                -----
1692                five
1693                six
1694                seven
1695                "},
1696    );
1697
1698    multibuffer.update(cx, |multibuffer, cx| {
1699        multibuffer.set_excerpts_for_path(
1700            path1.clone(),
1701            buf1.clone(),
1702            vec![Point::row_range(0..1), Point::row_range(5..6)],
1703            2,
1704            cx,
1705        );
1706    });
1707
1708    assert_excerpts_match(
1709        &multibuffer,
1710        cx,
1711        indoc! {"-----
1712                    zero
1713                    one
1714                    two
1715                    three
1716                    four
1717                    five
1718                    six
1719                    seven
1720                    "},
1721    );
1722
1723    multibuffer.update(cx, |multibuffer, cx| {
1724        multibuffer.set_excerpts_for_path(
1725            path2.clone(),
1726            buf2.clone(),
1727            vec![Point::row_range(2..3)],
1728            2,
1729            cx,
1730        );
1731    });
1732
1733    assert_excerpts_match(
1734        &multibuffer,
1735        cx,
1736        indoc! {"-----
1737                zero
1738                one
1739                two
1740                three
1741                four
1742                five
1743                six
1744                seven
1745                -----
1746                000
1747                111
1748                222
1749                333
1750                444
1751                555
1752                "},
1753    );
1754
1755    multibuffer.update(cx, |multibuffer, cx| {
1756        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1757    });
1758
1759    multibuffer.update(cx, |multibuffer, cx| {
1760        multibuffer.set_excerpts_for_path(
1761            path1.clone(),
1762            buf1.clone(),
1763            vec![Point::row_range(3..4)],
1764            2,
1765            cx,
1766        );
1767    });
1768
1769    assert_excerpts_match(
1770        &multibuffer,
1771        cx,
1772        indoc! {"-----
1773                one
1774                two
1775                three
1776                four
1777                five
1778                six
1779                -----
1780                000
1781                111
1782                222
1783                333
1784                444
1785                555
1786                "},
1787    );
1788
1789    multibuffer.update(cx, |multibuffer, cx| {
1790        multibuffer.set_excerpts_for_path(
1791            path1.clone(),
1792            buf1.clone(),
1793            vec![Point::row_range(3..4)],
1794            2,
1795            cx,
1796        );
1797    });
1798}
1799
1800#[gpui::test]
1801fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
1802    let base_text_1 = indoc!(
1803        "
1804        one
1805        two
1806            three
1807        four
1808        five
1809        six
1810        "
1811    );
1812    let text_1 = indoc!(
1813        "
1814        ZERO
1815        one
1816        TWO
1817            three
1818        six
1819        "
1820    );
1821    let base_text_2 = indoc!(
1822        "
1823        seven
1824          eight
1825        nine
1826        ten
1827        eleven
1828        twelve
1829        "
1830    );
1831    let text_2 = indoc!(
1832        "
1833          eight
1834        nine
1835        eleven
1836        THIRTEEN
1837        FOURTEEN
1838        "
1839    );
1840
1841    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
1842    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
1843    let change_set_1 =
1844        cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_1.to_string(), &buffer_1, cx));
1845    let change_set_2 =
1846        cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_2.to_string(), &buffer_2, cx));
1847    cx.run_until_parked();
1848
1849    let multibuffer = cx.new(|cx| {
1850        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1851        multibuffer.push_excerpts(
1852            buffer_1.clone(),
1853            [ExcerptRange {
1854                context: text::Anchor::MIN..text::Anchor::MAX,
1855                primary: None,
1856            }],
1857            cx,
1858        );
1859        multibuffer.push_excerpts(
1860            buffer_2.clone(),
1861            [ExcerptRange {
1862                context: text::Anchor::MIN..text::Anchor::MAX,
1863                primary: None,
1864            }],
1865            cx,
1866        );
1867        multibuffer.add_change_set(change_set_1.clone(), cx);
1868        multibuffer.add_change_set(change_set_2.clone(), cx);
1869        multibuffer
1870    });
1871
1872    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1873        (multibuffer.snapshot(cx), multibuffer.subscribe())
1874    });
1875    assert_eq!(
1876        snapshot.text(),
1877        indoc!(
1878            "
1879            ZERO
1880            one
1881            TWO
1882                three
1883            six
1884
1885              eight
1886            nine
1887            eleven
1888            THIRTEEN
1889            FOURTEEN
1890            "
1891        ),
1892    );
1893
1894    multibuffer.update(cx, |multibuffer, cx| {
1895        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1896    });
1897
1898    assert_new_snapshot(
1899        &multibuffer,
1900        &mut snapshot,
1901        &mut subscription,
1902        cx,
1903        indoc!(
1904            "
1905            + ZERO
1906              one
1907            - two
1908            + TWO
1909                  three
1910            - four
1911            - five
1912              six
1913
1914            - seven
1915                eight
1916              nine
1917            - ten
1918              eleven
1919            - twelve
1920            + THIRTEEN
1921            + FOURTEEN
1922            "
1923        ),
1924    );
1925
1926    let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
1927    let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
1928    let base_id_1 = change_set_1.read_with(cx, |change_set, _| {
1929        change_set.base_text.as_ref().unwrap().remote_id()
1930    });
1931    let base_id_2 = change_set_2.read_with(cx, |change_set, _| {
1932        change_set.base_text.as_ref().unwrap().remote_id()
1933    });
1934
1935    let buffer_lines = (0..=snapshot.max_row().0)
1936        .map(|row| {
1937            let (buffer, range) = snapshot.buffer_line_for_row(MultiBufferRow(row))?;
1938            Some((
1939                buffer.remote_id(),
1940                buffer.text_for_range(range).collect::<String>(),
1941            ))
1942        })
1943        .collect::<Vec<_>>();
1944    pretty_assertions::assert_eq!(
1945        buffer_lines,
1946        [
1947            Some((id_1, "ZERO".into())),
1948            Some((id_1, "one".into())),
1949            Some((base_id_1, "two".into())),
1950            Some((id_1, "TWO".into())),
1951            Some((id_1, "    three".into())),
1952            Some((base_id_1, "four".into())),
1953            Some((base_id_1, "five".into())),
1954            Some((id_1, "six".into())),
1955            Some((id_1, "".into())),
1956            Some((base_id_2, "seven".into())),
1957            Some((id_2, "  eight".into())),
1958            Some((id_2, "nine".into())),
1959            Some((base_id_2, "ten".into())),
1960            Some((id_2, "eleven".into())),
1961            Some((base_id_2, "twelve".into())),
1962            Some((id_2, "THIRTEEN".into())),
1963            Some((id_2, "FOURTEEN".into())),
1964            Some((id_2, "".into())),
1965        ]
1966    );
1967
1968    assert_position_translation(&snapshot);
1969    assert_line_indents(&snapshot);
1970
1971    assert_eq!(
1972        snapshot
1973            .diff_hunks_in_range(0..snapshot.len())
1974            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1975            .collect::<Vec<_>>(),
1976        &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
1977    );
1978
1979    buffer_2.update(cx, |buffer, cx| {
1980        buffer.edit_via_marked_text(
1981            indoc!(
1982                "
1983                  eight
1984                «»eleven
1985                THIRTEEN
1986                FOURTEEN
1987                "
1988            ),
1989            None,
1990            cx,
1991        );
1992    });
1993
1994    assert_new_snapshot(
1995        &multibuffer,
1996        &mut snapshot,
1997        &mut subscription,
1998        cx,
1999        indoc!(
2000            "
2001            + ZERO
2002              one
2003            - two
2004            + TWO
2005                  three
2006            - four
2007            - five
2008              six
2009
2010            - seven
2011                eight
2012              eleven
2013            - twelve
2014            + THIRTEEN
2015            + FOURTEEN
2016            "
2017        ),
2018    );
2019
2020    assert_line_indents(&snapshot);
2021}
2022
2023/// A naive implementation of a multi-buffer that does not maintain
2024/// any derived state, used for comparison in a randomized test.
2025#[derive(Default)]
2026struct ReferenceMultibuffer {
2027    excerpts: Vec<ReferenceExcerpt>,
2028    change_sets: HashMap<BufferId, Entity<BufferChangeSet>>,
2029}
2030
2031struct ReferenceExcerpt {
2032    id: ExcerptId,
2033    buffer: Entity<Buffer>,
2034    range: Range<text::Anchor>,
2035    expanded_diff_hunks: Vec<text::Anchor>,
2036}
2037
2038#[derive(Debug)]
2039struct ReferenceRegion {
2040    range: Range<usize>,
2041    buffer_start: Option<Point>,
2042    status: Option<DiffHunkStatus>,
2043}
2044
2045impl ReferenceMultibuffer {
2046    fn expand_excerpts(&mut self, excerpts: &HashSet<ExcerptId>, line_count: u32, cx: &App) {
2047        if line_count == 0 {
2048            return;
2049        }
2050
2051        for id in excerpts {
2052            let excerpt = self.excerpts.iter_mut().find(|e| e.id == *id).unwrap();
2053            let snapshot = excerpt.buffer.read(cx).snapshot();
2054            let mut point_range = excerpt.range.to_point(&snapshot);
2055            point_range.start = Point::new(point_range.start.row.saturating_sub(line_count), 0);
2056            point_range.end =
2057                snapshot.clip_point(Point::new(point_range.end.row + line_count, 0), Bias::Left);
2058            point_range.end.column = snapshot.line_len(point_range.end.row);
2059            excerpt.range =
2060                snapshot.anchor_before(point_range.start)..snapshot.anchor_after(point_range.end);
2061        }
2062    }
2063
2064    fn remove_excerpt(&mut self, id: ExcerptId, cx: &App) {
2065        let ix = self
2066            .excerpts
2067            .iter()
2068            .position(|excerpt| excerpt.id == id)
2069            .unwrap();
2070        let excerpt = self.excerpts.remove(ix);
2071        let buffer = excerpt.buffer.read(cx);
2072        log::info!(
2073            "Removing excerpt {}: {:?}",
2074            ix,
2075            buffer
2076                .text_for_range(excerpt.range.to_offset(buffer))
2077                .collect::<String>(),
2078        );
2079    }
2080
2081    fn insert_excerpt_after(
2082        &mut self,
2083        prev_id: ExcerptId,
2084        new_excerpt_id: ExcerptId,
2085        (buffer_handle, anchor_range): (Entity<Buffer>, Range<text::Anchor>),
2086    ) {
2087        let excerpt_ix = if prev_id == ExcerptId::max() {
2088            self.excerpts.len()
2089        } else {
2090            self.excerpts
2091                .iter()
2092                .position(|excerpt| excerpt.id == prev_id)
2093                .unwrap()
2094                + 1
2095        };
2096        self.excerpts.insert(
2097            excerpt_ix,
2098            ReferenceExcerpt {
2099                id: new_excerpt_id,
2100                buffer: buffer_handle,
2101                range: anchor_range,
2102                expanded_diff_hunks: Vec::new(),
2103            },
2104        );
2105    }
2106
2107    fn expand_diff_hunks(&mut self, excerpt_id: ExcerptId, range: Range<text::Anchor>, cx: &App) {
2108        let excerpt = self
2109            .excerpts
2110            .iter_mut()
2111            .find(|e| e.id == excerpt_id)
2112            .unwrap();
2113        let buffer = excerpt.buffer.read(cx).snapshot();
2114        let buffer_id = buffer.remote_id();
2115        let Some(change_set) = self.change_sets.get(&buffer_id) else {
2116            return;
2117        };
2118        let diff = change_set.read(cx).diff_to_buffer.clone();
2119        let excerpt_range = excerpt.range.to_offset(&buffer);
2120        if excerpt_range.is_empty() {
2121            return;
2122        }
2123        for hunk in diff.hunks_intersecting_range(range, &buffer) {
2124            let hunk_range = hunk.buffer_range.to_offset(&buffer);
2125            let hunk_precedes_excerpt = hunk
2126                .buffer_range
2127                .end
2128                .cmp(&excerpt.range.start, &buffer)
2129                .is_lt();
2130            let hunk_follows_excerpt = hunk
2131                .buffer_range
2132                .start
2133                .cmp(&excerpt.range.end, &buffer)
2134                .is_ge();
2135            if hunk_precedes_excerpt || hunk_follows_excerpt {
2136                continue;
2137            }
2138
2139            if let Err(ix) = excerpt
2140                .expanded_diff_hunks
2141                .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
2142            {
2143                log::info!(
2144                    "expanding diff hunk {:?}. excerpt: {:?}",
2145                    hunk_range,
2146                    excerpt_range
2147                );
2148                excerpt
2149                    .expanded_diff_hunks
2150                    .insert(ix, hunk.buffer_range.start);
2151            }
2152        }
2153    }
2154
2155    fn expected_content(&self, cx: &App) -> (String, Vec<RowInfo>, HashSet<MultiBufferRow>) {
2156        let mut text = String::new();
2157        let mut regions = Vec::<ReferenceRegion>::new();
2158        let mut excerpt_boundary_rows = HashSet::default();
2159        for excerpt in &self.excerpts {
2160            excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
2161            let buffer = excerpt.buffer.read(cx);
2162            let buffer_range = excerpt.range.to_offset(buffer);
2163            let change_set = self.change_sets.get(&buffer.remote_id()).unwrap().read(cx);
2164            let diff = change_set.diff_to_buffer.clone();
2165            let base_buffer = change_set.base_text.as_ref().unwrap();
2166
2167            let mut offset = buffer_range.start;
2168            let mut hunks = diff
2169                .hunks_intersecting_range(excerpt.range.clone(), buffer)
2170                .peekable();
2171
2172            while let Some(hunk) = hunks.next() {
2173                if !hunk.buffer_range.start.is_valid(&buffer) {
2174                    continue;
2175                }
2176
2177                // Ignore hunks that are outside the excerpt range.
2178                let mut hunk_range = hunk.buffer_range.to_offset(buffer);
2179                hunk_range.end = hunk_range.end.min(buffer_range.end);
2180                if hunk_range.start > buffer_range.end
2181                    || hunk_range.end < buffer_range.start
2182                    || buffer_range.is_empty()
2183                {
2184                    continue;
2185                }
2186
2187                if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| {
2188                    expanded_anchor.to_offset(&buffer).max(buffer_range.start)
2189                        == hunk_range.start.max(buffer_range.start)
2190                }) {
2191                    continue;
2192                }
2193
2194                if hunk_range.start >= offset {
2195                    // Add the buffer text before the hunk
2196                    let len = text.len();
2197                    text.extend(buffer.text_for_range(offset..hunk_range.start));
2198                    regions.push(ReferenceRegion {
2199                        range: len..text.len(),
2200                        buffer_start: Some(buffer.offset_to_point(offset)),
2201                        status: None,
2202                    });
2203
2204                    // Add the deleted text for the hunk.
2205                    if !hunk.diff_base_byte_range.is_empty() {
2206                        let mut base_text = base_buffer
2207                            .text_for_range(hunk.diff_base_byte_range.clone())
2208                            .collect::<String>();
2209                        if !base_text.ends_with('\n') {
2210                            base_text.push('\n');
2211                        }
2212                        let len = text.len();
2213                        text.push_str(&base_text);
2214                        regions.push(ReferenceRegion {
2215                            range: len..text.len(),
2216                            buffer_start: Some(
2217                                base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
2218                            ),
2219                            status: Some(DiffHunkStatus::Removed),
2220                        });
2221                    }
2222
2223                    offset = hunk_range.start;
2224                }
2225
2226                // Add the inserted text for the hunk.
2227                if hunk_range.end > offset {
2228                    let len = text.len();
2229                    text.extend(buffer.text_for_range(offset..hunk_range.end));
2230                    regions.push(ReferenceRegion {
2231                        range: len..text.len(),
2232                        buffer_start: Some(buffer.offset_to_point(offset)),
2233                        status: Some(DiffHunkStatus::Added),
2234                    });
2235                    offset = hunk_range.end;
2236                }
2237            }
2238
2239            // Add the buffer text for the rest of the excerpt.
2240            let len = text.len();
2241            text.extend(buffer.text_for_range(offset..buffer_range.end));
2242            text.push('\n');
2243            regions.push(ReferenceRegion {
2244                range: len..text.len(),
2245                buffer_start: Some(buffer.offset_to_point(offset)),
2246                status: None,
2247            });
2248        }
2249
2250        // Remove final trailing newline.
2251        if self.excerpts.is_empty() {
2252            regions.push(ReferenceRegion {
2253                range: 0..1,
2254                buffer_start: Some(Point::new(0, 0)),
2255                status: None,
2256            });
2257        } else {
2258            text.pop();
2259        }
2260
2261        // Retrieve the row info using the region that contains
2262        // the start of each multi-buffer line.
2263        let mut ix = 0;
2264        let row_infos = text
2265            .split('\n')
2266            .map(|line| {
2267                let row_info = regions
2268                    .iter()
2269                    .find(|region| region.range.contains(&ix))
2270                    .map_or(RowInfo::default(), |region| {
2271                        let buffer_row = region.buffer_start.map(|start_point| {
2272                            start_point.row
2273                                + text[region.range.start..ix].matches('\n').count() as u32
2274                        });
2275                        RowInfo {
2276                            diff_status: region.status,
2277                            buffer_row,
2278                            multibuffer_row: Some(MultiBufferRow(
2279                                text[..ix].matches('\n').count() as u32
2280                            )),
2281                        }
2282                    });
2283                ix += line.len() + 1;
2284                row_info
2285            })
2286            .collect();
2287
2288        (text, row_infos, excerpt_boundary_rows)
2289    }
2290
2291    fn diffs_updated(&mut self, cx: &App) {
2292        for excerpt in &mut self.excerpts {
2293            let buffer = excerpt.buffer.read(cx).snapshot();
2294            let excerpt_range = excerpt.range.to_offset(&buffer);
2295            let buffer_id = buffer.remote_id();
2296            let diff = &self
2297                .change_sets
2298                .get(&buffer_id)
2299                .unwrap()
2300                .read(cx)
2301                .diff_to_buffer;
2302            let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer).peekable();
2303            excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
2304                if !hunk_anchor.is_valid(&buffer) {
2305                    return false;
2306                }
2307                while let Some(hunk) = hunks.peek() {
2308                    match hunk.buffer_range.start.cmp(&hunk_anchor, &buffer) {
2309                        cmp::Ordering::Less => {
2310                            hunks.next();
2311                        }
2312                        cmp::Ordering::Equal => {
2313                            let hunk_range = hunk.buffer_range.to_offset(&buffer);
2314                            return hunk_range.end >= excerpt_range.start
2315                                && hunk_range.start <= excerpt_range.end;
2316                        }
2317                        cmp::Ordering::Greater => break,
2318                    }
2319                }
2320                false
2321            });
2322        }
2323    }
2324
2325    fn add_change_set(&mut self, change_set: Entity<BufferChangeSet>, cx: &mut App) {
2326        let buffer_id = change_set.read(cx).buffer_id;
2327        self.change_sets.insert(buffer_id, change_set);
2328    }
2329}
2330
2331#[gpui::test(iterations = 100)]
2332async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
2333    let operations = env::var("OPERATIONS")
2334        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2335        .unwrap_or(10);
2336
2337    let mut buffers: Vec<Entity<Buffer>> = Vec::new();
2338    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2339    let mut reference = ReferenceMultibuffer::default();
2340    let mut anchors = Vec::new();
2341    let mut old_versions = Vec::new();
2342    let mut needs_diff_calculation = false;
2343
2344    for _ in 0..operations {
2345        match rng.gen_range(0..100) {
2346            0..=14 if !buffers.is_empty() => {
2347                let buffer = buffers.choose(&mut rng).unwrap();
2348                buffer.update(cx, |buf, cx| {
2349                    let edit_count = rng.gen_range(1..5);
2350                    buf.randomly_edit(&mut rng, edit_count, cx);
2351                    needs_diff_calculation = true;
2352                });
2353                cx.update(|cx| reference.diffs_updated(cx));
2354            }
2355            15..=19 if !reference.excerpts.is_empty() => {
2356                multibuffer.update(cx, |multibuffer, cx| {
2357                    let ids = multibuffer.excerpt_ids();
2358                    let mut excerpts = HashSet::default();
2359                    for _ in 0..rng.gen_range(0..ids.len()) {
2360                        excerpts.extend(ids.choose(&mut rng).copied());
2361                    }
2362
2363                    let line_count = rng.gen_range(0..5);
2364
2365                    let excerpt_ixs = excerpts
2366                        .iter()
2367                        .map(|id| reference.excerpts.iter().position(|e| e.id == *id).unwrap())
2368                        .collect::<Vec<_>>();
2369                    log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
2370                    multibuffer.expand_excerpts(
2371                        excerpts.iter().cloned(),
2372                        line_count,
2373                        ExpandExcerptDirection::UpAndDown,
2374                        cx,
2375                    );
2376
2377                    reference.expand_excerpts(&excerpts, line_count, cx);
2378                });
2379            }
2380            20..=29 if !reference.excerpts.is_empty() => {
2381                let mut ids_to_remove = vec![];
2382                for _ in 0..rng.gen_range(1..=3) {
2383                    let Some(excerpt) = reference.excerpts.choose(&mut rng) else {
2384                        break;
2385                    };
2386                    let id = excerpt.id;
2387                    cx.update(|cx| reference.remove_excerpt(id, cx));
2388                    ids_to_remove.push(id);
2389                }
2390                let snapshot =
2391                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2392                ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
2393                drop(snapshot);
2394                multibuffer.update(cx, |multibuffer, cx| {
2395                    multibuffer.remove_excerpts(ids_to_remove, cx)
2396                });
2397            }
2398            30..=39 if !reference.excerpts.is_empty() => {
2399                let multibuffer =
2400                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2401                let offset =
2402                    multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
2403                let bias = if rng.gen() { Bias::Left } else { Bias::Right };
2404                log::info!("Creating anchor at {} with bias {:?}", offset, bias);
2405                anchors.push(multibuffer.anchor_at(offset, bias));
2406                anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
2407            }
2408            40..=44 if !anchors.is_empty() => {
2409                let multibuffer =
2410                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2411                let prev_len = anchors.len();
2412                anchors = multibuffer
2413                    .refresh_anchors(&anchors)
2414                    .into_iter()
2415                    .map(|a| a.1)
2416                    .collect();
2417
2418                // Ensure the newly-refreshed anchors point to a valid excerpt and don't
2419                // overshoot its boundaries.
2420                assert_eq!(anchors.len(), prev_len);
2421                for anchor in &anchors {
2422                    if anchor.excerpt_id == ExcerptId::min()
2423                        || anchor.excerpt_id == ExcerptId::max()
2424                    {
2425                        continue;
2426                    }
2427
2428                    let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
2429                    assert_eq!(excerpt.id, anchor.excerpt_id);
2430                    assert!(excerpt.contains(anchor));
2431                }
2432            }
2433            45..=55 if !reference.excerpts.is_empty() => {
2434                multibuffer.update(cx, |multibuffer, cx| {
2435                    let snapshot = multibuffer.snapshot(cx);
2436                    let excerpt_ix = rng.gen_range(0..reference.excerpts.len());
2437                    let excerpt = &reference.excerpts[excerpt_ix];
2438                    let start = excerpt.range.start;
2439                    let end = excerpt.range.end;
2440                    let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
2441                        ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
2442
2443                    log::info!("expanding diff hunks for excerpt {:?}", excerpt_ix);
2444                    reference.expand_diff_hunks(excerpt.id, start..end, cx);
2445                    multibuffer.expand_diff_hunks(vec![range], cx);
2446                });
2447            }
2448            56..=85 if needs_diff_calculation => {
2449                multibuffer.update(cx, |multibuffer, cx| {
2450                    for buffer in multibuffer.all_buffers() {
2451                        let snapshot = buffer.read(cx).snapshot();
2452                        let _ = multibuffer
2453                            .change_set_for(snapshot.remote_id())
2454                            .unwrap()
2455                            .update(cx, |change_set, cx| {
2456                                log::info!(
2457                                    "recalculating diff for buffer {:?}",
2458                                    snapshot.remote_id(),
2459                                );
2460                                change_set.recalculate_diff(snapshot.text, cx)
2461                            });
2462                    }
2463                    reference.diffs_updated(cx);
2464                    needs_diff_calculation = false;
2465                });
2466            }
2467            _ => {
2468                let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
2469                    let base_text = util::RandomCharIter::new(&mut rng)
2470                        .take(256)
2471                        .collect::<String>();
2472
2473                    let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
2474                    let change_set = cx.new(|cx| BufferChangeSet::new(&buffer, cx));
2475                    change_set
2476                        .update(cx, |change_set, cx| {
2477                            let snapshot = buffer.read(cx).snapshot();
2478                            change_set.set_base_text(base_text, snapshot.text, cx)
2479                        })
2480                        .await
2481                        .unwrap();
2482
2483                    multibuffer.update(cx, |multibuffer, cx| {
2484                        reference.add_change_set(change_set.clone(), cx);
2485                        multibuffer.add_change_set(change_set, cx)
2486                    });
2487                    buffers.push(buffer);
2488                    buffers.last().unwrap()
2489                } else {
2490                    buffers.choose(&mut rng).unwrap()
2491                };
2492
2493                let prev_excerpt_ix = rng.gen_range(0..=reference.excerpts.len());
2494                let prev_excerpt_id = reference
2495                    .excerpts
2496                    .get(prev_excerpt_ix)
2497                    .map_or(ExcerptId::max(), |e| e.id);
2498                let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
2499
2500                let (range, anchor_range) = buffer_handle.read_with(cx, |buffer, _| {
2501                    let end_row = rng.gen_range(0..=buffer.max_point().row);
2502                    let start_row = rng.gen_range(0..=end_row);
2503                    let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
2504                    let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
2505                    let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
2506
2507                    log::info!(
2508                        "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
2509                        excerpt_ix,
2510                        reference.excerpts.len(),
2511                        buffer.remote_id(),
2512                        buffer.text(),
2513                        start_ix..end_ix,
2514                        &buffer.text()[start_ix..end_ix]
2515                    );
2516
2517                    (start_ix..end_ix, anchor_range)
2518                });
2519
2520                let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
2521                    multibuffer
2522                        .insert_excerpts_after(
2523                            prev_excerpt_id,
2524                            buffer_handle.clone(),
2525                            [ExcerptRange {
2526                                context: range,
2527                                primary: None,
2528                            }],
2529                            cx,
2530                        )
2531                        .pop()
2532                        .unwrap()
2533                });
2534
2535                reference.insert_excerpt_after(
2536                    prev_excerpt_id,
2537                    excerpt_id,
2538                    (buffer_handle.clone(), anchor_range),
2539                );
2540            }
2541        }
2542
2543        if rng.gen_bool(0.3) {
2544            multibuffer.update(cx, |multibuffer, cx| {
2545                old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
2546            })
2547        }
2548
2549        let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2550        let actual_text = snapshot.text();
2551        let actual_boundary_rows = snapshot
2552            .excerpt_boundaries_in_range(0..)
2553            .filter_map(|b| if b.next.is_some() { Some(b.row) } else { None })
2554            .collect::<HashSet<_>>();
2555        let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2556        let actual_diff = format_diff(&actual_text, &actual_row_infos, &actual_boundary_rows);
2557
2558        let (expected_text, expected_row_infos, expected_boundary_rows) =
2559            cx.update(|cx| reference.expected_content(cx));
2560        let expected_diff =
2561            format_diff(&expected_text, &expected_row_infos, &expected_boundary_rows);
2562
2563        log::info!("Multibuffer content:\n{}", actual_diff);
2564
2565        assert_eq!(
2566            actual_row_infos.len(),
2567            actual_text.split('\n').count(),
2568            "line count: {}",
2569            actual_text.split('\n').count()
2570        );
2571        pretty_assertions::assert_eq!(actual_diff, expected_diff);
2572        pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
2573        pretty_assertions::assert_eq!(actual_text, expected_text);
2574
2575        for _ in 0..5 {
2576            let start_row = rng.gen_range(0..=expected_row_infos.len());
2577            assert_eq!(
2578                snapshot
2579                    .row_infos(MultiBufferRow(start_row as u32))
2580                    .collect::<Vec<_>>(),
2581                &expected_row_infos[start_row..],
2582                "buffer_rows({})",
2583                start_row
2584            );
2585        }
2586
2587        assert_eq!(
2588            snapshot.widest_line_number(),
2589            expected_row_infos
2590                .into_iter()
2591                .filter_map(
2592                    |info| if info.diff_status == Some(DiffHunkStatus::Removed) {
2593                        None
2594                    } else {
2595                        info.buffer_row
2596                    }
2597                )
2598                .max()
2599                .unwrap()
2600                + 1
2601        );
2602
2603        assert_consistent_line_numbers(&snapshot);
2604        assert_position_translation(&snapshot);
2605
2606        for (row, line) in expected_text.split('\n').enumerate() {
2607            assert_eq!(
2608                snapshot.line_len(MultiBufferRow(row as u32)),
2609                line.len() as u32,
2610                "line_len({}).",
2611                row
2612            );
2613        }
2614
2615        let text_rope = Rope::from(expected_text.as_str());
2616        for _ in 0..10 {
2617            let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2618            let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
2619
2620            let text_for_range = snapshot
2621                .text_for_range(start_ix..end_ix)
2622                .collect::<String>();
2623            assert_eq!(
2624                text_for_range,
2625                &expected_text[start_ix..end_ix],
2626                "incorrect text for range {:?}",
2627                start_ix..end_ix
2628            );
2629
2630            let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
2631            assert_eq!(
2632                snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
2633                expected_summary,
2634                "incorrect summary for range {:?}",
2635                start_ix..end_ix
2636            );
2637        }
2638
2639        // Anchor resolution
2640        let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
2641        assert_eq!(anchors.len(), summaries.len());
2642        for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
2643            assert!(resolved_offset <= snapshot.len());
2644            assert_eq!(
2645                snapshot.summary_for_anchor::<usize>(anchor),
2646                resolved_offset,
2647                "anchor: {:?}",
2648                anchor
2649            );
2650        }
2651
2652        for _ in 0..10 {
2653            let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2654            assert_eq!(
2655                snapshot.reversed_chars_at(end_ix).collect::<String>(),
2656                expected_text[..end_ix].chars().rev().collect::<String>(),
2657            );
2658        }
2659
2660        for _ in 0..10 {
2661            let end_ix = rng.gen_range(0..=text_rope.len());
2662            let start_ix = rng.gen_range(0..=end_ix);
2663            assert_eq!(
2664                snapshot
2665                    .bytes_in_range(start_ix..end_ix)
2666                    .flatten()
2667                    .copied()
2668                    .collect::<Vec<_>>(),
2669                expected_text.as_bytes()[start_ix..end_ix].to_vec(),
2670                "bytes_in_range({:?})",
2671                start_ix..end_ix,
2672            );
2673        }
2674    }
2675
2676    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2677    for (old_snapshot, subscription) in old_versions {
2678        let edits = subscription.consume().into_inner();
2679
2680        log::info!(
2681            "applying subscription edits to old text: {:?}: {:?}",
2682            old_snapshot.text(),
2683            edits,
2684        );
2685
2686        let mut text = old_snapshot.text();
2687        for edit in edits {
2688            let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
2689            text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
2690        }
2691        assert_eq!(text.to_string(), snapshot.text());
2692    }
2693}
2694
2695#[gpui::test]
2696fn test_history(cx: &mut App) {
2697    let test_settings = SettingsStore::test(cx);
2698    cx.set_global(test_settings);
2699    let group_interval: Duration = Duration::from_millis(1);
2700    let buffer_1 = cx.new(|cx| {
2701        let mut buf = Buffer::local("1234", cx);
2702        buf.set_group_interval(group_interval);
2703        buf
2704    });
2705    let buffer_2 = cx.new(|cx| {
2706        let mut buf = Buffer::local("5678", cx);
2707        buf.set_group_interval(group_interval);
2708        buf
2709    });
2710    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2711    multibuffer.update(cx, |this, _| {
2712        this.history.group_interval = group_interval;
2713    });
2714    multibuffer.update(cx, |multibuffer, cx| {
2715        multibuffer.push_excerpts(
2716            buffer_1.clone(),
2717            [ExcerptRange {
2718                context: 0..buffer_1.read(cx).len(),
2719                primary: None,
2720            }],
2721            cx,
2722        );
2723        multibuffer.push_excerpts(
2724            buffer_2.clone(),
2725            [ExcerptRange {
2726                context: 0..buffer_2.read(cx).len(),
2727                primary: None,
2728            }],
2729            cx,
2730        );
2731    });
2732
2733    let mut now = Instant::now();
2734
2735    multibuffer.update(cx, |multibuffer, cx| {
2736        let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
2737        multibuffer.edit(
2738            [
2739                (Point::new(0, 0)..Point::new(0, 0), "A"),
2740                (Point::new(1, 0)..Point::new(1, 0), "A"),
2741            ],
2742            None,
2743            cx,
2744        );
2745        multibuffer.edit(
2746            [
2747                (Point::new(0, 1)..Point::new(0, 1), "B"),
2748                (Point::new(1, 1)..Point::new(1, 1), "B"),
2749            ],
2750            None,
2751            cx,
2752        );
2753        multibuffer.end_transaction_at(now, cx);
2754        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2755
2756        // Verify edited ranges for transaction 1
2757        assert_eq!(
2758            multibuffer.edited_ranges_for_transaction(transaction_1, cx),
2759            &[
2760                Point::new(0, 0)..Point::new(0, 2),
2761                Point::new(1, 0)..Point::new(1, 2)
2762            ]
2763        );
2764
2765        // Edit buffer 1 through the multibuffer
2766        now += 2 * group_interval;
2767        multibuffer.start_transaction_at(now, cx);
2768        multibuffer.edit([(2..2, "C")], None, cx);
2769        multibuffer.end_transaction_at(now, cx);
2770        assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
2771
2772        // Edit buffer 1 independently
2773        buffer_1.update(cx, |buffer_1, cx| {
2774            buffer_1.start_transaction_at(now);
2775            buffer_1.edit([(3..3, "D")], None, cx);
2776            buffer_1.end_transaction_at(now, cx);
2777
2778            now += 2 * group_interval;
2779            buffer_1.start_transaction_at(now);
2780            buffer_1.edit([(4..4, "E")], None, cx);
2781            buffer_1.end_transaction_at(now, cx);
2782        });
2783        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2784
2785        // An undo in the multibuffer undoes the multibuffer transaction
2786        // and also any individual buffer edits that have occurred since
2787        // that transaction.
2788        multibuffer.undo(cx);
2789        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2790
2791        multibuffer.undo(cx);
2792        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2793
2794        multibuffer.redo(cx);
2795        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2796
2797        multibuffer.redo(cx);
2798        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2799
2800        // Undo buffer 2 independently.
2801        buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
2802        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
2803
2804        // An undo in the multibuffer undoes the components of the
2805        // the last multibuffer transaction that are not already undone.
2806        multibuffer.undo(cx);
2807        assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
2808
2809        multibuffer.undo(cx);
2810        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2811
2812        multibuffer.redo(cx);
2813        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2814
2815        buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
2816        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2817
2818        // Redo stack gets cleared after an edit.
2819        now += 2 * group_interval;
2820        multibuffer.start_transaction_at(now, cx);
2821        multibuffer.edit([(0..0, "X")], None, cx);
2822        multibuffer.end_transaction_at(now, cx);
2823        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2824        multibuffer.redo(cx);
2825        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2826        multibuffer.undo(cx);
2827        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2828        multibuffer.undo(cx);
2829        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2830
2831        // Transactions can be grouped manually.
2832        multibuffer.redo(cx);
2833        multibuffer.redo(cx);
2834        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2835        multibuffer.group_until_transaction(transaction_1, cx);
2836        multibuffer.undo(cx);
2837        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2838        multibuffer.redo(cx);
2839        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2840    });
2841}
2842
2843#[gpui::test]
2844async fn test_enclosing_indent(cx: &mut TestAppContext) {
2845    async fn enclosing_indent(
2846        text: &str,
2847        buffer_row: u32,
2848        cx: &mut TestAppContext,
2849    ) -> Option<(Range<u32>, LineIndent)> {
2850        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2851        let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
2852        let (range, indent) = snapshot
2853            .enclosing_indent(MultiBufferRow(buffer_row))
2854            .await?;
2855        Some((range.start.0..range.end.0, indent))
2856    }
2857
2858    assert_eq!(
2859        enclosing_indent(
2860            indoc!(
2861                "
2862                fn b() {
2863                    if c {
2864                        let d = 2;
2865                    }
2866                }
2867                "
2868            ),
2869            1,
2870            cx,
2871        )
2872        .await,
2873        Some((
2874            1..2,
2875            LineIndent {
2876                tabs: 0,
2877                spaces: 4,
2878                line_blank: false,
2879            }
2880        ))
2881    );
2882
2883    assert_eq!(
2884        enclosing_indent(
2885            indoc!(
2886                "
2887                fn b() {
2888                    if c {
2889                        let d = 2;
2890                    }
2891                }
2892                "
2893            ),
2894            2,
2895            cx,
2896        )
2897        .await,
2898        Some((
2899            1..2,
2900            LineIndent {
2901                tabs: 0,
2902                spaces: 4,
2903                line_blank: false,
2904            }
2905        ))
2906    );
2907
2908    assert_eq!(
2909        enclosing_indent(
2910            indoc!(
2911                "
2912                fn b() {
2913                    if c {
2914                        let d = 2;
2915
2916                        let e = 5;
2917                    }
2918                }
2919                "
2920            ),
2921            3,
2922            cx,
2923        )
2924        .await,
2925        Some((
2926            1..4,
2927            LineIndent {
2928                tabs: 0,
2929                spaces: 4,
2930                line_blank: false,
2931            }
2932        ))
2933    );
2934}
2935
2936fn format_diff(
2937    text: &str,
2938    row_infos: &Vec<RowInfo>,
2939    boundary_rows: &HashSet<MultiBufferRow>,
2940) -> String {
2941    let has_diff = row_infos.iter().any(|info| info.diff_status.is_some());
2942    text.split('\n')
2943        .enumerate()
2944        .zip(row_infos)
2945        .map(|((ix, line), info)| {
2946            let marker = match info.diff_status {
2947                Some(DiffHunkStatus::Added) => "+ ",
2948                Some(DiffHunkStatus::Removed) => "- ",
2949                Some(DiffHunkStatus::Modified) => unreachable!(),
2950                None => {
2951                    if has_diff && !line.is_empty() {
2952                        "  "
2953                    } else {
2954                        ""
2955                    }
2956                }
2957            };
2958            let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
2959                if has_diff {
2960                    "  ----------\n"
2961                } else {
2962                    "---------\n"
2963                }
2964            } else {
2965                ""
2966            };
2967            format!("{boundary_row}{marker}{line}")
2968        })
2969        .collect::<Vec<_>>()
2970        .join("\n")
2971}
2972
2973#[track_caller]
2974fn assert_excerpts_match(
2975    multibuffer: &Entity<MultiBuffer>,
2976    cx: &mut TestAppContext,
2977    expected: &str,
2978) {
2979    let mut output = String::new();
2980    multibuffer.read_with(cx, |multibuffer, cx| {
2981        for (_, buffer, range) in multibuffer.snapshot(cx).excerpts() {
2982            output.push_str("-----\n");
2983            output.extend(buffer.text_for_range(range.context));
2984            if !output.ends_with('\n') {
2985                output.push('\n');
2986            }
2987        }
2988    });
2989    assert_eq!(output, expected);
2990}
2991
2992#[track_caller]
2993fn assert_new_snapshot(
2994    multibuffer: &Entity<MultiBuffer>,
2995    snapshot: &mut MultiBufferSnapshot,
2996    subscription: &mut Subscription,
2997    cx: &mut TestAppContext,
2998    expected_diff: &str,
2999) {
3000    let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3001    let actual_text = new_snapshot.text();
3002    let line_infos = new_snapshot
3003        .row_infos(MultiBufferRow(0))
3004        .collect::<Vec<_>>();
3005    let actual_diff = format_diff(&actual_text, &line_infos, &Default::default());
3006    pretty_assertions::assert_eq!(actual_diff, expected_diff);
3007    check_edits(
3008        snapshot,
3009        &new_snapshot,
3010        &subscription.consume().into_inner(),
3011    );
3012    *snapshot = new_snapshot;
3013}
3014
3015#[track_caller]
3016fn check_edits(
3017    old_snapshot: &MultiBufferSnapshot,
3018    new_snapshot: &MultiBufferSnapshot,
3019    edits: &[Edit<usize>],
3020) {
3021    let mut text = old_snapshot.text();
3022    let new_text = new_snapshot.text();
3023    for edit in edits.iter().rev() {
3024        if !text.is_char_boundary(edit.old.start)
3025            || !text.is_char_boundary(edit.old.end)
3026            || !new_text.is_char_boundary(edit.new.start)
3027            || !new_text.is_char_boundary(edit.new.end)
3028        {
3029            panic!(
3030                "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
3031                edits, text, new_text
3032            );
3033        }
3034
3035        text.replace_range(
3036            edit.old.start..edit.old.end,
3037            &new_text[edit.new.start..edit.new.end],
3038        );
3039    }
3040
3041    pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
3042}
3043
3044#[track_caller]
3045fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
3046    let full_text = snapshot.text();
3047    for ix in 0..full_text.len() {
3048        let mut chunks = snapshot.chunks(0..snapshot.len(), false);
3049        chunks.seek(ix..snapshot.len());
3050        let tail = chunks.map(|chunk| chunk.text).collect::<String>();
3051        assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
3052    }
3053}
3054
3055#[track_caller]
3056fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
3057    let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3058    for start_row in 1..all_line_numbers.len() {
3059        let line_numbers = snapshot
3060            .row_infos(MultiBufferRow(start_row as u32))
3061            .collect::<Vec<_>>();
3062        assert_eq!(
3063            line_numbers,
3064            all_line_numbers[start_row..],
3065            "start_row: {start_row}"
3066        );
3067    }
3068}
3069
3070#[track_caller]
3071fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
3072    let text = Rope::from(snapshot.text());
3073
3074    let mut left_anchors = Vec::new();
3075    let mut right_anchors = Vec::new();
3076    let mut offsets = Vec::new();
3077    let mut points = Vec::new();
3078    for offset in 0..=text.len() + 1 {
3079        let clipped_left = snapshot.clip_offset(offset, Bias::Left);
3080        let clipped_right = snapshot.clip_offset(offset, Bias::Right);
3081        assert_eq!(
3082            clipped_left,
3083            text.clip_offset(offset, Bias::Left),
3084            "clip_offset({offset:?}, Left)"
3085        );
3086        assert_eq!(
3087            clipped_right,
3088            text.clip_offset(offset, Bias::Right),
3089            "clip_offset({offset:?}, Right)"
3090        );
3091        assert_eq!(
3092            snapshot.offset_to_point(clipped_left),
3093            text.offset_to_point(clipped_left),
3094            "offset_to_point({clipped_left})"
3095        );
3096        assert_eq!(
3097            snapshot.offset_to_point(clipped_right),
3098            text.offset_to_point(clipped_right),
3099            "offset_to_point({clipped_right})"
3100        );
3101        let anchor_after = snapshot.anchor_after(clipped_left);
3102        assert_eq!(
3103            anchor_after.to_offset(snapshot),
3104            clipped_left,
3105            "anchor_after({clipped_left}).to_offset {anchor_after:?}"
3106        );
3107        let anchor_before = snapshot.anchor_before(clipped_left);
3108        assert_eq!(
3109            anchor_before.to_offset(snapshot),
3110            clipped_left,
3111            "anchor_before({clipped_left}).to_offset"
3112        );
3113        left_anchors.push(anchor_before);
3114        right_anchors.push(anchor_after);
3115        offsets.push(clipped_left);
3116        points.push(text.offset_to_point(clipped_left));
3117    }
3118
3119    for row in 0..text.max_point().row {
3120        for column in 0..text.line_len(row) + 1 {
3121            let point = Point { row, column };
3122            let clipped_left = snapshot.clip_point(point, Bias::Left);
3123            let clipped_right = snapshot.clip_point(point, Bias::Right);
3124            assert_eq!(
3125                clipped_left,
3126                text.clip_point(point, Bias::Left),
3127                "clip_point({point:?}, Left)"
3128            );
3129            assert_eq!(
3130                clipped_right,
3131                text.clip_point(point, Bias::Right),
3132                "clip_point({point:?}, Right)"
3133            );
3134            assert_eq!(
3135                snapshot.point_to_offset(clipped_left),
3136                text.point_to_offset(clipped_left),
3137                "point_to_offset({clipped_left:?})"
3138            );
3139            assert_eq!(
3140                snapshot.point_to_offset(clipped_right),
3141                text.point_to_offset(clipped_right),
3142                "point_to_offset({clipped_right:?})"
3143            );
3144        }
3145    }
3146
3147    assert_eq!(
3148        snapshot.summaries_for_anchors::<usize, _>(&left_anchors),
3149        offsets,
3150        "left_anchors <-> offsets"
3151    );
3152    assert_eq!(
3153        snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
3154        points,
3155        "left_anchors <-> points"
3156    );
3157    assert_eq!(
3158        snapshot.summaries_for_anchors::<usize, _>(&right_anchors),
3159        offsets,
3160        "right_anchors <-> offsets"
3161    );
3162    assert_eq!(
3163        snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
3164        points,
3165        "right_anchors <-> points"
3166    );
3167
3168    for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
3169        for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
3170            if ix > 0 {
3171                if *offset == 252 {
3172                    if offset > &offsets[ix - 1] {
3173                        let prev_anchor = left_anchors[ix - 1];
3174                        assert!(
3175                            anchor.cmp(&prev_anchor, snapshot).is_gt(),
3176                            "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
3177                            offsets[ix],
3178                            offsets[ix - 1],
3179                        );
3180                        assert!(
3181                            prev_anchor.cmp(&anchor, snapshot).is_lt(),
3182                            "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
3183                            offsets[ix - 1],
3184                            offsets[ix],
3185                        );
3186                    }
3187                }
3188            }
3189        }
3190    }
3191}
3192
3193fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
3194    let max_row = snapshot.max_point().row;
3195    let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
3196    let text = text::Buffer::new(0, buffer_id, snapshot.text());
3197    let mut line_indents = text
3198        .line_indents_in_row_range(0..max_row + 1)
3199        .collect::<Vec<_>>();
3200    for start_row in 0..snapshot.max_point().row {
3201        pretty_assertions::assert_eq!(
3202            snapshot
3203                .line_indents(MultiBufferRow(start_row), |_| true)
3204                .map(|(row, indent, _)| (row.0, indent))
3205                .collect::<Vec<_>>(),
3206            &line_indents[(start_row as usize)..],
3207            "line_indents({start_row})"
3208        );
3209    }
3210
3211    line_indents.reverse();
3212    pretty_assertions::assert_eq!(
3213        snapshot
3214            .reversed_line_indents(MultiBufferRow(max_row), |_| true)
3215            .map(|(row, indent, _)| (row.0, indent))
3216            .collect::<Vec<_>>(),
3217        &line_indents[..],
3218        "reversed_line_indents({max_row})"
3219    );
3220}