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