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