multi_buffer_tests.rs

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