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 std::time::{Duration, Instant};
  11use util::RandomCharIter;
  12use util::rel_path::rel_path;
  13use util::test::sample_text;
  14
  15#[ctor::ctor]
  16fn init_logger() {
  17    zlog::init_test();
  18}
  19
  20#[gpui::test]
  21fn test_empty_singleton(cx: &mut App) {
  22    let buffer = cx.new(|cx| Buffer::local("", cx));
  23    let buffer_id = buffer.read(cx).remote_id();
  24    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
  25    let snapshot = multibuffer.read(cx).snapshot(cx);
  26    assert_eq!(snapshot.text(), "");
  27    assert_eq!(
  28        snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
  29        [RowInfo {
  30            buffer_id: Some(buffer_id),
  31            buffer_row: Some(0),
  32            multibuffer_row: Some(MultiBufferRow(0)),
  33            diff_status: None,
  34            expand_info: None,
  35            wrapped_buffer_row: None,
  36        }]
  37    );
  38}
  39
  40#[gpui::test]
  41fn test_singleton(cx: &mut App) {
  42    let buffer = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
  43    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
  44
  45    let snapshot = multibuffer.read(cx).snapshot(cx);
  46    assert_eq!(snapshot.text(), buffer.read(cx).text());
  47
  48    assert_eq!(
  49        snapshot
  50            .row_infos(MultiBufferRow(0))
  51            .map(|info| info.buffer_row)
  52            .collect::<Vec<_>>(),
  53        (0..buffer.read(cx).row_count())
  54            .map(Some)
  55            .collect::<Vec<_>>()
  56    );
  57    assert_consistent_line_numbers(&snapshot);
  58
  59    buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx));
  60    let snapshot = multibuffer.read(cx).snapshot(cx);
  61
  62    assert_eq!(snapshot.text(), buffer.read(cx).text());
  63    assert_eq!(
  64        snapshot
  65            .row_infos(MultiBufferRow(0))
  66            .map(|info| info.buffer_row)
  67            .collect::<Vec<_>>(),
  68        (0..buffer.read(cx).row_count())
  69            .map(Some)
  70            .collect::<Vec<_>>()
  71    );
  72    assert_consistent_line_numbers(&snapshot);
  73}
  74
  75#[gpui::test]
  76fn test_buffer_point_to_anchor_at_end_of_singleton_buffer(cx: &mut App) {
  77    let buffer = cx.new(|cx| Buffer::local("abc", cx));
  78    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
  79
  80    let excerpt_id = multibuffer
  81        .read(cx)
  82        .excerpt_ids()
  83        .into_iter()
  84        .next()
  85        .unwrap();
  86    let anchor = multibuffer
  87        .read(cx)
  88        .buffer_point_to_anchor(&buffer, Point::new(0, 3), cx);
  89
  90    assert_eq!(
  91        anchor,
  92        Some(Anchor::in_buffer(
  93            excerpt_id,
  94            buffer.read(cx).snapshot().anchor_after(Point::new(0, 3)),
  95        ))
  96    );
  97}
  98
  99#[gpui::test]
 100fn test_remote(cx: &mut App) {
 101    let host_buffer = cx.new(|cx| Buffer::local("a", cx));
 102    let guest_buffer = cx.new(|cx| {
 103        let state = host_buffer.read(cx).to_proto(cx);
 104        let ops = cx
 105            .foreground_executor()
 106            .block_on(host_buffer.read(cx).serialize_ops(None, cx));
 107        let mut buffer =
 108            Buffer::from_proto(ReplicaId::REMOTE_SERVER, Capability::ReadWrite, state, None)
 109                .unwrap();
 110        buffer.apply_ops(
 111            ops.into_iter()
 112                .map(|op| language::proto::deserialize_operation(op).unwrap()),
 113            cx,
 114        );
 115        buffer
 116    });
 117    let multibuffer = cx.new(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx));
 118    let snapshot = multibuffer.read(cx).snapshot(cx);
 119    assert_eq!(snapshot.text(), "a");
 120
 121    guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx));
 122    let snapshot = multibuffer.read(cx).snapshot(cx);
 123    assert_eq!(snapshot.text(), "ab");
 124
 125    guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx));
 126    let snapshot = multibuffer.read(cx).snapshot(cx);
 127    assert_eq!(snapshot.text(), "abc");
 128}
 129
 130#[gpui::test]
 131fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
 132    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(7, 6, 'a'), cx));
 133    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(7, 6, 'g'), cx));
 134    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 135
 136    let events = Arc::new(RwLock::new(Vec::<Event>::new()));
 137    multibuffer.update(cx, |_, cx| {
 138        let events = events.clone();
 139        cx.subscribe(&multibuffer, move |_, _, event, _| {
 140            if let Event::Edited { .. } = event {
 141                events.write().push(event.clone())
 142            }
 143        })
 144        .detach();
 145    });
 146
 147    let subscription = multibuffer.update(cx, |multibuffer, cx| {
 148        let subscription = multibuffer.subscribe();
 149        multibuffer.set_excerpt_ranges_for_path(
 150            PathKey::sorted(0),
 151            buffer_1.clone(),
 152            &buffer_1.read(cx).snapshot(),
 153            vec![ExcerptRange::new(Point::new(1, 2)..Point::new(2, 5))],
 154            cx,
 155        );
 156        assert_eq!(
 157            subscription.consume().into_inner(),
 158            [Edit {
 159                old: MultiBufferOffset(0)..MultiBufferOffset(0),
 160                new: MultiBufferOffset(0)..MultiBufferOffset(10)
 161            }]
 162        );
 163
 164        multibuffer.set_excerpt_ranges_for_path(
 165            PathKey::sorted(0),
 166            buffer_1.clone(),
 167            &buffer_1.read(cx).snapshot(),
 168            vec![
 169                ExcerptRange::new(Point::new(1, 2)..Point::new(2, 5)),
 170                ExcerptRange::new(Point::new(5, 3)..Point::new(6, 4)),
 171            ],
 172            cx,
 173        );
 174        multibuffer.set_excerpt_ranges_for_path(
 175            PathKey::sorted(1),
 176            buffer_2.clone(),
 177            &buffer_2.read(cx).snapshot(),
 178            vec![ExcerptRange::new(Point::new(3, 1)..Point::new(3, 3))],
 179            cx,
 180        );
 181        assert_eq!(
 182            subscription.consume().into_inner(),
 183            [Edit {
 184                old: MultiBufferOffset(10)..MultiBufferOffset(10),
 185                new: MultiBufferOffset(10)..MultiBufferOffset(22)
 186            }]
 187        );
 188
 189        subscription
 190    });
 191
 192    // Adding excerpts emits an edited event.
 193    assert_eq!(
 194        events.read().as_slice(),
 195        &[
 196            Event::Edited {
 197                edited_buffer: None,
 198                is_local: true,
 199            },
 200            Event::Edited {
 201                edited_buffer: None,
 202                is_local: true,
 203            },
 204            Event::Edited {
 205                edited_buffer: None,
 206                is_local: true,
 207            }
 208        ]
 209    );
 210
 211    let snapshot = multibuffer.read(cx).snapshot(cx);
 212    assert_eq!(
 213        snapshot.text(),
 214        indoc!(
 215            "
 216            bbbb
 217            ccccc
 218            fff
 219            gggg
 220            jj"
 221        ),
 222    );
 223    assert_eq!(
 224        snapshot
 225            .row_infos(MultiBufferRow(0))
 226            .map(|info| info.buffer_row)
 227            .collect::<Vec<_>>(),
 228        [Some(1), Some(2), Some(5), Some(6), Some(3)]
 229    );
 230    assert_eq!(
 231        snapshot
 232            .row_infos(MultiBufferRow(2))
 233            .map(|info| info.buffer_row)
 234            .collect::<Vec<_>>(),
 235        [Some(5), Some(6), Some(3)]
 236    );
 237    assert_eq!(
 238        snapshot
 239            .row_infos(MultiBufferRow(4))
 240            .map(|info| info.buffer_row)
 241            .collect::<Vec<_>>(),
 242        [Some(3)]
 243    );
 244    assert!(
 245        snapshot
 246            .row_infos(MultiBufferRow(5))
 247            .map(|info| info.buffer_row)
 248            .collect::<Vec<_>>()
 249            .is_empty()
 250    );
 251
 252    assert_eq!(
 253        boundaries_in_range(Point::new(0, 0)..Point::new(4, 2), &snapshot),
 254        &[
 255            (MultiBufferRow(0), "bbbb\nccccc".to_string(), true),
 256            (MultiBufferRow(2), "fff\ngggg".to_string(), false),
 257            (MultiBufferRow(4), "jj".to_string(), true),
 258        ]
 259    );
 260    assert_eq!(
 261        boundaries_in_range(Point::new(0, 0)..Point::new(2, 0), &snapshot),
 262        &[(MultiBufferRow(0), "bbbb\nccccc".to_string(), true)]
 263    );
 264    assert_eq!(
 265        boundaries_in_range(Point::new(1, 0)..Point::new(1, 5), &snapshot),
 266        &[]
 267    );
 268    assert_eq!(
 269        boundaries_in_range(Point::new(1, 0)..Point::new(2, 0), &snapshot),
 270        &[]
 271    );
 272    assert_eq!(
 273        boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
 274        &[(MultiBufferRow(2), "fff\ngggg".to_string(), false)]
 275    );
 276    assert_eq!(
 277        boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
 278        &[(MultiBufferRow(2), "fff\ngggg".to_string(), false)]
 279    );
 280    assert_eq!(
 281        boundaries_in_range(Point::new(2, 0)..Point::new(3, 0), &snapshot),
 282        &[(MultiBufferRow(2), "fff\ngggg".to_string(), false)]
 283    );
 284    assert_eq!(
 285        boundaries_in_range(Point::new(4, 0)..Point::new(4, 2), &snapshot),
 286        &[(MultiBufferRow(4), "jj".to_string(), true)]
 287    );
 288    assert_eq!(
 289        boundaries_in_range(Point::new(4, 2)..Point::new(4, 2), &snapshot),
 290        &[]
 291    );
 292
 293    buffer_1.update(cx, |buffer, cx| {
 294        let text = "\n";
 295        buffer.edit(
 296            [
 297                (Point::new(0, 0)..Point::new(0, 0), text),
 298                (Point::new(2, 1)..Point::new(2, 3), text),
 299            ],
 300            None,
 301            cx,
 302        );
 303    });
 304
 305    let snapshot = multibuffer.read(cx).snapshot(cx);
 306    assert_eq!(
 307        snapshot.text(),
 308        concat!(
 309            "bbbb\n", // Preserve newlines
 310            "c\n",    //
 311            "cc\n",   //
 312            "fff\n",  //
 313            "gggg\n", //
 314            "jj"      //
 315        )
 316    );
 317
 318    assert_eq!(
 319        subscription.consume().into_inner(),
 320        [Edit {
 321            old: MultiBufferOffset(6)..MultiBufferOffset(8),
 322            new: MultiBufferOffset(6)..MultiBufferOffset(7)
 323        }]
 324    );
 325
 326    let snapshot = multibuffer.read(cx).snapshot(cx);
 327    assert_eq!(
 328        snapshot.clip_point(Point::new(0, 5), Bias::Left),
 329        Point::new(0, 4)
 330    );
 331    assert_eq!(
 332        snapshot.clip_point(Point::new(0, 5), Bias::Right),
 333        Point::new(0, 4)
 334    );
 335    assert_eq!(
 336        snapshot.clip_point(Point::new(5, 1), Bias::Right),
 337        Point::new(5, 1)
 338    );
 339    assert_eq!(
 340        snapshot.clip_point(Point::new(5, 2), Bias::Right),
 341        Point::new(5, 2)
 342    );
 343    assert_eq!(
 344        snapshot.clip_point(Point::new(5, 3), Bias::Right),
 345        Point::new(5, 2)
 346    );
 347
 348    let snapshot = multibuffer.update(cx, |multibuffer, cx| {
 349        multibuffer.remove_excerpts_for_path(PathKey::sorted(1), cx);
 350        multibuffer.snapshot(cx)
 351    });
 352
 353    assert_eq!(
 354        snapshot.text(),
 355        concat!(
 356            "bbbb\n", // Preserve newlines
 357            "c\n",    //
 358            "cc\n",   //
 359            "fff\n",  //
 360            "gggg",   //
 361        )
 362    );
 363
 364    fn boundaries_in_range(
 365        range: Range<Point>,
 366        snapshot: &MultiBufferSnapshot,
 367    ) -> Vec<(MultiBufferRow, String, bool)> {
 368        snapshot
 369            .excerpt_boundaries_in_range(range)
 370            .map(|boundary| {
 371                let starts_new_buffer = boundary.starts_new_buffer();
 372                (
 373                    boundary.row,
 374                    boundary
 375                        .next
 376                        .buffer
 377                        .text_for_range(boundary.next.range.context)
 378                        .collect::<String>(),
 379                    starts_new_buffer,
 380                )
 381            })
 382            .collect::<Vec<_>>()
 383    }
 384}
 385
 386#[gpui::test]
 387async fn test_diff_boundary_anchors(cx: &mut TestAppContext) {
 388    let base_text = "one\ntwo\nthree\n";
 389    let text = "one\nthree\n";
 390    let buffer = cx.new(|cx| Buffer::local(text, cx));
 391    let diff = cx
 392        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
 393    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 394    multibuffer.update(cx, |multibuffer, cx| multibuffer.add_diff(diff, cx));
 395
 396    let (before, after) = multibuffer.update(cx, |multibuffer, cx| {
 397        let before = multibuffer.snapshot(cx).anchor_before(Point::new(1, 0));
 398        let after = multibuffer.snapshot(cx).anchor_after(Point::new(1, 0));
 399        multibuffer.set_all_diff_hunks_expanded(cx);
 400        (before, after)
 401    });
 402    cx.run_until_parked();
 403
 404    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 405    let actual_text = snapshot.text();
 406    let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
 407    let actual_diff = format_diff(&actual_text, &actual_row_infos, &Default::default(), None);
 408    pretty_assertions::assert_eq!(
 409        actual_diff,
 410        indoc! {
 411            "  one
 412             - two
 413               three
 414             "
 415        },
 416    );
 417
 418    multibuffer.update(cx, |multibuffer, cx| {
 419        let snapshot = multibuffer.snapshot(cx);
 420        assert_eq!(before.to_point(&snapshot), Point::new(1, 0));
 421        assert_eq!(after.to_point(&snapshot), Point::new(2, 0));
 422        assert_eq!(
 423            vec![Point::new(1, 0), Point::new(2, 0),],
 424            snapshot.summaries_for_anchors::<Point, _>(&[before, after]),
 425        )
 426    })
 427}
 428
 429#[gpui::test]
 430async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
 431    let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n";
 432    let text = "one\nfour\nseven\n";
 433    let buffer = cx.new(|cx| Buffer::local(text, cx));
 434    let diff = cx
 435        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
 436    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 437    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
 438        (multibuffer.snapshot(cx), multibuffer.subscribe())
 439    });
 440
 441    multibuffer.update(cx, |multibuffer, cx| {
 442        multibuffer.add_diff(diff, cx);
 443        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
 444    });
 445
 446    assert_new_snapshot(
 447        &multibuffer,
 448        &mut snapshot,
 449        &mut subscription,
 450        cx,
 451        indoc! {
 452            "  one
 453             - two
 454             - three
 455               four
 456             - five
 457             - six
 458               seven
 459             - eight
 460            "
 461        },
 462    );
 463
 464    assert_eq!(
 465        snapshot
 466            .diff_hunks_in_range(Point::new(1, 0)..Point::MAX)
 467            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
 468            .collect::<Vec<_>>(),
 469        vec![1..3, 4..6, 7..8]
 470    );
 471
 472    assert_eq!(snapshot.diff_hunk_before(Point::new(1, 1)), None,);
 473    assert_eq!(
 474        snapshot.diff_hunk_before(Point::new(7, 0)),
 475        Some(MultiBufferRow(4))
 476    );
 477    assert_eq!(
 478        snapshot.diff_hunk_before(Point::new(4, 0)),
 479        Some(MultiBufferRow(1))
 480    );
 481
 482    multibuffer.update(cx, |multibuffer, cx| {
 483        multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
 484    });
 485
 486    assert_new_snapshot(
 487        &multibuffer,
 488        &mut snapshot,
 489        &mut subscription,
 490        cx,
 491        indoc! {
 492            "
 493            one
 494            four
 495            seven
 496            "
 497        },
 498    );
 499
 500    assert_eq!(
 501        snapshot.diff_hunk_before(Point::new(2, 0)),
 502        Some(MultiBufferRow(1)),
 503    );
 504    assert_eq!(
 505        snapshot.diff_hunk_before(Point::new(4, 0)),
 506        Some(MultiBufferRow(2))
 507    );
 508}
 509
 510#[gpui::test]
 511async fn test_diff_hunks_in_range_query_starting_at_added_row(cx: &mut TestAppContext) {
 512    let base_text = "one\ntwo\nthree\n";
 513    let text = "one\nTWO\nthree\n";
 514    let buffer = cx.new(|cx| Buffer::local(text, cx));
 515    let diff = cx
 516        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
 517    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 518    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
 519        (multibuffer.snapshot(cx), multibuffer.subscribe())
 520    });
 521
 522    multibuffer.update(cx, |multibuffer, cx| {
 523        multibuffer.add_diff(diff, cx);
 524        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
 525    });
 526
 527    assert_new_snapshot(
 528        &multibuffer,
 529        &mut snapshot,
 530        &mut subscription,
 531        cx,
 532        indoc! {
 533            "  one
 534             - two
 535             + TWO
 536               three
 537            "
 538        },
 539    );
 540
 541    assert_eq!(
 542        snapshot
 543            .diff_hunks_in_range(Point::new(2, 0)..Point::MAX)
 544            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
 545            .collect::<Vec<_>>(),
 546        vec![1..3],
 547        "querying starting at the added row should still return the full hunk including deleted lines"
 548    );
 549}
 550
 551#[gpui::test]
 552async fn test_inverted_diff_hunks_in_range(cx: &mut TestAppContext) {
 553    let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n";
 554    let text = "ZERO\none\nTHREE\nfour\nseven\nEIGHT\nNINE\n";
 555    let buffer = cx.new(|cx| Buffer::local(text, cx));
 556    let diff = cx
 557        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
 558    let base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
 559    let multibuffer = cx.new(|cx| MultiBuffer::singleton(base_text_buffer.clone(), cx));
 560    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
 561        (multibuffer.snapshot(cx), multibuffer.subscribe())
 562    });
 563
 564    multibuffer.update(cx, |multibuffer, cx| {
 565        multibuffer.add_inverted_diff(diff, buffer.clone(), cx);
 566    });
 567
 568    assert_new_snapshot(
 569        &multibuffer,
 570        &mut snapshot,
 571        &mut subscription,
 572        cx,
 573        indoc! {
 574            "  one
 575             - two
 576             - three
 577               four
 578             - five
 579             - six
 580               seven
 581             - eight
 582            "
 583        },
 584    );
 585
 586    assert_eq!(
 587        snapshot
 588            .diff_hunks_in_range(Point::new(0, 0)..Point::MAX)
 589            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
 590            .collect::<Vec<_>>(),
 591        vec![0..0, 1..3, 4..6, 7..8]
 592    );
 593
 594    assert_eq!(
 595        snapshot.diff_hunk_before(Point::new(1, 1)),
 596        Some(MultiBufferRow(0))
 597    );
 598    assert_eq!(
 599        snapshot.diff_hunk_before(Point::new(7, 0)),
 600        Some(MultiBufferRow(4))
 601    );
 602    assert_eq!(
 603        snapshot.diff_hunk_before(Point::new(4, 0)),
 604        Some(MultiBufferRow(1))
 605    );
 606}
 607
 608#[gpui::test]
 609async fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) {
 610    let base_text = "one\ntwo\nfour\nfive\nsix\nseven\n";
 611    let text = "one\ntwo\nTHREE\nfour\nfive\nseven\n";
 612    let buffer = cx.new(|cx| Buffer::local(text, cx));
 613    let diff = cx
 614        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
 615    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
 616
 617    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
 618        multibuffer.add_diff(diff.clone(), cx);
 619        (multibuffer.snapshot(cx), multibuffer.subscribe())
 620    });
 621
 622    cx.executor().run_until_parked();
 623    multibuffer.update(cx, |multibuffer, cx| {
 624        multibuffer.set_all_diff_hunks_expanded(cx);
 625    });
 626
 627    assert_new_snapshot(
 628        &multibuffer,
 629        &mut snapshot,
 630        &mut subscription,
 631        cx,
 632        indoc! {
 633            "
 634              one
 635              two
 636            + THREE
 637              four
 638              five
 639            - six
 640              seven
 641            "
 642        },
 643    );
 644
 645    // Insert a newline within an insertion hunk
 646    multibuffer.update(cx, |multibuffer, cx| {
 647        multibuffer.edit([(Point::new(2, 0)..Point::new(2, 0), "__\n__")], None, cx);
 648    });
 649    assert_new_snapshot(
 650        &multibuffer,
 651        &mut snapshot,
 652        &mut subscription,
 653        cx,
 654        indoc! {
 655            "
 656              one
 657              two
 658            + __
 659            + __THREE
 660              four
 661              five
 662            - six
 663              seven
 664            "
 665        },
 666    );
 667
 668    // Delete the newline before a deleted hunk.
 669    multibuffer.update(cx, |multibuffer, cx| {
 670        multibuffer.edit([(Point::new(5, 4)..Point::new(6, 0), "")], None, cx);
 671    });
 672    assert_new_snapshot(
 673        &multibuffer,
 674        &mut snapshot,
 675        &mut subscription,
 676        cx,
 677        indoc! {
 678            "
 679              one
 680              two
 681            + __
 682            + __THREE
 683              four
 684              fiveseven
 685            "
 686        },
 687    );
 688
 689    multibuffer.update(cx, |multibuffer, cx| multibuffer.undo(cx));
 690    assert_new_snapshot(
 691        &multibuffer,
 692        &mut snapshot,
 693        &mut subscription,
 694        cx,
 695        indoc! {
 696            "
 697              one
 698              two
 699            + __
 700            + __THREE
 701              four
 702              five
 703            - six
 704              seven
 705            "
 706        },
 707    );
 708
 709    // Cannot (yet) insert at the beginning of a deleted hunk.
 710    // (because it would put the newline in the wrong place)
 711    multibuffer.update(cx, |multibuffer, cx| {
 712        multibuffer.edit([(Point::new(6, 0)..Point::new(6, 0), "\n")], None, cx);
 713    });
 714    assert_new_snapshot(
 715        &multibuffer,
 716        &mut snapshot,
 717        &mut subscription,
 718        cx,
 719        indoc! {
 720            "
 721              one
 722              two
 723            + __
 724            + __THREE
 725              four
 726              five
 727            - six
 728              seven
 729            "
 730        },
 731    );
 732
 733    // Replace a range that ends in a deleted hunk.
 734    multibuffer.update(cx, |multibuffer, cx| {
 735        multibuffer.edit([(Point::new(5, 2)..Point::new(6, 2), "fty-")], None, cx);
 736    });
 737    assert_new_snapshot(
 738        &multibuffer,
 739        &mut snapshot,
 740        &mut subscription,
 741        cx,
 742        indoc! {
 743            "
 744              one
 745              two
 746            + __
 747            + __THREE
 748              four
 749              fifty-seven
 750            "
 751        },
 752    );
 753}
 754
 755#[gpui::test]
 756fn test_excerpt_events(cx: &mut App) {
 757    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'a'), cx));
 758    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'm'), cx));
 759
 760    let leader_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 761    let follower_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 762    let follower_edit_event_count = Arc::new(RwLock::new(0));
 763
 764    follower_multibuffer.update(cx, |_, cx| {
 765        let follower_edit_event_count = follower_edit_event_count.clone();
 766        cx.subscribe(
 767            &leader_multibuffer,
 768            move |follower, _, event, cx| match event.clone() {
 769                Event::ExcerptsAdded {
 770                    buffer,
 771                    predecessor,
 772                    excerpts,
 773                } => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx),
 774                Event::ExcerptsRemoved { ids, .. } => follower.remove_excerpts(ids, cx),
 775                Event::Edited { .. } => {
 776                    *follower_edit_event_count.write() += 1;
 777                }
 778                _ => {}
 779            },
 780        )
 781        .detach();
 782    });
 783
 784    let buffer_1_snapshot = buffer_1.read(cx).snapshot();
 785    let buffer_2_snapshot = buffer_2.read(cx).snapshot();
 786    leader_multibuffer.update(cx, |leader, cx| {
 787        leader.set_excerpt_ranges_for_path(
 788            PathKey::sorted(0),
 789            buffer_1.clone(),
 790            &buffer_1_snapshot,
 791            vec![
 792                ExcerptRange::new((0..8).to_point(&buffer_1_snapshot)),
 793                ExcerptRange::new((22..26).to_point(&buffer_1_snapshot)),
 794            ],
 795            cx,
 796        );
 797        leader.set_excerpt_ranges_for_path(
 798            PathKey::sorted(1),
 799            buffer_2.clone(),
 800            &buffer_2_snapshot,
 801            vec![
 802                ExcerptRange::new((0..5).to_point(&buffer_2_snapshot)),
 803                ExcerptRange::new((20..25).to_point(&buffer_2_snapshot)),
 804            ],
 805            cx,
 806        );
 807    });
 808    assert_eq!(
 809        leader_multibuffer.read(cx).snapshot(cx).text(),
 810        follower_multibuffer.read(cx).snapshot(cx).text(),
 811    );
 812    assert_eq!(*follower_edit_event_count.read(), 2);
 813
 814    leader_multibuffer.update(cx, |leader, cx| {
 815        leader.set_excerpt_ranges_for_path(
 816            PathKey::sorted(0),
 817            buffer_1.clone(),
 818            &buffer_1_snapshot,
 819            vec![ExcerptRange::new((0..8).to_point(&buffer_1_snapshot))],
 820            cx,
 821        );
 822        leader.set_excerpt_ranges_for_path(
 823            PathKey::sorted(1),
 824            buffer_2,
 825            &buffer_2_snapshot,
 826            vec![ExcerptRange::new((0..5).to_point(&buffer_2_snapshot))],
 827            cx,
 828        );
 829    });
 830    assert_eq!(
 831        leader_multibuffer.read(cx).snapshot(cx).text(),
 832        follower_multibuffer.read(cx).snapshot(cx).text(),
 833    );
 834    assert_eq!(*follower_edit_event_count.read(), 4);
 835
 836    leader_multibuffer.update(cx, |leader, cx| {
 837        leader.clear(cx);
 838    });
 839    assert_eq!(
 840        leader_multibuffer.read(cx).snapshot(cx).text(),
 841        follower_multibuffer.read(cx).snapshot(cx).text(),
 842    );
 843    assert_eq!(*follower_edit_event_count.read(), 5);
 844}
 845
 846#[gpui::test]
 847fn test_expand_excerpts(cx: &mut App) {
 848    let buffer = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
 849    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 850
 851    multibuffer.update(cx, |multibuffer, cx| {
 852        multibuffer.set_excerpts_for_path(
 853            PathKey::for_buffer(&buffer, cx),
 854            buffer,
 855            vec![
 856                // Note that in this test, this first excerpt
 857                // does not contain a new line
 858                Point::new(3, 2)..Point::new(3, 3),
 859                Point::new(7, 1)..Point::new(7, 3),
 860                Point::new(15, 0)..Point::new(15, 0),
 861            ],
 862            1,
 863            cx,
 864        )
 865    });
 866
 867    let snapshot = multibuffer.read(cx).snapshot(cx);
 868
 869    assert_eq!(
 870        snapshot.text(),
 871        concat!(
 872            "ccc\n", //
 873            "ddd\n", //
 874            "eee",   //
 875            "\n",    // End of excerpt
 876            "ggg\n", //
 877            "hhh\n", //
 878            "iii",   //
 879            "\n",    // End of excerpt
 880            "ooo\n", //
 881            "ppp\n", //
 882            "qqq",   // End of excerpt
 883        )
 884    );
 885    drop(snapshot);
 886
 887    multibuffer.update(cx, |multibuffer, cx| {
 888        let line_zero = multibuffer.snapshot(cx).anchor_before(Point::new(0, 0));
 889        multibuffer.expand_excerpts(
 890            multibuffer.excerpt_ids(),
 891            1,
 892            ExpandExcerptDirection::UpAndDown,
 893            cx,
 894        );
 895        let snapshot = multibuffer.snapshot(cx);
 896        let line_two = snapshot.anchor_before(Point::new(2, 0));
 897        assert_eq!(line_two.cmp(&line_zero, &snapshot), cmp::Ordering::Greater);
 898    });
 899
 900    let snapshot = multibuffer.read(cx).snapshot(cx);
 901
 902    assert_eq!(
 903        snapshot.text(),
 904        concat!(
 905            "bbb\n", //
 906            "ccc\n", //
 907            "ddd\n", //
 908            "eee\n", //
 909            "fff\n", //
 910            "ggg\n", //
 911            "hhh\n", //
 912            "iii\n", //
 913            "jjj\n", // End of excerpt
 914            "nnn\n", //
 915            "ooo\n", //
 916            "ppp\n", //
 917            "qqq\n", //
 918            "rrr",   // End of excerpt
 919        )
 920    );
 921}
 922
 923#[gpui::test(iterations = 100)]
 924async fn test_set_anchored_excerpts_for_path(cx: &mut TestAppContext) {
 925    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
 926    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(15, 4, 'a'), cx));
 927    let snapshot_1 = buffer_1.update(cx, |buffer, _| buffer.snapshot());
 928    let snapshot_2 = buffer_2.update(cx, |buffer, _| buffer.snapshot());
 929    let ranges_1 = vec![
 930        snapshot_1.anchor_before(Point::new(3, 2))..snapshot_1.anchor_before(Point::new(4, 2)),
 931        snapshot_1.anchor_before(Point::new(7, 1))..snapshot_1.anchor_before(Point::new(7, 3)),
 932        snapshot_1.anchor_before(Point::new(15, 0))..snapshot_1.anchor_before(Point::new(15, 0)),
 933    ];
 934    let ranges_2 = vec![
 935        snapshot_2.anchor_before(Point::new(2, 1))..snapshot_2.anchor_before(Point::new(3, 1)),
 936        snapshot_2.anchor_before(Point::new(10, 0))..snapshot_2.anchor_before(Point::new(10, 2)),
 937    ];
 938
 939    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 940    let anchor_ranges_1 = multibuffer
 941        .update(cx, |multibuffer, cx| {
 942            multibuffer.set_anchored_excerpts_for_path(
 943                PathKey::for_buffer(&buffer_1, cx),
 944                buffer_1.clone(),
 945                ranges_1,
 946                2,
 947                cx,
 948            )
 949        })
 950        .await;
 951    let snapshot_1 = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 952    assert_eq!(
 953        anchor_ranges_1
 954            .iter()
 955            .map(|range| range.to_point(&snapshot_1))
 956            .collect::<Vec<_>>(),
 957        vec![
 958            Point::new(2, 2)..Point::new(3, 2),
 959            Point::new(6, 1)..Point::new(6, 3),
 960            Point::new(11, 0)..Point::new(11, 0),
 961        ]
 962    );
 963    let anchor_ranges_2 = multibuffer
 964        .update(cx, |multibuffer, cx| {
 965            multibuffer.set_anchored_excerpts_for_path(
 966                PathKey::for_buffer(&buffer_2, cx),
 967                buffer_2.clone(),
 968                ranges_2,
 969                2,
 970                cx,
 971            )
 972        })
 973        .await;
 974    let snapshot_2 = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 975    assert_eq!(
 976        anchor_ranges_2
 977            .iter()
 978            .map(|range| range.to_point(&snapshot_2))
 979            .collect::<Vec<_>>(),
 980        vec![
 981            Point::new(16, 1)..Point::new(17, 1),
 982            Point::new(22, 0)..Point::new(22, 2)
 983        ]
 984    );
 985
 986    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 987    assert_eq!(
 988        snapshot.text(),
 989        concat!(
 990            "bbb\n", // buffer_1
 991            "ccc\n", //
 992            "ddd\n", // <-- excerpt 1
 993            "eee\n", // <-- excerpt 1
 994            "fff\n", //
 995            "ggg\n", //
 996            "hhh\n", // <-- excerpt 2
 997            "iii\n", //
 998            "jjj\n", //
 999            //
1000            "nnn\n", //
1001            "ooo\n", //
1002            "ppp\n", // <-- excerpt 3
1003            "qqq\n", //
1004            "rrr\n", //
1005            //
1006            "aaaa\n", // buffer 2
1007            "bbbb\n", //
1008            "cccc\n", // <-- excerpt 4
1009            "dddd\n", // <-- excerpt 4
1010            "eeee\n", //
1011            "ffff\n", //
1012            //
1013            "iiii\n", //
1014            "jjjj\n", //
1015            "kkkk\n", // <-- excerpt 5
1016            "llll\n", //
1017            "mmmm",   //
1018        )
1019    );
1020}
1021
1022#[gpui::test]
1023fn test_empty_multibuffer(cx: &mut App) {
1024    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1025
1026    let snapshot = multibuffer.read(cx).snapshot(cx);
1027    assert_eq!(snapshot.text(), "");
1028    assert_eq!(
1029        snapshot
1030            .row_infos(MultiBufferRow(0))
1031            .map(|info| info.buffer_row)
1032            .collect::<Vec<_>>(),
1033        &[Some(0)]
1034    );
1035    assert!(
1036        snapshot
1037            .row_infos(MultiBufferRow(1))
1038            .map(|info| info.buffer_row)
1039            .collect::<Vec<_>>()
1040            .is_empty(),
1041    );
1042}
1043
1044#[gpui::test]
1045async fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
1046    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1047    let buffer = cx.new(|cx| Buffer::local("", cx));
1048    let base_text = "a\nb\nc";
1049
1050    let diff = cx
1051        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
1052    multibuffer.update(cx, |multibuffer, cx| {
1053        multibuffer.set_excerpt_ranges_for_path(
1054            PathKey::sorted(0),
1055            buffer.clone(),
1056            &buffer.read(cx).snapshot(),
1057            vec![ExcerptRange::new(Point::zero()..Point::zero())],
1058            cx,
1059        );
1060        multibuffer.set_all_diff_hunks_expanded(cx);
1061        multibuffer.add_diff(diff.clone(), cx);
1062    });
1063    cx.run_until_parked();
1064
1065    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1066    assert_eq!(snapshot.text(), "a\nb\nc\n");
1067
1068    let hunk = snapshot
1069        .diff_hunks_in_range(Point::new(1, 1)..Point::new(1, 1))
1070        .next()
1071        .unwrap();
1072
1073    assert_eq!(hunk.diff_base_byte_range.start, BufferOffset(0));
1074
1075    let buf2 = cx.new(|cx| Buffer::local("X", cx));
1076    multibuffer.update(cx, |multibuffer, cx| {
1077        multibuffer.set_excerpts_for_path(
1078            PathKey::sorted(1),
1079            buf2,
1080            [Point::new(0, 0)..Point::new(0, 1)],
1081            0,
1082            cx,
1083        );
1084    });
1085
1086    buffer.update(cx, |buffer, cx| {
1087        buffer.edit([(0..0, "a\nb\nc")], None, cx);
1088        diff.update(cx, |diff, cx| {
1089            diff.recalculate_diff_sync(&buffer.text_snapshot(), cx);
1090        });
1091        assert_eq!(buffer.text(), "a\nb\nc")
1092    });
1093    cx.run_until_parked();
1094
1095    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1096    assert_eq!(snapshot.text(), "a\nb\nc\nX");
1097
1098    buffer.update(cx, |buffer, cx| {
1099        buffer.undo(cx);
1100        diff.update(cx, |diff, cx| {
1101            diff.recalculate_diff_sync(&buffer.text_snapshot(), cx);
1102        });
1103        assert_eq!(buffer.text(), "")
1104    });
1105    cx.run_until_parked();
1106
1107    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1108    assert_eq!(snapshot.text(), "a\nb\nc\n\nX");
1109}
1110
1111#[gpui::test]
1112fn test_singleton_multibuffer_anchors(cx: &mut App) {
1113    let buffer = cx.new(|cx| Buffer::local("abcd", cx));
1114    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
1115    let old_snapshot = multibuffer.read(cx).snapshot(cx);
1116    buffer.update(cx, |buffer, cx| {
1117        buffer.edit([(0..0, "X")], None, cx);
1118        buffer.edit([(5..5, "Y")], None, cx);
1119    });
1120    let new_snapshot = multibuffer.read(cx).snapshot(cx);
1121
1122    assert_eq!(old_snapshot.text(), "abcd");
1123    assert_eq!(new_snapshot.text(), "XabcdY");
1124
1125    assert_eq!(
1126        old_snapshot
1127            .anchor_before(MultiBufferOffset(0))
1128            .to_offset(&new_snapshot),
1129        MultiBufferOffset(0)
1130    );
1131    assert_eq!(
1132        old_snapshot
1133            .anchor_after(MultiBufferOffset(0))
1134            .to_offset(&new_snapshot),
1135        MultiBufferOffset(1)
1136    );
1137    assert_eq!(
1138        old_snapshot
1139            .anchor_before(MultiBufferOffset(4))
1140            .to_offset(&new_snapshot),
1141        MultiBufferOffset(5)
1142    );
1143    assert_eq!(
1144        old_snapshot
1145            .anchor_after(MultiBufferOffset(4))
1146            .to_offset(&new_snapshot),
1147        MultiBufferOffset(6)
1148    );
1149}
1150
1151#[gpui::test]
1152fn test_multibuffer_anchors(cx: &mut App) {
1153    let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1154    let buffer_2 = cx.new(|cx| Buffer::local("efghi", cx));
1155    let multibuffer = cx.new(|cx| {
1156        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1157        multibuffer.set_excerpts_for_path(
1158            PathKey::sorted(0),
1159            buffer_1.clone(),
1160            [Point::new(0, 0)..Point::new(0, 4)],
1161            0,
1162            cx,
1163        );
1164        multibuffer.set_excerpts_for_path(
1165            PathKey::sorted(1),
1166            buffer_2.clone(),
1167            [Point::new(0, 0)..Point::new(0, 5)],
1168            0,
1169            cx,
1170        );
1171        multibuffer
1172    });
1173    let old_snapshot = multibuffer.read(cx).snapshot(cx);
1174
1175    assert_eq!(
1176        old_snapshot
1177            .anchor_before(MultiBufferOffset(0))
1178            .to_offset(&old_snapshot),
1179        MultiBufferOffset(0)
1180    );
1181    assert_eq!(
1182        old_snapshot
1183            .anchor_after(MultiBufferOffset(0))
1184            .to_offset(&old_snapshot),
1185        MultiBufferOffset(0)
1186    );
1187    assert_eq!(Anchor::min().to_offset(&old_snapshot), MultiBufferOffset(0));
1188    assert_eq!(Anchor::min().to_offset(&old_snapshot), MultiBufferOffset(0));
1189    assert_eq!(
1190        Anchor::max().to_offset(&old_snapshot),
1191        MultiBufferOffset(10)
1192    );
1193    assert_eq!(
1194        Anchor::max().to_offset(&old_snapshot),
1195        MultiBufferOffset(10)
1196    );
1197
1198    buffer_1.update(cx, |buffer, cx| {
1199        buffer.edit([(0..0, "W")], None, cx);
1200        buffer.edit([(5..5, "X")], None, cx);
1201    });
1202    buffer_2.update(cx, |buffer, cx| {
1203        buffer.edit([(0..0, "Y")], None, cx);
1204        buffer.edit([(6..6, "Z")], None, cx);
1205    });
1206    let new_snapshot = multibuffer.read(cx).snapshot(cx);
1207
1208    assert_eq!(old_snapshot.text(), "abcd\nefghi");
1209    assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
1210
1211    assert_eq!(
1212        old_snapshot
1213            .anchor_before(MultiBufferOffset(0))
1214            .to_offset(&new_snapshot),
1215        MultiBufferOffset(0)
1216    );
1217    assert_eq!(
1218        old_snapshot
1219            .anchor_after(MultiBufferOffset(0))
1220            .to_offset(&new_snapshot),
1221        MultiBufferOffset(1)
1222    );
1223    assert_eq!(
1224        old_snapshot
1225            .anchor_before(MultiBufferOffset(1))
1226            .to_offset(&new_snapshot),
1227        MultiBufferOffset(2)
1228    );
1229    assert_eq!(
1230        old_snapshot
1231            .anchor_after(MultiBufferOffset(1))
1232            .to_offset(&new_snapshot),
1233        MultiBufferOffset(2)
1234    );
1235    assert_eq!(
1236        old_snapshot
1237            .anchor_before(MultiBufferOffset(2))
1238            .to_offset(&new_snapshot),
1239        MultiBufferOffset(3)
1240    );
1241    assert_eq!(
1242        old_snapshot
1243            .anchor_after(MultiBufferOffset(2))
1244            .to_offset(&new_snapshot),
1245        MultiBufferOffset(3)
1246    );
1247    assert_eq!(
1248        old_snapshot
1249            .anchor_before(MultiBufferOffset(5))
1250            .to_offset(&new_snapshot),
1251        MultiBufferOffset(7)
1252    );
1253    assert_eq!(
1254        old_snapshot
1255            .anchor_after(MultiBufferOffset(5))
1256            .to_offset(&new_snapshot),
1257        MultiBufferOffset(8)
1258    );
1259    assert_eq!(
1260        old_snapshot
1261            .anchor_before(MultiBufferOffset(10))
1262            .to_offset(&new_snapshot),
1263        MultiBufferOffset(13)
1264    );
1265    assert_eq!(
1266        old_snapshot
1267            .anchor_after(MultiBufferOffset(10))
1268            .to_offset(&new_snapshot),
1269        MultiBufferOffset(14)
1270    );
1271}
1272
1273#[gpui::test]
1274fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
1275    let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1276    let buffer_2 = cx.new(|cx| Buffer::local("ABCDEFGHIJKLMNOP", cx));
1277    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1278
1279    // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
1280    // Add an excerpt from buffer 1 that spans this new insertion.
1281    buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx));
1282    let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| {
1283        let buffer_1_snapshot = buffer_1.read(cx).snapshot();
1284        multibuffer.set_excerpt_ranges_for_path(
1285            PathKey::sorted(0),
1286            buffer_1,
1287            &buffer_1_snapshot,
1288            vec![ExcerptRange::new((0..7).to_point(&buffer_1_snapshot))],
1289            cx,
1290        );
1291        multibuffer.excerpt_ids().into_iter().next().unwrap()
1292    });
1293
1294    let snapshot_1 = multibuffer.read(cx).snapshot(cx);
1295    assert_eq!(snapshot_1.text(), "abcd123");
1296
1297    // Replace the buffer 1 excerpt with new excerpts from buffer 2.
1298    let (excerpt_id_2, _excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
1299        multibuffer.remove_excerpts_for_path(PathKey::sorted(0), cx);
1300        let snapshot_2 = buffer_2.read(cx).snapshot();
1301        multibuffer.set_excerpt_ranges_for_path(
1302            PathKey::sorted(1),
1303            buffer_2.clone(),
1304            &buffer_2.read(cx).snapshot(),
1305            vec![
1306                ExcerptRange::new((0..4).to_point(&snapshot_2)),
1307                ExcerptRange::new((6..10).to_point(&snapshot_2)),
1308                ExcerptRange::new((12..16).to_point(&snapshot_2)),
1309            ],
1310            cx,
1311        );
1312        let mut ids = multibuffer
1313            .excerpts_for_buffer(buffer_2.read(cx).remote_id(), cx)
1314            .into_iter()
1315            .map(|(id, _, _)| id);
1316        (ids.next().unwrap(), ids.next().unwrap())
1317    });
1318    let snapshot_2 = multibuffer.read(cx).snapshot(cx);
1319    assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP");
1320
1321    // The old excerpt id doesn't get reused.
1322    assert_ne!(excerpt_id_2, excerpt_id_1);
1323
1324    // Resolve some anchors from the previous snapshot in the new snapshot.
1325    // The current excerpts are from a different buffer, so we don't attempt to
1326    // resolve the old text anchor in the new buffer.
1327    assert_eq!(
1328        snapshot_2.summary_for_anchor::<MultiBufferOffset>(
1329            &snapshot_1.anchor_before(MultiBufferOffset(2))
1330        ),
1331        MultiBufferOffset(0)
1332    );
1333    assert_eq!(
1334        snapshot_2.summaries_for_anchors::<MultiBufferOffset, _>(&[
1335            snapshot_1.anchor_before(MultiBufferOffset(2)),
1336            snapshot_1.anchor_after(MultiBufferOffset(3))
1337        ]),
1338        vec![MultiBufferOffset(0), MultiBufferOffset(0)]
1339    );
1340
1341    // Refresh anchors from the old snapshot. The return value indicates that both
1342    // anchors lost their original excerpt.
1343    let refresh = snapshot_2.refresh_anchors(&[
1344        snapshot_1.anchor_before(MultiBufferOffset(2)),
1345        snapshot_1.anchor_after(MultiBufferOffset(3)),
1346    ]);
1347    assert_eq!(
1348        refresh,
1349        &[
1350            (0, snapshot_2.anchor_before(MultiBufferOffset(0)), false),
1351            (1, snapshot_2.anchor_after(MultiBufferOffset(0)), false),
1352        ]
1353    );
1354
1355    // Replace the middle excerpt with a smaller excerpt in buffer 2,
1356    // that intersects the old excerpt.
1357    multibuffer.update(cx, |multibuffer, cx| {
1358        let snapshot_2 = buffer_2.read(cx).snapshot();
1359        multibuffer.set_excerpt_ranges_for_path(
1360            PathKey::sorted(1),
1361            buffer_2.clone(),
1362            &buffer_2.read(cx).snapshot(),
1363            vec![
1364                ExcerptRange::new((0..4).to_point(&snapshot_2)),
1365                ExcerptRange::new((12..16).to_point(&snapshot_2)),
1366            ],
1367            cx,
1368        );
1369        multibuffer.set_excerpt_ranges_for_path(
1370            PathKey::sorted(1),
1371            buffer_2.clone(),
1372            &buffer_2.read(cx).snapshot(),
1373            vec![
1374                ExcerptRange::new((0..4).to_point(&snapshot_2)),
1375                ExcerptRange::new((5..8).to_point(&snapshot_2)),
1376                ExcerptRange::new((12..16).to_point(&snapshot_2)),
1377            ],
1378            cx,
1379        );
1380    });
1381
1382    let snapshot_3 = multibuffer.read(cx).snapshot(cx);
1383    assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP");
1384
1385    // Resolve some anchors from the previous snapshot in the new snapshot.
1386    // The third anchor can't be resolved, since its excerpt has been removed,
1387    // so it resolves to the same position as its predecessor.
1388    let anchors = [
1389        snapshot_2.anchor_before(MultiBufferOffset(0)),
1390        snapshot_2.anchor_after(MultiBufferOffset(2)),
1391        snapshot_2.anchor_after(MultiBufferOffset(6)),
1392        snapshot_2.anchor_after(MultiBufferOffset(14)),
1393    ];
1394    assert_eq!(
1395        snapshot_3.summaries_for_anchors::<MultiBufferOffset, _>(&anchors),
1396        &[
1397            MultiBufferOffset(0),
1398            MultiBufferOffset(2),
1399            MultiBufferOffset(9),
1400            MultiBufferOffset(13)
1401        ]
1402    );
1403
1404    let new_anchors = snapshot_3.refresh_anchors(&anchors);
1405    assert_eq!(
1406        new_anchors.iter().map(|a| (a.0, a.2)).collect::<Vec<_>>(),
1407        &[(0, true), (1, true), (2, true), (3, true)]
1408    );
1409    assert_eq!(
1410        snapshot_3.summaries_for_anchors::<MultiBufferOffset, _>(new_anchors.iter().map(|a| &a.1)),
1411        &[
1412            MultiBufferOffset(0),
1413            MultiBufferOffset(2),
1414            MultiBufferOffset(7),
1415            MultiBufferOffset(13)
1416        ]
1417    );
1418}
1419
1420#[gpui::test]
1421async fn test_basic_diff_hunks(cx: &mut TestAppContext) {
1422    let text = indoc!(
1423        "
1424        ZERO
1425        one
1426        TWO
1427        three
1428        six
1429        "
1430    );
1431    let base_text = indoc!(
1432        "
1433        one
1434        two
1435        three
1436        four
1437        five
1438        six
1439        "
1440    );
1441
1442    let buffer = cx.new(|cx| Buffer::local(text, cx));
1443    let diff = cx
1444        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
1445    cx.run_until_parked();
1446
1447    let multibuffer = cx.new(|cx| {
1448        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1449        multibuffer.add_diff(diff.clone(), cx);
1450        multibuffer
1451    });
1452
1453    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1454        (multibuffer.snapshot(cx), multibuffer.subscribe())
1455    });
1456    assert_eq!(
1457        snapshot.text(),
1458        indoc!(
1459            "
1460            ZERO
1461            one
1462            TWO
1463            three
1464            six
1465            "
1466        ),
1467    );
1468
1469    multibuffer.update(cx, |multibuffer, cx| {
1470        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1471    });
1472
1473    assert_new_snapshot(
1474        &multibuffer,
1475        &mut snapshot,
1476        &mut subscription,
1477        cx,
1478        indoc!(
1479            "
1480            + ZERO
1481              one
1482            - two
1483            + TWO
1484              three
1485            - four
1486            - five
1487              six
1488            "
1489        ),
1490    );
1491
1492    assert_eq!(
1493        snapshot
1494            .row_infos(MultiBufferRow(0))
1495            .map(|info| (info.buffer_row, info.diff_status))
1496            .collect::<Vec<_>>(),
1497        vec![
1498            (Some(0), Some(DiffHunkStatus::added_none())),
1499            (Some(1), None),
1500            (Some(1), Some(DiffHunkStatus::deleted_none())),
1501            (Some(2), Some(DiffHunkStatus::added_none())),
1502            (Some(3), None),
1503            (Some(3), Some(DiffHunkStatus::deleted_none())),
1504            (Some(4), Some(DiffHunkStatus::deleted_none())),
1505            (Some(4), None),
1506            (Some(5), None)
1507        ]
1508    );
1509
1510    assert_chunks_in_ranges(&snapshot);
1511    assert_consistent_line_numbers(&snapshot);
1512    assert_position_translation(&snapshot);
1513    assert_line_indents(&snapshot);
1514
1515    multibuffer.update(cx, |multibuffer, cx| {
1516        multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
1517    });
1518    assert_new_snapshot(
1519        &multibuffer,
1520        &mut snapshot,
1521        &mut subscription,
1522        cx,
1523        indoc!(
1524            "
1525            ZERO
1526            one
1527            TWO
1528            three
1529            six
1530            "
1531        ),
1532    );
1533
1534    assert_chunks_in_ranges(&snapshot);
1535    assert_consistent_line_numbers(&snapshot);
1536    assert_position_translation(&snapshot);
1537    assert_line_indents(&snapshot);
1538
1539    // Expand the first diff hunk
1540    multibuffer.update(cx, |multibuffer, cx| {
1541        let position = multibuffer.read(cx).anchor_before(Point::new(2, 2));
1542        multibuffer.expand_diff_hunks(vec![position..position], cx)
1543    });
1544    assert_new_snapshot(
1545        &multibuffer,
1546        &mut snapshot,
1547        &mut subscription,
1548        cx,
1549        indoc!(
1550            "
1551              ZERO
1552              one
1553            - two
1554            + TWO
1555              three
1556              six
1557            "
1558        ),
1559    );
1560
1561    // Expand the second diff hunk
1562    multibuffer.update(cx, |multibuffer, cx| {
1563        let start = multibuffer.read(cx).anchor_before(Point::new(4, 0));
1564        let end = multibuffer.read(cx).anchor_before(Point::new(5, 0));
1565        multibuffer.expand_diff_hunks(vec![start..end], cx)
1566    });
1567    assert_new_snapshot(
1568        &multibuffer,
1569        &mut snapshot,
1570        &mut subscription,
1571        cx,
1572        indoc!(
1573            "
1574              ZERO
1575              one
1576            - two
1577            + TWO
1578              three
1579            - four
1580            - five
1581              six
1582            "
1583        ),
1584    );
1585
1586    assert_chunks_in_ranges(&snapshot);
1587    assert_consistent_line_numbers(&snapshot);
1588    assert_position_translation(&snapshot);
1589    assert_line_indents(&snapshot);
1590
1591    // Edit the buffer before the first hunk
1592    buffer.update(cx, |buffer, cx| {
1593        buffer.edit_via_marked_text(
1594            indoc!(
1595                "
1596                ZERO
1597                one« hundred
1598                  thousand»
1599                TWO
1600                three
1601                six
1602                "
1603            ),
1604            None,
1605            cx,
1606        );
1607    });
1608    assert_new_snapshot(
1609        &multibuffer,
1610        &mut snapshot,
1611        &mut subscription,
1612        cx,
1613        indoc!(
1614            "
1615              ZERO
1616              one hundred
1617                thousand
1618            - two
1619            + TWO
1620              three
1621            - four
1622            - five
1623              six
1624            "
1625        ),
1626    );
1627
1628    assert_chunks_in_ranges(&snapshot);
1629    assert_consistent_line_numbers(&snapshot);
1630    assert_position_translation(&snapshot);
1631    assert_line_indents(&snapshot);
1632
1633    // Recalculate the diff, changing the first diff hunk.
1634    diff.update(cx, |diff, cx| {
1635        diff.recalculate_diff_sync(&buffer.read(cx).text_snapshot(), cx);
1636    });
1637    cx.run_until_parked();
1638    assert_new_snapshot(
1639        &multibuffer,
1640        &mut snapshot,
1641        &mut subscription,
1642        cx,
1643        indoc!(
1644            "
1645              ZERO
1646              one hundred
1647                thousand
1648              TWO
1649              three
1650            - four
1651            - five
1652              six
1653            "
1654        ),
1655    );
1656
1657    assert_eq!(
1658        snapshot
1659            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.len())
1660            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1661            .collect::<Vec<_>>(),
1662        &[0..4, 5..7]
1663    );
1664}
1665
1666#[gpui::test]
1667async fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
1668    let text = indoc!(
1669        "
1670        one
1671        TWO
1672        THREE
1673        four
1674        FIVE
1675        six
1676        "
1677    );
1678    let base_text = indoc!(
1679        "
1680        one
1681        four
1682        five
1683        six
1684        "
1685    );
1686
1687    let buffer = cx.new(|cx| Buffer::local(text, cx));
1688    let diff = cx
1689        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
1690    cx.run_until_parked();
1691
1692    let multibuffer = cx.new(|cx| {
1693        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1694        multibuffer.add_diff(diff.clone(), cx);
1695        multibuffer
1696    });
1697
1698    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1699        (multibuffer.snapshot(cx), multibuffer.subscribe())
1700    });
1701
1702    multibuffer.update(cx, |multibuffer, cx| {
1703        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1704    });
1705
1706    assert_new_snapshot(
1707        &multibuffer,
1708        &mut snapshot,
1709        &mut subscription,
1710        cx,
1711        indoc!(
1712            "
1713              one
1714            + TWO
1715            + THREE
1716              four
1717            - five
1718            + FIVE
1719              six
1720            "
1721        ),
1722    );
1723
1724    // Regression test: expanding diff hunks that are already expanded should not change anything.
1725    multibuffer.update(cx, |multibuffer, cx| {
1726        multibuffer.expand_diff_hunks(
1727            vec![
1728                snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_before(Point::new(2, 0)),
1729            ],
1730            cx,
1731        );
1732    });
1733
1734    assert_new_snapshot(
1735        &multibuffer,
1736        &mut snapshot,
1737        &mut subscription,
1738        cx,
1739        indoc!(
1740            "
1741              one
1742            + TWO
1743            + THREE
1744              four
1745            - five
1746            + FIVE
1747              six
1748            "
1749        ),
1750    );
1751
1752    // Now collapse all diff hunks
1753    multibuffer.update(cx, |multibuffer, cx| {
1754        multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1755    });
1756
1757    assert_new_snapshot(
1758        &multibuffer,
1759        &mut snapshot,
1760        &mut subscription,
1761        cx,
1762        indoc!(
1763            "
1764            one
1765            TWO
1766            THREE
1767            four
1768            FIVE
1769            six
1770            "
1771        ),
1772    );
1773
1774    // Expand the hunks again, but this time provide two ranges that are both within the same hunk
1775    // Target the first hunk which is between "one" and "four"
1776    multibuffer.update(cx, |multibuffer, cx| {
1777        multibuffer.expand_diff_hunks(
1778            vec![
1779                snapshot.anchor_before(Point::new(4, 0))..snapshot.anchor_before(Point::new(4, 0)),
1780                snapshot.anchor_before(Point::new(4, 2))..snapshot.anchor_before(Point::new(4, 2)),
1781            ],
1782            cx,
1783        );
1784    });
1785    assert_new_snapshot(
1786        &multibuffer,
1787        &mut snapshot,
1788        &mut subscription,
1789        cx,
1790        indoc!(
1791            "
1792              one
1793              TWO
1794              THREE
1795              four
1796            - five
1797            + FIVE
1798              six
1799            "
1800        ),
1801    );
1802}
1803
1804#[gpui::test]
1805fn test_set_excerpts_for_buffer_ordering(cx: &mut TestAppContext) {
1806    let buf1 = cx.new(|cx| {
1807        Buffer::local(
1808            indoc! {
1809            "zero
1810            one
1811            two
1812            two.five
1813            three
1814            four
1815            five
1816            six
1817            seven
1818            eight
1819            nine
1820            ten
1821            eleven
1822            ",
1823            },
1824            cx,
1825        )
1826    });
1827    let path1: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
1828
1829    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1830    multibuffer.update(cx, |multibuffer, cx| {
1831        multibuffer.set_excerpts_for_path(
1832            path1.clone(),
1833            buf1.clone(),
1834            vec![
1835                Point::row_range(1..2),
1836                Point::row_range(6..7),
1837                Point::row_range(11..12),
1838            ],
1839            1,
1840            cx,
1841        );
1842    });
1843
1844    assert_excerpts_match(
1845        &multibuffer,
1846        cx,
1847        indoc! {
1848            "-----
1849            zero
1850            one
1851            two
1852            two.five
1853            -----
1854            four
1855            five
1856            six
1857            seven
1858            -----
1859            nine
1860            ten
1861            eleven
1862            "
1863        },
1864    );
1865
1866    buf1.update(cx, |buffer, cx| buffer.edit([(0..5, "")], None, cx));
1867
1868    multibuffer.update(cx, |multibuffer, cx| {
1869        multibuffer.set_excerpts_for_path(
1870            path1.clone(),
1871            buf1.clone(),
1872            vec![
1873                Point::row_range(0..3),
1874                Point::row_range(5..7),
1875                Point::row_range(10..11),
1876            ],
1877            1,
1878            cx,
1879        );
1880    });
1881
1882    assert_excerpts_match(
1883        &multibuffer,
1884        cx,
1885        indoc! {
1886            "-----
1887             one
1888             two
1889             two.five
1890             three
1891             four
1892             five
1893             six
1894             seven
1895             eight
1896             nine
1897             ten
1898             eleven
1899            "
1900        },
1901    );
1902}
1903
1904#[gpui::test]
1905fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
1906    let buf1 = cx.new(|cx| {
1907        Buffer::local(
1908            indoc! {
1909            "zero
1910            one
1911            two
1912            three
1913            four
1914            five
1915            six
1916            seven
1917            ",
1918            },
1919            cx,
1920        )
1921    });
1922    let path1: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
1923    let buf2 = cx.new(|cx| {
1924        Buffer::local(
1925            indoc! {
1926            "000
1927            111
1928            222
1929            333
1930            444
1931            555
1932            666
1933            777
1934            888
1935            999
1936            "
1937            },
1938            cx,
1939        )
1940    });
1941    let path2 = PathKey::with_sort_prefix(1, rel_path("root").into_arc());
1942
1943    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1944    multibuffer.update(cx, |multibuffer, cx| {
1945        multibuffer.set_excerpts_for_path(
1946            path1.clone(),
1947            buf1.clone(),
1948            vec![Point::row_range(0..1)],
1949            2,
1950            cx,
1951        );
1952    });
1953
1954    assert_excerpts_match(
1955        &multibuffer,
1956        cx,
1957        indoc! {
1958        "-----
1959        zero
1960        one
1961        two
1962        three
1963        "
1964        },
1965    );
1966
1967    multibuffer.update(cx, |multibuffer, cx| {
1968        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1969    });
1970
1971    assert_excerpts_match(&multibuffer, cx, "");
1972
1973    multibuffer.update(cx, |multibuffer, cx| {
1974        multibuffer.set_excerpts_for_path(
1975            path1.clone(),
1976            buf1.clone(),
1977            vec![Point::row_range(0..1), Point::row_range(7..8)],
1978            2,
1979            cx,
1980        );
1981    });
1982
1983    assert_excerpts_match(
1984        &multibuffer,
1985        cx,
1986        indoc! {"-----
1987                zero
1988                one
1989                two
1990                three
1991                -----
1992                five
1993                six
1994                seven
1995                "},
1996    );
1997
1998    multibuffer.update(cx, |multibuffer, cx| {
1999        multibuffer.set_excerpts_for_path(
2000            path1.clone(),
2001            buf1.clone(),
2002            vec![Point::row_range(0..1), Point::row_range(5..6)],
2003            2,
2004            cx,
2005        );
2006    });
2007
2008    assert_excerpts_match(
2009        &multibuffer,
2010        cx,
2011        indoc! {"-----
2012                    zero
2013                    one
2014                    two
2015                    three
2016                    four
2017                    five
2018                    six
2019                    seven
2020                    "},
2021    );
2022
2023    multibuffer.update(cx, |multibuffer, cx| {
2024        multibuffer.set_excerpts_for_path(
2025            path2.clone(),
2026            buf2.clone(),
2027            vec![Point::row_range(2..3)],
2028            2,
2029            cx,
2030        );
2031    });
2032
2033    assert_excerpts_match(
2034        &multibuffer,
2035        cx,
2036        indoc! {"-----
2037                zero
2038                one
2039                two
2040                three
2041                four
2042                five
2043                six
2044                seven
2045                -----
2046                000
2047                111
2048                222
2049                333
2050                444
2051                555
2052                "},
2053    );
2054
2055    multibuffer.update(cx, |multibuffer, cx| {
2056        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
2057    });
2058
2059    multibuffer.update(cx, |multibuffer, cx| {
2060        multibuffer.set_excerpts_for_path(
2061            path1.clone(),
2062            buf1.clone(),
2063            vec![Point::row_range(3..4)],
2064            2,
2065            cx,
2066        );
2067    });
2068
2069    assert_excerpts_match(
2070        &multibuffer,
2071        cx,
2072        indoc! {"-----
2073                one
2074                two
2075                three
2076                four
2077                five
2078                six
2079                -----
2080                000
2081                111
2082                222
2083                333
2084                444
2085                555
2086                "},
2087    );
2088
2089    multibuffer.update(cx, |multibuffer, cx| {
2090        multibuffer.set_excerpts_for_path(
2091            path1.clone(),
2092            buf1.clone(),
2093            vec![Point::row_range(3..4)],
2094            2,
2095            cx,
2096        );
2097    });
2098}
2099
2100#[gpui::test]
2101fn test_set_excerpts_for_buffer_rename(cx: &mut TestAppContext) {
2102    let buf1 = cx.new(|cx| {
2103        Buffer::local(
2104            indoc! {
2105            "zero
2106            one
2107            two
2108            three
2109            four
2110            five
2111            six
2112            seven
2113            ",
2114            },
2115            cx,
2116        )
2117    });
2118    let path: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
2119    let buf2 = cx.new(|cx| {
2120        Buffer::local(
2121            indoc! {
2122            "000
2123            111
2124            222
2125            333
2126            "
2127            },
2128            cx,
2129        )
2130    });
2131
2132    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2133    multibuffer.update(cx, |multibuffer, cx| {
2134        multibuffer.set_excerpts_for_path(
2135            path.clone(),
2136            buf1.clone(),
2137            vec![Point::row_range(1..1), Point::row_range(4..5)],
2138            1,
2139            cx,
2140        );
2141    });
2142
2143    assert_excerpts_match(
2144        &multibuffer,
2145        cx,
2146        indoc! {
2147        "-----
2148        zero
2149        one
2150        two
2151        three
2152        four
2153        five
2154        six
2155        "
2156        },
2157    );
2158
2159    multibuffer.update(cx, |multibuffer, cx| {
2160        multibuffer.set_excerpts_for_path(
2161            path.clone(),
2162            buf2.clone(),
2163            vec![Point::row_range(0..1)],
2164            2,
2165            cx,
2166        );
2167    });
2168
2169    assert_excerpts_match(
2170        &multibuffer,
2171        cx,
2172        indoc! {"-----
2173                000
2174                111
2175                222
2176                333
2177                "},
2178    );
2179}
2180
2181#[gpui::test]
2182async fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
2183    let base_text_1 = indoc!(
2184        "
2185        one
2186        two
2187            three
2188        four
2189        five
2190        six
2191        "
2192    );
2193    let text_1 = indoc!(
2194        "
2195        ZERO
2196        one
2197        TWO
2198            three
2199        six
2200        "
2201    );
2202    let base_text_2 = indoc!(
2203        "
2204        seven
2205          eight
2206        nine
2207        ten
2208        eleven
2209        twelve
2210        "
2211    );
2212    let text_2 = indoc!(
2213        "
2214          eight
2215        nine
2216        eleven
2217        THIRTEEN
2218        FOURTEEN
2219        "
2220    );
2221
2222    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
2223    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
2224    let diff_1 = cx.new(|cx| {
2225        BufferDiff::new_with_base_text(base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
2226    });
2227    let diff_2 = cx.new(|cx| {
2228        BufferDiff::new_with_base_text(base_text_2, &buffer_2.read(cx).text_snapshot(), cx)
2229    });
2230    cx.run_until_parked();
2231
2232    let multibuffer = cx.new(|cx| {
2233        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
2234        multibuffer.set_excerpts_for_path(
2235            PathKey::sorted(0),
2236            buffer_1.clone(),
2237            [Point::zero()..buffer_1.read(cx).max_point()],
2238            0,
2239            cx,
2240        );
2241        multibuffer.set_excerpts_for_path(
2242            PathKey::sorted(1),
2243            buffer_2.clone(),
2244            [Point::zero()..buffer_2.read(cx).max_point()],
2245            0,
2246            cx,
2247        );
2248        multibuffer.add_diff(diff_1.clone(), cx);
2249        multibuffer.add_diff(diff_2.clone(), cx);
2250        multibuffer
2251    });
2252
2253    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
2254        (multibuffer.snapshot(cx), multibuffer.subscribe())
2255    });
2256    assert_eq!(
2257        snapshot.text(),
2258        indoc!(
2259            "
2260            ZERO
2261            one
2262            TWO
2263                three
2264            six
2265
2266              eight
2267            nine
2268            eleven
2269            THIRTEEN
2270            FOURTEEN
2271            "
2272        ),
2273    );
2274
2275    multibuffer.update(cx, |multibuffer, cx| {
2276        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
2277    });
2278
2279    assert_new_snapshot(
2280        &multibuffer,
2281        &mut snapshot,
2282        &mut subscription,
2283        cx,
2284        indoc!(
2285            "
2286            + ZERO
2287              one
2288            - two
2289            + TWO
2290                  three
2291            - four
2292            - five
2293              six
2294
2295            - seven
2296                eight
2297              nine
2298            - ten
2299              eleven
2300            - twelve
2301            + THIRTEEN
2302            + FOURTEEN
2303            "
2304        ),
2305    );
2306
2307    let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
2308    let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
2309    let base_id_1 = diff_1.read_with(cx, |diff, cx| diff.base_text(cx).remote_id());
2310    let base_id_2 = diff_2.read_with(cx, |diff, cx| diff.base_text(cx).remote_id());
2311
2312    let buffer_lines = (0..=snapshot.max_row().0)
2313        .map(|row| {
2314            let (buffer, range) = snapshot.buffer_line_for_row(MultiBufferRow(row))?;
2315            Some((
2316                buffer.remote_id(),
2317                buffer.text_for_range(range).collect::<String>(),
2318            ))
2319        })
2320        .collect::<Vec<_>>();
2321    pretty_assertions::assert_eq!(
2322        buffer_lines,
2323        [
2324            Some((id_1, "ZERO".into())),
2325            Some((id_1, "one".into())),
2326            Some((base_id_1, "two".into())),
2327            Some((id_1, "TWO".into())),
2328            Some((id_1, "    three".into())),
2329            Some((base_id_1, "four".into())),
2330            Some((base_id_1, "five".into())),
2331            Some((id_1, "six".into())),
2332            Some((id_1, "".into())),
2333            Some((base_id_2, "seven".into())),
2334            Some((id_2, "  eight".into())),
2335            Some((id_2, "nine".into())),
2336            Some((base_id_2, "ten".into())),
2337            Some((id_2, "eleven".into())),
2338            Some((base_id_2, "twelve".into())),
2339            Some((id_2, "THIRTEEN".into())),
2340            Some((id_2, "FOURTEEN".into())),
2341            Some((id_2, "".into())),
2342        ]
2343    );
2344
2345    let buffer_ids_by_range = [
2346        (Point::new(0, 0)..Point::new(0, 0), &[id_1] as &[_]),
2347        (Point::new(0, 0)..Point::new(2, 0), &[id_1]),
2348        (Point::new(2, 0)..Point::new(2, 0), &[id_1]),
2349        (Point::new(3, 0)..Point::new(3, 0), &[id_1]),
2350        (Point::new(8, 0)..Point::new(9, 0), &[id_1]),
2351        (Point::new(8, 0)..Point::new(10, 0), &[id_1, id_2]),
2352        (Point::new(9, 0)..Point::new(9, 0), &[id_2]),
2353    ];
2354    for (range, buffer_ids) in buffer_ids_by_range {
2355        assert_eq!(
2356            snapshot
2357                .buffer_ids_for_range(range.clone())
2358                .collect::<Vec<_>>(),
2359            buffer_ids,
2360            "buffer_ids_for_range({range:?}"
2361        );
2362    }
2363
2364    assert_position_translation(&snapshot);
2365    assert_line_indents(&snapshot);
2366
2367    assert_eq!(
2368        snapshot
2369            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.len())
2370            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
2371            .collect::<Vec<_>>(),
2372        &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
2373    );
2374
2375    buffer_2.update(cx, |buffer, cx| {
2376        buffer.edit_via_marked_text(
2377            indoc!(
2378                "
2379                  eight
2380                «»eleven
2381                THIRTEEN
2382                FOURTEEN
2383                "
2384            ),
2385            None,
2386            cx,
2387        );
2388    });
2389
2390    assert_new_snapshot(
2391        &multibuffer,
2392        &mut snapshot,
2393        &mut subscription,
2394        cx,
2395        indoc!(
2396            "
2397            + ZERO
2398              one
2399            - two
2400            + TWO
2401                  three
2402            - four
2403            - five
2404              six
2405
2406            - seven
2407                eight
2408              eleven
2409            - twelve
2410            + THIRTEEN
2411            + FOURTEEN
2412            "
2413        ),
2414    );
2415
2416    assert_line_indents(&snapshot);
2417}
2418
2419/// A naive implementation of a multi-buffer that does not maintain
2420/// any derived state, used for comparison in a randomized test.
2421#[derive(Default)]
2422struct ReferenceMultibuffer {
2423    excerpts: Vec<ReferenceExcerpt>,
2424    diffs: HashMap<BufferId, Entity<BufferDiff>>,
2425    inverted_diffs: HashMap<BufferId, (Entity<BufferDiff>, Entity<language::Buffer>)>,
2426}
2427
2428#[derive(Debug)]
2429struct ReferenceExcerpt {
2430    id: ExcerptId,
2431    buffer: Entity<Buffer>,
2432    range: Range<text::Anchor>,
2433    expanded_diff_hunks: Vec<text::Anchor>,
2434}
2435
2436#[derive(Debug)]
2437struct ReferenceRegion {
2438    buffer_id: Option<BufferId>,
2439    range: Range<usize>,
2440    buffer_range: Option<Range<Point>>,
2441    status: Option<DiffHunkStatus>,
2442    excerpt_id: Option<ExcerptId>,
2443}
2444
2445impl ReferenceMultibuffer {
2446    fn expand_excerpts(&mut self, excerpts: &HashSet<ExcerptId>, line_count: u32, cx: &App) {
2447        if line_count == 0 {
2448            return;
2449        }
2450
2451        for id in excerpts {
2452            let excerpt = self.excerpts.iter_mut().find(|e| e.id == *id).unwrap();
2453            let snapshot = excerpt.buffer.read(cx).snapshot();
2454            let mut point_range = excerpt.range.to_point(&snapshot);
2455            point_range.start = Point::new(point_range.start.row.saturating_sub(line_count), 0);
2456            point_range.end =
2457                snapshot.clip_point(Point::new(point_range.end.row + line_count, 0), Bias::Left);
2458            point_range.end.column = snapshot.line_len(point_range.end.row);
2459            excerpt.range =
2460                snapshot.anchor_before(point_range.start)..snapshot.anchor_after(point_range.end);
2461        }
2462    }
2463
2464    fn remove_excerpt(&mut self, id: ExcerptId, cx: &App) {
2465        let ix = self
2466            .excerpts
2467            .iter()
2468            .position(|excerpt| excerpt.id == id)
2469            .unwrap();
2470        let excerpt = self.excerpts.remove(ix);
2471        let buffer = excerpt.buffer.read(cx);
2472        let buffer_id = buffer.remote_id();
2473        log::info!(
2474            "Removing excerpt {}: {:?}",
2475            ix,
2476            buffer
2477                .text_for_range(excerpt.range.to_offset(buffer))
2478                .collect::<String>(),
2479        );
2480        if !self
2481            .excerpts
2482            .iter()
2483            .any(|excerpt| excerpt.buffer.read(cx).remote_id() == buffer_id)
2484        {
2485            self.diffs.remove(&buffer_id);
2486            self.inverted_diffs.remove(&buffer_id);
2487        }
2488    }
2489
2490    fn insert_excerpt_after(
2491        &mut self,
2492        prev_id: ExcerptId,
2493        new_excerpt_id: ExcerptId,
2494        (buffer_handle, anchor_range): (Entity<Buffer>, Range<text::Anchor>),
2495    ) {
2496        let excerpt_ix = if prev_id == ExcerptId::max() {
2497            self.excerpts.len()
2498        } else {
2499            self.excerpts
2500                .iter()
2501                .position(|excerpt| excerpt.id == prev_id)
2502                .unwrap()
2503                + 1
2504        };
2505        self.excerpts.insert(
2506            excerpt_ix,
2507            ReferenceExcerpt {
2508                id: new_excerpt_id,
2509                buffer: buffer_handle,
2510                range: anchor_range,
2511                expanded_diff_hunks: Vec::new(),
2512            },
2513        );
2514    }
2515
2516    fn expand_diff_hunks(&mut self, excerpt_id: ExcerptId, range: Range<text::Anchor>, cx: &App) {
2517        let excerpt = self
2518            .excerpts
2519            .iter_mut()
2520            .find(|e| e.id == excerpt_id)
2521            .unwrap();
2522        let buffer = excerpt.buffer.read(cx).snapshot();
2523        let buffer_id = buffer.remote_id();
2524
2525        // Skip inverted excerpts - hunks are always expanded
2526        if self.inverted_diffs.contains_key(&buffer_id) {
2527            return;
2528        }
2529
2530        let Some(diff) = self.diffs.get(&buffer_id) else {
2531            return;
2532        };
2533        let excerpt_range = excerpt.range.to_offset(&buffer);
2534        for hunk in diff
2535            .read(cx)
2536            .snapshot(cx)
2537            .hunks_intersecting_range(range, &buffer)
2538        {
2539            let hunk_range = hunk.buffer_range.to_offset(&buffer);
2540            if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end {
2541                continue;
2542            }
2543            if let Err(ix) = excerpt
2544                .expanded_diff_hunks
2545                .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
2546            {
2547                log::info!(
2548                    "expanding diff hunk {:?}. excerpt:{:?}, excerpt range:{:?}",
2549                    hunk_range,
2550                    excerpt_id,
2551                    excerpt_range
2552                );
2553                excerpt
2554                    .expanded_diff_hunks
2555                    .insert(ix, hunk.buffer_range.start);
2556            } else {
2557                log::trace!("hunk {hunk_range:?} already expanded in excerpt {excerpt_id:?}");
2558            }
2559        }
2560    }
2561
2562    fn expected_content(&self, cx: &App) -> (String, Vec<RowInfo>, HashSet<MultiBufferRow>) {
2563        let mut text = String::new();
2564        let mut regions = Vec::<ReferenceRegion>::new();
2565        let mut excerpt_boundary_rows = HashSet::default();
2566        for excerpt in &self.excerpts {
2567            excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
2568            let buffer = excerpt.buffer.read(cx);
2569            let buffer_id = buffer.remote_id();
2570            let buffer_range = excerpt.range.to_offset(buffer);
2571
2572            if let Some((diff, main_buffer)) = self.inverted_diffs.get(&buffer_id) {
2573                let diff_snapshot = diff.read(cx).snapshot(cx);
2574                let main_buffer_snapshot = main_buffer.read(cx).snapshot();
2575
2576                let mut offset = buffer_range.start;
2577                for hunk in diff_snapshot.hunks_intersecting_base_text_range(
2578                    buffer_range.clone(),
2579                    &main_buffer_snapshot.text,
2580                ) {
2581                    let mut hunk_base_range = hunk.diff_base_byte_range.clone();
2582
2583                    hunk_base_range.end = hunk_base_range.end.min(buffer_range.end);
2584                    if hunk_base_range.start > buffer_range.end
2585                        || hunk_base_range.start < buffer_range.start
2586                    {
2587                        continue;
2588                    }
2589
2590                    // Add the text before the hunk
2591                    if hunk_base_range.start >= offset {
2592                        let len = text.len();
2593                        text.extend(buffer.text_for_range(offset..hunk_base_range.start));
2594                        if text.len() > len {
2595                            regions.push(ReferenceRegion {
2596                                buffer_id: Some(buffer_id),
2597                                range: len..text.len(),
2598                                buffer_range: Some(
2599                                    (offset..hunk_base_range.start).to_point(&buffer),
2600                                ),
2601                                status: None,
2602                                excerpt_id: Some(excerpt.id),
2603                            });
2604                        }
2605                    }
2606
2607                    // Add the "deleted" region (base text that's not in main)
2608                    if !hunk_base_range.is_empty() {
2609                        let len = text.len();
2610                        text.extend(buffer.text_for_range(hunk_base_range.clone()));
2611                        regions.push(ReferenceRegion {
2612                            buffer_id: Some(buffer_id),
2613                            range: len..text.len(),
2614                            buffer_range: Some(hunk_base_range.to_point(&buffer)),
2615                            status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
2616                            excerpt_id: Some(excerpt.id),
2617                        });
2618                    }
2619
2620                    offset = hunk_base_range.end;
2621                }
2622
2623                // Add remaining buffer text
2624                let len = text.len();
2625                text.extend(buffer.text_for_range(offset..buffer_range.end));
2626                text.push('\n');
2627                regions.push(ReferenceRegion {
2628                    buffer_id: Some(buffer_id),
2629                    range: len..text.len(),
2630                    buffer_range: Some((offset..buffer_range.end).to_point(&buffer)),
2631                    status: None,
2632                    excerpt_id: Some(excerpt.id),
2633                });
2634            } else {
2635                let diff = self.diffs.get(&buffer_id).unwrap().read(cx).snapshot(cx);
2636                let base_buffer = diff.base_text();
2637
2638                let mut offset = buffer_range.start;
2639                let hunks = diff
2640                    .hunks_intersecting_range(excerpt.range.clone(), buffer)
2641                    .peekable();
2642
2643                for hunk in hunks {
2644                    // Ignore hunks that are outside the excerpt range.
2645                    let mut hunk_range = hunk.buffer_range.to_offset(buffer);
2646
2647                    hunk_range.end = hunk_range.end.min(buffer_range.end);
2648                    if hunk_range.start > buffer_range.end || hunk_range.start < buffer_range.start
2649                    {
2650                        log::trace!("skipping hunk outside excerpt range");
2651                        continue;
2652                    }
2653
2654                    if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| {
2655                        expanded_anchor.to_offset(buffer).max(buffer_range.start)
2656                            == hunk_range.start.max(buffer_range.start)
2657                    }) {
2658                        log::trace!("skipping a hunk that's not marked as expanded");
2659                        continue;
2660                    }
2661
2662                    if !hunk.buffer_range.start.is_valid(buffer) {
2663                        log::trace!("skipping hunk with deleted start: {:?}", hunk.range);
2664                        continue;
2665                    }
2666
2667                    if hunk_range.start >= offset {
2668                        // Add the buffer text before the hunk
2669                        let len = text.len();
2670                        text.extend(buffer.text_for_range(offset..hunk_range.start));
2671                        if text.len() > len {
2672                            regions.push(ReferenceRegion {
2673                                buffer_id: Some(buffer_id),
2674                                range: len..text.len(),
2675                                buffer_range: Some((offset..hunk_range.start).to_point(&buffer)),
2676                                status: None,
2677                                excerpt_id: Some(excerpt.id),
2678                            });
2679                        }
2680
2681                        // Add the deleted text for the hunk.
2682                        if !hunk.diff_base_byte_range.is_empty() {
2683                            let mut base_text = base_buffer
2684                                .text_for_range(hunk.diff_base_byte_range.clone())
2685                                .collect::<String>();
2686                            if !base_text.ends_with('\n') {
2687                                base_text.push('\n');
2688                            }
2689                            let len = text.len();
2690                            text.push_str(&base_text);
2691                            regions.push(ReferenceRegion {
2692                                buffer_id: Some(base_buffer.remote_id()),
2693                                range: len..text.len(),
2694                                buffer_range: Some(
2695                                    hunk.diff_base_byte_range.to_point(&base_buffer),
2696                                ),
2697                                status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
2698                                excerpt_id: Some(excerpt.id),
2699                            });
2700                        }
2701
2702                        offset = hunk_range.start;
2703                    }
2704
2705                    // Add the inserted text for the hunk.
2706                    if hunk_range.end > offset {
2707                        let len = text.len();
2708                        text.extend(buffer.text_for_range(offset..hunk_range.end));
2709                        let range = len..text.len();
2710                        let region = ReferenceRegion {
2711                            buffer_id: Some(buffer_id),
2712                            range,
2713                            buffer_range: Some((offset..hunk_range.end).to_point(&buffer)),
2714                            status: Some(DiffHunkStatus::added(hunk.secondary_status)),
2715                            excerpt_id: Some(excerpt.id),
2716                        };
2717                        offset = hunk_range.end;
2718                        regions.push(region);
2719                    }
2720                }
2721
2722                // Add the buffer text for the rest of the excerpt.
2723                let len = text.len();
2724                text.extend(buffer.text_for_range(offset..buffer_range.end));
2725                text.push('\n');
2726                regions.push(ReferenceRegion {
2727                    buffer_id: Some(buffer_id),
2728                    range: len..text.len(),
2729                    buffer_range: Some((offset..buffer_range.end).to_point(&buffer)),
2730                    status: None,
2731                    excerpt_id: Some(excerpt.id),
2732                });
2733            }
2734        }
2735
2736        // Remove final trailing newline.
2737        if self.excerpts.is_empty() {
2738            regions.push(ReferenceRegion {
2739                buffer_id: None,
2740                range: 0..1,
2741                buffer_range: Some(Point::new(0, 0)..Point::new(0, 1)),
2742                status: None,
2743                excerpt_id: None,
2744            });
2745        } else {
2746            text.pop();
2747        }
2748
2749        // Retrieve the row info using the region that contains
2750        // the start of each multi-buffer line.
2751        let mut ix = 0;
2752        let row_infos = text
2753            .split('\n')
2754            .map(|line| {
2755                let row_info = regions
2756                    .iter()
2757                    .position(|region| region.range.contains(&ix))
2758                    .map_or(RowInfo::default(), |region_ix| {
2759                        let region = &regions[region_ix];
2760                        let buffer_row = region.buffer_range.as_ref().map(|buffer_range| {
2761                            buffer_range.start.row
2762                                + text[region.range.start..ix].matches('\n').count() as u32
2763                        });
2764                        let main_buffer = self
2765                            .excerpts
2766                            .iter()
2767                            .find(|e| e.id == region.excerpt_id.unwrap())
2768                            .map(|e| e.buffer.clone());
2769                        let is_excerpt_start = region_ix == 0
2770                            || &regions[region_ix - 1].excerpt_id != &region.excerpt_id
2771                            || regions[region_ix - 1].range.is_empty();
2772                        let mut is_excerpt_end = region_ix == regions.len() - 1
2773                            || &regions[region_ix + 1].excerpt_id != &region.excerpt_id;
2774                        let is_start = !text[region.range.start..ix].contains('\n');
2775                        let mut is_end = if region.range.end > text.len() {
2776                            !text[ix..].contains('\n')
2777                        } else {
2778                            text[ix..region.range.end.min(text.len())]
2779                                .matches('\n')
2780                                .count()
2781                                == 1
2782                        };
2783                        if region_ix < regions.len() - 1
2784                            && !text[ix..].contains("\n")
2785                            && (region.status == Some(DiffHunkStatus::added_none())
2786                                || region.status.is_some_and(|s| s.is_deleted()))
2787                            && regions[region_ix + 1].excerpt_id == region.excerpt_id
2788                            && regions[region_ix + 1].range.start == text.len()
2789                        {
2790                            is_end = true;
2791                            is_excerpt_end = true;
2792                        }
2793                        let multibuffer_row =
2794                            MultiBufferRow(text[..ix].matches('\n').count() as u32);
2795                        let mut expand_direction = None;
2796                        if let Some(buffer) = &main_buffer {
2797                            let buffer_row = buffer_row.unwrap();
2798                            let needs_expand_up = is_excerpt_start && is_start && buffer_row > 0;
2799                            let needs_expand_down = is_excerpt_end
2800                                && is_end
2801                                && buffer.read(cx).max_point().row > buffer_row;
2802                            expand_direction = if needs_expand_up && needs_expand_down {
2803                                Some(ExpandExcerptDirection::UpAndDown)
2804                            } else if needs_expand_up {
2805                                Some(ExpandExcerptDirection::Up)
2806                            } else if needs_expand_down {
2807                                Some(ExpandExcerptDirection::Down)
2808                            } else {
2809                                None
2810                            };
2811                        }
2812                        RowInfo {
2813                            buffer_id: region.buffer_id,
2814                            diff_status: region.status,
2815                            buffer_row,
2816                            wrapped_buffer_row: None,
2817
2818                            multibuffer_row: Some(multibuffer_row),
2819                            expand_info: expand_direction.zip(region.excerpt_id).map(
2820                                |(direction, excerpt_id)| ExpandInfo {
2821                                    direction,
2822                                    excerpt_id,
2823                                },
2824                            ),
2825                        }
2826                    });
2827                ix += line.len() + 1;
2828                row_info
2829            })
2830            .collect();
2831
2832        (text, row_infos, excerpt_boundary_rows)
2833    }
2834
2835    fn diffs_updated(&mut self, cx: &App) {
2836        for excerpt in &mut self.excerpts {
2837            let buffer = excerpt.buffer.read(cx).snapshot();
2838            let buffer_id = buffer.remote_id();
2839
2840            // Skip inverted diff excerpts - hunks are always expanded
2841            if self.inverted_diffs.contains_key(&buffer_id) {
2842                continue;
2843            }
2844
2845            let excerpt_range = excerpt.range.to_offset(&buffer);
2846            let Some(diff) = self.diffs.get(&buffer_id) else {
2847                continue;
2848            };
2849            let diff = diff.read(cx).snapshot(cx);
2850            let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer).peekable();
2851            excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
2852                if !hunk_anchor.is_valid(&buffer) {
2853                    return false;
2854                }
2855                while let Some(hunk) = hunks.peek() {
2856                    match hunk.buffer_range.start.cmp(hunk_anchor, &buffer) {
2857                        cmp::Ordering::Less => {
2858                            hunks.next();
2859                        }
2860                        cmp::Ordering::Equal => {
2861                            let hunk_range = hunk.buffer_range.to_offset(&buffer);
2862                            return hunk_range.end >= excerpt_range.start
2863                                && hunk_range.start <= excerpt_range.end;
2864                        }
2865                        cmp::Ordering::Greater => break,
2866                    }
2867                }
2868                false
2869            });
2870        }
2871    }
2872
2873    fn add_diff(&mut self, diff: Entity<BufferDiff>, cx: &mut App) {
2874        let buffer_id = diff.read(cx).buffer_id;
2875        self.diffs.insert(buffer_id, diff);
2876    }
2877
2878    fn add_inverted_diff(
2879        &mut self,
2880        diff: Entity<BufferDiff>,
2881        main_buffer: Entity<language::Buffer>,
2882        cx: &App,
2883    ) {
2884        let base_text_buffer_id = diff.read(cx).base_text(cx).remote_id();
2885        self.inverted_diffs
2886            .insert(base_text_buffer_id, (diff, main_buffer));
2887    }
2888}
2889
2890#[gpui::test(iterations = 100)]
2891async fn test_random_set_ranges(cx: &mut TestAppContext, mut rng: StdRng) {
2892    let base_text = "a\n".repeat(100);
2893    let buf = cx.update(|cx| cx.new(|cx| Buffer::local(base_text, cx)));
2894    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2895
2896    let operations = env::var("OPERATIONS")
2897        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2898        .unwrap_or(10);
2899
2900    fn row_ranges(ranges: &Vec<Range<Point>>) -> Vec<Range<u32>> {
2901        ranges
2902            .iter()
2903            .map(|range| range.start.row..range.end.row)
2904            .collect()
2905    }
2906
2907    for _ in 0..operations {
2908        let snapshot = buf.update(cx, |buf, _| buf.snapshot());
2909        let num_ranges = rng.random_range(0..=10);
2910        let max_row = snapshot.max_point().row;
2911        let mut ranges = (0..num_ranges)
2912            .map(|_| {
2913                let start = rng.random_range(0..max_row);
2914                let end = rng.random_range(start + 1..max_row + 1);
2915                Point::row_range(start..end)
2916            })
2917            .collect::<Vec<_>>();
2918        ranges.sort_by_key(|range| range.start);
2919        log::info!("Setting ranges: {:?}", row_ranges(&ranges));
2920        let (created, _) = multibuffer.update(cx, |multibuffer, cx| {
2921            multibuffer.set_excerpts_for_path(
2922                PathKey::for_buffer(&buf, cx),
2923                buf.clone(),
2924                ranges.clone(),
2925                2,
2926                cx,
2927            )
2928        });
2929
2930        assert_eq!(created.len(), ranges.len());
2931
2932        let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2933        let mut last_end = None;
2934        let mut seen_ranges = Vec::default();
2935
2936        for (_, buf, range) in snapshot.excerpts() {
2937            let start = range.context.start.to_point(buf);
2938            let end = range.context.end.to_point(buf);
2939            seen_ranges.push(start..end);
2940
2941            if let Some(last_end) = last_end.take() {
2942                assert!(
2943                    start > last_end,
2944                    "multibuffer has out-of-order ranges: {:?}; {:?} <= {:?}",
2945                    row_ranges(&seen_ranges),
2946                    start,
2947                    last_end
2948                )
2949            }
2950
2951            ranges.retain(|range| range.start < start || range.end > end);
2952
2953            last_end = Some(end)
2954        }
2955
2956        assert!(
2957            ranges.is_empty(),
2958            "multibuffer {:?} did not include all ranges: {:?}",
2959            row_ranges(&seen_ranges),
2960            row_ranges(&ranges)
2961        );
2962    }
2963}
2964
2965#[gpui::test(iterations = 100)]
2966async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
2967    let operations = env::var("OPERATIONS")
2968        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2969        .unwrap_or(10);
2970    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2971    let mut buffers: Vec<Entity<Buffer>> = Vec::new();
2972    let mut base_texts: HashMap<BufferId, String> = HashMap::default();
2973    let mut reference = ReferenceMultibuffer::default();
2974    let mut anchors = Vec::new();
2975    let mut old_versions = Vec::new();
2976    let mut needs_diff_calculation = false;
2977    let mut inverted_diff_main_buffers: HashMap<BufferId, Entity<BufferDiff>> = HashMap::default();
2978    for _ in 0..operations {
2979        match rng.random_range(0..100) {
2980            0..=14 if !buffers.is_empty() => {
2981                let buffer = buffers.choose(&mut rng).unwrap();
2982                buffer.update(cx, |buf, cx| {
2983                    let edit_count = rng.random_range(1..5);
2984                    buf.randomly_edit(&mut rng, edit_count, cx);
2985                    log::info!("buffer text:\n{}", buf.text());
2986                    needs_diff_calculation = true;
2987                });
2988                cx.update(|cx| reference.diffs_updated(cx));
2989            }
2990            15..=19 if !reference.excerpts.is_empty() => {
2991                multibuffer.update(cx, |multibuffer, cx| {
2992                    let ids = multibuffer.excerpt_ids();
2993                    let mut excerpts = HashSet::default();
2994                    for _ in 0..rng.random_range(0..ids.len()) {
2995                        excerpts.extend(ids.choose(&mut rng).copied());
2996                    }
2997
2998                    let line_count = rng.random_range(0..5);
2999
3000                    let excerpt_ixs = excerpts
3001                        .iter()
3002                        .map(|id| reference.excerpts.iter().position(|e| e.id == *id).unwrap())
3003                        .collect::<Vec<_>>();
3004                    log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
3005                    multibuffer.expand_excerpts(
3006                        excerpts.iter().cloned(),
3007                        line_count,
3008                        ExpandExcerptDirection::UpAndDown,
3009                        cx,
3010                    );
3011
3012                    reference.expand_excerpts(&excerpts, line_count, cx);
3013                });
3014            }
3015            20..=29 if !reference.excerpts.is_empty() => {
3016                let mut ids_to_remove = vec![];
3017                for _ in 0..rng.random_range(1..=3) {
3018                    let Some(excerpt) = reference.excerpts.choose(&mut rng) else {
3019                        break;
3020                    };
3021                    let id = excerpt.id;
3022                    cx.update(|cx| reference.remove_excerpt(id, cx));
3023                    ids_to_remove.push(id);
3024                }
3025                let snapshot =
3026                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3027                ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
3028                drop(snapshot);
3029                multibuffer.update(cx, |multibuffer, cx| {
3030                    multibuffer.remove_excerpts(ids_to_remove, cx)
3031                });
3032            }
3033            30..=39 if !reference.excerpts.is_empty() => {
3034                let multibuffer =
3035                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3036                let offset = multibuffer.clip_offset(
3037                    MultiBufferOffset(rng.random_range(0..=multibuffer.len().0)),
3038                    Bias::Left,
3039                );
3040                let bias = if rng.random() {
3041                    Bias::Left
3042                } else {
3043                    Bias::Right
3044                };
3045                log::info!("Creating anchor at {} with bias {:?}", offset.0, bias);
3046                anchors.push(multibuffer.anchor_at(offset, bias));
3047                anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
3048            }
3049            40..=44 if !anchors.is_empty() => {
3050                let multibuffer =
3051                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3052                let prev_len = anchors.len();
3053                anchors = multibuffer
3054                    .refresh_anchors(&anchors)
3055                    .into_iter()
3056                    .map(|a| a.1)
3057                    .collect();
3058
3059                // Ensure the newly-refreshed anchors point to a valid excerpt and don't
3060                // overshoot its boundaries.
3061                assert_eq!(anchors.len(), prev_len);
3062                for anchor in &anchors {
3063                    if anchor.excerpt_id == ExcerptId::min()
3064                        || anchor.excerpt_id == ExcerptId::max()
3065                    {
3066                        continue;
3067                    }
3068
3069                    let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
3070                    assert_eq!(excerpt.id, anchor.excerpt_id);
3071                    assert!(excerpt.contains(anchor));
3072                }
3073            }
3074            45..=55 if !reference.excerpts.is_empty() => {
3075                multibuffer.update(cx, |multibuffer, cx| {
3076                    let snapshot = multibuffer.snapshot(cx);
3077                    let excerpt_ix = rng.random_range(0..reference.excerpts.len());
3078                    let excerpt = &reference.excerpts[excerpt_ix];
3079
3080                    // Skip inverted excerpts - hunks can't be collapsed
3081                    let buffer_id = excerpt.buffer.read(cx).remote_id();
3082                    if reference.inverted_diffs.contains_key(&buffer_id) {
3083                        return;
3084                    }
3085
3086                    let start = excerpt.range.start;
3087                    let end = excerpt.range.end;
3088                    let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
3089                        ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
3090
3091                    log::info!(
3092                        "expanding diff hunks in range {:?} (excerpt id {:?}, index {excerpt_ix:?}, buffer id {:?})",
3093                        range.to_offset(&snapshot),
3094                        excerpt.id,
3095                        buffer_id,
3096                    );
3097                    reference.expand_diff_hunks(excerpt.id, start..end, cx);
3098                    multibuffer.expand_diff_hunks(vec![range], cx);
3099                });
3100            }
3101            56..=85 if needs_diff_calculation => {
3102                multibuffer.update(cx, |multibuffer, cx| {
3103                    for buffer in multibuffer.all_buffers() {
3104                        let snapshot = buffer.read(cx).snapshot();
3105                        let buffer_id = snapshot.remote_id();
3106
3107                        if let Some(diff) = multibuffer.diff_for(buffer_id) {
3108                            diff.update(cx, |diff, cx| {
3109                                log::info!("recalculating diff for buffer {:?}", buffer_id,);
3110                                diff.recalculate_diff_sync(&snapshot.text, cx);
3111                            });
3112                        }
3113
3114                        if let Some(inverted_diff) = inverted_diff_main_buffers.get(&buffer_id) {
3115                            inverted_diff.update(cx, |diff, cx| {
3116                                log::info!(
3117                                    "recalculating inverted diff for main buffer {:?}",
3118                                    buffer_id,
3119                                );
3120                                diff.recalculate_diff_sync(&snapshot.text, cx);
3121                            });
3122                        }
3123                    }
3124                    reference.diffs_updated(cx);
3125                    needs_diff_calculation = false;
3126                });
3127            }
3128            _ => {
3129                // Decide if we're creating a new buffer or reusing an existing one
3130                let create_new_buffer = buffers.is_empty() || rng.random_bool(0.4);
3131
3132                let prev_excerpt_ix = rng.random_range(0..=reference.excerpts.len());
3133                let prev_excerpt_id = reference
3134                    .excerpts
3135                    .get(prev_excerpt_ix)
3136                    .map_or(ExcerptId::max(), |e| e.id);
3137                let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
3138
3139                let (excerpt_buffer, diff, inverted_main_buffer) = if create_new_buffer {
3140                    let create_inverted = rng.random_bool(0.3);
3141
3142                    if create_inverted {
3143                        let mut main_buffer_text = util::RandomCharIter::new(&mut rng)
3144                            .take(256)
3145                            .collect::<String>();
3146                        let main_buffer = cx.new(|cx| Buffer::local(main_buffer_text.clone(), cx));
3147                        text::LineEnding::normalize(&mut main_buffer_text);
3148                        let main_buffer_id =
3149                            main_buffer.read_with(cx, |buffer, _| buffer.remote_id());
3150                        base_texts.insert(main_buffer_id, main_buffer_text.clone());
3151                        buffers.push(main_buffer.clone());
3152
3153                        let diff = cx.new(|cx| {
3154                            BufferDiff::new_with_base_text(
3155                                &main_buffer_text,
3156                                &main_buffer.read(cx).text_snapshot(),
3157                                cx,
3158                            )
3159                        });
3160
3161                        let base_text_buffer =
3162                            diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
3163
3164                        // Track for recalculation when main buffer is edited
3165                        inverted_diff_main_buffers.insert(main_buffer_id, diff.clone());
3166
3167                        (base_text_buffer, diff, Some(main_buffer))
3168                    } else {
3169                        let mut base_text = util::RandomCharIter::new(&mut rng)
3170                            .take(256)
3171                            .collect::<String>();
3172
3173                        let buffer_handle = cx.new(|cx| Buffer::local(base_text.clone(), cx));
3174                        text::LineEnding::normalize(&mut base_text);
3175                        let buffer_id = buffer_handle.read_with(cx, |buffer, _| buffer.remote_id());
3176                        base_texts.insert(buffer_id, base_text.clone());
3177                        buffers.push(buffer_handle.clone());
3178
3179                        let diff = cx.new(|cx| {
3180                            BufferDiff::new_with_base_text(
3181                                &base_text,
3182                                &buffer_handle.read(cx).text_snapshot(),
3183                                cx,
3184                            )
3185                        });
3186
3187                        (buffer_handle, diff, None)
3188                    }
3189                } else {
3190                    // Reuse an existing buffer
3191                    let buffer_handle = buffers.choose(&mut rng).unwrap().clone();
3192                    let buffer_id = buffer_handle.read_with(cx, |buffer, _| buffer.remote_id());
3193
3194                    if let Some(diff) = inverted_diff_main_buffers.get(&buffer_id) {
3195                        let base_text_buffer =
3196                            diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
3197                        (base_text_buffer, diff.clone(), Some(buffer_handle))
3198                    } else {
3199                        // Get existing diff or create new one for regular buffer
3200                        let diff = multibuffer
3201                            .read_with(cx, |mb, _| mb.diff_for(buffer_id))
3202                            .unwrap_or_else(|| {
3203                                let base_text = base_texts.get(&buffer_id).unwrap();
3204                                cx.new(|cx| {
3205                                    BufferDiff::new_with_base_text(
3206                                        base_text,
3207                                        &buffer_handle.read(cx).text_snapshot(),
3208                                        cx,
3209                                    )
3210                                })
3211                            });
3212                        (buffer_handle, diff, None)
3213                    }
3214                };
3215
3216                let (range, anchor_range) = excerpt_buffer.read_with(cx, |buffer, _| {
3217                    let end_row = rng.random_range(0..=buffer.max_point().row);
3218                    let start_row = rng.random_range(0..=end_row);
3219                    let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
3220                    let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
3221                    let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
3222
3223                    log::info!(
3224                        "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
3225                        excerpt_ix,
3226                        reference.excerpts.len(),
3227                        buffer.remote_id(),
3228                        buffer.text(),
3229                        start_ix..end_ix,
3230                        &buffer.text()[start_ix..end_ix]
3231                    );
3232
3233                    (start_ix..end_ix, anchor_range)
3234                });
3235
3236                let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
3237                    multibuffer
3238                        .insert_excerpts_after(
3239                            prev_excerpt_id,
3240                            excerpt_buffer.clone(),
3241                            [ExcerptRange::new(range.clone())],
3242                            cx,
3243                        )
3244                        .pop()
3245                        .unwrap()
3246                });
3247
3248                reference.insert_excerpt_after(
3249                    prev_excerpt_id,
3250                    excerpt_id,
3251                    (excerpt_buffer.clone(), anchor_range),
3252                );
3253
3254                let excerpt_buffer_id =
3255                    excerpt_buffer.read_with(cx, |buffer, _| buffer.remote_id());
3256                multibuffer.update(cx, |multibuffer, cx| {
3257                    if multibuffer.diff_for(excerpt_buffer_id).is_none() {
3258                        if let Some(main_buffer) = inverted_main_buffer {
3259                            reference.add_inverted_diff(diff.clone(), main_buffer.clone(), cx);
3260                            multibuffer.add_inverted_diff(diff, main_buffer, cx);
3261                        } else {
3262                            reference.add_diff(diff.clone(), cx);
3263                            multibuffer.add_diff(diff, cx);
3264                        }
3265                    }
3266                });
3267            }
3268        }
3269
3270        if rng.random_bool(0.3) {
3271            multibuffer.update(cx, |multibuffer, cx| {
3272                old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
3273            })
3274        }
3275
3276        multibuffer.read_with(cx, |multibuffer, cx| {
3277            check_multibuffer(multibuffer, &reference, &anchors, cx, &mut rng);
3278        });
3279    }
3280    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3281    for (old_snapshot, subscription) in old_versions {
3282        check_multibuffer_edits(&snapshot, &old_snapshot, subscription);
3283    }
3284}
3285
3286fn check_multibuffer(
3287    multibuffer: &MultiBuffer,
3288    reference: &ReferenceMultibuffer,
3289    anchors: &[Anchor],
3290    cx: &App,
3291    rng: &mut StdRng,
3292) {
3293    let snapshot = multibuffer.snapshot(cx);
3294    let actual_text = snapshot.text();
3295    let actual_boundary_rows = snapshot
3296        .excerpt_boundaries_in_range(MultiBufferOffset(0)..)
3297        .map(|b| b.row)
3298        .collect::<HashSet<_>>();
3299    let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3300
3301    let (expected_text, expected_row_infos, expected_boundary_rows) =
3302        reference.expected_content(cx);
3303
3304    let has_diff = actual_row_infos
3305        .iter()
3306        .any(|info| info.diff_status.is_some())
3307        || expected_row_infos
3308            .iter()
3309            .any(|info| info.diff_status.is_some());
3310    let actual_diff = format_diff(
3311        &actual_text,
3312        &actual_row_infos,
3313        &actual_boundary_rows,
3314        Some(has_diff),
3315    );
3316    let expected_diff = format_diff(
3317        &expected_text,
3318        &expected_row_infos,
3319        &expected_boundary_rows,
3320        Some(has_diff),
3321    );
3322
3323    log::info!("Multibuffer content:\n{}", actual_diff);
3324
3325    assert_eq!(
3326        actual_row_infos.len(),
3327        actual_text.split('\n').count(),
3328        "line count: {}",
3329        actual_text.split('\n').count()
3330    );
3331    pretty_assertions::assert_eq!(actual_diff, expected_diff);
3332    pretty_assertions::assert_eq!(actual_text, expected_text);
3333    pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
3334
3335    for _ in 0..5 {
3336        let start_row = rng.random_range(0..=expected_row_infos.len());
3337        assert_eq!(
3338            snapshot
3339                .row_infos(MultiBufferRow(start_row as u32))
3340                .collect::<Vec<_>>(),
3341            &expected_row_infos[start_row..],
3342            "buffer_rows({})",
3343            start_row
3344        );
3345    }
3346
3347    assert_eq!(
3348        snapshot.widest_line_number(),
3349        expected_row_infos
3350            .into_iter()
3351            .filter_map(|info| {
3352                // For inverted diffs, deleted rows are visible and should be counted.
3353                // Only filter out deleted rows that are NOT from inverted diffs.
3354                let is_inverted_diff = info
3355                    .buffer_id
3356                    .is_some_and(|id| reference.inverted_diffs.contains_key(&id));
3357                if info.diff_status.is_some_and(|status| status.is_deleted()) && !is_inverted_diff {
3358                    None
3359                } else {
3360                    info.buffer_row
3361                }
3362            })
3363            .max()
3364            .unwrap()
3365            + 1
3366    );
3367    let reference_ranges = reference
3368        .excerpts
3369        .iter()
3370        .map(|excerpt| {
3371            (
3372                excerpt.id,
3373                excerpt.range.to_offset(&excerpt.buffer.read(cx).snapshot()),
3374            )
3375        })
3376        .collect::<HashMap<_, _>>();
3377    for i in 0..snapshot.len().0 {
3378        let excerpt = snapshot
3379            .excerpt_containing(MultiBufferOffset(i)..MultiBufferOffset(i))
3380            .unwrap();
3381        assert_eq!(
3382            excerpt.buffer_range().start.0..excerpt.buffer_range().end.0,
3383            reference_ranges[&excerpt.id()]
3384        );
3385    }
3386
3387    assert_consistent_line_numbers(&snapshot);
3388    assert_position_translation(&snapshot);
3389
3390    for (row, line) in expected_text.split('\n').enumerate() {
3391        assert_eq!(
3392            snapshot.line_len(MultiBufferRow(row as u32)),
3393            line.len() as u32,
3394            "line_len({}).",
3395            row
3396        );
3397    }
3398
3399    let text_rope = Rope::from(expected_text.as_str());
3400    for _ in 0..10 {
3401        let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
3402        let start_ix = text_rope.clip_offset(rng.random_range(0..=end_ix), Bias::Left);
3403
3404        let text_for_range = snapshot
3405            .text_for_range(MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix))
3406            .collect::<String>();
3407        assert_eq!(
3408            text_for_range,
3409            &expected_text[start_ix..end_ix],
3410            "incorrect text for range {:?}",
3411            start_ix..end_ix
3412        );
3413
3414        let expected_summary =
3415            MBTextSummary::from(TextSummary::from(&expected_text[start_ix..end_ix]));
3416        assert_eq!(
3417            snapshot.text_summary_for_range::<MBTextSummary, _>(
3418                MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix)
3419            ),
3420            expected_summary,
3421            "incorrect summary for range {:?}",
3422            start_ix..end_ix
3423        );
3424    }
3425
3426    // Anchor resolution
3427    let summaries = snapshot.summaries_for_anchors::<MultiBufferOffset, _>(anchors);
3428    assert_eq!(anchors.len(), summaries.len());
3429    for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
3430        assert!(resolved_offset <= snapshot.len());
3431        assert_eq!(
3432            snapshot.summary_for_anchor::<MultiBufferOffset>(anchor),
3433            resolved_offset,
3434            "anchor: {:?}",
3435            anchor
3436        );
3437    }
3438
3439    for _ in 0..10 {
3440        let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
3441        assert_eq!(
3442            snapshot
3443                .reversed_chars_at(MultiBufferOffset(end_ix))
3444                .collect::<String>(),
3445            expected_text[..end_ix].chars().rev().collect::<String>(),
3446        );
3447    }
3448
3449    for _ in 0..10 {
3450        let end_ix = rng.random_range(0..=text_rope.len());
3451        let end_ix = text_rope.floor_char_boundary(end_ix);
3452        let start_ix = rng.random_range(0..=end_ix);
3453        let start_ix = text_rope.floor_char_boundary(start_ix);
3454        assert_eq!(
3455            snapshot
3456                .bytes_in_range(MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix))
3457                .flatten()
3458                .copied()
3459                .collect::<Vec<_>>(),
3460            expected_text.as_bytes()[start_ix..end_ix].to_vec(),
3461            "bytes_in_range({:?})",
3462            start_ix..end_ix,
3463        );
3464    }
3465}
3466
3467fn check_multibuffer_edits(
3468    snapshot: &MultiBufferSnapshot,
3469    old_snapshot: &MultiBufferSnapshot,
3470    subscription: Subscription<MultiBufferOffset>,
3471) {
3472    let edits = subscription.consume().into_inner();
3473
3474    log::info!(
3475        "applying subscription edits to old text: {:?}: {:#?}",
3476        old_snapshot.text(),
3477        edits,
3478    );
3479
3480    let mut text = old_snapshot.text();
3481    for edit in edits {
3482        let new_text: String = snapshot
3483            .text_for_range(edit.new.start..edit.new.end)
3484            .collect();
3485        text.replace_range(
3486            (edit.new.start.0..edit.new.start.0 + (edit.old.end.0 - edit.old.start.0)).clone(),
3487            &new_text,
3488        );
3489        pretty_assertions::assert_eq!(
3490            &text[0..edit.new.end.0],
3491            snapshot
3492                .text_for_range(MultiBufferOffset(0)..edit.new.end)
3493                .collect::<String>()
3494        );
3495    }
3496    pretty_assertions::assert_eq!(text, snapshot.text());
3497}
3498
3499#[gpui::test]
3500fn test_history(cx: &mut App) {
3501    let test_settings = SettingsStore::test(cx);
3502    cx.set_global(test_settings);
3503
3504    let group_interval: Duration = Duration::from_millis(1);
3505    let buffer_1 = cx.new(|cx| {
3506        let mut buf = Buffer::local("1234", cx);
3507        buf.set_group_interval(group_interval);
3508        buf
3509    });
3510    let buffer_2 = cx.new(|cx| {
3511        let mut buf = Buffer::local("5678", cx);
3512        buf.set_group_interval(group_interval);
3513        buf
3514    });
3515    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
3516    multibuffer.update(cx, |this, cx| {
3517        this.set_group_interval(group_interval, cx);
3518    });
3519    multibuffer.update(cx, |multibuffer, cx| {
3520        multibuffer.set_excerpts_for_path(
3521            PathKey::sorted(0),
3522            buffer_1.clone(),
3523            [Point::zero()..buffer_1.read(cx).max_point()],
3524            0,
3525            cx,
3526        );
3527        multibuffer.set_excerpts_for_path(
3528            PathKey::sorted(1),
3529            buffer_2.clone(),
3530            [Point::zero()..buffer_2.read(cx).max_point()],
3531            0,
3532            cx,
3533        );
3534    });
3535
3536    let mut now = Instant::now();
3537
3538    multibuffer.update(cx, |multibuffer, cx| {
3539        let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
3540        multibuffer.edit(
3541            [
3542                (Point::new(0, 0)..Point::new(0, 0), "A"),
3543                (Point::new(1, 0)..Point::new(1, 0), "A"),
3544            ],
3545            None,
3546            cx,
3547        );
3548        multibuffer.edit(
3549            [
3550                (Point::new(0, 1)..Point::new(0, 1), "B"),
3551                (Point::new(1, 1)..Point::new(1, 1), "B"),
3552            ],
3553            None,
3554            cx,
3555        );
3556        multibuffer.end_transaction_at(now, cx);
3557        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3558
3559        // Verify edited ranges for transaction 1
3560        assert_eq!(
3561            multibuffer.edited_ranges_for_transaction(transaction_1, cx),
3562            &[
3563                Point::new(0, 0)..Point::new(0, 2),
3564                Point::new(1, 0)..Point::new(1, 2)
3565            ]
3566        );
3567
3568        // Edit buffer 1 through the multibuffer
3569        now += 2 * group_interval;
3570        multibuffer.start_transaction_at(now, cx);
3571        multibuffer.edit(
3572            [(MultiBufferOffset(2)..MultiBufferOffset(2), "C")],
3573            None,
3574            cx,
3575        );
3576        multibuffer.end_transaction_at(now, cx);
3577        assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
3578
3579        // Edit buffer 1 independently
3580        buffer_1.update(cx, |buffer_1, cx| {
3581            buffer_1.start_transaction_at(now);
3582            buffer_1.edit([(3..3, "D")], None, cx);
3583            buffer_1.end_transaction_at(now, cx);
3584
3585            now += 2 * group_interval;
3586            buffer_1.start_transaction_at(now);
3587            buffer_1.edit([(4..4, "E")], None, cx);
3588            buffer_1.end_transaction_at(now, cx);
3589        });
3590        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
3591
3592        // An undo in the multibuffer undoes the multibuffer transaction
3593        // and also any individual buffer edits that have occurred since
3594        // that transaction.
3595        multibuffer.undo(cx);
3596        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3597
3598        multibuffer.undo(cx);
3599        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3600
3601        multibuffer.redo(cx);
3602        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3603
3604        multibuffer.redo(cx);
3605        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
3606
3607        // Undo buffer 2 independently.
3608        buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
3609        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
3610
3611        // An undo in the multibuffer undoes the components of the
3612        // the last multibuffer transaction that are not already undone.
3613        multibuffer.undo(cx);
3614        assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
3615
3616        multibuffer.undo(cx);
3617        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3618
3619        multibuffer.redo(cx);
3620        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3621
3622        buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
3623        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
3624
3625        // Redo stack gets cleared after an edit.
3626        now += 2 * group_interval;
3627        multibuffer.start_transaction_at(now, cx);
3628        multibuffer.edit(
3629            [(MultiBufferOffset(0)..MultiBufferOffset(0), "X")],
3630            None,
3631            cx,
3632        );
3633        multibuffer.end_transaction_at(now, cx);
3634        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3635        multibuffer.redo(cx);
3636        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3637        multibuffer.undo(cx);
3638        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
3639        multibuffer.undo(cx);
3640        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3641
3642        // Transactions can be grouped manually.
3643        multibuffer.redo(cx);
3644        multibuffer.redo(cx);
3645        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3646        multibuffer.group_until_transaction(transaction_1, cx);
3647        multibuffer.undo(cx);
3648        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3649        multibuffer.redo(cx);
3650        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3651    });
3652}
3653
3654#[gpui::test]
3655async fn test_enclosing_indent(cx: &mut TestAppContext) {
3656    async fn enclosing_indent(
3657        text: &str,
3658        buffer_row: u32,
3659        cx: &mut TestAppContext,
3660    ) -> Option<(Range<u32>, LineIndent)> {
3661        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3662        let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
3663        let (range, indent) = snapshot
3664            .enclosing_indent(MultiBufferRow(buffer_row))
3665            .await?;
3666        Some((range.start.0..range.end.0, indent))
3667    }
3668
3669    assert_eq!(
3670        enclosing_indent(
3671            indoc!(
3672                "
3673                fn b() {
3674                    if c {
3675                        let d = 2;
3676                    }
3677                }
3678                "
3679            ),
3680            1,
3681            cx,
3682        )
3683        .await,
3684        Some((
3685            1..2,
3686            LineIndent {
3687                tabs: 0,
3688                spaces: 4,
3689                line_blank: false,
3690            }
3691        ))
3692    );
3693
3694    assert_eq!(
3695        enclosing_indent(
3696            indoc!(
3697                "
3698                fn b() {
3699                    if c {
3700                        let d = 2;
3701                    }
3702                }
3703                "
3704            ),
3705            2,
3706            cx,
3707        )
3708        .await,
3709        Some((
3710            1..2,
3711            LineIndent {
3712                tabs: 0,
3713                spaces: 4,
3714                line_blank: false,
3715            }
3716        ))
3717    );
3718
3719    assert_eq!(
3720        enclosing_indent(
3721            indoc!(
3722                "
3723                fn b() {
3724                    if c {
3725                        let d = 2;
3726
3727                        let e = 5;
3728                    }
3729                }
3730                "
3731            ),
3732            3,
3733            cx,
3734        )
3735        .await,
3736        Some((
3737            1..4,
3738            LineIndent {
3739                tabs: 0,
3740                spaces: 4,
3741                line_blank: false,
3742            }
3743        ))
3744    );
3745}
3746
3747#[gpui::test]
3748async fn test_summaries_for_anchors(cx: &mut TestAppContext) {
3749    let base_text_1 = indoc!(
3750        "
3751        bar
3752        "
3753    );
3754    let text_1 = indoc!(
3755        "
3756        BAR
3757        "
3758    );
3759    let base_text_2 = indoc!(
3760        "
3761        foo
3762        "
3763    );
3764    let text_2 = indoc!(
3765        "
3766        FOO
3767        "
3768    );
3769
3770    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3771    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
3772    let diff_1 = cx.new(|cx| {
3773        BufferDiff::new_with_base_text(base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
3774    });
3775    let diff_2 = cx.new(|cx| {
3776        BufferDiff::new_with_base_text(base_text_2, &buffer_2.read(cx).text_snapshot(), cx)
3777    });
3778    cx.run_until_parked();
3779
3780    let mut ids = vec![];
3781    let multibuffer = cx.new(|cx| {
3782        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
3783        multibuffer.set_all_diff_hunks_expanded(cx);
3784        multibuffer.set_excerpts_for_path(
3785            PathKey::sorted(0),
3786            buffer_1.clone(),
3787            [Point::zero()..buffer_1.read(cx).max_point()],
3788            0,
3789            cx,
3790        );
3791        multibuffer.set_excerpts_for_path(
3792            PathKey::sorted(1),
3793            buffer_2.clone(),
3794            [Point::zero()..buffer_2.read(cx).max_point()],
3795            0,
3796            cx,
3797        );
3798        multibuffer.add_diff(diff_1.clone(), cx);
3799        multibuffer.add_diff(diff_2.clone(), cx);
3800        ids = multibuffer.excerpt_ids();
3801        multibuffer
3802    });
3803
3804    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3805        (multibuffer.snapshot(cx), multibuffer.subscribe())
3806    });
3807
3808    assert_new_snapshot(
3809        &multibuffer,
3810        &mut snapshot,
3811        &mut subscription,
3812        cx,
3813        indoc!(
3814            "
3815            - bar
3816            + BAR
3817
3818            - foo
3819            + FOO
3820            "
3821        ),
3822    );
3823
3824    let anchor_1 = Anchor::in_buffer(ids[0], text::Anchor::MIN);
3825    let point_1 = snapshot.summaries_for_anchors::<Point, _>([&anchor_1])[0];
3826    assert_eq!(point_1, Point::new(0, 0));
3827
3828    let anchor_2 = Anchor::in_buffer(ids[1], text::Anchor::MIN);
3829    let point_2 = snapshot.summaries_for_anchors::<Point, _>([&anchor_2])[0];
3830    assert_eq!(point_2, Point::new(3, 0));
3831}
3832
3833#[gpui::test]
3834async fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
3835    let base_text_1 = "one\ntwo".to_owned();
3836    let text_1 = "one\n".to_owned();
3837
3838    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3839    let diff_1 = cx.new(|cx| {
3840        BufferDiff::new_with_base_text(&base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
3841    });
3842    cx.run_until_parked();
3843
3844    let multibuffer = cx.new(|cx| {
3845        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
3846        multibuffer.set_excerpts_for_path(
3847            PathKey::sorted(0),
3848            buffer_1.clone(),
3849            [Point::zero()..buffer_1.read(cx).max_point()],
3850            0,
3851            cx,
3852        );
3853        multibuffer.add_diff(diff_1.clone(), cx);
3854        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
3855        multibuffer
3856    });
3857
3858    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3859        (multibuffer.snapshot(cx), multibuffer.subscribe())
3860    });
3861
3862    assert_new_snapshot(
3863        &multibuffer,
3864        &mut snapshot,
3865        &mut subscription,
3866        cx,
3867        indoc!(
3868            "
3869              one
3870            - two
3871            "
3872        ),
3873    );
3874
3875    assert_eq!(snapshot.max_point(), Point::new(2, 0));
3876    assert_eq!(snapshot.len().0, 8);
3877
3878    assert_eq!(
3879        snapshot
3880            .dimensions_from_points::<Point>([Point::new(2, 0)])
3881            .collect::<Vec<_>>(),
3882        vec![Point::new(2, 0)]
3883    );
3884
3885    let (_, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3886    assert_eq!(translated_offset.0, "one\n".len());
3887    let (_, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3888    assert_eq!(translated_point, Point::new(1, 0));
3889
3890    // The same, for an excerpt that's not at the end of the multibuffer.
3891
3892    let text_2 = "foo\n".to_owned();
3893    let buffer_2 = cx.new(|cx| Buffer::local(&text_2, cx));
3894    multibuffer.update(cx, |multibuffer, cx| {
3895        multibuffer.set_excerpt_ranges_for_path(
3896            PathKey::sorted(1),
3897            buffer_2.clone(),
3898            &buffer_2.read(cx).snapshot(),
3899            vec![ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
3900            cx,
3901        );
3902    });
3903
3904    assert_new_snapshot(
3905        &multibuffer,
3906        &mut snapshot,
3907        &mut subscription,
3908        cx,
3909        indoc!(
3910            "
3911              one
3912            - two
3913
3914              foo
3915            "
3916        ),
3917    );
3918
3919    assert_eq!(
3920        snapshot
3921            .dimensions_from_points::<Point>([Point::new(2, 0)])
3922            .collect::<Vec<_>>(),
3923        vec![Point::new(2, 0)]
3924    );
3925
3926    let buffer_1_id = buffer_1.read_with(cx, |buffer_1, _| buffer_1.remote_id());
3927    let (buffer, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3928    assert_eq!(buffer.remote_id(), buffer_1_id);
3929    assert_eq!(translated_offset.0, "one\n".len());
3930    let (buffer, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3931    assert_eq!(buffer.remote_id(), buffer_1_id);
3932    assert_eq!(translated_point, Point::new(1, 0));
3933}
3934
3935fn format_diff(
3936    text: &str,
3937    row_infos: &Vec<RowInfo>,
3938    boundary_rows: &HashSet<MultiBufferRow>,
3939    has_diff: Option<bool>,
3940) -> String {
3941    let has_diff =
3942        has_diff.unwrap_or_else(|| row_infos.iter().any(|info| info.diff_status.is_some()));
3943    text.split('\n')
3944        .enumerate()
3945        .zip(row_infos)
3946        .map(|((ix, line), info)| {
3947            let marker = match info.diff_status.map(|status| status.kind) {
3948                Some(DiffHunkStatusKind::Added) => "+ ",
3949                Some(DiffHunkStatusKind::Deleted) => "- ",
3950                Some(DiffHunkStatusKind::Modified) => unreachable!(),
3951                None => {
3952                    if has_diff && !line.is_empty() {
3953                        "  "
3954                    } else {
3955                        ""
3956                    }
3957                }
3958            };
3959            let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
3960                if has_diff {
3961                    "  ----------\n"
3962                } else {
3963                    "---------\n"
3964                }
3965            } else {
3966                ""
3967            };
3968            let expand = info
3969                .expand_info
3970                .map(|expand_info| match expand_info.direction {
3971                    ExpandExcerptDirection::Up => " [↑]",
3972                    ExpandExcerptDirection::Down => " [↓]",
3973                    ExpandExcerptDirection::UpAndDown => " [↕]",
3974                })
3975                .unwrap_or_default();
3976
3977            format!("{boundary_row}{marker}{line}{expand}")
3978            // let mbr = info
3979            //     .multibuffer_row
3980            //     .map(|row| format!("{:0>3}", row.0))
3981            //     .unwrap_or_else(|| "???".to_string());
3982            // let byte_range = format!("{byte_range_start:0>3}..{byte_range_end:0>3}");
3983            // format!("{boundary_row}Row: {mbr}, Bytes: {byte_range} | {marker}{line}{expand}")
3984        })
3985        .collect::<Vec<_>>()
3986        .join("\n")
3987}
3988
3989// fn format_transforms(snapshot: &MultiBufferSnapshot) -> String {
3990//     snapshot
3991//         .diff_transforms
3992//         .iter()
3993//         .map(|transform| {
3994//             let (kind, summary) = match transform {
3995//                 DiffTransform::DeletedHunk { summary, .. } => ("   Deleted", (*summary).into()),
3996//                 DiffTransform::FilteredInsertedHunk { summary, .. } => ("  Filtered", *summary),
3997//                 DiffTransform::InsertedHunk { summary, .. } => ("  Inserted", *summary),
3998//                 DiffTransform::Unmodified { summary, .. } => ("Unmodified", *summary),
3999//             };
4000//             format!("{kind}(len: {}, lines: {:?})", summary.len, summary.lines)
4001//         })
4002//         .join("\n")
4003// }
4004
4005// fn format_excerpts(snapshot: &MultiBufferSnapshot) -> String {
4006//     snapshot
4007//         .excerpts
4008//         .iter()
4009//         .map(|excerpt| {
4010//             format!(
4011//                 "Excerpt(buffer_range = {:?}, lines = {:?}, has_trailing_newline = {:?})",
4012//                 excerpt.range.context.to_point(&excerpt.buffer),
4013//                 excerpt.text_summary.lines,
4014//                 excerpt.has_trailing_newline
4015//             )
4016//         })
4017//         .join("\n")
4018// }
4019
4020#[gpui::test]
4021async fn test_singleton_with_inverted_diff(cx: &mut TestAppContext) {
4022    let text = indoc!(
4023        "
4024        ZERO
4025        one
4026        TWO
4027        three
4028        six
4029        "
4030    );
4031    let base_text = indoc!(
4032        "
4033        one
4034        two
4035        three
4036        four
4037        five
4038        six
4039        "
4040    );
4041
4042    let buffer = cx.new(|cx| Buffer::local(text, cx));
4043    let diff = cx
4044        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
4045    cx.run_until_parked();
4046
4047    let base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4048
4049    let multibuffer = cx.new(|cx| {
4050        let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4051        multibuffer.set_all_diff_hunks_expanded(cx);
4052        multibuffer.add_inverted_diff(diff.clone(), buffer.clone(), cx);
4053        multibuffer
4054    });
4055
4056    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4057        (multibuffer.snapshot(cx), multibuffer.subscribe())
4058    });
4059
4060    assert_eq!(snapshot.text(), base_text);
4061    assert_new_snapshot(
4062        &multibuffer,
4063        &mut snapshot,
4064        &mut subscription,
4065        cx,
4066        indoc!(
4067            "
4068              one
4069            - two
4070              three
4071            - four
4072            - five
4073              six
4074            "
4075        ),
4076    );
4077
4078    buffer.update(cx, |buffer, cx| {
4079        buffer.edit_via_marked_text(
4080            indoc!(
4081                "
4082                ZERO
4083                one
4084                «<inserted>»W«O
4085                T»hree
4086                six
4087                "
4088            ),
4089            None,
4090            cx,
4091        );
4092    });
4093    cx.run_until_parked();
4094    let update = diff
4095        .update(cx, |diff, cx| {
4096            diff.update_diff(
4097                buffer.read(cx).text_snapshot(),
4098                Some(base_text.into()),
4099                None,
4100                None,
4101                cx,
4102            )
4103        })
4104        .await;
4105    diff.update(cx, |diff, cx| {
4106        diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4107    })
4108    .await;
4109    cx.run_until_parked();
4110
4111    assert_new_snapshot(
4112        &multibuffer,
4113        &mut snapshot,
4114        &mut subscription,
4115        cx,
4116        indoc! {
4117            "
4118              one
4119            - two
4120            - three
4121            - four
4122            - five
4123              six
4124            "
4125        },
4126    );
4127
4128    buffer.update(cx, |buffer, cx| {
4129        buffer.set_text("ZERO\nONE\nTWO\n", cx);
4130    });
4131    cx.run_until_parked();
4132    let update = diff
4133        .update(cx, |diff, cx| {
4134            diff.update_diff(
4135                buffer.read(cx).text_snapshot(),
4136                Some(base_text.into()),
4137                None,
4138                None,
4139                cx,
4140            )
4141        })
4142        .await;
4143    diff.update(cx, |diff, cx| {
4144        diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4145    })
4146    .await;
4147    cx.run_until_parked();
4148
4149    assert_new_snapshot(
4150        &multibuffer,
4151        &mut snapshot,
4152        &mut subscription,
4153        cx,
4154        indoc! {
4155            "
4156            - one
4157            - two
4158            - three
4159            - four
4160            - five
4161            - six
4162            "
4163        },
4164    );
4165
4166    diff.update(cx, |diff, cx| {
4167        diff.set_base_text(
4168            Some("new base\n".into()),
4169            None,
4170            buffer.read(cx).text_snapshot(),
4171            cx,
4172        )
4173    })
4174    .await
4175    .unwrap();
4176    cx.run_until_parked();
4177
4178    assert_new_snapshot(
4179        &multibuffer,
4180        &mut snapshot,
4181        &mut subscription,
4182        cx,
4183        indoc! {"
4184            - new base
4185        "},
4186    );
4187}
4188
4189#[gpui::test]
4190async fn test_inverted_diff_base_text_change(cx: &mut TestAppContext) {
4191    let base_text = "aaa\nbbb\nccc\n";
4192    let text = "ddd\n";
4193    let buffer = cx.new(|cx| Buffer::local(text, cx));
4194    let diff = cx
4195        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
4196    cx.run_until_parked();
4197
4198    let base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4199
4200    let multibuffer = cx.new(|cx| {
4201        let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4202        multibuffer.set_all_diff_hunks_expanded(cx);
4203        multibuffer.add_inverted_diff(diff.clone(), buffer.clone(), cx);
4204        multibuffer
4205    });
4206
4207    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4208        (multibuffer.snapshot(cx), multibuffer.subscribe())
4209    });
4210
4211    assert_eq!(snapshot.text(), base_text);
4212    assert_new_snapshot(
4213        &multibuffer,
4214        &mut snapshot,
4215        &mut subscription,
4216        cx,
4217        indoc!(
4218            "
4219            - aaa
4220            - bbb
4221            - ccc
4222            "
4223        ),
4224    );
4225
4226    let update = diff
4227        .update(cx, |diff, cx| {
4228            diff.update_diff(
4229                buffer.read(cx).text_snapshot(),
4230                Some("ddd\n".into()),
4231                Some(true),
4232                None,
4233                cx,
4234            )
4235        })
4236        .await;
4237    diff.update(cx, |diff, cx| {
4238        diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4239    })
4240    .detach();
4241
4242    let _hunks: Vec<_> = multibuffer
4243        .read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx))
4244        .diff_hunks()
4245        .collect();
4246}
4247
4248#[gpui::test]
4249async fn test_inverted_diff_secondary_version_mismatch(cx: &mut TestAppContext) {
4250    let base_text = "one\ntwo\nthree\nfour\nfive\n";
4251    let index_text = "one\nTWO\nthree\nfour\nfive\n";
4252    let buffer_text = "one\nTWO\nthree\nFOUR\nfive\n";
4253
4254    let buffer = cx.new(|cx| Buffer::local(buffer_text, cx));
4255
4256    let unstaged_diff = cx
4257        .new(|cx| BufferDiff::new_with_base_text(index_text, &buffer.read(cx).text_snapshot(), cx));
4258    cx.run_until_parked();
4259
4260    let uncommitted_diff = cx.new(|cx| {
4261        let mut diff =
4262            BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx);
4263        diff.set_secondary_diff(unstaged_diff.clone());
4264        diff
4265    });
4266    cx.run_until_parked();
4267
4268    buffer.update(cx, |buffer, cx| {
4269        buffer.edit([(0..0, "ZERO\n")], None, cx);
4270    });
4271
4272    let update = unstaged_diff
4273        .update(cx, |diff, cx| {
4274            diff.update_diff(
4275                buffer.read(cx).text_snapshot(),
4276                Some(index_text.into()),
4277                None,
4278                None,
4279                cx,
4280            )
4281        })
4282        .await;
4283    unstaged_diff
4284        .update(cx, |diff, cx| {
4285            diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4286        })
4287        .await;
4288
4289    let base_text_buffer =
4290        uncommitted_diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4291
4292    let multibuffer = cx.new(|cx| {
4293        let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4294        multibuffer.set_all_diff_hunks_expanded(cx);
4295        multibuffer.add_inverted_diff(uncommitted_diff.clone(), buffer.clone(), cx);
4296        multibuffer
4297    });
4298
4299    let _hunks: Vec<_> = multibuffer
4300        .read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx))
4301        .diff_hunks()
4302        .collect();
4303}
4304
4305#[track_caller]
4306fn assert_excerpts_match(
4307    multibuffer: &Entity<MultiBuffer>,
4308    cx: &mut TestAppContext,
4309    expected: &str,
4310) {
4311    let mut output = String::new();
4312    multibuffer.read_with(cx, |multibuffer, cx| {
4313        for (_, buffer, range) in multibuffer.snapshot(cx).excerpts() {
4314            output.push_str("-----\n");
4315            output.extend(buffer.text_for_range(range.context));
4316            if !output.ends_with('\n') {
4317                output.push('\n');
4318            }
4319        }
4320    });
4321    assert_eq!(output, expected);
4322}
4323
4324#[track_caller]
4325fn assert_new_snapshot(
4326    multibuffer: &Entity<MultiBuffer>,
4327    snapshot: &mut MultiBufferSnapshot,
4328    subscription: &mut Subscription<MultiBufferOffset>,
4329    cx: &mut TestAppContext,
4330    expected_diff: &str,
4331) {
4332    let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
4333    let actual_text = new_snapshot.text();
4334    let line_infos = new_snapshot
4335        .row_infos(MultiBufferRow(0))
4336        .collect::<Vec<_>>();
4337    let actual_diff = format_diff(&actual_text, &line_infos, &Default::default(), None);
4338    pretty_assertions::assert_eq!(actual_diff, expected_diff);
4339    check_edits(
4340        snapshot,
4341        &new_snapshot,
4342        &subscription.consume().into_inner(),
4343    );
4344    *snapshot = new_snapshot;
4345}
4346
4347#[track_caller]
4348fn check_edits(
4349    old_snapshot: &MultiBufferSnapshot,
4350    new_snapshot: &MultiBufferSnapshot,
4351    edits: &[Edit<MultiBufferOffset>],
4352) {
4353    let mut text = old_snapshot.text();
4354    let new_text = new_snapshot.text();
4355    for edit in edits.iter().rev() {
4356        if !text.is_char_boundary(edit.old.start.0)
4357            || !text.is_char_boundary(edit.old.end.0)
4358            || !new_text.is_char_boundary(edit.new.start.0)
4359            || !new_text.is_char_boundary(edit.new.end.0)
4360        {
4361            panic!(
4362                "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
4363                edits, text, new_text
4364            );
4365        }
4366
4367        text.replace_range(
4368            edit.old.start.0..edit.old.end.0,
4369            &new_text[edit.new.start.0..edit.new.end.0],
4370        );
4371    }
4372
4373    pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
4374}
4375
4376#[track_caller]
4377fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
4378    let full_text = snapshot.text();
4379    for ix in 0..full_text.len() {
4380        let mut chunks = snapshot.chunks(MultiBufferOffset(0)..snapshot.len(), false);
4381        chunks.seek(MultiBufferOffset(ix)..snapshot.len());
4382        let tail = chunks.map(|chunk| chunk.text).collect::<String>();
4383        assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
4384    }
4385}
4386
4387#[track_caller]
4388fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
4389    let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
4390    for start_row in 1..all_line_numbers.len() {
4391        let line_numbers = snapshot
4392            .row_infos(MultiBufferRow(start_row as u32))
4393            .collect::<Vec<_>>();
4394        assert_eq!(
4395            line_numbers,
4396            all_line_numbers[start_row..],
4397            "start_row: {start_row}"
4398        );
4399    }
4400}
4401
4402#[track_caller]
4403fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
4404    let text = Rope::from(snapshot.text());
4405
4406    let mut left_anchors = Vec::new();
4407    let mut right_anchors = Vec::new();
4408    let mut offsets = Vec::new();
4409    let mut points = Vec::new();
4410    for offset in 0..=text.len() + 1 {
4411        let offset = MultiBufferOffset(offset);
4412        let clipped_left = snapshot.clip_offset(offset, Bias::Left);
4413        let clipped_right = snapshot.clip_offset(offset, Bias::Right);
4414        assert_eq!(
4415            clipped_left.0,
4416            text.clip_offset(offset.0, Bias::Left),
4417            "clip_offset({offset:?}, Left)"
4418        );
4419        assert_eq!(
4420            clipped_right.0,
4421            text.clip_offset(offset.0, Bias::Right),
4422            "clip_offset({offset:?}, Right)"
4423        );
4424        assert_eq!(
4425            snapshot.offset_to_point(clipped_left),
4426            text.offset_to_point(clipped_left.0),
4427            "offset_to_point({})",
4428            clipped_left.0
4429        );
4430        assert_eq!(
4431            snapshot.offset_to_point(clipped_right),
4432            text.offset_to_point(clipped_right.0),
4433            "offset_to_point({})",
4434            clipped_right.0
4435        );
4436        let anchor_after = snapshot.anchor_after(clipped_left);
4437        assert_eq!(
4438            anchor_after.to_offset(snapshot),
4439            clipped_left,
4440            "anchor_after({}).to_offset {anchor_after:?}",
4441            clipped_left.0
4442        );
4443        let anchor_before = snapshot.anchor_before(clipped_left);
4444        assert_eq!(
4445            anchor_before.to_offset(snapshot),
4446            clipped_left,
4447            "anchor_before({}).to_offset",
4448            clipped_left.0
4449        );
4450        left_anchors.push(anchor_before);
4451        right_anchors.push(anchor_after);
4452        offsets.push(clipped_left);
4453        points.push(text.offset_to_point(clipped_left.0));
4454    }
4455
4456    for row in 0..text.max_point().row {
4457        for column in 0..text.line_len(row) + 1 {
4458            let point = Point { row, column };
4459            let clipped_left = snapshot.clip_point(point, Bias::Left);
4460            let clipped_right = snapshot.clip_point(point, Bias::Right);
4461            assert_eq!(
4462                clipped_left,
4463                text.clip_point(point, Bias::Left),
4464                "clip_point({point:?}, Left)"
4465            );
4466            assert_eq!(
4467                clipped_right,
4468                text.clip_point(point, Bias::Right),
4469                "clip_point({point:?}, Right)"
4470            );
4471            assert_eq!(
4472                snapshot.point_to_offset(clipped_left).0,
4473                text.point_to_offset(clipped_left),
4474                "point_to_offset({clipped_left:?})"
4475            );
4476            assert_eq!(
4477                snapshot.point_to_offset(clipped_right).0,
4478                text.point_to_offset(clipped_right),
4479                "point_to_offset({clipped_right:?})"
4480            );
4481        }
4482    }
4483
4484    assert_eq!(
4485        snapshot.summaries_for_anchors::<MultiBufferOffset, _>(&left_anchors),
4486        offsets,
4487        "left_anchors <-> offsets"
4488    );
4489    assert_eq!(
4490        snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
4491        points,
4492        "left_anchors <-> points"
4493    );
4494    assert_eq!(
4495        snapshot.summaries_for_anchors::<MultiBufferOffset, _>(&right_anchors),
4496        offsets,
4497        "right_anchors <-> offsets"
4498    );
4499    assert_eq!(
4500        snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
4501        points,
4502        "right_anchors <-> points"
4503    );
4504
4505    for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
4506        for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
4507            if ix > 0 && *offset == MultiBufferOffset(252) && offset > &offsets[ix - 1] {
4508                let prev_anchor = left_anchors[ix - 1];
4509                assert!(
4510                    anchor.cmp(&prev_anchor, snapshot).is_gt(),
4511                    "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
4512                    offsets[ix],
4513                    offsets[ix - 1],
4514                );
4515                assert!(
4516                    prev_anchor.cmp(anchor, snapshot).is_lt(),
4517                    "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
4518                    offsets[ix - 1],
4519                    offsets[ix],
4520                );
4521            }
4522        }
4523    }
4524
4525    if let Some((buffer, offset)) = snapshot.point_to_buffer_offset(snapshot.max_point()) {
4526        assert!(offset.0 <= buffer.len());
4527    }
4528    if let Some((buffer, point, _)) = snapshot.point_to_buffer_point(snapshot.max_point()) {
4529        assert!(point <= buffer.max_point());
4530    }
4531}
4532
4533fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
4534    let max_row = snapshot.max_point().row;
4535    let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
4536    let text = text::Buffer::new(ReplicaId::LOCAL, buffer_id, snapshot.text());
4537    let mut line_indents = text
4538        .line_indents_in_row_range(0..max_row + 1)
4539        .collect::<Vec<_>>();
4540    for start_row in 0..snapshot.max_point().row {
4541        pretty_assertions::assert_eq!(
4542            snapshot
4543                .line_indents(MultiBufferRow(start_row), |_| true)
4544                .map(|(row, indent, _)| (row.0, indent))
4545                .collect::<Vec<_>>(),
4546            &line_indents[(start_row as usize)..],
4547            "line_indents({start_row})"
4548        );
4549    }
4550
4551    line_indents.reverse();
4552    pretty_assertions::assert_eq!(
4553        snapshot
4554            .reversed_line_indents(MultiBufferRow(max_row), |_| true)
4555            .map(|(row, indent, _)| (row.0, indent))
4556            .collect::<Vec<_>>(),
4557        &line_indents[..],
4558        "reversed_line_indents({max_row})"
4559    );
4560}
4561
4562#[gpui::test]
4563fn test_new_empty_buffer_uses_untitled_title(cx: &mut App) {
4564    let buffer = cx.new(|cx| Buffer::local("", cx));
4565    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
4566
4567    assert_eq!(multibuffer.read(cx).title(cx), "untitled");
4568}
4569
4570#[gpui::test]
4571fn test_new_empty_buffer_uses_untitled_title_when_only_contains_whitespace(cx: &mut App) {
4572    let buffer = cx.new(|cx| Buffer::local("\n ", cx));
4573    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
4574
4575    assert_eq!(multibuffer.read(cx).title(cx), "untitled");
4576}
4577
4578#[gpui::test]
4579fn test_new_empty_buffer_takes_first_line_for_title(cx: &mut App) {
4580    let buffer = cx.new(|cx| Buffer::local("Hello World\nSecond line", cx));
4581    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
4582
4583    assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
4584}
4585
4586#[gpui::test]
4587fn test_new_empty_buffer_takes_trimmed_first_line_for_title(cx: &mut App) {
4588    let buffer = cx.new(|cx| Buffer::local("\nHello, World ", cx));
4589    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
4590
4591    assert_eq!(multibuffer.read(cx).title(cx), "Hello, World");
4592}
4593
4594#[gpui::test]
4595fn test_new_empty_buffer_uses_truncated_first_line_for_title(cx: &mut App) {
4596    let title = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee";
4597    let title_after = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd";
4598    let buffer = cx.new(|cx| Buffer::local(title, cx));
4599    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
4600
4601    assert_eq!(multibuffer.read(cx).title(cx), title_after);
4602}
4603
4604#[gpui::test]
4605fn test_new_empty_buffer_uses_truncated_first_line_for_title_after_merging_adjacent_spaces(
4606    cx: &mut App,
4607) {
4608    let title = "aaaaaaaaaabbbbbbbbbb    ccccccccccddddddddddeeeeeeeeee";
4609    let title_after = "aaaaaaaaaabbbbbbbbbb ccccccccccddddddddd";
4610    let buffer = cx.new(|cx| Buffer::local(title, cx));
4611    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
4612
4613    assert_eq!(multibuffer.read(cx).title(cx), title_after);
4614}
4615
4616#[gpui::test]
4617fn test_new_empty_buffers_title_can_be_set(cx: &mut App) {
4618    let buffer = cx.new(|cx| Buffer::local("Hello World", cx));
4619    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
4620    assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
4621
4622    multibuffer.update(cx, |multibuffer, cx| {
4623        multibuffer.set_title("Hey".into(), cx)
4624    });
4625    assert_eq!(multibuffer.read(cx).title(cx), "Hey");
4626}
4627
4628#[gpui::test(iterations = 100)]
4629fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) {
4630    let multibuffer = if rng.random() {
4631        let len = rng.random_range(0..10000);
4632        let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
4633        let buffer = cx.new(|cx| Buffer::local(text, cx));
4634        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
4635    } else {
4636        MultiBuffer::build_random(&mut rng, cx)
4637    };
4638
4639    let snapshot = multibuffer.read(cx).snapshot(cx);
4640
4641    let chunks = snapshot.chunks(MultiBufferOffset(0)..snapshot.len(), false);
4642
4643    for chunk in chunks {
4644        let chunk_text = chunk.text;
4645        let chars_bitmap = chunk.chars;
4646        let tabs_bitmap = chunk.tabs;
4647
4648        if chunk_text.is_empty() {
4649            assert_eq!(
4650                chars_bitmap, 0,
4651                "Empty chunk should have empty chars bitmap"
4652            );
4653            assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
4654            continue;
4655        }
4656
4657        assert!(
4658            chunk_text.len() <= 128,
4659            "Chunk text length {} exceeds 128 bytes",
4660            chunk_text.len()
4661        );
4662
4663        // Verify chars bitmap
4664        let char_indices = chunk_text
4665            .char_indices()
4666            .map(|(i, _)| i)
4667            .collect::<Vec<_>>();
4668
4669        for byte_idx in 0..chunk_text.len() {
4670            let should_have_bit = char_indices.contains(&byte_idx);
4671            let has_bit = chars_bitmap & (1 << byte_idx) != 0;
4672
4673            if has_bit != should_have_bit {
4674                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
4675                eprintln!("Char indices: {:?}", char_indices);
4676                eprintln!("Chars bitmap: {:#b}", chars_bitmap);
4677            }
4678
4679            assert_eq!(
4680                has_bit, should_have_bit,
4681                "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
4682                byte_idx, chunk_text, should_have_bit, has_bit
4683            );
4684        }
4685
4686        for (byte_idx, byte) in chunk_text.bytes().enumerate() {
4687            let is_tab = byte == b'\t';
4688            let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
4689
4690            if has_bit != is_tab {
4691                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
4692                eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
4693                assert_eq!(
4694                    has_bit, is_tab,
4695                    "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
4696                    byte_idx, chunk_text, byte as char, is_tab, has_bit
4697                );
4698            }
4699        }
4700    }
4701}
4702
4703#[gpui::test(iterations = 10)]
4704fn test_random_chunk_bitmaps_with_diffs(cx: &mut App, mut rng: StdRng) {
4705    let settings_store = SettingsStore::test(cx);
4706    cx.set_global(settings_store);
4707    use buffer_diff::BufferDiff;
4708    use util::RandomCharIter;
4709
4710    let multibuffer = if rng.random() {
4711        let len = rng.random_range(100..10000);
4712        let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
4713        let buffer = cx.new(|cx| Buffer::local(text, cx));
4714        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
4715    } else {
4716        MultiBuffer::build_random(&mut rng, cx)
4717    };
4718
4719    let _diff_count = rng.random_range(1..5);
4720    let mut diffs = Vec::new();
4721
4722    multibuffer.update(cx, |multibuffer, cx| {
4723        for buffer_id in multibuffer.excerpt_buffer_ids() {
4724            if rng.random_bool(0.7) {
4725                if let Some(buffer_handle) = multibuffer.buffer(buffer_id) {
4726                    let buffer_text = buffer_handle.read(cx).text();
4727                    let mut base_text = String::new();
4728
4729                    for line in buffer_text.lines() {
4730                        if rng.random_bool(0.3) {
4731                            continue;
4732                        } else if rng.random_bool(0.3) {
4733                            let line_len = rng.random_range(0..50);
4734                            let modified_line = RandomCharIter::new(&mut rng)
4735                                .take(line_len)
4736                                .collect::<String>();
4737                            base_text.push_str(&modified_line);
4738                            base_text.push('\n');
4739                        } else {
4740                            base_text.push_str(line);
4741                            base_text.push('\n');
4742                        }
4743                    }
4744
4745                    if rng.random_bool(0.5) {
4746                        let extra_lines = rng.random_range(1..5);
4747                        for _ in 0..extra_lines {
4748                            let line_len = rng.random_range(0..50);
4749                            let extra_line = RandomCharIter::new(&mut rng)
4750                                .take(line_len)
4751                                .collect::<String>();
4752                            base_text.push_str(&extra_line);
4753                            base_text.push('\n');
4754                        }
4755                    }
4756
4757                    let diff = cx.new(|cx| {
4758                        BufferDiff::new_with_base_text(
4759                            &base_text,
4760                            &buffer_handle.read(cx).text_snapshot(),
4761                            cx,
4762                        )
4763                    });
4764                    diffs.push(diff.clone());
4765                    multibuffer.add_diff(diff, cx);
4766                }
4767            }
4768        }
4769    });
4770
4771    multibuffer.update(cx, |multibuffer, cx| {
4772        if rng.random_bool(0.5) {
4773            multibuffer.set_all_diff_hunks_expanded(cx);
4774        } else {
4775            let snapshot = multibuffer.snapshot(cx);
4776            let text = snapshot.text();
4777
4778            let mut ranges = Vec::new();
4779            for _ in 0..rng.random_range(1..5) {
4780                if snapshot.len().0 == 0 {
4781                    break;
4782                }
4783
4784                let diff_size = rng.random_range(5..1000);
4785                let mut start = rng.random_range(0..snapshot.len().0);
4786
4787                while !text.is_char_boundary(start) {
4788                    start = start.saturating_sub(1);
4789                }
4790
4791                let mut end = rng.random_range(start..snapshot.len().0.min(start + diff_size));
4792
4793                while !text.is_char_boundary(end) {
4794                    end = end.saturating_add(1);
4795                }
4796                let start_anchor = snapshot.anchor_after(MultiBufferOffset(start));
4797                let end_anchor = snapshot.anchor_before(MultiBufferOffset(end));
4798                ranges.push(start_anchor..end_anchor);
4799            }
4800            multibuffer.expand_diff_hunks(ranges, cx);
4801        }
4802    });
4803
4804    let snapshot = multibuffer.read(cx).snapshot(cx);
4805
4806    let chunks = snapshot.chunks(MultiBufferOffset(0)..snapshot.len(), false);
4807
4808    for chunk in chunks {
4809        let chunk_text = chunk.text;
4810        let chars_bitmap = chunk.chars;
4811        let tabs_bitmap = chunk.tabs;
4812
4813        if chunk_text.is_empty() {
4814            assert_eq!(
4815                chars_bitmap, 0,
4816                "Empty chunk should have empty chars bitmap"
4817            );
4818            assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
4819            continue;
4820        }
4821
4822        assert!(
4823            chunk_text.len() <= 128,
4824            "Chunk text length {} exceeds 128 bytes",
4825            chunk_text.len()
4826        );
4827
4828        let char_indices = chunk_text
4829            .char_indices()
4830            .map(|(i, _)| i)
4831            .collect::<Vec<_>>();
4832
4833        for byte_idx in 0..chunk_text.len() {
4834            let should_have_bit = char_indices.contains(&byte_idx);
4835            let has_bit = chars_bitmap & (1 << byte_idx) != 0;
4836
4837            if has_bit != should_have_bit {
4838                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
4839                eprintln!("Char indices: {:?}", char_indices);
4840                eprintln!("Chars bitmap: {:#b}", chars_bitmap);
4841            }
4842
4843            assert_eq!(
4844                has_bit, should_have_bit,
4845                "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
4846                byte_idx, chunk_text, should_have_bit, has_bit
4847            );
4848        }
4849
4850        for (byte_idx, byte) in chunk_text.bytes().enumerate() {
4851            let is_tab = byte == b'\t';
4852            let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
4853
4854            if has_bit != is_tab {
4855                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
4856                eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
4857                assert_eq!(
4858                    has_bit, is_tab,
4859                    "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
4860                    byte_idx, chunk_text, byte as char, is_tab, has_bit
4861                );
4862            }
4863        }
4864    }
4865}
4866
4867fn collect_word_diffs(
4868    base_text: &str,
4869    modified_text: &str,
4870    cx: &mut TestAppContext,
4871) -> Vec<String> {
4872    let buffer = cx.new(|cx| Buffer::local(modified_text, cx));
4873    let diff = cx
4874        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
4875    cx.run_until_parked();
4876
4877    let multibuffer = cx.new(|cx| {
4878        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
4879        multibuffer.add_diff(diff.clone(), cx);
4880        multibuffer
4881    });
4882
4883    multibuffer.update(cx, |multibuffer, cx| {
4884        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
4885    });
4886
4887    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
4888    let text = snapshot.text();
4889
4890    snapshot
4891        .diff_hunks()
4892        .flat_map(|hunk| hunk.word_diffs)
4893        .map(|range| text[range.start.0..range.end.0].to_string())
4894        .collect()
4895}
4896
4897#[gpui::test]
4898async fn test_word_diff_simple_replacement(cx: &mut TestAppContext) {
4899    let settings_store = cx.update(|cx| SettingsStore::test(cx));
4900    cx.set_global(settings_store);
4901
4902    let base_text = "hello world foo bar\n";
4903    let modified_text = "hello WORLD foo BAR\n";
4904
4905    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
4906
4907    assert_eq!(word_diffs, vec!["world", "bar", "WORLD", "BAR"]);
4908}
4909
4910#[gpui::test]
4911async fn test_word_diff_white_space(cx: &mut TestAppContext) {
4912    let settings_store = cx.update(|cx| SettingsStore::test(cx));
4913    cx.set_global(settings_store);
4914
4915    let base_text = "hello world foo bar\n";
4916    let modified_text = "    hello world foo bar\n";
4917
4918    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
4919
4920    assert_eq!(word_diffs, vec!["    "]);
4921}
4922
4923#[gpui::test]
4924async fn test_word_diff_consecutive_modified_lines(cx: &mut TestAppContext) {
4925    let settings_store = cx.update(|cx| SettingsStore::test(cx));
4926    cx.set_global(settings_store);
4927
4928    let base_text = "aaa bbb\nccc ddd\n";
4929    let modified_text = "aaa BBB\nccc DDD\n";
4930
4931    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
4932
4933    assert_eq!(
4934        word_diffs,
4935        vec!["bbb", "ddd", "BBB", "DDD"],
4936        "consecutive modified lines should produce word diffs when line counts match"
4937    );
4938}
4939
4940#[gpui::test]
4941async fn test_word_diff_modified_lines_with_deletion_between(cx: &mut TestAppContext) {
4942    let settings_store = cx.update(|cx| SettingsStore::test(cx));
4943    cx.set_global(settings_store);
4944
4945    let base_text = "aaa bbb\ndeleted line\nccc ddd\n";
4946    let modified_text = "aaa BBB\nccc DDD\n";
4947
4948    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
4949
4950    assert_eq!(
4951        word_diffs,
4952        Vec::<String>::new(),
4953        "modified lines with a deleted line between should not produce word diffs"
4954    );
4955}
4956
4957#[gpui::test]
4958async fn test_word_diff_disabled(cx: &mut TestAppContext) {
4959    let settings_store = cx.update(|cx| {
4960        let mut settings_store = SettingsStore::test(cx);
4961        settings_store.update_user_settings(cx, |settings| {
4962            settings.project.all_languages.defaults.word_diff_enabled = Some(false);
4963        });
4964        settings_store
4965    });
4966    cx.set_global(settings_store);
4967
4968    let base_text = "hello world\n";
4969    let modified_text = "hello WORLD\n";
4970
4971    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
4972
4973    assert_eq!(
4974        word_diffs,
4975        Vec::<String>::new(),
4976        "word diffs should be empty when disabled"
4977    );
4978}
4979
4980/// Tests `excerpt_containing` and `excerpts_for_range` (functions mapping multi-buffer text-coordinates to excerpts)
4981#[gpui::test]
4982fn test_excerpts_containment_functions(cx: &mut App) {
4983    // Multibuffer content for these tests:
4984    //    0123
4985    // 0: aa0
4986    // 1: aa1
4987    //    -----
4988    // 2: bb0
4989    // 3: bb1
4990    //    -----MultiBufferOffset(0)..
4991    // 4: cc0
4992
4993    let buffer_1 = cx.new(|cx| Buffer::local("aa0\naa1", cx));
4994    let buffer_2 = cx.new(|cx| Buffer::local("bb0\nbb1", cx));
4995    let buffer_3 = cx.new(|cx| Buffer::local("cc0", cx));
4996
4997    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
4998
4999    let (excerpt_1_id, excerpt_2_id, excerpt_3_id) = multibuffer.update(cx, |multibuffer, cx| {
5000        multibuffer.set_excerpts_for_path(
5001            PathKey::sorted(0),
5002            buffer_1.clone(),
5003            [Point::new(0, 0)..Point::new(1, 3)],
5004            0,
5005            cx,
5006        );
5007
5008        multibuffer.set_excerpts_for_path(
5009            PathKey::sorted(1),
5010            buffer_2.clone(),
5011            [Point::new(0, 0)..Point::new(1, 3)],
5012            0,
5013            cx,
5014        );
5015
5016        multibuffer.set_excerpts_for_path(
5017            PathKey::sorted(2),
5018            buffer_3.clone(),
5019            [Point::new(0, 0)..Point::new(0, 3)],
5020            0,
5021            cx,
5022        );
5023
5024        let mut ids = multibuffer.excerpt_ids().into_iter();
5025        (
5026            ids.next().unwrap(),
5027            ids.next().unwrap(),
5028            ids.next().unwrap(),
5029        )
5030    });
5031
5032    let snapshot = multibuffer.read(cx).snapshot(cx);
5033
5034    assert_eq!(snapshot.text(), "aa0\naa1\nbb0\nbb1\ncc0");
5035
5036    //// Test `excerpts_for_range`
5037
5038    let p00 = snapshot.point_to_offset(Point::new(0, 0));
5039    let p10 = snapshot.point_to_offset(Point::new(1, 0));
5040    let p20 = snapshot.point_to_offset(Point::new(2, 0));
5041    let p23 = snapshot.point_to_offset(Point::new(2, 3));
5042    let p13 = snapshot.point_to_offset(Point::new(1, 3));
5043    let p40 = snapshot.point_to_offset(Point::new(4, 0));
5044    let p43 = snapshot.point_to_offset(Point::new(4, 3));
5045
5046    let excerpts: Vec<_> = snapshot.excerpts_for_range(p00..p00).collect();
5047    assert_eq!(excerpts.len(), 1);
5048    assert_eq!(excerpts[0].id, excerpt_1_id);
5049
5050    // Cursor at very end of excerpt 3
5051    let excerpts: Vec<_> = snapshot.excerpts_for_range(p43..p43).collect();
5052    assert_eq!(excerpts.len(), 1);
5053    assert_eq!(excerpts[0].id, excerpt_3_id);
5054
5055    let excerpts: Vec<_> = snapshot.excerpts_for_range(p00..p23).collect();
5056    assert_eq!(excerpts.len(), 2);
5057    assert_eq!(excerpts[0].id, excerpt_1_id);
5058    assert_eq!(excerpts[1].id, excerpt_2_id);
5059
5060    // This range represent an selection with end-point just inside excerpt_2
5061    // Today we only expand the first excerpt, but another interpretation that
5062    // we could consider is expanding both here
5063    let excerpts: Vec<_> = snapshot.excerpts_for_range(p10..p20).collect();
5064    assert_eq!(excerpts.len(), 1);
5065    assert_eq!(excerpts[0].id, excerpt_1_id);
5066
5067    //// Test that `excerpts_for_range` and `excerpt_containing` agree for all single offsets (cursor positions)
5068    for offset in 0..=snapshot.len().0 {
5069        let offset = MultiBufferOffset(offset);
5070        let excerpts_for_range: Vec<_> = snapshot.excerpts_for_range(offset..offset).collect();
5071        assert_eq!(
5072            excerpts_for_range.len(),
5073            1,
5074            "Expected exactly one excerpt for offset {offset}",
5075        );
5076
5077        let excerpt_containing = snapshot.excerpt_containing(offset..offset);
5078        assert!(
5079            excerpt_containing.is_some(),
5080            "Expected excerpt_containing to find excerpt for offset {offset}",
5081        );
5082
5083        assert_eq!(
5084            excerpts_for_range[0].id,
5085            excerpt_containing.unwrap().id(),
5086            "excerpts_for_range and excerpt_containing should agree for offset {offset}",
5087        );
5088    }
5089
5090    //// Test `excerpt_containing` behavior with ranges:
5091
5092    // Ranges intersecting a single-excerpt
5093    let containing = snapshot.excerpt_containing(p00..p13);
5094    assert!(containing.is_some());
5095    assert_eq!(containing.unwrap().id(), excerpt_1_id);
5096
5097    // Ranges intersecting multiple excerpts (should return None)
5098    let containing = snapshot.excerpt_containing(p20..p40);
5099    assert!(
5100        containing.is_none(),
5101        "excerpt_containing should return None for ranges spanning multiple excerpts"
5102    );
5103}
5104
5105#[gpui::test]
5106fn test_range_to_buffer_ranges_with_range_bounds(cx: &mut App) {
5107    use std::ops::Bound;
5108
5109    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb", cx));
5110    let buffer_2 = cx.new(|cx| Buffer::local("ccc", cx));
5111
5112    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5113    let (excerpt_1_id, excerpt_2_id) = multibuffer.update(cx, |multibuffer, cx| {
5114        multibuffer.set_excerpts_for_path(
5115            PathKey::sorted(0),
5116            buffer_1.clone(),
5117            [Point::new(0, 0)..Point::new(1, 3)],
5118            0,
5119            cx,
5120        );
5121
5122        multibuffer.set_excerpts_for_path(
5123            PathKey::sorted(1),
5124            buffer_2.clone(),
5125            [Point::new(0, 0)..Point::new(0, 3)],
5126            0,
5127            cx,
5128        );
5129
5130        let excerpt_ids = multibuffer.excerpt_ids();
5131
5132        (excerpt_ids[0], excerpt_ids[1])
5133    });
5134
5135    let snapshot = multibuffer.read(cx).snapshot(cx);
5136    assert_eq!(snapshot.text(), "aaa\nbbb\nccc");
5137
5138    let excerpt_2_start = Point::new(2, 0);
5139
5140    let ranges_half_open = snapshot.range_to_buffer_ranges(Point::zero()..excerpt_2_start);
5141    assert_eq!(
5142        ranges_half_open.len(),
5143        1,
5144        "Half-open range ending at excerpt start should EXCLUDE that excerpt"
5145    );
5146    assert_eq!(ranges_half_open[0].2, excerpt_1_id);
5147
5148    let ranges_inclusive = snapshot.range_to_buffer_ranges(Point::zero()..=excerpt_2_start);
5149    assert_eq!(
5150        ranges_inclusive.len(),
5151        2,
5152        "Inclusive range ending at excerpt start should INCLUDE that excerpt"
5153    );
5154    assert_eq!(ranges_inclusive[0].2, excerpt_1_id);
5155    assert_eq!(ranges_inclusive[1].2, excerpt_2_id);
5156
5157    let ranges_unbounded =
5158        snapshot.range_to_buffer_ranges((Bound::Included(Point::zero()), Bound::Unbounded));
5159    assert_eq!(
5160        ranges_unbounded.len(),
5161        2,
5162        "Unbounded end should include all excerpts"
5163    );
5164    assert_eq!(ranges_unbounded[0].2, excerpt_1_id);
5165    assert_eq!(ranges_unbounded[1].2, excerpt_2_id);
5166
5167    let ranges_excluded_end = snapshot.range_to_buffer_ranges((
5168        Bound::Included(Point::zero()),
5169        Bound::Excluded(excerpt_2_start),
5170    ));
5171    assert_eq!(
5172        ranges_excluded_end.len(),
5173        1,
5174        "Excluded end bound should exclude excerpt starting at that point"
5175    );
5176    assert_eq!(ranges_excluded_end[0].2, excerpt_1_id);
5177
5178    let buffer_empty = cx.new(|cx| Buffer::local("", cx));
5179    let multibuffer_trailing_empty = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5180    let (te_excerpt_1_id, te_excerpt_2_id) =
5181        multibuffer_trailing_empty.update(cx, |multibuffer, cx| {
5182            multibuffer.set_excerpts_for_path(
5183                PathKey::sorted(0),
5184                buffer_1.clone(),
5185                [Point::new(0, 0)..Point::new(1, 3)],
5186                0,
5187                cx,
5188            );
5189
5190            multibuffer.set_excerpts_for_path(
5191                PathKey::sorted(1),
5192                buffer_empty.clone(),
5193                [Point::new(0, 0)..Point::new(0, 0)],
5194                0,
5195                cx,
5196            );
5197
5198            let excerpt_ids = multibuffer.excerpt_ids();
5199            (excerpt_ids[0], excerpt_ids[1])
5200        });
5201
5202    let snapshot_trailing = multibuffer_trailing_empty.read(cx).snapshot(cx);
5203    assert_eq!(snapshot_trailing.text(), "aaa\nbbb\n");
5204
5205    let max_point = snapshot_trailing.max_point();
5206
5207    let ranges_half_open_max = snapshot_trailing.range_to_buffer_ranges(Point::zero()..max_point);
5208    assert_eq!(
5209        ranges_half_open_max.len(),
5210        1,
5211        "Half-open range to max_point should EXCLUDE trailing empty excerpt at max_point"
5212    );
5213    assert_eq!(ranges_half_open_max[0].2, te_excerpt_1_id);
5214
5215    let ranges_inclusive_max = snapshot_trailing.range_to_buffer_ranges(Point::zero()..=max_point);
5216    assert_eq!(
5217        ranges_inclusive_max.len(),
5218        2,
5219        "Inclusive range to max_point should INCLUDE trailing empty excerpt"
5220    );
5221    assert_eq!(ranges_inclusive_max[0].2, te_excerpt_1_id);
5222    assert_eq!(ranges_inclusive_max[1].2, te_excerpt_2_id);
5223
5224    let ranges_unbounded_trailing = snapshot_trailing
5225        .range_to_buffer_ranges((Bound::Included(Point::zero()), Bound::Unbounded));
5226    assert_eq!(
5227        ranges_unbounded_trailing.len(),
5228        2,
5229        "Unbounded end should include trailing empty excerpt"
5230    );
5231    assert_eq!(ranges_unbounded_trailing[0].2, te_excerpt_1_id);
5232    assert_eq!(ranges_unbounded_trailing[1].2, te_excerpt_2_id);
5233}
5234
5235#[gpui::test]
5236fn test_cannot_seek_backward_after_excerpt_replacement(cx: &mut TestAppContext) {
5237    let buffer_b_text: String = (0..50).map(|i| format!("line_b {i}\n")).collect();
5238    let buffer_b = cx.new(|cx| Buffer::local(buffer_b_text, cx));
5239
5240    let buffer_c_text: String = (0..10).map(|i| format!("line_c {i}\n")).collect();
5241    let buffer_c = cx.new(|cx| Buffer::local(buffer_c_text, cx));
5242
5243    let buffer_d_text: String = (0..10).map(|i| format!("line_d {i}\n")).collect();
5244    let buffer_d = cx.new(|cx| Buffer::local(buffer_d_text, cx));
5245
5246    let path_b = PathKey::with_sort_prefix(0, rel_path("bbb.rs").into_arc());
5247    let path_c = PathKey::with_sort_prefix(0, rel_path("ddd.rs").into_arc());
5248    let path_d = PathKey::with_sort_prefix(0, rel_path("ccc.rs").into_arc());
5249
5250    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5251
5252    multibuffer.update(cx, |multibuffer, cx| {
5253        multibuffer.set_excerpts_for_path(
5254            path_b.clone(),
5255            buffer_b.clone(),
5256            vec![
5257                Point::row_range(0..3),
5258                Point::row_range(15..18),
5259                Point::row_range(30..33),
5260            ],
5261            0,
5262            cx,
5263        );
5264    });
5265
5266    multibuffer.update(cx, |multibuffer, cx| {
5267        multibuffer.set_excerpts_for_path(
5268            path_c.clone(),
5269            buffer_c.clone(),
5270            vec![Point::row_range(0..3)],
5271            0,
5272            cx,
5273        );
5274    });
5275
5276    let (anchor_in_e_b2, anchor_in_e_b3) = multibuffer.read_with(cx, |multibuffer, cx| {
5277        let snapshot = multibuffer.snapshot(cx);
5278        let excerpt_ids: Vec<ExcerptId> = snapshot.excerpts().map(|(id, _, _)| id).collect();
5279        assert_eq!(excerpt_ids.len(), 4, "expected 4 excerpts (3×B + 1×C)");
5280
5281        let e_b2_id = excerpt_ids[1];
5282        let e_b3_id = excerpt_ids[2];
5283
5284        let e_b2 = snapshot.excerpt(e_b2_id).expect("E_B2 should exist");
5285        let e_b3 = snapshot.excerpt(e_b3_id).expect("E_B3 should exist");
5286
5287        let anchor_b2 = Anchor::in_buffer(e_b2_id, e_b2.range.context.start);
5288        let anchor_b3 = Anchor::in_buffer(e_b3_id, e_b3.range.context.start);
5289        (anchor_b2, anchor_b3)
5290    });
5291
5292    multibuffer.update(cx, |multibuffer, cx| {
5293        multibuffer.set_excerpts_for_path(
5294            path_b.clone(),
5295            buffer_b.clone(),
5296            vec![Point::row_range(0..3), Point::row_range(28..36)],
5297            0,
5298            cx,
5299        );
5300    });
5301
5302    multibuffer.update(cx, |multibuffer, cx| {
5303        multibuffer.set_excerpts_for_path(
5304            path_d.clone(),
5305            buffer_d.clone(),
5306            vec![Point::row_range(0..3)],
5307            0,
5308            cx,
5309        );
5310    });
5311
5312    multibuffer.read_with(cx, |multibuffer, cx| {
5313        let snapshot = multibuffer.snapshot(cx);
5314        snapshot.summaries_for_anchors::<Point, _>(&[anchor_in_e_b2, anchor_in_e_b3]);
5315    });
5316}