multi_buffer_tests.rs

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