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: Range<Point>,
2902    // if this is a deleted hunk, the main buffer anchor to which the deleted content is attached
2903    deleted_hunk_anchor: Option<text::Anchor>,
2904    status: Option<DiffHunkStatus>,
2905    excerpt: Option<ReferenceExcerpt>,
2906}
2907
2908impl ReferenceMultibuffer {
2909    fn expand_excerpts(
2910        &mut self,
2911        excerpts: &HashSet<ExcerptRange<text::Anchor>>,
2912        line_count: u32,
2913        cx: &mut App,
2914    ) {
2915        use text::AnchorRangeExt as _;
2916
2917        if line_count == 0 || excerpts.is_empty() {
2918            return;
2919        }
2920
2921        let mut excerpts_by_buffer: HashMap<BufferId, Vec<ExcerptRange<text::Anchor>>> =
2922            HashMap::default();
2923        for excerpt in excerpts {
2924            excerpts_by_buffer
2925                .entry(excerpt.context.start.buffer_id)
2926                .or_default()
2927                .push(excerpt.clone())
2928        }
2929
2930        for (buffer_id, excerpts_to_expand) in excerpts_by_buffer {
2931            let mut buffer = None;
2932            let mut buffer_snapshot = None;
2933            let mut path = None;
2934            let mut path_key_index = None;
2935            let mut new_ranges =
2936                self.excerpts
2937                    .iter()
2938                    .filter(|excerpt| excerpt.range.start.buffer_id == buffer_id)
2939                    .map(|excerpt| {
2940                        let snapshot = excerpt.buffer.read(cx).snapshot();
2941                        let mut range = excerpt.range.to_point(&snapshot);
2942                        if excerpts_to_expand.iter().any(|info| {
2943                            excerpt.range.contains_anchor(info.context.start, &snapshot)
2944                        }) {
2945                            range.start = Point::new(range.start.row.saturating_sub(line_count), 0);
2946                            range.end = snapshot
2947                                .clip_point(Point::new(range.end.row + line_count, 0), Bias::Left);
2948                            range.end.column = snapshot.line_len(range.end.row);
2949                        }
2950                        buffer = Some(excerpt.buffer.clone());
2951                        buffer_snapshot = Some(snapshot);
2952                        path = Some(excerpt.path_key.clone());
2953                        path_key_index = Some(excerpt.path_key_index);
2954                        ExcerptRange::new(range)
2955                    })
2956                    .collect::<Vec<_>>();
2957
2958            new_ranges.sort_by(|l, r| l.context.start.cmp(&r.context.start));
2959
2960            self.set_excerpts(
2961                path.unwrap(),
2962                path_key_index.unwrap(),
2963                buffer.unwrap(),
2964                &buffer_snapshot.unwrap(),
2965                new_ranges,
2966                cx,
2967            );
2968        }
2969    }
2970
2971    fn set_excerpts(
2972        &mut self,
2973        path_key: PathKey,
2974        path_key_index: PathKeyIndex,
2975        buffer: Entity<Buffer>,
2976        buffer_snapshot: &BufferSnapshot,
2977        ranges: Vec<ExcerptRange<Point>>,
2978        cx: &mut App,
2979    ) {
2980        self.excerpts.retain(|excerpt| {
2981            excerpt.path_key != path_key && excerpt.buffer.entity_id() != buffer.entity_id()
2982        });
2983
2984        let ranges = MultiBuffer::merge_excerpt_ranges(&ranges);
2985
2986        let (Ok(ix) | Err(ix)) = self
2987            .excerpts
2988            .binary_search_by(|probe| probe.path_key.cmp(&path_key));
2989        self.excerpts.splice(
2990            ix..ix,
2991            ranges.into_iter().map(|range| ReferenceExcerpt {
2992                path_key: path_key.clone(),
2993                path_key_index,
2994                buffer: buffer.clone(),
2995                range: buffer_snapshot.anchor_before(range.context.start)
2996                    ..buffer_snapshot.anchor_after(range.context.end),
2997            }),
2998        );
2999        self.update_expanded_diff_hunks_for_buffer(buffer_snapshot.remote_id(), cx);
3000    }
3001
3002    fn expand_diff_hunks(&mut self, path_key: PathKey, range: Range<text::Anchor>, cx: &App) {
3003        let excerpt = self
3004            .excerpts
3005            .iter_mut()
3006            .find(|e| {
3007                e.path_key == path_key
3008                    && e.range
3009                        .start
3010                        .cmp(&range.start, &e.buffer.read(cx).snapshot())
3011                        .is_le()
3012                    && e.range
3013                        .end
3014                        .cmp(&range.end, &e.buffer.read(cx).snapshot())
3015                        .is_ge()
3016            })
3017            .unwrap();
3018        let buffer = excerpt.buffer.read(cx).snapshot();
3019        let buffer_id = buffer.remote_id();
3020
3021        // Skip inverted excerpts - hunks are always expanded
3022        if self.inverted_diffs.contains_key(&buffer_id) {
3023            return;
3024        }
3025
3026        let Some(diff) = self.diffs.get(&buffer_id) else {
3027            return;
3028        };
3029        let excerpt_range = excerpt.range.to_point(&buffer);
3030        let expanded_diff_hunks = self
3031            .expanded_diff_hunks_by_buffer
3032            .entry(buffer_id)
3033            .or_default();
3034        for hunk in diff
3035            .read(cx)
3036            .snapshot(cx)
3037            .hunks_intersecting_range(range, &buffer)
3038        {
3039            let hunk_range = hunk.buffer_range.to_point(&buffer);
3040            if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end {
3041                continue;
3042            }
3043            if let Err(ix) = expanded_diff_hunks
3044                .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
3045            {
3046                log::info!(
3047                    "expanding diff hunk {:?}. excerpt range: {:?}, buffer {:?}",
3048                    hunk_range,
3049                    excerpt_range,
3050                    buffer.remote_id()
3051                );
3052                expanded_diff_hunks.insert(ix, hunk.buffer_range.start);
3053            } else {
3054                log::trace!("hunk {hunk_range:?} already expanded in excerpt");
3055            }
3056        }
3057    }
3058
3059    fn expected_content(
3060        &self,
3061        cx: &App,
3062    ) -> (
3063        String,
3064        Vec<RowInfo>,
3065        HashSet<MultiBufferRow>,
3066        Vec<ReferenceRegion>,
3067    ) {
3068        use util::maybe;
3069
3070        let mut text = String::new();
3071        let mut regions = Vec::<ReferenceRegion>::new();
3072        let mut excerpt_boundary_rows = HashSet::default();
3073        for excerpt in &self.excerpts {
3074            excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
3075            let buffer = excerpt.buffer.read(cx);
3076            let buffer_id = buffer.remote_id();
3077            let buffer_range = excerpt.range.to_offset(buffer);
3078
3079            if let Some((diff, main_buffer)) = self.inverted_diffs.get(&buffer_id) {
3080                let diff_snapshot = diff.read(cx).snapshot(cx);
3081                let main_buffer_snapshot = main_buffer.read(cx).snapshot();
3082
3083                let mut offset = buffer_range.start;
3084                for hunk in diff_snapshot.hunks_intersecting_base_text_range(
3085                    buffer_range.clone(),
3086                    &main_buffer_snapshot.text,
3087                ) {
3088                    let mut hunk_base_range = hunk.diff_base_byte_range.clone();
3089
3090                    hunk_base_range.end = hunk_base_range.end.min(buffer_range.end);
3091                    if hunk_base_range.start > buffer_range.end
3092                        || hunk_base_range.start < buffer_range.start
3093                    {
3094                        continue;
3095                    }
3096
3097                    // Add the text before the hunk
3098                    if hunk_base_range.start >= offset {
3099                        let len = text.len();
3100                        text.extend(buffer.text_for_range(offset..hunk_base_range.start));
3101                        if text.len() > len {
3102                            regions.push(ReferenceRegion {
3103                                buffer_id: Some(buffer_id),
3104                                range: len..text.len(),
3105                                buffer_range: (offset..hunk_base_range.start).to_point(&buffer),
3106                                status: None,
3107                                excerpt: Some(excerpt.clone()),
3108                                deleted_hunk_anchor: None,
3109                            });
3110                        }
3111                    }
3112
3113                    // Add the "deleted" region (base text that's not in main)
3114                    if !hunk_base_range.is_empty() {
3115                        let len = text.len();
3116                        text.extend(buffer.text_for_range(hunk_base_range.clone()));
3117                        regions.push(ReferenceRegion {
3118                            buffer_id: Some(buffer_id),
3119                            range: len..text.len(),
3120                            buffer_range: hunk_base_range.to_point(&buffer),
3121                            status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
3122                            excerpt: Some(excerpt.clone()),
3123                            deleted_hunk_anchor: None,
3124                        });
3125                    }
3126
3127                    offset = hunk_base_range.end;
3128                }
3129
3130                // Add remaining buffer text
3131                let len = text.len();
3132                text.extend(buffer.text_for_range(offset..buffer_range.end));
3133                text.push('\n');
3134                regions.push(ReferenceRegion {
3135                    buffer_id: Some(buffer_id),
3136                    range: len..text.len(),
3137                    buffer_range: (offset..buffer_range.end).to_point(&buffer),
3138                    status: None,
3139                    excerpt: Some(excerpt.clone()),
3140                    deleted_hunk_anchor: None,
3141                });
3142            } else {
3143                let diff = self.diffs.get(&buffer_id).unwrap().read(cx).snapshot(cx);
3144                let base_buffer = diff.base_text();
3145
3146                let mut offset = buffer_range.start;
3147                let hunks = diff
3148                    .hunks_intersecting_range(excerpt.range.clone(), buffer)
3149                    .peekable();
3150
3151                for hunk in hunks {
3152                    // Ignore hunks that are outside the excerpt range.
3153                    let mut hunk_range = hunk.buffer_range.to_offset(buffer);
3154
3155                    hunk_range.end = hunk_range.end.min(buffer_range.end);
3156                    if hunk_range.start > buffer_range.end || hunk_range.start < buffer_range.start
3157                    {
3158                        log::trace!("skipping hunk outside excerpt range");
3159                        continue;
3160                    }
3161
3162                    if !self
3163                        .expanded_diff_hunks_by_buffer
3164                        .get(&buffer_id)
3165                        .cloned()
3166                        .into_iter()
3167                        .flatten()
3168                        .any(|expanded_anchor| {
3169                            expanded_anchor
3170                                .cmp(&hunk.buffer_range.start, buffer)
3171                                .is_eq()
3172                        })
3173                    {
3174                        log::trace!("skipping a hunk that's not marked as expanded");
3175                        continue;
3176                    }
3177
3178                    if !hunk.buffer_range.start.is_valid(buffer) {
3179                        log::trace!("skipping hunk with deleted start: {:?}", hunk.range);
3180                        continue;
3181                    }
3182
3183                    if hunk_range.start >= offset {
3184                        // Add the buffer text before the hunk
3185                        let len = text.len();
3186                        text.extend(buffer.text_for_range(offset..hunk_range.start));
3187                        if text.len() > len {
3188                            regions.push(ReferenceRegion {
3189                                buffer_id: Some(buffer_id),
3190                                range: len..text.len(),
3191                                buffer_range: (offset..hunk_range.start).to_point(&buffer),
3192                                status: None,
3193                                excerpt: Some(excerpt.clone()),
3194                                deleted_hunk_anchor: None,
3195                            });
3196                        }
3197
3198                        // Add the deleted text for the hunk.
3199                        if !hunk.diff_base_byte_range.is_empty() {
3200                            let mut base_text = base_buffer
3201                                .text_for_range(hunk.diff_base_byte_range.clone())
3202                                .collect::<String>();
3203                            if !base_text.ends_with('\n') {
3204                                base_text.push('\n');
3205                            }
3206                            let len = text.len();
3207                            text.push_str(&base_text);
3208                            regions.push(ReferenceRegion {
3209                                buffer_id: Some(base_buffer.remote_id()),
3210                                range: len..text.len(),
3211                                buffer_range: hunk.diff_base_byte_range.to_point(&base_buffer),
3212                                status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
3213                                excerpt: Some(excerpt.clone()),
3214                                deleted_hunk_anchor: Some(hunk.buffer_range.start),
3215                            });
3216                        }
3217
3218                        offset = hunk_range.start;
3219                    }
3220
3221                    // Add the inserted text for the hunk.
3222                    if hunk_range.end > offset {
3223                        let len = text.len();
3224                        text.extend(buffer.text_for_range(offset..hunk_range.end));
3225                        let range = len..text.len();
3226                        let region = ReferenceRegion {
3227                            buffer_id: Some(buffer_id),
3228                            range,
3229                            buffer_range: (offset..hunk_range.end).to_point(&buffer),
3230                            status: Some(DiffHunkStatus::added(hunk.secondary_status)),
3231                            excerpt: Some(excerpt.clone()),
3232                            deleted_hunk_anchor: None,
3233                        };
3234                        offset = hunk_range.end;
3235                        regions.push(region);
3236                    }
3237                }
3238
3239                // Add the buffer text for the rest of the excerpt.
3240                let len = text.len();
3241                text.extend(buffer.text_for_range(offset..buffer_range.end));
3242                text.push('\n');
3243                regions.push(ReferenceRegion {
3244                    buffer_id: Some(buffer_id),
3245                    range: len..text.len(),
3246                    buffer_range: (offset..buffer_range.end).to_point(&buffer),
3247                    status: None,
3248                    excerpt: Some(excerpt.clone()),
3249                    deleted_hunk_anchor: None,
3250                });
3251            }
3252        }
3253
3254        // Remove final trailing newline.
3255        if self.excerpts.is_empty() {
3256            regions.push(ReferenceRegion {
3257                buffer_id: None,
3258                range: 0..1,
3259                buffer_range: Point::new(0, 0)..Point::new(0, 1),
3260                status: None,
3261                excerpt: None,
3262                deleted_hunk_anchor: None,
3263            });
3264        } else {
3265            text.pop();
3266            let region = regions.last_mut().unwrap();
3267            assert!(region.deleted_hunk_anchor.is_none());
3268            region.range.end -= 1;
3269        }
3270
3271        // Retrieve the row info using the region that contains
3272        // the start of each multi-buffer line.
3273        let mut ix = 0;
3274        let row_infos = text
3275            .split('\n')
3276            .map(|line| {
3277                let row_info = regions
3278                    .iter()
3279                    .rposition(|region| {
3280                        region.range.contains(&ix) || (ix == text.len() && ix == region.range.end)
3281                    })
3282                    .map_or(RowInfo::default(), |region_ix| {
3283                        let region = regions[region_ix].clone();
3284                        let buffer_row = region.buffer_range.start.row
3285                            + text[region.range.start..ix].matches('\n').count() as u32;
3286                        let main_buffer = region.excerpt.as_ref().map(|e| e.buffer.clone());
3287                        let excerpt_range = region.excerpt.as_ref().map(|e| &e.range);
3288                        let is_excerpt_start = region_ix == 0
3289                            || regions[region_ix - 1].excerpt.as_ref().map(|e| &e.range)
3290                                != excerpt_range
3291                            || regions[region_ix - 1].range.is_empty();
3292                        let mut is_excerpt_end = region_ix == regions.len() - 1
3293                            || regions[region_ix + 1].excerpt.as_ref().map(|e| &e.range)
3294                                != excerpt_range;
3295                        let is_start = !text[region.range.start..ix].contains('\n');
3296                        let is_last_region = region_ix == regions.len() - 1;
3297                        let mut is_end = if region.range.end > text.len() {
3298                            !text[ix..].contains('\n')
3299                        } else {
3300                            let remaining_newlines = text[ix..region.range.end.min(text.len())]
3301                                .matches('\n')
3302                                .count();
3303                            remaining_newlines == if is_last_region { 0 } else { 1 }
3304                        };
3305                        if region_ix < regions.len() - 1
3306                            && !text[ix..].contains("\n")
3307                            && (region.status == Some(DiffHunkStatus::added_none())
3308                                || region.status.is_some_and(|s| s.is_deleted()))
3309                            && regions[region_ix + 1].excerpt.as_ref().map(|e| &e.range)
3310                                == excerpt_range
3311                            && regions[region_ix + 1].range.start == text.len()
3312                        {
3313                            is_end = true;
3314                            is_excerpt_end = true;
3315                        }
3316                        let multibuffer_row =
3317                            MultiBufferRow(text[..ix].matches('\n').count() as u32);
3318                        let mut expand_direction = None;
3319                        if let Some(buffer) = &main_buffer {
3320                            let needs_expand_up = is_excerpt_start && is_start && buffer_row > 0;
3321                            let needs_expand_down = is_excerpt_end
3322                                && is_end
3323                                && buffer.read(cx).max_point().row > buffer_row;
3324                            expand_direction = if needs_expand_up && needs_expand_down {
3325                                Some(ExpandExcerptDirection::UpAndDown)
3326                            } else if needs_expand_up {
3327                                Some(ExpandExcerptDirection::Up)
3328                            } else if needs_expand_down {
3329                                Some(ExpandExcerptDirection::Down)
3330                            } else {
3331                                None
3332                            };
3333                        }
3334                        RowInfo {
3335                            buffer_id: region.buffer_id,
3336                            diff_status: region.status,
3337                            buffer_row: Some(buffer_row),
3338                            wrapped_buffer_row: None,
3339
3340                            multibuffer_row: Some(multibuffer_row),
3341                            expand_info: maybe!({
3342                                let direction = expand_direction?;
3343                                let excerpt = region.excerpt.as_ref()?;
3344                                Some(ExpandInfo {
3345                                    direction,
3346                                    start_anchor: Anchor::in_buffer(
3347                                        excerpt.path_key_index,
3348                                        excerpt.range.start,
3349                                    ),
3350                                })
3351                            }),
3352                        }
3353                    });
3354                ix += line.len() + 1;
3355                row_info
3356            })
3357            .collect();
3358
3359        (text, row_infos, excerpt_boundary_rows, regions)
3360    }
3361
3362    fn diffs_updated(&mut self, cx: &mut App) {
3363        let buffer_ids = self.diffs.keys().copied().collect::<Vec<_>>();
3364        for buffer_id in buffer_ids {
3365            self.update_expanded_diff_hunks_for_buffer(buffer_id, cx);
3366        }
3367    }
3368
3369    fn add_diff(&mut self, diff: Entity<BufferDiff>, cx: &mut App) {
3370        let buffer_id = diff.read(cx).buffer_id;
3371        self.diffs.insert(buffer_id, diff);
3372    }
3373
3374    fn add_inverted_diff(
3375        &mut self,
3376        diff: Entity<BufferDiff>,
3377        main_buffer: Entity<language::Buffer>,
3378        cx: &App,
3379    ) {
3380        let base_text_buffer_id = diff.read(cx).base_text(cx).remote_id();
3381        self.inverted_diffs
3382            .insert(base_text_buffer_id, (diff, main_buffer));
3383    }
3384
3385    fn update_expanded_diff_hunks_for_buffer(&mut self, buffer_id: BufferId, cx: &mut App) {
3386        let excerpts = self
3387            .excerpts
3388            .iter()
3389            .filter(|excerpt| excerpt.buffer.read(cx).remote_id() == buffer_id)
3390            .collect::<Vec<_>>();
3391        let Some(buffer) = excerpts.first().map(|excerpt| excerpt.buffer.clone()) else {
3392            self.expanded_diff_hunks_by_buffer.remove(&buffer_id);
3393            return;
3394        };
3395        let buffer_snapshot = buffer.read(cx).snapshot();
3396        let Some(diff) = self.diffs.get(&buffer_id) else {
3397            self.expanded_diff_hunks_by_buffer.remove(&buffer_id);
3398            return;
3399        };
3400        let diff = diff.read(cx).snapshot(cx);
3401        let hunks = diff
3402            .hunks_in_row_range(0..u32::MAX, &buffer_snapshot)
3403            .collect::<Vec<_>>();
3404        self.expanded_diff_hunks_by_buffer
3405            .entry(buffer_id)
3406            .or_default()
3407            .retain(|hunk_anchor| {
3408                if !hunk_anchor.is_valid(&buffer_snapshot) {
3409                    return false;
3410                }
3411
3412                let Ok(ix) = hunks.binary_search_by(|hunk| {
3413                    hunk.buffer_range.start.cmp(hunk_anchor, &buffer_snapshot)
3414                }) else {
3415                    return false;
3416                };
3417                let hunk_range = hunks[ix].buffer_range.to_point(&buffer_snapshot);
3418                excerpts.iter().any(|excerpt| {
3419                    let excerpt_range = excerpt.range.to_point(&buffer_snapshot);
3420                    hunk_range.start >= excerpt_range.start && hunk_range.start <= excerpt_range.end
3421                })
3422            });
3423    }
3424
3425    fn anchor_to_offset(&self, anchor: &Anchor, cx: &App) -> Option<MultiBufferOffset> {
3426        if anchor.diff_base_anchor().is_some() {
3427            panic!("reference multibuffer cannot yet resolve anchors inside deleted hunks");
3428        }
3429        let (anchor, snapshot, path_key) = self.anchor_to_buffer_anchor(anchor, cx)?;
3430        // TODO(cole) can maybe make this and expected content call a common function instead
3431        let (text, _, _, regions) = self.expected_content(cx);
3432
3433        // Locate the first region that contains or is past the putative location of the buffer anchor
3434        let ix = regions.partition_point(|region| {
3435            let excerpt = region
3436                .excerpt
3437                .as_ref()
3438                .expect("should have no buffers in empty reference multibuffer");
3439            excerpt
3440                .path_key
3441                .cmp(&path_key)
3442                .then_with(|| {
3443                    if excerpt.range.end.cmp(&anchor, &snapshot).is_lt() {
3444                        Ordering::Less
3445                    } else if excerpt.range.start.cmp(&anchor, &snapshot).is_gt() {
3446                        Ordering::Greater
3447                    } else {
3448                        Ordering::Equal
3449                    }
3450                })
3451                .then_with(|| {
3452                    if let Some(deleted_hunk_anchor) = region.deleted_hunk_anchor {
3453                        deleted_hunk_anchor.cmp(&anchor, &snapshot)
3454                    } else {
3455                        let point = anchor.to_point(&snapshot);
3456                        assert_eq!(region.buffer_id, Some(snapshot.remote_id()));
3457                        if region.buffer_range.end < point {
3458                            Ordering::Less
3459                        } else if region.buffer_range.start > point {
3460                            Ordering::Greater
3461                        } else {
3462                            Ordering::Equal
3463                        }
3464                    }
3465                })
3466                .is_lt()
3467        });
3468
3469        let Some(region) = regions.get(ix) else {
3470            return Some(MultiBufferOffset(text.len()));
3471        };
3472
3473        let offset = if region.buffer_id == Some(snapshot.remote_id()) {
3474            let buffer_offset = anchor.to_offset(&snapshot);
3475            let buffer_range = region.buffer_range.to_offset(&snapshot);
3476            assert!(buffer_offset <= buffer_range.end);
3477            let overshoot = buffer_offset.saturating_sub(buffer_range.start);
3478            region.range.start + overshoot
3479        } else {
3480            region.range.start
3481        };
3482        Some(MultiBufferOffset(offset))
3483    }
3484
3485    fn anchor_to_buffer_anchor(
3486        &self,
3487        anchor: &Anchor,
3488        cx: &App,
3489    ) -> Option<(text::Anchor, BufferSnapshot, PathKey)> {
3490        let (excerpt, anchor) = match anchor {
3491            Anchor::Min => {
3492                let excerpt = self.excerpts.first()?;
3493                (excerpt, excerpt.range.start)
3494            }
3495            Anchor::Excerpt(excerpt_anchor) => (
3496                self.excerpts.iter().find(|excerpt| {
3497                    excerpt.buffer.read(cx).remote_id() == excerpt_anchor.buffer_id()
3498                })?,
3499                excerpt_anchor.text_anchor,
3500            ),
3501            Anchor::Max => {
3502                let excerpt = self.excerpts.last()?;
3503                (excerpt, excerpt.range.end)
3504            }
3505        };
3506
3507        Some((
3508            anchor,
3509            excerpt.buffer.read(cx).snapshot(),
3510            excerpt.path_key.clone(),
3511        ))
3512    }
3513}
3514
3515#[gpui::test(iterations = 100)]
3516async fn test_random_set_ranges(cx: &mut TestAppContext, mut rng: StdRng) {
3517    let base_text = "a\n".repeat(100);
3518    let buf = cx.update(|cx| cx.new(|cx| Buffer::local(base_text, cx)));
3519    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
3520
3521    let operations = env::var("OPERATIONS")
3522        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3523        .unwrap_or(10);
3524
3525    fn row_ranges(ranges: &Vec<Range<Point>>) -> Vec<Range<u32>> {
3526        ranges
3527            .iter()
3528            .map(|range| range.start.row..range.end.row)
3529            .collect()
3530    }
3531
3532    for _ in 0..operations {
3533        let snapshot = buf.update(cx, |buf, _| buf.snapshot());
3534        let num_ranges = rng.random_range(0..=10);
3535        let max_row = snapshot.max_point().row;
3536        let mut ranges = (0..num_ranges)
3537            .map(|_| {
3538                let start = rng.random_range(0..max_row);
3539                let end = rng.random_range(start + 1..max_row + 1);
3540                Point::row_range(start..end)
3541            })
3542            .collect::<Vec<_>>();
3543        ranges.sort_by_key(|range| range.start);
3544        log::info!("Setting ranges: {:?}", row_ranges(&ranges));
3545        multibuffer.update(cx, |multibuffer, cx| {
3546            multibuffer.set_excerpts_for_path(
3547                PathKey::for_buffer(&buf, cx),
3548                buf.clone(),
3549                ranges.clone(),
3550                2,
3551                cx,
3552            )
3553        });
3554
3555        let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3556        let mut last_end = None;
3557        let mut seen_ranges = Vec::default();
3558
3559        for info in snapshot.excerpts() {
3560            let buffer_snapshot = snapshot
3561                .buffer_for_id(info.context.start.buffer_id)
3562                .unwrap();
3563            let start = info.context.start.to_point(buffer_snapshot);
3564            let end = info.context.end.to_point(buffer_snapshot);
3565            seen_ranges.push(start..end);
3566
3567            if let Some(last_end) = last_end.take() {
3568                assert!(
3569                    start > last_end,
3570                    "multibuffer has out-of-order ranges: {:?}; {:?} <= {:?}",
3571                    row_ranges(&seen_ranges),
3572                    start,
3573                    last_end
3574                )
3575            }
3576
3577            ranges.retain(|range| range.start < start || range.end > end);
3578
3579            last_end = Some(end)
3580        }
3581
3582        assert!(
3583            ranges.is_empty(),
3584            "multibuffer {:?} did not include all ranges: {:?}",
3585            row_ranges(&seen_ranges),
3586            row_ranges(&ranges)
3587        );
3588    }
3589}
3590
3591#[gpui::test(iterations = 100)]
3592async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
3593    let operations = env::var("OPERATIONS")
3594        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3595        .unwrap_or(10);
3596    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
3597    let mut buffers: Vec<Entity<Buffer>> = Vec::new();
3598    let mut base_texts: HashMap<BufferId, String> = HashMap::default();
3599    let mut reference = ReferenceMultibuffer::default();
3600    let mut anchors = Vec::new();
3601    let mut old_versions = Vec::new();
3602    let mut needs_diff_calculation = false;
3603    let mut inverted_diff_main_buffers: HashMap<BufferId, Entity<BufferDiff>> = HashMap::default();
3604    for _ in 0..operations {
3605        match rng.random_range(0..100) {
3606            0..=14 if !buffers.is_empty() => {
3607                let buffer = buffers.choose(&mut rng).unwrap();
3608                buffer.update(cx, |buf, cx| {
3609                    let edit_count = rng.random_range(1..5);
3610                    buf.randomly_edit(&mut rng, edit_count, cx);
3611                    log::info!("buffer text:\n{}", buf.text());
3612                    needs_diff_calculation = true;
3613                });
3614                cx.update(|cx| reference.diffs_updated(cx));
3615            }
3616            15..=24 if !reference.excerpts.is_empty() => {
3617                multibuffer.update(cx, |multibuffer, cx| {
3618                    let snapshot = multibuffer.snapshot(cx);
3619                    let infos = snapshot.excerpts().collect::<Vec<_>>();
3620                    let mut excerpts = HashSet::default();
3621                    for _ in 0..rng.random_range(0..infos.len()) {
3622                        excerpts.extend(infos.choose(&mut rng).cloned());
3623                    }
3624
3625                    let line_count = rng.random_range(0..5);
3626
3627                    let excerpt_ixs = excerpts
3628                        .iter()
3629                        .map(|info| {
3630                            reference
3631                                .excerpts
3632                                .iter()
3633                                .position(|e| e.range == info.context)
3634                                .unwrap()
3635                        })
3636                        .collect::<Vec<_>>();
3637                    log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
3638                    multibuffer.expand_excerpts(
3639                        excerpts
3640                            .iter()
3641                            .map(|info| snapshot.anchor_in_excerpt(info.context.end).unwrap()),
3642                        line_count,
3643                        ExpandExcerptDirection::UpAndDown,
3644                        cx,
3645                    );
3646
3647                    reference.expand_excerpts(&excerpts, line_count, cx);
3648                });
3649            }
3650            25..=34 if !reference.excerpts.is_empty() => {
3651                let multibuffer =
3652                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3653                let offset = multibuffer.clip_offset(
3654                    MultiBufferOffset(rng.random_range(0..=multibuffer.len().0)),
3655                    Bias::Left,
3656                );
3657                let bias = if rng.random() {
3658                    Bias::Left
3659                } else {
3660                    Bias::Right
3661                };
3662                log::info!("Creating anchor at {} with bias {:?}", offset.0, bias);
3663                anchors.push(multibuffer.anchor_at(offset, bias));
3664                anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
3665            }
3666            35..=45 if !reference.excerpts.is_empty() => {
3667                multibuffer.update(cx, |multibuffer, cx| {
3668                    let snapshot = multibuffer.snapshot(cx);
3669                    let excerpt_ix = rng.random_range(0..reference.excerpts.len());
3670                    let excerpt = &reference.excerpts[excerpt_ix];
3671
3672                    // Skip inverted excerpts - hunks can't be collapsed
3673                    let buffer_id = excerpt.buffer.read(cx).remote_id();
3674                    if reference.inverted_diffs.contains_key(&buffer_id) {
3675                        return;
3676                    }
3677
3678                    let start = excerpt.range.start;
3679                    let end = excerpt.range.end;
3680                    let range = snapshot.anchor_in_excerpt(start).unwrap()
3681                        ..snapshot.anchor_in_excerpt(end).unwrap();
3682
3683                    log::info!(
3684                        "expanding diff hunks in range {:?} (excerpt index {excerpt_ix:?}, buffer id {:?})",
3685                        range.to_point(&snapshot),
3686                        buffer_id,
3687                    );
3688                    reference.expand_diff_hunks(excerpt.path_key.clone(), start..end, cx);
3689                    multibuffer.expand_diff_hunks(vec![range], cx);
3690                });
3691            }
3692            46..=75 if needs_diff_calculation => {
3693                multibuffer.update(cx, |multibuffer, cx| {
3694                    for buffer in multibuffer.all_buffers() {
3695                        let snapshot = buffer.read(cx).snapshot();
3696                        let buffer_id = snapshot.remote_id();
3697
3698                        if let Some(diff) = multibuffer.diff_for(buffer_id) {
3699                            diff.update(cx, |diff, cx| {
3700                                log::info!("recalculating diff for buffer {:?}", buffer_id,);
3701                                diff.recalculate_diff_sync(&snapshot.text, cx);
3702                            });
3703                        }
3704
3705                        if let Some(inverted_diff) = inverted_diff_main_buffers.get(&buffer_id) {
3706                            inverted_diff.update(cx, |diff, cx| {
3707                                log::info!(
3708                                    "recalculating inverted diff for main buffer {:?}",
3709                                    buffer_id,
3710                                );
3711                                diff.recalculate_diff_sync(&snapshot.text, cx);
3712                            });
3713                        }
3714                    }
3715                    reference.diffs_updated(cx);
3716                    needs_diff_calculation = false;
3717                });
3718            }
3719            _ => {
3720                // Decide if we're creating a new buffer or reusing an existing one
3721                let create_new_buffer = buffers.is_empty() || rng.random_bool(0.4);
3722
3723                let (excerpt_buffer, diff, inverted_main_buffer) = if create_new_buffer {
3724                    let create_inverted = rng.random_bool(0.3);
3725
3726                    if create_inverted {
3727                        let mut main_buffer_text = util::RandomCharIter::new(&mut rng)
3728                            .take(256)
3729                            .collect::<String>();
3730                        let main_buffer = cx.new(|cx| Buffer::local(main_buffer_text.clone(), cx));
3731                        text::LineEnding::normalize(&mut main_buffer_text);
3732                        let main_buffer_id =
3733                            main_buffer.read_with(cx, |buffer, _| buffer.remote_id());
3734                        base_texts.insert(main_buffer_id, main_buffer_text.clone());
3735                        buffers.push(main_buffer.clone());
3736
3737                        let diff = cx.new(|cx| {
3738                            BufferDiff::new_with_base_text(
3739                                &main_buffer_text,
3740                                &main_buffer.read(cx).text_snapshot(),
3741                                cx,
3742                            )
3743                        });
3744
3745                        let base_text_buffer =
3746                            diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
3747
3748                        // Track for recalculation when main buffer is edited
3749                        inverted_diff_main_buffers.insert(main_buffer_id, diff.clone());
3750
3751                        (base_text_buffer, diff, Some(main_buffer))
3752                    } else {
3753                        let mut base_text = util::RandomCharIter::new(&mut rng)
3754                            .take(256)
3755                            .collect::<String>();
3756
3757                        let buffer_handle = cx.new(|cx| Buffer::local(base_text.clone(), cx));
3758                        text::LineEnding::normalize(&mut base_text);
3759                        let buffer_id = buffer_handle.read_with(cx, |buffer, _| buffer.remote_id());
3760                        base_texts.insert(buffer_id, base_text.clone());
3761                        buffers.push(buffer_handle.clone());
3762
3763                        let diff = cx.new(|cx| {
3764                            BufferDiff::new_with_base_text(
3765                                &base_text,
3766                                &buffer_handle.read(cx).text_snapshot(),
3767                                cx,
3768                            )
3769                        });
3770
3771                        (buffer_handle, diff, None)
3772                    }
3773                } else {
3774                    // Reuse an existing buffer
3775                    let buffer_handle = buffers.choose(&mut rng).unwrap().clone();
3776                    let buffer_id = buffer_handle.read_with(cx, |buffer, _| buffer.remote_id());
3777
3778                    if let Some(diff) = inverted_diff_main_buffers.get(&buffer_id) {
3779                        let base_text_buffer =
3780                            diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
3781                        (base_text_buffer, diff.clone(), Some(buffer_handle))
3782                    } else {
3783                        // Get existing diff or create new one for regular buffer
3784                        let diff = multibuffer
3785                            .read_with(cx, |mb, _| mb.diff_for(buffer_id))
3786                            .unwrap_or_else(|| {
3787                                let base_text = base_texts.get(&buffer_id).unwrap();
3788                                cx.new(|cx| {
3789                                    BufferDiff::new_with_base_text(
3790                                        base_text,
3791                                        &buffer_handle.read(cx).text_snapshot(),
3792                                        cx,
3793                                    )
3794                                })
3795                            });
3796                        (buffer_handle, diff, None)
3797                    }
3798                };
3799
3800                let excerpt_buffer_snapshot =
3801                    excerpt_buffer.read_with(cx, |excerpt_buffer, _| excerpt_buffer.snapshot());
3802                let mut ranges = reference
3803                    .excerpts
3804                    .iter()
3805                    .filter(|excerpt| excerpt.buffer == excerpt_buffer)
3806                    .map(|excerpt| excerpt.range.to_point(&excerpt_buffer_snapshot))
3807                    .collect::<Vec<_>>();
3808                mutate_excerpt_ranges(&mut rng, &mut ranges, &excerpt_buffer_snapshot, 1);
3809                let ranges = ranges
3810                    .iter()
3811                    .cloned()
3812                    .map(ExcerptRange::new)
3813                    .collect::<Vec<_>>();
3814                let path = cx.update(|cx| PathKey::for_buffer(&excerpt_buffer, cx));
3815                let path_key_index = multibuffer.update(cx, |multibuffer, _| {
3816                    multibuffer.get_or_create_path_key_index(&path)
3817                });
3818
3819                multibuffer.update(cx, |multibuffer, cx| {
3820                    multibuffer.set_excerpt_ranges_for_path(
3821                        path.clone(),
3822                        excerpt_buffer.clone(),
3823                        &excerpt_buffer_snapshot,
3824                        ranges.clone(),
3825                        cx,
3826                    )
3827                });
3828
3829                cx.update(|cx| {
3830                    reference.set_excerpts(
3831                        path,
3832                        path_key_index,
3833                        excerpt_buffer.clone(),
3834                        &excerpt_buffer_snapshot,
3835                        ranges,
3836                        cx,
3837                    )
3838                });
3839
3840                let excerpt_buffer_id =
3841                    excerpt_buffer.read_with(cx, |buffer, _| buffer.remote_id());
3842                multibuffer.update(cx, |multibuffer, cx| {
3843                    if multibuffer.diff_for(excerpt_buffer_id).is_none() {
3844                        if let Some(main_buffer) = inverted_main_buffer {
3845                            reference.add_inverted_diff(diff.clone(), main_buffer.clone(), cx);
3846                            multibuffer.add_inverted_diff(diff, main_buffer, cx);
3847                        } else {
3848                            reference.add_diff(diff.clone(), cx);
3849                            multibuffer.add_diff(diff, cx);
3850                        }
3851                    }
3852                });
3853            }
3854        }
3855
3856        if rng.random_bool(0.3) {
3857            multibuffer.update(cx, |multibuffer, cx| {
3858                old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
3859            })
3860        }
3861
3862        multibuffer.read_with(cx, |multibuffer, cx| {
3863            check_multibuffer(multibuffer, &reference, &anchors, cx, &mut rng);
3864        });
3865    }
3866    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3867    for (old_snapshot, subscription) in old_versions {
3868        check_multibuffer_edits(&snapshot, &old_snapshot, subscription);
3869    }
3870}
3871
3872fn mutate_excerpt_ranges(
3873    rng: &mut StdRng,
3874    existing_ranges: &mut Vec<Range<Point>>,
3875    buffer: &BufferSnapshot,
3876    operations: u32,
3877) {
3878    let mut ranges_to_add = Vec::new();
3879
3880    for _ in 0..operations {
3881        match rng.random_range(0..5) {
3882            0..=1 if !existing_ranges.is_empty() => {
3883                let index = rng.random_range(0..existing_ranges.len());
3884                log::info!("Removing excerpt at index {index}");
3885                existing_ranges.remove(index);
3886            }
3887            _ => {
3888                let end_row = rng.random_range(0..=buffer.max_point().row);
3889                let start_row = rng.random_range(0..=end_row);
3890                let end_col = buffer.line_len(end_row);
3891                log::info!(
3892                    "Inserting excerpt for buffer {:?}, row range {:?}",
3893                    buffer.remote_id(),
3894                    start_row..end_row
3895                );
3896                ranges_to_add.push(Point::new(start_row, 0)..Point::new(end_row, end_col));
3897            }
3898        }
3899    }
3900
3901    existing_ranges.extend(ranges_to_add);
3902    existing_ranges.sort_by(|l, r| l.start.cmp(&r.start));
3903}
3904
3905fn check_multibuffer(
3906    multibuffer: &MultiBuffer,
3907    reference: &ReferenceMultibuffer,
3908    anchors: &[Anchor],
3909    cx: &App,
3910    rng: &mut StdRng,
3911) {
3912    let snapshot = multibuffer.snapshot(cx);
3913    let actual_text = snapshot.text();
3914    let actual_boundary_rows = snapshot
3915        .excerpt_boundaries_in_range(MultiBufferOffset(0)..)
3916        .map(|b| b.row)
3917        .collect::<HashSet<_>>();
3918    let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3919
3920    let anchors_to_check = anchors
3921        .iter()
3922        .filter_map(|anchor| {
3923            snapshot
3924                .anchor_to_buffer_anchor(*anchor)
3925                .map(|(anchor, _)| anchor)
3926        })
3927        // Intentionally mix in some anchors that are (in general) not contained in any excerpt
3928        .chain(
3929            reference
3930                .excerpts
3931                .iter()
3932                .map(|excerpt| excerpt.buffer.read(cx).remote_id())
3933                .dedup()
3934                .flat_map(|buffer_id| {
3935                    [
3936                        text::Anchor::min_for_buffer(buffer_id),
3937                        text::Anchor::max_for_buffer(buffer_id),
3938                    ]
3939                }),
3940        )
3941        .map(|anchor| snapshot.anchor_in_buffer(anchor).unwrap())
3942        .collect::<Vec<_>>();
3943
3944    let (expected_text, expected_row_infos, expected_boundary_rows, _) =
3945        reference.expected_content(cx);
3946    let expected_anchor_offsets = anchors_to_check
3947        .iter()
3948        .map(|anchor| reference.anchor_to_offset(anchor, cx).unwrap())
3949        .collect::<Vec<_>>();
3950
3951    let has_diff = actual_row_infos
3952        .iter()
3953        .any(|info| info.diff_status.is_some())
3954        || expected_row_infos
3955            .iter()
3956            .any(|info| info.diff_status.is_some());
3957    let actual_diff = format_diff(
3958        &actual_text,
3959        &actual_row_infos,
3960        &actual_boundary_rows,
3961        Some(has_diff),
3962    );
3963    let expected_diff = format_diff(
3964        &expected_text,
3965        &expected_row_infos,
3966        &expected_boundary_rows,
3967        Some(has_diff),
3968    );
3969
3970    log::info!("Multibuffer content:\n{}", actual_diff);
3971
3972    assert_eq!(
3973        actual_row_infos.len(),
3974        actual_text.split('\n').count(),
3975        "line count: {}",
3976        actual_text.split('\n').count()
3977    );
3978    pretty_assertions::assert_eq!(actual_diff, expected_diff);
3979    pretty_assertions::assert_eq!(actual_text, expected_text);
3980    pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
3981
3982    for _ in 0..5 {
3983        let start_row = rng.random_range(0..=expected_row_infos.len());
3984        assert_eq!(
3985            snapshot
3986                .row_infos(MultiBufferRow(start_row as u32))
3987                .collect::<Vec<_>>(),
3988            &expected_row_infos[start_row..],
3989            "buffer_rows({})",
3990            start_row
3991        );
3992    }
3993
3994    assert_eq!(
3995        snapshot.widest_line_number(),
3996        expected_row_infos
3997            .into_iter()
3998            .filter_map(|info| {
3999                // For inverted diffs, deleted rows are visible and should be counted.
4000                // Only filter out deleted rows that are NOT from inverted diffs.
4001                let is_inverted_diff = info
4002                    .buffer_id
4003                    .is_some_and(|id| reference.inverted_diffs.contains_key(&id));
4004                if info.diff_status.is_some_and(|status| status.is_deleted()) && !is_inverted_diff {
4005                    None
4006                } else {
4007                    info.buffer_row
4008                }
4009            })
4010            .max()
4011            .unwrap()
4012            + 1
4013    );
4014    for i in 0..snapshot.len().0 {
4015        let (_, excerpt_range) = snapshot
4016            .excerpt_containing(MultiBufferOffset(i)..MultiBufferOffset(i))
4017            .unwrap();
4018        reference
4019            .excerpts
4020            .iter()
4021            .find(|reference_excerpt| reference_excerpt.range == excerpt_range.context)
4022            .expect("corresponding excerpt should exist in reference multibuffer");
4023    }
4024
4025    assert_consistent_line_numbers(&snapshot);
4026    assert_position_translation(&snapshot);
4027
4028    for (row, line) in expected_text.split('\n').enumerate() {
4029        assert_eq!(
4030            snapshot.line_len(MultiBufferRow(row as u32)),
4031            line.len() as u32,
4032            "line_len({}).",
4033            row
4034        );
4035    }
4036
4037    let text_rope = Rope::from(expected_text.as_str());
4038    for _ in 0..10 {
4039        let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
4040        let start_ix = text_rope.clip_offset(rng.random_range(0..=end_ix), Bias::Left);
4041
4042        let text_for_range = snapshot
4043            .text_for_range(MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix))
4044            .collect::<String>();
4045        assert_eq!(
4046            text_for_range,
4047            &expected_text[start_ix..end_ix],
4048            "incorrect text for range {:?}",
4049            start_ix..end_ix
4050        );
4051
4052        let expected_summary =
4053            MBTextSummary::from(TextSummary::from(&expected_text[start_ix..end_ix]));
4054        assert_eq!(
4055            snapshot.text_summary_for_range::<MBTextSummary, _>(
4056                MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix)
4057            ),
4058            expected_summary,
4059            "incorrect summary for range {:?}",
4060            start_ix..end_ix
4061        );
4062    }
4063
4064    // Anchor resolution
4065    let summaries = snapshot.summaries_for_anchors::<MultiBufferOffset, _>(anchors);
4066    assert_eq!(anchors.len(), summaries.len());
4067    for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
4068        assert!(resolved_offset <= snapshot.len());
4069        assert_eq!(
4070            snapshot.summary_for_anchor::<MultiBufferOffset>(anchor),
4071            resolved_offset,
4072            "anchor: {:?}",
4073            anchor
4074        );
4075    }
4076
4077    let actual_anchor_offsets = anchors_to_check
4078        .into_iter()
4079        .map(|anchor| anchor.to_offset(&snapshot))
4080        .collect::<Vec<_>>();
4081    assert_eq!(
4082        actual_anchor_offsets, expected_anchor_offsets,
4083        "buffer anchor resolves to wrong offset"
4084    );
4085
4086    for _ in 0..10 {
4087        let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
4088        assert_eq!(
4089            snapshot
4090                .reversed_chars_at(MultiBufferOffset(end_ix))
4091                .collect::<String>(),
4092            expected_text[..end_ix].chars().rev().collect::<String>(),
4093        );
4094    }
4095
4096    for _ in 0..10 {
4097        let end_ix = rng.random_range(0..=text_rope.len());
4098        let end_ix = text_rope.floor_char_boundary(end_ix);
4099        let start_ix = rng.random_range(0..=end_ix);
4100        let start_ix = text_rope.floor_char_boundary(start_ix);
4101        assert_eq!(
4102            snapshot
4103                .bytes_in_range(MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix))
4104                .flatten()
4105                .copied()
4106                .collect::<Vec<_>>(),
4107            expected_text.as_bytes()[start_ix..end_ix].to_vec(),
4108            "bytes_in_range({:?})",
4109            start_ix..end_ix,
4110        );
4111    }
4112}
4113
4114fn check_multibuffer_edits(
4115    snapshot: &MultiBufferSnapshot,
4116    old_snapshot: &MultiBufferSnapshot,
4117    subscription: Subscription<MultiBufferOffset>,
4118) {
4119    let edits = subscription.consume().into_inner();
4120
4121    log::info!(
4122        "applying subscription edits to old text: {:?}: {:#?}",
4123        old_snapshot.text(),
4124        edits,
4125    );
4126
4127    let mut text = old_snapshot.text();
4128    for edit in edits {
4129        let new_text: String = snapshot
4130            .text_for_range(edit.new.start..edit.new.end)
4131            .collect();
4132        text.replace_range(
4133            (edit.new.start.0..edit.new.start.0 + (edit.old.end.0 - edit.old.start.0)).clone(),
4134            &new_text,
4135        );
4136        pretty_assertions::assert_eq!(
4137            &text[0..edit.new.end.0],
4138            snapshot
4139                .text_for_range(MultiBufferOffset(0)..edit.new.end)
4140                .collect::<String>()
4141        );
4142    }
4143    pretty_assertions::assert_eq!(text, snapshot.text());
4144}
4145
4146#[gpui::test]
4147fn test_history(cx: &mut App) {
4148    let test_settings = SettingsStore::test(cx);
4149    cx.set_global(test_settings);
4150
4151    let group_interval: Duration = Duration::from_millis(1);
4152    let buffer_1 = cx.new(|cx| {
4153        let mut buf = Buffer::local("1234", cx);
4154        buf.set_group_interval(group_interval);
4155        buf
4156    });
4157    let buffer_2 = cx.new(|cx| {
4158        let mut buf = Buffer::local("5678", cx);
4159        buf.set_group_interval(group_interval);
4160        buf
4161    });
4162    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
4163    multibuffer.update(cx, |this, cx| {
4164        this.set_group_interval(group_interval, cx);
4165    });
4166    multibuffer.update(cx, |multibuffer, cx| {
4167        multibuffer.set_excerpts_for_path(
4168            PathKey::sorted(0),
4169            buffer_1.clone(),
4170            [Point::zero()..buffer_1.read(cx).max_point()],
4171            0,
4172            cx,
4173        );
4174        multibuffer.set_excerpts_for_path(
4175            PathKey::sorted(1),
4176            buffer_2.clone(),
4177            [Point::zero()..buffer_2.read(cx).max_point()],
4178            0,
4179            cx,
4180        );
4181    });
4182
4183    let mut now = Instant::now();
4184
4185    multibuffer.update(cx, |multibuffer, cx| {
4186        let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
4187        multibuffer.edit(
4188            [
4189                (Point::new(0, 0)..Point::new(0, 0), "A"),
4190                (Point::new(1, 0)..Point::new(1, 0), "A"),
4191            ],
4192            None,
4193            cx,
4194        );
4195        multibuffer.edit(
4196            [
4197                (Point::new(0, 1)..Point::new(0, 1), "B"),
4198                (Point::new(1, 1)..Point::new(1, 1), "B"),
4199            ],
4200            None,
4201            cx,
4202        );
4203        multibuffer.end_transaction_at(now, cx);
4204        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4205
4206        // Verify edited ranges for transaction 1
4207        assert_eq!(
4208            multibuffer.edited_ranges_for_transaction(transaction_1, cx),
4209            &[
4210                MultiBufferOffset(0)..MultiBufferOffset(2),
4211                MultiBufferOffset(7)..MultiBufferOffset(9),
4212            ]
4213        );
4214
4215        // Edit buffer 1 through the multibuffer
4216        now += 2 * group_interval;
4217        multibuffer.start_transaction_at(now, cx);
4218        multibuffer.edit(
4219            [(MultiBufferOffset(2)..MultiBufferOffset(2), "C")],
4220            None,
4221            cx,
4222        );
4223        multibuffer.end_transaction_at(now, cx);
4224        assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
4225
4226        // Edit buffer 1 independently
4227        buffer_1.update(cx, |buffer_1, cx| {
4228            buffer_1.start_transaction_at(now);
4229            buffer_1.edit([(3..3, "D")], None, cx);
4230            buffer_1.end_transaction_at(now, cx);
4231
4232            now += 2 * group_interval;
4233            buffer_1.start_transaction_at(now);
4234            buffer_1.edit([(4..4, "E")], None, cx);
4235            buffer_1.end_transaction_at(now, cx);
4236        });
4237        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
4238
4239        // An undo in the multibuffer undoes the multibuffer transaction
4240        // and also any individual buffer edits that have occurred since
4241        // that transaction.
4242        multibuffer.undo(cx);
4243        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4244
4245        multibuffer.undo(cx);
4246        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4247
4248        multibuffer.redo(cx);
4249        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4250
4251        multibuffer.redo(cx);
4252        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
4253
4254        // Undo buffer 2 independently.
4255        buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
4256        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
4257
4258        // An undo in the multibuffer undoes the components of the
4259        // the last multibuffer transaction that are not already undone.
4260        multibuffer.undo(cx);
4261        assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
4262
4263        multibuffer.undo(cx);
4264        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4265
4266        multibuffer.redo(cx);
4267        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4268
4269        buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
4270        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
4271
4272        // Redo stack gets cleared after an edit.
4273        now += 2 * group_interval;
4274        multibuffer.start_transaction_at(now, cx);
4275        multibuffer.edit(
4276            [(MultiBufferOffset(0)..MultiBufferOffset(0), "X")],
4277            None,
4278            cx,
4279        );
4280        multibuffer.end_transaction_at(now, cx);
4281        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4282        multibuffer.redo(cx);
4283        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4284        multibuffer.undo(cx);
4285        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
4286        multibuffer.undo(cx);
4287        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4288
4289        // Transactions can be grouped manually.
4290        multibuffer.redo(cx);
4291        multibuffer.redo(cx);
4292        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4293        multibuffer.group_until_transaction(transaction_1, cx);
4294        multibuffer.undo(cx);
4295        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4296        multibuffer.redo(cx);
4297        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4298    });
4299}
4300
4301#[gpui::test]
4302async fn test_enclosing_indent(cx: &mut TestAppContext) {
4303    async fn enclosing_indent(
4304        text: &str,
4305        buffer_row: u32,
4306        cx: &mut TestAppContext,
4307    ) -> Option<(Range<u32>, LineIndent)> {
4308        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
4309        let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
4310        let (range, indent) = snapshot
4311            .enclosing_indent(MultiBufferRow(buffer_row))
4312            .await?;
4313        Some((range.start.0..range.end.0, indent))
4314    }
4315
4316    assert_eq!(
4317        enclosing_indent(
4318            indoc!(
4319                "
4320                fn b() {
4321                    if c {
4322                        let d = 2;
4323                    }
4324                }
4325                "
4326            ),
4327            1,
4328            cx,
4329        )
4330        .await,
4331        Some((
4332            1..2,
4333            LineIndent {
4334                tabs: 0,
4335                spaces: 4,
4336                line_blank: false,
4337            }
4338        ))
4339    );
4340
4341    assert_eq!(
4342        enclosing_indent(
4343            indoc!(
4344                "
4345                fn b() {
4346                    if c {
4347                        let d = 2;
4348                    }
4349                }
4350                "
4351            ),
4352            2,
4353            cx,
4354        )
4355        .await,
4356        Some((
4357            1..2,
4358            LineIndent {
4359                tabs: 0,
4360                spaces: 4,
4361                line_blank: false,
4362            }
4363        ))
4364    );
4365
4366    assert_eq!(
4367        enclosing_indent(
4368            indoc!(
4369                "
4370                fn b() {
4371                    if c {
4372                        let d = 2;
4373
4374                        let e = 5;
4375                    }
4376                }
4377                "
4378            ),
4379            3,
4380            cx,
4381        )
4382        .await,
4383        Some((
4384            1..4,
4385            LineIndent {
4386                tabs: 0,
4387                spaces: 4,
4388                line_blank: false,
4389            }
4390        ))
4391    );
4392}
4393
4394#[gpui::test]
4395async fn test_summaries_for_anchors(cx: &mut TestAppContext) {
4396    let base_text_1 = indoc!(
4397        "
4398        bar
4399        "
4400    );
4401    let text_1 = indoc!(
4402        "
4403        BAR
4404        "
4405    );
4406    let base_text_2 = indoc!(
4407        "
4408        foo
4409        "
4410    );
4411    let text_2 = indoc!(
4412        "
4413        FOO
4414        "
4415    );
4416
4417    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
4418    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
4419    let diff_1 = cx.new(|cx| {
4420        BufferDiff::new_with_base_text(base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
4421    });
4422    let diff_2 = cx.new(|cx| {
4423        BufferDiff::new_with_base_text(base_text_2, &buffer_2.read(cx).text_snapshot(), cx)
4424    });
4425    cx.run_until_parked();
4426
4427    let multibuffer = cx.new(|cx| {
4428        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
4429        multibuffer.set_all_diff_hunks_expanded(cx);
4430        multibuffer.set_excerpts_for_path(
4431            PathKey::sorted(0),
4432            buffer_1.clone(),
4433            [Point::zero()..buffer_1.read(cx).max_point()],
4434            0,
4435            cx,
4436        );
4437        multibuffer.set_excerpts_for_path(
4438            PathKey::sorted(1),
4439            buffer_2.clone(),
4440            [Point::zero()..buffer_2.read(cx).max_point()],
4441            0,
4442            cx,
4443        );
4444        multibuffer.add_diff(diff_1.clone(), cx);
4445        multibuffer.add_diff(diff_2.clone(), cx);
4446        multibuffer
4447    });
4448
4449    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4450        (multibuffer.snapshot(cx), multibuffer.subscribe())
4451    });
4452
4453    assert_new_snapshot(
4454        &multibuffer,
4455        &mut snapshot,
4456        &mut subscription,
4457        cx,
4458        indoc!(
4459            "
4460            - bar
4461            + BAR
4462
4463            - foo
4464            + FOO
4465            "
4466        ),
4467    );
4468
4469    let anchor_1 = multibuffer.read_with(cx, |multibuffer, cx| {
4470        multibuffer
4471            .snapshot(cx)
4472            .anchor_in_excerpt(text::Anchor::min_for_buffer(buffer_1.read(cx).remote_id()))
4473            .unwrap()
4474    });
4475    let point_1 = snapshot.summaries_for_anchors::<Point, _>([&anchor_1])[0];
4476    assert_eq!(point_1, Point::new(0, 0));
4477
4478    let anchor_2 = multibuffer.read_with(cx, |multibuffer, cx| {
4479        multibuffer
4480            .snapshot(cx)
4481            .anchor_in_excerpt(text::Anchor::min_for_buffer(buffer_2.read(cx).remote_id()))
4482            .unwrap()
4483    });
4484    let point_2 = snapshot.summaries_for_anchors::<Point, _>([&anchor_2])[0];
4485    assert_eq!(point_2, Point::new(3, 0));
4486}
4487
4488#[gpui::test]
4489async fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
4490    let base_text_1 = "one\ntwo".to_owned();
4491    let text_1 = "one\n".to_owned();
4492
4493    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
4494    let diff_1 = cx.new(|cx| {
4495        BufferDiff::new_with_base_text(&base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
4496    });
4497    cx.run_until_parked();
4498
4499    let multibuffer = cx.new(|cx| {
4500        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
4501        multibuffer.set_excerpts_for_path(
4502            PathKey::sorted(0),
4503            buffer_1.clone(),
4504            [Point::zero()..buffer_1.read(cx).max_point()],
4505            0,
4506            cx,
4507        );
4508        multibuffer.add_diff(diff_1.clone(), cx);
4509        multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
4510        multibuffer
4511    });
4512
4513    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4514        (multibuffer.snapshot(cx), multibuffer.subscribe())
4515    });
4516
4517    assert_new_snapshot(
4518        &multibuffer,
4519        &mut snapshot,
4520        &mut subscription,
4521        cx,
4522        indoc!(
4523            "
4524              one
4525            - two
4526            "
4527        ),
4528    );
4529
4530    assert_eq!(snapshot.max_point(), Point::new(2, 0));
4531    assert_eq!(snapshot.len().0, 8);
4532
4533    assert_eq!(
4534        snapshot
4535            .dimensions_from_points::<Point>([Point::new(2, 0)])
4536            .collect::<Vec<_>>(),
4537        vec![Point::new(2, 0)]
4538    );
4539
4540    let (_, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
4541    assert_eq!(translated_offset.0, "one\n".len());
4542    let (_, translated_point) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
4543    assert_eq!(translated_point, Point::new(1, 0));
4544
4545    // The same, for an excerpt that's not at the end of the multibuffer.
4546
4547    let text_2 = "foo\n".to_owned();
4548    let buffer_2 = cx.new(|cx| Buffer::local(&text_2, cx));
4549    multibuffer.update(cx, |multibuffer, cx| {
4550        multibuffer.set_excerpt_ranges_for_path(
4551            PathKey::sorted(1),
4552            buffer_2.clone(),
4553            &buffer_2.read(cx).snapshot(),
4554            vec![ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
4555            cx,
4556        );
4557    });
4558
4559    assert_new_snapshot(
4560        &multibuffer,
4561        &mut snapshot,
4562        &mut subscription,
4563        cx,
4564        indoc!(
4565            "
4566              one
4567            - two
4568
4569              foo
4570            "
4571        ),
4572    );
4573
4574    assert_eq!(
4575        snapshot
4576            .dimensions_from_points::<Point>([Point::new(2, 0)])
4577            .collect::<Vec<_>>(),
4578        vec![Point::new(2, 0)]
4579    );
4580
4581    let buffer_1_id = buffer_1.read_with(cx, |buffer_1, _| buffer_1.remote_id());
4582    let (buffer, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
4583    assert_eq!(buffer.remote_id(), buffer_1_id);
4584    assert_eq!(translated_offset.0, "one\n".len());
4585    let (buffer, translated_point) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
4586    assert_eq!(buffer.remote_id(), buffer_1_id);
4587    assert_eq!(translated_point, Point::new(1, 0));
4588}
4589
4590fn format_diff(
4591    text: &str,
4592    row_infos: &Vec<RowInfo>,
4593    boundary_rows: &HashSet<MultiBufferRow>,
4594    has_diff: Option<bool>,
4595) -> String {
4596    let has_diff =
4597        has_diff.unwrap_or_else(|| row_infos.iter().any(|info| info.diff_status.is_some()));
4598    text.split('\n')
4599        .enumerate()
4600        .zip(row_infos)
4601        .map(|((ix, line), info)| {
4602            let marker = match info.diff_status.map(|status| status.kind) {
4603                Some(DiffHunkStatusKind::Added) => "+ ",
4604                Some(DiffHunkStatusKind::Deleted) => "- ",
4605                Some(DiffHunkStatusKind::Modified) => unreachable!(),
4606                None => {
4607                    if has_diff && !line.is_empty() {
4608                        "  "
4609                    } else {
4610                        ""
4611                    }
4612                }
4613            };
4614            let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
4615                if has_diff {
4616                    "  ----------\n"
4617                } else {
4618                    "---------\n"
4619                }
4620            } else {
4621                ""
4622            };
4623            let expand = info
4624                .expand_info
4625                .as_ref()
4626                .map(|expand_info| match expand_info.direction {
4627                    ExpandExcerptDirection::Up => " [↑]",
4628                    ExpandExcerptDirection::Down => " [↓]",
4629                    ExpandExcerptDirection::UpAndDown => " [↕]",
4630                })
4631                .unwrap_or_default();
4632
4633            format!("{boundary_row}{marker}{line}{expand}")
4634            // let mbr = info
4635            //     .multibuffer_row
4636            //     .map(|row| format!("{:0>3}", row.0))
4637            //     .unwrap_or_else(|| "???".to_string());
4638            // let byte_range = format!("{byte_range_start:0>3}..{byte_range_end:0>3}");
4639            // format!("{boundary_row}Row: {mbr}, Bytes: {byte_range} | {marker}{line}{expand}")
4640        })
4641        .collect::<Vec<_>>()
4642        .join("\n")
4643}
4644
4645// fn format_transforms(snapshot: &MultiBufferSnapshot) -> String {
4646//     snapshot
4647//         .diff_transforms
4648//         .iter()
4649//         .map(|transform| {
4650//             let (kind, summary) = match transform {
4651//                 DiffTransform::DeletedHunk { summary, .. } => ("   Deleted", (*summary).into()),
4652//                 DiffTransform::FilteredInsertedHunk { summary, .. } => ("  Filtered", *summary),
4653//                 DiffTransform::InsertedHunk { summary, .. } => ("  Inserted", *summary),
4654//                 DiffTransform::Unmodified { summary, .. } => ("Unmodified", *summary),
4655//             };
4656//             format!("{kind}(len: {}, lines: {:?})", summary.len, summary.lines)
4657//         })
4658//         .join("\n")
4659// }
4660
4661// fn format_excerpts(snapshot: &MultiBufferSnapshot) -> String {
4662//     snapshot
4663//         .excerpts
4664//         .iter()
4665//         .map(|excerpt| {
4666//             format!(
4667//                 "Excerpt(buffer_range = {:?}, lines = {:?}, has_trailing_newline = {:?})",
4668//                 excerpt.range.context.to_point(&excerpt.buffer),
4669//                 excerpt.text_summary.lines,
4670//                 excerpt.has_trailing_newline
4671//             )
4672//         })
4673//         .join("\n")
4674// }
4675
4676#[gpui::test]
4677async fn test_singleton_with_inverted_diff(cx: &mut TestAppContext) {
4678    let text = indoc!(
4679        "
4680        ZERO
4681        one
4682        TWO
4683        three
4684        six
4685        "
4686    );
4687    let base_text = indoc!(
4688        "
4689        one
4690        two
4691        three
4692        four
4693        five
4694        six
4695        "
4696    );
4697
4698    let buffer = cx.new(|cx| Buffer::local(text, cx));
4699    let diff = cx
4700        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
4701    cx.run_until_parked();
4702
4703    let base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4704
4705    let multibuffer = cx.new(|cx| {
4706        let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4707        multibuffer.set_all_diff_hunks_expanded(cx);
4708        multibuffer.add_inverted_diff(diff.clone(), buffer.clone(), cx);
4709        multibuffer
4710    });
4711
4712    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4713        (multibuffer.snapshot(cx), multibuffer.subscribe())
4714    });
4715
4716    assert_eq!(snapshot.text(), base_text);
4717    assert_new_snapshot(
4718        &multibuffer,
4719        &mut snapshot,
4720        &mut subscription,
4721        cx,
4722        indoc!(
4723            "
4724              one
4725            - two
4726              three
4727            - four
4728            - five
4729              six
4730            "
4731        ),
4732    );
4733
4734    buffer.update(cx, |buffer, cx| {
4735        buffer.edit_via_marked_text(
4736            indoc!(
4737                "
4738                ZERO
4739                one
4740                «<inserted>»W«O
4741                T»hree
4742                six
4743                "
4744            ),
4745            None,
4746            cx,
4747        );
4748    });
4749    cx.run_until_parked();
4750    let update = diff
4751        .update(cx, |diff, cx| {
4752            diff.update_diff(
4753                buffer.read(cx).text_snapshot(),
4754                Some(base_text.into()),
4755                None,
4756                None,
4757                cx,
4758            )
4759        })
4760        .await;
4761    diff.update(cx, |diff, cx| {
4762        diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4763    })
4764    .await;
4765    cx.run_until_parked();
4766
4767    assert_new_snapshot(
4768        &multibuffer,
4769        &mut snapshot,
4770        &mut subscription,
4771        cx,
4772        indoc! {
4773            "
4774              one
4775            - two
4776            - three
4777            - four
4778            - five
4779              six
4780            "
4781        },
4782    );
4783
4784    buffer.update(cx, |buffer, cx| {
4785        buffer.set_text("ZERO\nONE\nTWO\n", cx);
4786    });
4787    cx.run_until_parked();
4788    let update = diff
4789        .update(cx, |diff, cx| {
4790            diff.update_diff(
4791                buffer.read(cx).text_snapshot(),
4792                Some(base_text.into()),
4793                None,
4794                None,
4795                cx,
4796            )
4797        })
4798        .await;
4799    diff.update(cx, |diff, cx| {
4800        diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4801    })
4802    .await;
4803    cx.run_until_parked();
4804
4805    assert_new_snapshot(
4806        &multibuffer,
4807        &mut snapshot,
4808        &mut subscription,
4809        cx,
4810        indoc! {
4811            "
4812            - one
4813            - two
4814            - three
4815            - four
4816            - five
4817            - six
4818            "
4819        },
4820    );
4821
4822    diff.update(cx, |diff, cx| {
4823        diff.set_base_text(
4824            Some("new base\n".into()),
4825            None,
4826            buffer.read(cx).text_snapshot(),
4827            cx,
4828        )
4829    })
4830    .await
4831    .unwrap();
4832    cx.run_until_parked();
4833
4834    assert_new_snapshot(
4835        &multibuffer,
4836        &mut snapshot,
4837        &mut subscription,
4838        cx,
4839        indoc! {"
4840            - new base
4841        "},
4842    );
4843}
4844
4845#[gpui::test]
4846async fn test_inverted_diff_base_text_change(cx: &mut TestAppContext) {
4847    let base_text = "aaa\nbbb\nccc\n";
4848    let text = "ddd\n";
4849    let buffer = cx.new(|cx| Buffer::local(text, cx));
4850    let diff = cx
4851        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
4852    cx.run_until_parked();
4853
4854    let base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4855
4856    let multibuffer = cx.new(|cx| {
4857        let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4858        multibuffer.set_all_diff_hunks_expanded(cx);
4859        multibuffer.add_inverted_diff(diff.clone(), buffer.clone(), cx);
4860        multibuffer
4861    });
4862
4863    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4864        (multibuffer.snapshot(cx), multibuffer.subscribe())
4865    });
4866
4867    assert_eq!(snapshot.text(), base_text);
4868    assert_new_snapshot(
4869        &multibuffer,
4870        &mut snapshot,
4871        &mut subscription,
4872        cx,
4873        indoc!(
4874            "
4875            - aaa
4876            - bbb
4877            - ccc
4878            "
4879        ),
4880    );
4881
4882    let update = diff
4883        .update(cx, |diff, cx| {
4884            diff.update_diff(
4885                buffer.read(cx).text_snapshot(),
4886                Some("ddd\n".into()),
4887                Some(true),
4888                None,
4889                cx,
4890            )
4891        })
4892        .await;
4893    diff.update(cx, |diff, cx| {
4894        diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4895    })
4896    .detach();
4897
4898    let _hunks: Vec<_> = multibuffer
4899        .read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx))
4900        .diff_hunks()
4901        .collect();
4902}
4903
4904#[gpui::test]
4905async fn test_inverted_diff_secondary_version_mismatch(cx: &mut TestAppContext) {
4906    let base_text = "one\ntwo\nthree\nfour\nfive\n";
4907    let index_text = "one\nTWO\nthree\nfour\nfive\n";
4908    let buffer_text = "one\nTWO\nthree\nFOUR\nfive\n";
4909
4910    let buffer = cx.new(|cx| Buffer::local(buffer_text, cx));
4911
4912    let unstaged_diff = cx
4913        .new(|cx| BufferDiff::new_with_base_text(index_text, &buffer.read(cx).text_snapshot(), cx));
4914    cx.run_until_parked();
4915
4916    let uncommitted_diff = cx.new(|cx| {
4917        let mut diff =
4918            BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx);
4919        diff.set_secondary_diff(unstaged_diff.clone());
4920        diff
4921    });
4922    cx.run_until_parked();
4923
4924    buffer.update(cx, |buffer, cx| {
4925        buffer.edit([(0..0, "ZERO\n")], None, cx);
4926    });
4927
4928    let update = unstaged_diff
4929        .update(cx, |diff, cx| {
4930            diff.update_diff(
4931                buffer.read(cx).text_snapshot(),
4932                Some(index_text.into()),
4933                None,
4934                None,
4935                cx,
4936            )
4937        })
4938        .await;
4939    unstaged_diff
4940        .update(cx, |diff, cx| {
4941            diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4942        })
4943        .await;
4944
4945    let base_text_buffer =
4946        uncommitted_diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4947
4948    let multibuffer = cx.new(|cx| {
4949        let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4950        multibuffer.set_all_diff_hunks_expanded(cx);
4951        multibuffer.add_inverted_diff(uncommitted_diff.clone(), buffer.clone(), cx);
4952        multibuffer
4953    });
4954
4955    let _hunks: Vec<_> = multibuffer
4956        .read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx))
4957        .diff_hunks()
4958        .collect();
4959}
4960
4961#[track_caller]
4962fn assert_excerpts_match(
4963    multibuffer: &Entity<MultiBuffer>,
4964    cx: &mut TestAppContext,
4965    expected: &str,
4966) {
4967    let mut output = String::new();
4968    multibuffer.read_with(cx, |multibuffer, cx| {
4969        let snapshot = multibuffer.snapshot(cx);
4970        for excerpt in multibuffer.snapshot(cx).excerpts() {
4971            output.push_str("-----\n");
4972            output.extend(
4973                snapshot
4974                    .buffer_for_id(excerpt.context.start.buffer_id)
4975                    .unwrap()
4976                    .text_for_range(excerpt.context),
4977            );
4978            if !output.ends_with('\n') {
4979                output.push('\n');
4980            }
4981        }
4982    });
4983    assert_eq!(output, expected);
4984}
4985
4986#[track_caller]
4987fn assert_new_snapshot(
4988    multibuffer: &Entity<MultiBuffer>,
4989    snapshot: &mut MultiBufferSnapshot,
4990    subscription: &mut Subscription<MultiBufferOffset>,
4991    cx: &mut TestAppContext,
4992    expected_diff: &str,
4993) {
4994    let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
4995    let actual_text = new_snapshot.text();
4996    let line_infos = new_snapshot
4997        .row_infos(MultiBufferRow(0))
4998        .collect::<Vec<_>>();
4999    let actual_diff = format_diff(&actual_text, &line_infos, &Default::default(), None);
5000    pretty_assertions::assert_eq!(actual_diff, expected_diff);
5001    check_edits(
5002        snapshot,
5003        &new_snapshot,
5004        &subscription.consume().into_inner(),
5005    );
5006    *snapshot = new_snapshot;
5007}
5008
5009#[track_caller]
5010fn check_edits(
5011    old_snapshot: &MultiBufferSnapshot,
5012    new_snapshot: &MultiBufferSnapshot,
5013    edits: &[Edit<MultiBufferOffset>],
5014) {
5015    let mut text = old_snapshot.text();
5016    let new_text = new_snapshot.text();
5017    for edit in edits.iter().rev() {
5018        if !text.is_char_boundary(edit.old.start.0)
5019            || !text.is_char_boundary(edit.old.end.0)
5020            || !new_text.is_char_boundary(edit.new.start.0)
5021            || !new_text.is_char_boundary(edit.new.end.0)
5022        {
5023            panic!(
5024                "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
5025                edits, text, new_text
5026            );
5027        }
5028
5029        text.replace_range(
5030            edit.old.start.0..edit.old.end.0,
5031            &new_text[edit.new.start.0..edit.new.end.0],
5032        );
5033    }
5034
5035    pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
5036}
5037
5038#[track_caller]
5039fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
5040    let full_text = snapshot.text();
5041    for ix in 0..full_text.len() {
5042        let mut chunks = snapshot.chunks(
5043            MultiBufferOffset(0)..snapshot.len(),
5044            LanguageAwareStyling {
5045                tree_sitter: false,
5046                diagnostics: false,
5047            },
5048        );
5049        chunks.seek(MultiBufferOffset(ix)..snapshot.len());
5050        let tail = chunks.map(|chunk| chunk.text).collect::<String>();
5051        assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
5052    }
5053}
5054
5055#[track_caller]
5056fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
5057    let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
5058    for start_row in 1..all_line_numbers.len() {
5059        let line_numbers = snapshot
5060            .row_infos(MultiBufferRow(start_row as u32))
5061            .collect::<Vec<_>>();
5062        assert_eq!(
5063            line_numbers,
5064            all_line_numbers[start_row..],
5065            "start_row: {start_row}"
5066        );
5067    }
5068}
5069
5070#[track_caller]
5071fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
5072    let text = Rope::from(snapshot.text());
5073
5074    let mut left_anchors = Vec::new();
5075    let mut right_anchors = Vec::new();
5076    let mut offsets = Vec::new();
5077    let mut points = Vec::new();
5078    for offset in 0..=text.len() + 1 {
5079        let offset = MultiBufferOffset(offset);
5080        let clipped_left = snapshot.clip_offset(offset, Bias::Left);
5081        let clipped_right = snapshot.clip_offset(offset, Bias::Right);
5082        assert_eq!(
5083            clipped_left.0,
5084            text.clip_offset(offset.0, Bias::Left),
5085            "clip_offset({offset:?}, Left)"
5086        );
5087        assert_eq!(
5088            clipped_right.0,
5089            text.clip_offset(offset.0, Bias::Right),
5090            "clip_offset({offset:?}, Right)"
5091        );
5092        assert_eq!(
5093            snapshot.offset_to_point(clipped_left),
5094            text.offset_to_point(clipped_left.0),
5095            "offset_to_point({})",
5096            clipped_left.0
5097        );
5098        assert_eq!(
5099            snapshot.offset_to_point(clipped_right),
5100            text.offset_to_point(clipped_right.0),
5101            "offset_to_point({})",
5102            clipped_right.0
5103        );
5104        let anchor_after = snapshot.anchor_after(clipped_left);
5105        assert_eq!(
5106            anchor_after.to_offset(snapshot),
5107            clipped_left,
5108            "anchor_after({}).to_offset {anchor_after:?}",
5109            clipped_left.0
5110        );
5111        let anchor_before = snapshot.anchor_before(clipped_left);
5112        assert_eq!(
5113            anchor_before.to_offset(snapshot),
5114            clipped_left,
5115            "anchor_before({}).to_offset",
5116            clipped_left.0
5117        );
5118        left_anchors.push(anchor_before);
5119        right_anchors.push(anchor_after);
5120        offsets.push(clipped_left);
5121        points.push(text.offset_to_point(clipped_left.0));
5122    }
5123
5124    for row in 0..text.max_point().row {
5125        for column in 0..text.line_len(row) + 1 {
5126            let point = Point { row, column };
5127            let clipped_left = snapshot.clip_point(point, Bias::Left);
5128            let clipped_right = snapshot.clip_point(point, Bias::Right);
5129            assert_eq!(
5130                clipped_left,
5131                text.clip_point(point, Bias::Left),
5132                "clip_point({point:?}, Left)"
5133            );
5134            assert_eq!(
5135                clipped_right,
5136                text.clip_point(point, Bias::Right),
5137                "clip_point({point:?}, Right)"
5138            );
5139            assert_eq!(
5140                snapshot.point_to_offset(clipped_left).0,
5141                text.point_to_offset(clipped_left),
5142                "point_to_offset({clipped_left:?})"
5143            );
5144            assert_eq!(
5145                snapshot.point_to_offset(clipped_right).0,
5146                text.point_to_offset(clipped_right),
5147                "point_to_offset({clipped_right:?})"
5148            );
5149        }
5150    }
5151
5152    assert_eq!(
5153        snapshot.summaries_for_anchors::<MultiBufferOffset, _>(&left_anchors),
5154        offsets,
5155        "left_anchors <-> offsets"
5156    );
5157    assert_eq!(
5158        snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
5159        points,
5160        "left_anchors <-> points"
5161    );
5162    assert_eq!(
5163        snapshot.summaries_for_anchors::<MultiBufferOffset, _>(&right_anchors),
5164        offsets,
5165        "right_anchors <-> offsets"
5166    );
5167    assert_eq!(
5168        snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
5169        points,
5170        "right_anchors <-> points"
5171    );
5172
5173    for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
5174        for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
5175            if ix > 0 && *offset == MultiBufferOffset(252) && offset > &offsets[ix - 1] {
5176                let prev_anchor = left_anchors[ix - 1];
5177                assert!(
5178                    anchor.cmp(&prev_anchor, snapshot).is_gt(),
5179                    "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
5180                    offsets[ix],
5181                    offsets[ix - 1],
5182                );
5183                assert!(
5184                    prev_anchor.cmp(anchor, snapshot).is_lt(),
5185                    "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
5186                    offsets[ix - 1],
5187                    offsets[ix],
5188                );
5189            }
5190        }
5191    }
5192
5193    if let Some((buffer, offset)) = snapshot.point_to_buffer_offset(snapshot.max_point()) {
5194        assert!(offset.0 <= buffer.len());
5195    }
5196    if let Some((buffer, point)) = snapshot.point_to_buffer_point(snapshot.max_point()) {
5197        assert!(point <= buffer.max_point());
5198    }
5199}
5200
5201fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
5202    let max_row = snapshot.max_point().row;
5203    let buffer_id = snapshot.excerpts().next().unwrap().context.start.buffer_id;
5204    let text = text::Buffer::new(ReplicaId::LOCAL, buffer_id, snapshot.text());
5205    let mut line_indents = text
5206        .line_indents_in_row_range(0..max_row + 1)
5207        .collect::<Vec<_>>();
5208    for start_row in 0..snapshot.max_point().row {
5209        pretty_assertions::assert_eq!(
5210            snapshot
5211                .line_indents(MultiBufferRow(start_row), |_| true)
5212                .map(|(row, indent, _)| (row.0, indent))
5213                .collect::<Vec<_>>(),
5214            &line_indents[(start_row as usize)..],
5215            "line_indents({start_row})"
5216        );
5217    }
5218
5219    line_indents.reverse();
5220    pretty_assertions::assert_eq!(
5221        snapshot
5222            .reversed_line_indents(MultiBufferRow(max_row), |_| true)
5223            .map(|(row, indent, _)| (row.0, indent))
5224            .collect::<Vec<_>>(),
5225        &line_indents[..],
5226        "reversed_line_indents({max_row})"
5227    );
5228}
5229
5230#[gpui::test]
5231fn test_new_empty_buffer_uses_untitled_title(cx: &mut App) {
5232    let buffer = cx.new(|cx| Buffer::local("", cx));
5233    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5234
5235    assert_eq!(multibuffer.read(cx).title(cx), "untitled");
5236}
5237
5238#[gpui::test]
5239fn test_new_empty_buffer_uses_untitled_title_when_only_contains_whitespace(cx: &mut App) {
5240    let buffer = cx.new(|cx| Buffer::local("\n ", cx));
5241    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5242
5243    assert_eq!(multibuffer.read(cx).title(cx), "untitled");
5244}
5245
5246#[gpui::test]
5247fn test_new_empty_buffer_takes_first_line_for_title(cx: &mut App) {
5248    let buffer = cx.new(|cx| Buffer::local("Hello World\nSecond line", cx));
5249    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5250
5251    assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
5252}
5253
5254#[gpui::test]
5255fn test_new_empty_buffer_takes_trimmed_first_line_for_title(cx: &mut App) {
5256    let buffer = cx.new(|cx| Buffer::local("\nHello, World ", cx));
5257    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5258
5259    assert_eq!(multibuffer.read(cx).title(cx), "Hello, World");
5260}
5261
5262#[gpui::test]
5263fn test_new_empty_buffer_uses_truncated_first_line_for_title(cx: &mut App) {
5264    let title = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee";
5265    let title_after = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd";
5266    let buffer = cx.new(|cx| Buffer::local(title, cx));
5267    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5268
5269    assert_eq!(multibuffer.read(cx).title(cx), title_after);
5270}
5271
5272#[gpui::test]
5273fn test_new_empty_buffer_uses_truncated_first_line_for_title_after_merging_adjacent_spaces(
5274    cx: &mut App,
5275) {
5276    let title = "aaaaaaaaaabbbbbbbbbb    ccccccccccddddddddddeeeeeeeeee";
5277    let title_after = "aaaaaaaaaabbbbbbbbbb ccccccccccddddddddd";
5278    let buffer = cx.new(|cx| Buffer::local(title, cx));
5279    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5280
5281    assert_eq!(multibuffer.read(cx).title(cx), title_after);
5282}
5283
5284#[gpui::test]
5285fn test_new_empty_buffers_title_can_be_set(cx: &mut App) {
5286    let buffer = cx.new(|cx| Buffer::local("Hello World", cx));
5287    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5288    assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
5289
5290    multibuffer.update(cx, |multibuffer, cx| {
5291        multibuffer.set_title("Hey".into(), cx)
5292    });
5293    assert_eq!(multibuffer.read(cx).title(cx), "Hey");
5294}
5295
5296#[gpui::test(iterations = 100)]
5297fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) {
5298    let multibuffer = if rng.random() {
5299        let len = rng.random_range(0..10000);
5300        let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
5301        let buffer = cx.new(|cx| Buffer::local(text, cx));
5302        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
5303    } else {
5304        MultiBuffer::build_random(&mut rng, cx)
5305    };
5306
5307    let snapshot = multibuffer.read(cx).snapshot(cx);
5308
5309    let chunks = snapshot.chunks(
5310        MultiBufferOffset(0)..snapshot.len(),
5311        LanguageAwareStyling {
5312            tree_sitter: false,
5313            diagnostics: false,
5314        },
5315    );
5316
5317    for chunk in chunks {
5318        let chunk_text = chunk.text;
5319        let chars_bitmap = chunk.chars;
5320        let tabs_bitmap = chunk.tabs;
5321
5322        if chunk_text.is_empty() {
5323            assert_eq!(
5324                chars_bitmap, 0,
5325                "Empty chunk should have empty chars bitmap"
5326            );
5327            assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
5328            continue;
5329        }
5330
5331        assert!(
5332            chunk_text.len() <= 128,
5333            "Chunk text length {} exceeds 128 bytes",
5334            chunk_text.len()
5335        );
5336
5337        // Verify chars bitmap
5338        let char_indices = chunk_text
5339            .char_indices()
5340            .map(|(i, _)| i)
5341            .collect::<Vec<_>>();
5342
5343        for byte_idx in 0..chunk_text.len() {
5344            let should_have_bit = char_indices.contains(&byte_idx);
5345            let has_bit = chars_bitmap & (1 << byte_idx) != 0;
5346
5347            if has_bit != should_have_bit {
5348                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5349                eprintln!("Char indices: {:?}", char_indices);
5350                eprintln!("Chars bitmap: {:#b}", chars_bitmap);
5351            }
5352
5353            assert_eq!(
5354                has_bit, should_have_bit,
5355                "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
5356                byte_idx, chunk_text, should_have_bit, has_bit
5357            );
5358        }
5359
5360        for (byte_idx, byte) in chunk_text.bytes().enumerate() {
5361            let is_tab = byte == b'\t';
5362            let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
5363
5364            if has_bit != is_tab {
5365                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5366                eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
5367                assert_eq!(
5368                    has_bit, is_tab,
5369                    "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
5370                    byte_idx, chunk_text, byte as char, is_tab, has_bit
5371                );
5372            }
5373        }
5374    }
5375}
5376
5377#[gpui::test(iterations = 10)]
5378fn test_random_chunk_bitmaps_with_diffs(cx: &mut App, mut rng: StdRng) {
5379    let settings_store = SettingsStore::test(cx);
5380    cx.set_global(settings_store);
5381    use buffer_diff::BufferDiff;
5382    use util::RandomCharIter;
5383
5384    let multibuffer = if rng.random() {
5385        let len = rng.random_range(100..10000);
5386        let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
5387        let buffer = cx.new(|cx| Buffer::local(text, cx));
5388        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
5389    } else {
5390        MultiBuffer::build_random(&mut rng, cx)
5391    };
5392
5393    let _diff_count = rng.random_range(1..5);
5394    let mut diffs = Vec::new();
5395
5396    multibuffer.update(cx, |multibuffer, cx| {
5397        let snapshot = multibuffer.snapshot(cx);
5398        for buffer_id in snapshot.all_buffer_ids() {
5399            if rng.random_bool(0.7) {
5400                if let Some(buffer_handle) = multibuffer.buffer(buffer_id) {
5401                    let buffer_text = buffer_handle.read(cx).text();
5402                    let mut base_text = String::new();
5403
5404                    for line in buffer_text.lines() {
5405                        if rng.random_bool(0.3) {
5406                            continue;
5407                        } else if rng.random_bool(0.3) {
5408                            let line_len = rng.random_range(0..50);
5409                            let modified_line = RandomCharIter::new(&mut rng)
5410                                .take(line_len)
5411                                .collect::<String>();
5412                            base_text.push_str(&modified_line);
5413                            base_text.push('\n');
5414                        } else {
5415                            base_text.push_str(line);
5416                            base_text.push('\n');
5417                        }
5418                    }
5419
5420                    if rng.random_bool(0.5) {
5421                        let extra_lines = rng.random_range(1..5);
5422                        for _ in 0..extra_lines {
5423                            let line_len = rng.random_range(0..50);
5424                            let extra_line = RandomCharIter::new(&mut rng)
5425                                .take(line_len)
5426                                .collect::<String>();
5427                            base_text.push_str(&extra_line);
5428                            base_text.push('\n');
5429                        }
5430                    }
5431
5432                    let diff = cx.new(|cx| {
5433                        BufferDiff::new_with_base_text(
5434                            &base_text,
5435                            &buffer_handle.read(cx).text_snapshot(),
5436                            cx,
5437                        )
5438                    });
5439                    diffs.push(diff.clone());
5440                    multibuffer.add_diff(diff, cx);
5441                }
5442            }
5443        }
5444    });
5445
5446    multibuffer.update(cx, |multibuffer, cx| {
5447        if rng.random_bool(0.5) {
5448            multibuffer.set_all_diff_hunks_expanded(cx);
5449        } else {
5450            let snapshot = multibuffer.snapshot(cx);
5451            let text = snapshot.text();
5452
5453            let mut ranges = Vec::new();
5454            for _ in 0..rng.random_range(1..5) {
5455                if snapshot.len().0 == 0 {
5456                    break;
5457                }
5458
5459                let diff_size = rng.random_range(5..1000);
5460                let mut start = rng.random_range(0..snapshot.len().0);
5461
5462                while !text.is_char_boundary(start) {
5463                    start = start.saturating_sub(1);
5464                }
5465
5466                let mut end = rng.random_range(start..snapshot.len().0.min(start + diff_size));
5467
5468                while !text.is_char_boundary(end) {
5469                    end = end.saturating_add(1);
5470                }
5471                let start_anchor = snapshot.anchor_after(MultiBufferOffset(start));
5472                let end_anchor = snapshot.anchor_before(MultiBufferOffset(end));
5473                ranges.push(start_anchor..end_anchor);
5474            }
5475            multibuffer.expand_diff_hunks(ranges, cx);
5476        }
5477    });
5478
5479    let snapshot = multibuffer.read(cx).snapshot(cx);
5480
5481    let chunks = snapshot.chunks(
5482        MultiBufferOffset(0)..snapshot.len(),
5483        LanguageAwareStyling {
5484            tree_sitter: false,
5485            diagnostics: false,
5486        },
5487    );
5488
5489    for chunk in chunks {
5490        let chunk_text = chunk.text;
5491        let chars_bitmap = chunk.chars;
5492        let tabs_bitmap = chunk.tabs;
5493
5494        if chunk_text.is_empty() {
5495            assert_eq!(
5496                chars_bitmap, 0,
5497                "Empty chunk should have empty chars bitmap"
5498            );
5499            assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
5500            continue;
5501        }
5502
5503        assert!(
5504            chunk_text.len() <= 128,
5505            "Chunk text length {} exceeds 128 bytes",
5506            chunk_text.len()
5507        );
5508
5509        let char_indices = chunk_text
5510            .char_indices()
5511            .map(|(i, _)| i)
5512            .collect::<Vec<_>>();
5513
5514        for byte_idx in 0..chunk_text.len() {
5515            let should_have_bit = char_indices.contains(&byte_idx);
5516            let has_bit = chars_bitmap & (1 << byte_idx) != 0;
5517
5518            if has_bit != should_have_bit {
5519                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5520                eprintln!("Char indices: {:?}", char_indices);
5521                eprintln!("Chars bitmap: {:#b}", chars_bitmap);
5522            }
5523
5524            assert_eq!(
5525                has_bit, should_have_bit,
5526                "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
5527                byte_idx, chunk_text, should_have_bit, has_bit
5528            );
5529        }
5530
5531        for (byte_idx, byte) in chunk_text.bytes().enumerate() {
5532            let is_tab = byte == b'\t';
5533            let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
5534
5535            if has_bit != is_tab {
5536                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5537                eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
5538                assert_eq!(
5539                    has_bit, is_tab,
5540                    "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
5541                    byte_idx, chunk_text, byte as char, is_tab, has_bit
5542                );
5543            }
5544        }
5545    }
5546}
5547
5548fn collect_word_diffs(
5549    base_text: &str,
5550    modified_text: &str,
5551    cx: &mut TestAppContext,
5552) -> Vec<String> {
5553    let buffer = cx.new(|cx| Buffer::local(modified_text, cx));
5554    let diff = cx
5555        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
5556    cx.run_until_parked();
5557
5558    let multibuffer = cx.new(|cx| {
5559        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
5560        multibuffer.add_diff(diff.clone(), cx);
5561        multibuffer
5562    });
5563
5564    multibuffer.update(cx, |multibuffer, cx| {
5565        multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
5566    });
5567
5568    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
5569    let text = snapshot.text();
5570
5571    snapshot
5572        .diff_hunks()
5573        .flat_map(|hunk| hunk.word_diffs)
5574        .map(|range| text[range.start.0..range.end.0].to_string())
5575        .collect()
5576}
5577
5578#[gpui::test]
5579async fn test_word_diff_simple_replacement(cx: &mut TestAppContext) {
5580    let settings_store = cx.update(|cx| SettingsStore::test(cx));
5581    cx.set_global(settings_store);
5582
5583    let base_text = "hello world foo bar\n";
5584    let modified_text = "hello WORLD foo BAR\n";
5585
5586    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5587
5588    assert_eq!(word_diffs, vec!["world", "bar", "WORLD", "BAR"]);
5589}
5590
5591#[gpui::test]
5592async fn test_word_diff_white_space(cx: &mut TestAppContext) {
5593    let settings_store = cx.update(|cx| SettingsStore::test(cx));
5594    cx.set_global(settings_store);
5595
5596    let base_text = "hello world foo bar\n";
5597    let modified_text = "    hello world foo bar\n";
5598
5599    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5600
5601    assert_eq!(word_diffs, vec!["    "]);
5602}
5603
5604#[gpui::test]
5605async fn test_word_diff_consecutive_modified_lines(cx: &mut TestAppContext) {
5606    let settings_store = cx.update(|cx| SettingsStore::test(cx));
5607    cx.set_global(settings_store);
5608
5609    let base_text = "aaa bbb\nccc ddd\n";
5610    let modified_text = "aaa BBB\nccc DDD\n";
5611
5612    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5613
5614    assert_eq!(
5615        word_diffs,
5616        vec!["bbb", "ddd", "BBB", "DDD"],
5617        "consecutive modified lines should produce word diffs when line counts match"
5618    );
5619}
5620
5621#[gpui::test]
5622async fn test_word_diff_modified_lines_with_deletion_between(cx: &mut TestAppContext) {
5623    let settings_store = cx.update(|cx| SettingsStore::test(cx));
5624    cx.set_global(settings_store);
5625
5626    let base_text = "aaa bbb\ndeleted line\nccc ddd\n";
5627    let modified_text = "aaa BBB\nccc DDD\n";
5628
5629    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5630
5631    assert_eq!(
5632        word_diffs,
5633        Vec::<String>::new(),
5634        "modified lines with a deleted line between should not produce word diffs"
5635    );
5636}
5637
5638#[gpui::test]
5639async fn test_word_diff_disabled(cx: &mut TestAppContext) {
5640    let settings_store = cx.update(|cx| {
5641        let mut settings_store = SettingsStore::test(cx);
5642        settings_store.update_user_settings(cx, |settings| {
5643            settings.project.all_languages.defaults.word_diff_enabled = Some(false);
5644        });
5645        settings_store
5646    });
5647    cx.set_global(settings_store);
5648
5649    let base_text = "hello world\n";
5650    let modified_text = "hello WORLD\n";
5651
5652    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5653
5654    assert_eq!(
5655        word_diffs,
5656        Vec::<String>::new(),
5657        "word diffs should be empty when disabled"
5658    );
5659}
5660
5661/// Tests `excerpt_containing` and `excerpts_for_range` (functions mapping multi-buffer text-coordinates to excerpts)
5662#[gpui::test]
5663fn test_excerpts_containment_functions(cx: &mut App) {
5664    // Multibuffer content for these tests:
5665    //    0123
5666    // 0: aa0
5667    // 1: aa1
5668    //    -----
5669    // 2: bb0
5670    // 3: bb1
5671    //    -----MultiBufferOffset(0)..
5672    // 4: cc0
5673
5674    let buffer_1 = cx.new(|cx| Buffer::local("aa0\naa1", cx));
5675    let buffer_2 = cx.new(|cx| Buffer::local("bb0\nbb1", cx));
5676    let buffer_3 = cx.new(|cx| Buffer::local("cc0", cx));
5677
5678    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5679
5680    let (excerpt_1_info, excerpt_2_info, excerpt_3_info) =
5681        multibuffer.update(cx, |multibuffer, cx| {
5682            multibuffer.set_excerpts_for_path(
5683                PathKey::sorted(0),
5684                buffer_1.clone(),
5685                [Point::new(0, 0)..Point::new(1, 3)],
5686                0,
5687                cx,
5688            );
5689
5690            multibuffer.set_excerpts_for_path(
5691                PathKey::sorted(1),
5692                buffer_2.clone(),
5693                [Point::new(0, 0)..Point::new(1, 3)],
5694                0,
5695                cx,
5696            );
5697
5698            multibuffer.set_excerpts_for_path(
5699                PathKey::sorted(2),
5700                buffer_3.clone(),
5701                [Point::new(0, 0)..Point::new(0, 3)],
5702                0,
5703                cx,
5704            );
5705
5706            let snapshot = multibuffer.snapshot(cx);
5707            let mut excerpts = snapshot.excerpts();
5708            (
5709                excerpts.next().unwrap(),
5710                excerpts.next().unwrap(),
5711                excerpts.next().unwrap(),
5712            )
5713        });
5714
5715    let snapshot = multibuffer.read(cx).snapshot(cx);
5716
5717    assert_eq!(snapshot.text(), "aa0\naa1\nbb0\nbb1\ncc0");
5718
5719    //// Test `excerpts_for_range`
5720
5721    let p00 = snapshot.point_to_offset(Point::new(0, 0));
5722    let p10 = snapshot.point_to_offset(Point::new(1, 0));
5723    let p20 = snapshot.point_to_offset(Point::new(2, 0));
5724    let p23 = snapshot.point_to_offset(Point::new(2, 3));
5725    let p13 = snapshot.point_to_offset(Point::new(1, 3));
5726    let p40 = snapshot.point_to_offset(Point::new(4, 0));
5727    let p43 = snapshot.point_to_offset(Point::new(4, 3));
5728
5729    let excerpts: Vec<_> = snapshot.excerpts_for_range(p00..p00).collect();
5730    assert_eq!(excerpts.len(), 1);
5731    assert_eq!(excerpts[0].range, excerpt_1_info);
5732
5733    // Cursor at very end of excerpt 3
5734    let excerpts: Vec<_> = snapshot.excerpts_for_range(p43..p43).collect();
5735    assert_eq!(excerpts.len(), 1);
5736    assert_eq!(excerpts[0].range, excerpt_3_info);
5737
5738    let excerpts: Vec<_> = snapshot.excerpts_for_range(p00..p23).collect();
5739    assert_eq!(excerpts.len(), 2);
5740    assert_eq!(excerpts[0].range, excerpt_1_info);
5741    assert_eq!(excerpts[1].range, excerpt_2_info);
5742
5743    // This range represent an selection with end-point just inside excerpt_2
5744    // Today we only expand the first excerpt, but another interpretation that
5745    // we could consider is expanding both here
5746    let excerpts: Vec<_> = snapshot.excerpts_for_range(p10..p20).collect();
5747    assert_eq!(excerpts.len(), 1);
5748    assert_eq!(excerpts[0].range, excerpt_1_info);
5749
5750    //// Test that `excerpts_for_range` and `excerpt_containing` agree for all single offsets (cursor positions)
5751    for offset in 0..=snapshot.len().0 {
5752        let offset = MultiBufferOffset(offset);
5753        let excerpts_for_range: Vec<_> = snapshot.excerpts_for_range(offset..offset).collect();
5754        assert_eq!(
5755            excerpts_for_range.len(),
5756            1,
5757            "Expected exactly one excerpt for offset {offset}",
5758        );
5759
5760        let (_, excerpt_containing) =
5761            snapshot
5762                .excerpt_containing(offset..offset)
5763                .unwrap_or_else(|| {
5764                    panic!("Expected excerpt_containing to find excerpt for offset {offset}")
5765                });
5766
5767        assert_eq!(
5768            excerpts_for_range[0].range, excerpt_containing,
5769            "excerpts_for_range and excerpt_containing should agree for offset {offset}",
5770        );
5771    }
5772
5773    //// Test `excerpt_containing` behavior with ranges:
5774
5775    // Ranges intersecting a single-excerpt
5776    let (_, containing) = snapshot.excerpt_containing(p00..p13).unwrap();
5777    assert_eq!(containing, excerpt_1_info);
5778
5779    // Ranges intersecting multiple excerpts (should return None)
5780    let containing = snapshot.excerpt_containing(p20..p40);
5781    assert!(
5782        containing.is_none(),
5783        "excerpt_containing should return None for ranges spanning multiple excerpts"
5784    );
5785}
5786
5787#[gpui::test]
5788fn test_range_to_buffer_ranges(cx: &mut App) {
5789    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb", cx));
5790    let buffer_2 = cx.new(|cx| Buffer::local("ccc", cx));
5791
5792    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5793    multibuffer.update(cx, |multibuffer, cx| {
5794        multibuffer.set_excerpts_for_path(
5795            PathKey::sorted(0),
5796            buffer_1.clone(),
5797            [Point::new(0, 0)..Point::new(1, 3)],
5798            0,
5799            cx,
5800        );
5801
5802        multibuffer.set_excerpts_for_path(
5803            PathKey::sorted(1),
5804            buffer_2.clone(),
5805            [Point::new(0, 0)..Point::new(0, 3)],
5806            0,
5807            cx,
5808        );
5809    });
5810
5811    let snapshot = multibuffer.read(cx).snapshot(cx);
5812    assert_eq!(snapshot.text(), "aaa\nbbb\nccc");
5813
5814    let excerpt_2_start = Point::new(2, 0);
5815
5816    let ranges_half_open = snapshot.range_to_buffer_ranges(Point::zero()..excerpt_2_start);
5817    assert_eq!(
5818        ranges_half_open.len(),
5819        1,
5820        "Half-open range ending at excerpt start should EXCLUDE that excerpt"
5821    );
5822    assert_eq!(ranges_half_open[0].1, BufferOffset(0)..BufferOffset(7));
5823    assert_eq!(
5824        ranges_half_open[0].0.remote_id(),
5825        buffer_1.read(cx).remote_id()
5826    );
5827
5828    let buffer_empty = cx.new(|cx| Buffer::local("", cx));
5829    let multibuffer_trailing_empty = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5830    let (_te_excerpt_1_info, _te_excerpt_2_info) =
5831        multibuffer_trailing_empty.update(cx, |multibuffer, cx| {
5832            multibuffer.set_excerpts_for_path(
5833                PathKey::sorted(0),
5834                buffer_1.clone(),
5835                [Point::new(0, 0)..Point::new(1, 3)],
5836                0,
5837                cx,
5838            );
5839
5840            multibuffer.set_excerpts_for_path(
5841                PathKey::sorted(1),
5842                buffer_empty.clone(),
5843                [Point::new(0, 0)..Point::new(0, 0)],
5844                0,
5845                cx,
5846            );
5847
5848            let snapshot = multibuffer.snapshot(cx);
5849            let mut infos = snapshot.excerpts();
5850            (infos.next().unwrap(), infos.next().unwrap())
5851        });
5852
5853    let snapshot_trailing = multibuffer_trailing_empty.read(cx).snapshot(cx);
5854    assert_eq!(snapshot_trailing.text(), "aaa\nbbb\n");
5855
5856    let max_point = snapshot_trailing.max_point();
5857
5858    let ranges_half_open_max = snapshot_trailing.range_to_buffer_ranges(Point::zero()..max_point);
5859    assert_eq!(
5860        ranges_half_open_max.len(),
5861        2,
5862        "Should include trailing empty excerpts"
5863    );
5864    assert_eq!(ranges_half_open_max[1].1, BufferOffset(0)..BufferOffset(0));
5865}
5866
5867#[gpui::test]
5868async fn test_buffer_range_to_excerpt_ranges(cx: &mut TestAppContext) {
5869    let base_text = indoc!(
5870        "
5871        aaa
5872        bbb
5873        ccc
5874        ddd
5875        eee
5876        ppp
5877        qqq
5878        rrr
5879        fff
5880        ggg
5881        hhh
5882        "
5883    );
5884    let text = indoc!(
5885        "
5886        aaa
5887        BBB
5888        ddd
5889        eee
5890        ppp
5891        qqq
5892        rrr
5893        FFF
5894        ggg
5895        hhh
5896        "
5897    );
5898
5899    let buffer = cx.new(|cx| Buffer::local(text, cx));
5900    let diff = cx
5901        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
5902    cx.run_until_parked();
5903
5904    let multibuffer = cx.new(|cx| {
5905        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
5906        multibuffer.set_excerpts_for_path(
5907            PathKey::sorted(0),
5908            buffer.clone(),
5909            [
5910                Point::new(0, 0)..Point::new(3, 3),
5911                Point::new(7, 0)..Point::new(9, 3),
5912            ],
5913            0,
5914            cx,
5915        );
5916        multibuffer.add_diff(diff.clone(), cx);
5917        multibuffer
5918    });
5919
5920    multibuffer.update(cx, |multibuffer, cx| {
5921        multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
5922    });
5923    cx.run_until_parked();
5924
5925    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
5926
5927    let actual_diff = format_diff(
5928        &snapshot.text(),
5929        &snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
5930        &Default::default(),
5931        None,
5932    );
5933    let expected_diff = indoc!(
5934        "
5935          aaa
5936        - bbb
5937        - ccc
5938        + BBB
5939          ddd
5940          eee [\u{2193}]
5941        - fff [\u{2191}]
5942        + FFF
5943          ggg
5944          hhh [\u{2193}]"
5945    );
5946    pretty_assertions::assert_eq!(actual_diff, expected_diff);
5947
5948    let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
5949
5950    let query_spanning_deleted_hunk = buffer_snapshot.anchor_after(Point::new(0, 0))
5951        ..buffer_snapshot.anchor_before(Point::new(1, 3));
5952    assert_eq!(
5953        snapshot
5954            .buffer_range_to_excerpt_ranges(query_spanning_deleted_hunk)
5955            .map(|range| range.to_point(&snapshot))
5956            .collect::<Vec<_>>(),
5957        vec![
5958            Point::new(0, 0)..Point::new(1, 0),
5959            Point::new(3, 0)..Point::new(3, 3),
5960        ],
5961    );
5962
5963    let query_within_contiguous_main_buffer = buffer_snapshot.anchor_after(Point::new(1, 0))
5964        ..buffer_snapshot.anchor_before(Point::new(2, 3));
5965    assert_eq!(
5966        snapshot
5967            .buffer_range_to_excerpt_ranges(query_within_contiguous_main_buffer)
5968            .map(|range| range.to_point(&snapshot))
5969            .collect::<Vec<_>>(),
5970        vec![Point::new(3, 0)..Point::new(4, 3)],
5971    );
5972
5973    let query_spanning_both_excerpts = buffer_snapshot.anchor_after(Point::new(2, 0))
5974        ..buffer_snapshot.anchor_before(Point::new(8, 3));
5975    assert_eq!(
5976        snapshot
5977            .buffer_range_to_excerpt_ranges(query_spanning_both_excerpts)
5978            .map(|range| range.to_point(&snapshot))
5979            .collect::<Vec<_>>(),
5980        vec![
5981            Point::new(4, 0)..Point::new(5, 3),
5982            Point::new(7, 0)..Point::new(8, 3),
5983        ],
5984    );
5985}
5986
5987#[gpui::test]
5988fn test_cannot_seek_backward_after_excerpt_replacement(cx: &mut TestAppContext) {
5989    let buffer_b_text: String = (0..50).map(|i| format!("line_b {i}\n")).collect();
5990    let buffer_b = cx.new(|cx| Buffer::local(buffer_b_text, cx));
5991
5992    let buffer_c_text: String = (0..10).map(|i| format!("line_c {i}\n")).collect();
5993    let buffer_c = cx.new(|cx| Buffer::local(buffer_c_text, cx));
5994
5995    let buffer_d_text: String = (0..10).map(|i| format!("line_d {i}\n")).collect();
5996    let buffer_d = cx.new(|cx| Buffer::local(buffer_d_text, cx));
5997
5998    let path_b = PathKey::with_sort_prefix(0, rel_path("bbb.rs").into_arc());
5999    let path_c = PathKey::with_sort_prefix(0, rel_path("ddd.rs").into_arc());
6000    let path_d = PathKey::with_sort_prefix(0, rel_path("ccc.rs").into_arc());
6001
6002    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
6003
6004    multibuffer.update(cx, |multibuffer, cx| {
6005        multibuffer.set_excerpts_for_path(
6006            path_b.clone(),
6007            buffer_b.clone(),
6008            vec![
6009                Point::row_range(0..3),
6010                Point::row_range(15..18),
6011                Point::row_range(30..33),
6012            ],
6013            0,
6014            cx,
6015        );
6016    });
6017
6018    multibuffer.update(cx, |multibuffer, cx| {
6019        multibuffer.set_excerpts_for_path(
6020            path_c.clone(),
6021            buffer_c.clone(),
6022            vec![Point::row_range(0..3)],
6023            0,
6024            cx,
6025        );
6026    });
6027
6028    let (anchor_in_e_b2, anchor_in_e_b3) = multibuffer.read_with(cx, |multibuffer, cx| {
6029        let snapshot = multibuffer.snapshot(cx);
6030        let excerpt_infos = snapshot.excerpts().collect::<Vec<_>>();
6031        assert_eq!(excerpt_infos.len(), 4, "expected 4 excerpts (3×B + 1×C)");
6032
6033        let e_b2_info = excerpt_infos[1].clone();
6034        let e_b3_info = excerpt_infos[2].clone();
6035
6036        let anchor_b2 = snapshot.anchor_in_excerpt(e_b2_info.context.start).unwrap();
6037        let anchor_b3 = snapshot.anchor_in_excerpt(e_b3_info.context.start).unwrap();
6038        (anchor_b2, anchor_b3)
6039    });
6040
6041    multibuffer.update(cx, |multibuffer, cx| {
6042        multibuffer.set_excerpts_for_path(
6043            path_b.clone(),
6044            buffer_b.clone(),
6045            vec![Point::row_range(0..3), Point::row_range(28..36)],
6046            0,
6047            cx,
6048        );
6049    });
6050
6051    multibuffer.update(cx, |multibuffer, cx| {
6052        multibuffer.set_excerpts_for_path(
6053            path_d.clone(),
6054            buffer_d.clone(),
6055            vec![Point::row_range(0..3)],
6056            0,
6057            cx,
6058        );
6059    });
6060
6061    multibuffer.read_with(cx, |multibuffer, cx| {
6062        let snapshot = multibuffer.snapshot(cx);
6063        snapshot.summaries_for_anchors::<Point, _>(&[anchor_in_e_b2, anchor_in_e_b3]);
6064    });
6065}
6066
6067#[gpui::test]
6068fn test_resolving_max_anchor_for_buffer(cx: &mut TestAppContext) {
6069    let dock_base_text = indoc! {"
6070        0
6071        1
6072        2
6073        3
6074        4
6075        5
6076        6
6077        7
6078        8
6079        9
6080        10
6081        11
6082        12
6083    "};
6084
6085    let dock_text = indoc! {"
6086        0
6087        4
6088        5
6089        6
6090        10
6091        11
6092        12
6093    "};
6094
6095    let dock_buffer = cx.new(|cx| Buffer::local(dock_text, cx));
6096    let diff = cx.new(|cx| {
6097        BufferDiff::new_with_base_text(dock_base_text, &dock_buffer.read(cx).snapshot(), cx)
6098    });
6099
6100    let workspace_text = "second buffer\n";
6101    let workspace_buffer = cx.new(|cx| Buffer::local(workspace_text, cx));
6102
6103    let dock_path = PathKey::with_sort_prefix(0, rel_path("").into_arc());
6104    let workspace_path = PathKey::with_sort_prefix(1, rel_path("").into_arc());
6105
6106    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
6107
6108    multibuffer.update(cx, |multibuffer, cx| {
6109        multibuffer.set_excerpt_ranges_for_path(
6110            dock_path,
6111            dock_buffer.clone(),
6112            &dock_buffer.read(cx).snapshot(),
6113            vec![
6114                ExcerptRange::new(Point::zero()..Point::new(1, 1)),
6115                ExcerptRange::new(Point::new(3, 0)..Point::new(4, 2)),
6116            ],
6117            cx,
6118        );
6119        multibuffer.set_excerpt_ranges_for_path(
6120            workspace_path,
6121            workspace_buffer.clone(),
6122            &workspace_buffer.read(cx).snapshot(),
6123            vec![ExcerptRange::new(
6124                Point::zero()..workspace_buffer.read(cx).max_point(),
6125            )],
6126            cx,
6127        );
6128        multibuffer.add_diff(diff, cx);
6129        multibuffer.set_all_diff_hunks_expanded(cx);
6130    });
6131
6132    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
6133    let diff = format_diff(
6134        &snapshot.text(),
6135        &snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
6136        &Default::default(),
6137        None,
6138    );
6139    assert_eq!(
6140        diff,
6141        indoc! {"
6142            0
6143          - 1
6144          - 2
6145          - 3
6146            4 [↓]
6147            6 [↑]
6148          - 7
6149          - 8
6150          - 9
6151            10 [↓]
6152            second buffer
6153        "}
6154    );
6155
6156    multibuffer.update(cx, |multibuffer, cx| {
6157        let snapshot = multibuffer.snapshot(cx);
6158        let point = snapshot
6159            .anchor_in_buffer(text::Anchor::max_for_buffer(
6160                dock_buffer.read(cx).remote_id(),
6161            ))
6162            .unwrap()
6163            .to_point(&snapshot);
6164        assert_eq!(point, Point::new(10, 0));
6165    })
6166}