multi_buffer_tests.rs

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