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