multi_buffer_tests.rs

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