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