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