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 util::test::sample_text;
  11
  12#[ctor::ctor]
  13fn init_logger() {
  14    if std::env::var("RUST_LOG").is_ok() {
  15        env_logger::init();
  16    }
  17}
  18
  19#[gpui::test]
  20fn test_empty_singleton(cx: &mut App) {
  21    let buffer = cx.new(|cx| Buffer::local("", cx));
  22    let buffer_id = buffer.read(cx).remote_id();
  23    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
  24    let snapshot = multibuffer.read(cx).snapshot(cx);
  25    assert_eq!(snapshot.text(), "");
  26    assert_eq!(
  27        snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
  28        [RowInfo {
  29            buffer_id: Some(buffer_id),
  30            buffer_row: Some(0),
  31            multibuffer_row: Some(MultiBufferRow(0)),
  32            diff_status: None
  33        }]
  34    );
  35}
  36
  37#[gpui::test]
  38fn test_singleton(cx: &mut App) {
  39    let buffer = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
  40    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
  41
  42    let snapshot = multibuffer.read(cx).snapshot(cx);
  43    assert_eq!(snapshot.text(), buffer.read(cx).text());
  44
  45    assert_eq!(
  46        snapshot
  47            .row_infos(MultiBufferRow(0))
  48            .map(|info| info.buffer_row)
  49            .collect::<Vec<_>>(),
  50        (0..buffer.read(cx).row_count())
  51            .map(Some)
  52            .collect::<Vec<_>>()
  53    );
  54    assert_consistent_line_numbers(&snapshot);
  55
  56    buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx));
  57    let snapshot = multibuffer.read(cx).snapshot(cx);
  58
  59    assert_eq!(snapshot.text(), buffer.read(cx).text());
  60    assert_eq!(
  61        snapshot
  62            .row_infos(MultiBufferRow(0))
  63            .map(|info| info.buffer_row)
  64            .collect::<Vec<_>>(),
  65        (0..buffer.read(cx).row_count())
  66            .map(Some)
  67            .collect::<Vec<_>>()
  68    );
  69    assert_consistent_line_numbers(&snapshot);
  70}
  71
  72#[gpui::test]
  73fn test_remote(cx: &mut App) {
  74    let host_buffer = cx.new(|cx| Buffer::local("a", cx));
  75    let guest_buffer = cx.new(|cx| {
  76        let state = host_buffer.read(cx).to_proto(cx);
  77        let ops = cx
  78            .background_executor()
  79            .block(host_buffer.read(cx).serialize_ops(None, cx));
  80        let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
  81        buffer.apply_ops(
  82            ops.into_iter()
  83                .map(|op| language::proto::deserialize_operation(op).unwrap()),
  84            cx,
  85        );
  86        buffer
  87    });
  88    let multibuffer = cx.new(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx));
  89    let snapshot = multibuffer.read(cx).snapshot(cx);
  90    assert_eq!(snapshot.text(), "a");
  91
  92    guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx));
  93    let snapshot = multibuffer.read(cx).snapshot(cx);
  94    assert_eq!(snapshot.text(), "ab");
  95
  96    guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx));
  97    let snapshot = multibuffer.read(cx).snapshot(cx);
  98    assert_eq!(snapshot.text(), "abc");
  99}
 100
 101#[gpui::test]
 102fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
 103    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
 104    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
 105    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 106
 107    let events = Arc::new(RwLock::new(Vec::<Event>::new()));
 108    multibuffer.update(cx, |_, cx| {
 109        let events = events.clone();
 110        cx.subscribe(&multibuffer, move |_, _, event, _| {
 111            if let Event::Edited { .. } = event {
 112                events.write().push(event.clone())
 113            }
 114        })
 115        .detach();
 116    });
 117
 118    let subscription = multibuffer.update(cx, |multibuffer, cx| {
 119        let subscription = multibuffer.subscribe();
 120        multibuffer.push_excerpts(
 121            buffer_1.clone(),
 122            [ExcerptRange {
 123                context: Point::new(1, 2)..Point::new(2, 5),
 124                primary: None,
 125            }],
 126            cx,
 127        );
 128        assert_eq!(
 129            subscription.consume().into_inner(),
 130            [Edit {
 131                old: 0..0,
 132                new: 0..10
 133            }]
 134        );
 135
 136        multibuffer.push_excerpts(
 137            buffer_1.clone(),
 138            [ExcerptRange {
 139                context: Point::new(3, 3)..Point::new(4, 4),
 140                primary: None,
 141            }],
 142            cx,
 143        );
 144        multibuffer.push_excerpts(
 145            buffer_2.clone(),
 146            [ExcerptRange {
 147                context: Point::new(3, 1)..Point::new(3, 3),
 148                primary: None,
 149            }],
 150            cx,
 151        );
 152        assert_eq!(
 153            subscription.consume().into_inner(),
 154            [Edit {
 155                old: 10..10,
 156                new: 10..22
 157            }]
 158        );
 159
 160        subscription
 161    });
 162
 163    // Adding excerpts emits an edited event.
 164    assert_eq!(
 165        events.read().as_slice(),
 166        &[
 167            Event::Edited {
 168                singleton_buffer_edited: false,
 169                edited_buffer: None,
 170            },
 171            Event::Edited {
 172                singleton_buffer_edited: false,
 173                edited_buffer: None,
 174            },
 175            Event::Edited {
 176                singleton_buffer_edited: false,
 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_eq!(
 216        snapshot
 217            .row_infos(MultiBufferRow(5))
 218            .map(|info| info.buffer_row)
 219            .collect::<Vec<_>>(),
 220        []
 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: 6..8,
 293            new: 6..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            .filter_map(|boundary| {
 344                let starts_new_buffer = boundary.starts_new_buffer();
 345                boundary.next.map(|next| {
 346                    (
 347                        boundary.row,
 348                        next.buffer
 349                            .text_for_range(next.range.context)
 350                            .collect::<String>(),
 351                        starts_new_buffer,
 352                    )
 353                })
 354            })
 355            .collect::<Vec<_>>()
 356    }
 357}
 358
 359#[gpui::test]
 360fn 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]
 402fn 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]
 482fn 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            [
 660                ExcerptRange {
 661                    context: 0..8,
 662                    primary: None,
 663                },
 664                ExcerptRange {
 665                    context: 12..16,
 666                    primary: None,
 667                },
 668            ],
 669            cx,
 670        );
 671        leader.insert_excerpts_after(
 672            leader.excerpt_ids()[0],
 673            buffer_2.clone(),
 674            [
 675                ExcerptRange {
 676                    context: 0..5,
 677                    primary: None,
 678                },
 679                ExcerptRange {
 680                    context: 10..15,
 681                    primary: None,
 682                },
 683            ],
 684            cx,
 685        )
 686    });
 687    assert_eq!(
 688        leader_multibuffer.read(cx).snapshot(cx).text(),
 689        follower_multibuffer.read(cx).snapshot(cx).text(),
 690    );
 691    assert_eq!(*follower_edit_event_count.read(), 2);
 692
 693    leader_multibuffer.update(cx, |leader, cx| {
 694        let excerpt_ids = leader.excerpt_ids();
 695        leader.remove_excerpts([excerpt_ids[1], excerpt_ids[3]], cx);
 696    });
 697    assert_eq!(
 698        leader_multibuffer.read(cx).snapshot(cx).text(),
 699        follower_multibuffer.read(cx).snapshot(cx).text(),
 700    );
 701    assert_eq!(*follower_edit_event_count.read(), 3);
 702
 703    // Removing an empty set of excerpts is a noop.
 704    leader_multibuffer.update(cx, |leader, cx| {
 705        leader.remove_excerpts([], cx);
 706    });
 707    assert_eq!(
 708        leader_multibuffer.read(cx).snapshot(cx).text(),
 709        follower_multibuffer.read(cx).snapshot(cx).text(),
 710    );
 711    assert_eq!(*follower_edit_event_count.read(), 3);
 712
 713    // Adding an empty set of excerpts is a noop.
 714    leader_multibuffer.update(cx, |leader, cx| {
 715        leader.push_excerpts::<usize>(buffer_2.clone(), [], cx);
 716    });
 717    assert_eq!(
 718        leader_multibuffer.read(cx).snapshot(cx).text(),
 719        follower_multibuffer.read(cx).snapshot(cx).text(),
 720    );
 721    assert_eq!(*follower_edit_event_count.read(), 3);
 722
 723    leader_multibuffer.update(cx, |leader, cx| {
 724        leader.clear(cx);
 725    });
 726    assert_eq!(
 727        leader_multibuffer.read(cx).snapshot(cx).text(),
 728        follower_multibuffer.read(cx).snapshot(cx).text(),
 729    );
 730    assert_eq!(*follower_edit_event_count.read(), 4);
 731}
 732
 733#[gpui::test]
 734fn test_expand_excerpts(cx: &mut App) {
 735    let buffer = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
 736    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 737
 738    multibuffer.update(cx, |multibuffer, cx| {
 739        multibuffer.push_excerpts_with_context_lines(
 740            buffer.clone(),
 741            vec![
 742                // Note that in this test, this first excerpt
 743                // does not contain a new line
 744                Point::new(3, 2)..Point::new(3, 3),
 745                Point::new(7, 1)..Point::new(7, 3),
 746                Point::new(15, 0)..Point::new(15, 0),
 747            ],
 748            1,
 749            cx,
 750        )
 751    });
 752
 753    let snapshot = multibuffer.read(cx).snapshot(cx);
 754
 755    assert_eq!(
 756        snapshot.text(),
 757        concat!(
 758            "ccc\n", //
 759            "ddd\n", //
 760            "eee",   //
 761            "\n",    // End of excerpt
 762            "ggg\n", //
 763            "hhh\n", //
 764            "iii",   //
 765            "\n",    // End of excerpt
 766            "ooo\n", //
 767            "ppp\n", //
 768            "qqq",   // End of excerpt
 769        )
 770    );
 771    drop(snapshot);
 772
 773    multibuffer.update(cx, |multibuffer, cx| {
 774        multibuffer.expand_excerpts(
 775            multibuffer.excerpt_ids(),
 776            1,
 777            ExpandExcerptDirection::UpAndDown,
 778            cx,
 779        )
 780    });
 781
 782    let snapshot = multibuffer.read(cx).snapshot(cx);
 783
 784    // Expanding context lines causes the line containing 'fff' to appear in two different excerpts.
 785    // We don't attempt to merge them, because removing the excerpt could create inconsistency with other layers
 786    // that are tracking excerpt ids.
 787    assert_eq!(
 788        snapshot.text(),
 789        concat!(
 790            "bbb\n", //
 791            "ccc\n", //
 792            "ddd\n", //
 793            "eee\n", //
 794            "fff\n", // End of excerpt
 795            "fff\n", //
 796            "ggg\n", //
 797            "hhh\n", //
 798            "iii\n", //
 799            "jjj\n", // End of excerpt
 800            "nnn\n", //
 801            "ooo\n", //
 802            "ppp\n", //
 803            "qqq\n", //
 804            "rrr",   // End of excerpt
 805        )
 806    );
 807}
 808
 809#[gpui::test]
 810fn test_push_excerpts_with_context_lines(cx: &mut App) {
 811    let buffer = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
 812    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 813    let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
 814        multibuffer.push_excerpts_with_context_lines(
 815            buffer.clone(),
 816            vec![
 817                // Note that in this test, this first excerpt
 818                // does contain a new line
 819                Point::new(3, 2)..Point::new(4, 2),
 820                Point::new(7, 1)..Point::new(7, 3),
 821                Point::new(15, 0)..Point::new(15, 0),
 822            ],
 823            2,
 824            cx,
 825        )
 826    });
 827
 828    let snapshot = multibuffer.read(cx).snapshot(cx);
 829    assert_eq!(
 830        snapshot.text(),
 831        concat!(
 832            "bbb\n", // Preserve newlines
 833            "ccc\n", //
 834            "ddd\n", //
 835            "eee\n", //
 836            "fff\n", //
 837            "ggg\n", //
 838            "hhh\n", //
 839            "iii\n", //
 840            "jjj\n", //
 841            "nnn\n", //
 842            "ooo\n", //
 843            "ppp\n", //
 844            "qqq\n", //
 845            "rrr",   //
 846        )
 847    );
 848
 849    assert_eq!(
 850        anchor_ranges
 851            .iter()
 852            .map(|range| range.to_point(&snapshot))
 853            .collect::<Vec<_>>(),
 854        vec![
 855            Point::new(2, 2)..Point::new(3, 2),
 856            Point::new(6, 1)..Point::new(6, 3),
 857            Point::new(11, 0)..Point::new(11, 0)
 858        ]
 859    );
 860}
 861
 862#[gpui::test(iterations = 100)]
 863async fn test_push_multiple_excerpts_with_context_lines(cx: &mut TestAppContext) {
 864    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
 865    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(15, 4, 'a'), cx));
 866    let snapshot_1 = buffer_1.update(cx, |buffer, _| buffer.snapshot());
 867    let snapshot_2 = buffer_2.update(cx, |buffer, _| buffer.snapshot());
 868    let ranges_1 = vec![
 869        snapshot_1.anchor_before(Point::new(3, 2))..snapshot_1.anchor_before(Point::new(4, 2)),
 870        snapshot_1.anchor_before(Point::new(7, 1))..snapshot_1.anchor_before(Point::new(7, 3)),
 871        snapshot_1.anchor_before(Point::new(15, 0))..snapshot_1.anchor_before(Point::new(15, 0)),
 872    ];
 873    let ranges_2 = vec![
 874        snapshot_2.anchor_before(Point::new(2, 1))..snapshot_2.anchor_before(Point::new(3, 1)),
 875        snapshot_2.anchor_before(Point::new(10, 0))..snapshot_2.anchor_before(Point::new(10, 2)),
 876    ];
 877
 878    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 879    let anchor_ranges = multibuffer
 880        .update(cx, |multibuffer, cx| {
 881            multibuffer.push_multiple_excerpts_with_context_lines(
 882                vec![(buffer_1.clone(), ranges_1), (buffer_2.clone(), ranges_2)],
 883                2,
 884                cx,
 885            )
 886        })
 887        .await;
 888
 889    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 890    assert_eq!(
 891        snapshot.text(),
 892        concat!(
 893            "bbb\n", // buffer_1
 894            "ccc\n", //
 895            "ddd\n", // <-- excerpt 1
 896            "eee\n", // <-- excerpt 1
 897            "fff\n", //
 898            "ggg\n", //
 899            "hhh\n", // <-- excerpt 2
 900            "iii\n", //
 901            "jjj\n", //
 902            //
 903            "nnn\n", //
 904            "ooo\n", //
 905            "ppp\n", // <-- excerpt 3
 906            "qqq\n", //
 907            "rrr\n", //
 908            //
 909            "aaaa\n", // buffer 2
 910            "bbbb\n", //
 911            "cccc\n", // <-- excerpt 4
 912            "dddd\n", // <-- excerpt 4
 913            "eeee\n", //
 914            "ffff\n", //
 915            //
 916            "iiii\n", //
 917            "jjjj\n", //
 918            "kkkk\n", // <-- excerpt 5
 919            "llll\n", //
 920            "mmmm",   //
 921        )
 922    );
 923
 924    assert_eq!(
 925        anchor_ranges
 926            .iter()
 927            .map(|range| range.to_point(&snapshot))
 928            .collect::<Vec<_>>(),
 929        vec![
 930            Point::new(2, 2)..Point::new(3, 2),
 931            Point::new(6, 1)..Point::new(6, 3),
 932            Point::new(11, 0)..Point::new(11, 0),
 933            Point::new(16, 1)..Point::new(17, 1),
 934            Point::new(22, 0)..Point::new(22, 2)
 935        ]
 936    );
 937}
 938
 939#[gpui::test]
 940fn test_empty_multibuffer(cx: &mut App) {
 941    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 942
 943    let snapshot = multibuffer.read(cx).snapshot(cx);
 944    assert_eq!(snapshot.text(), "");
 945    assert_eq!(
 946        snapshot
 947            .row_infos(MultiBufferRow(0))
 948            .map(|info| info.buffer_row)
 949            .collect::<Vec<_>>(),
 950        &[Some(0)]
 951    );
 952    assert_eq!(
 953        snapshot
 954            .row_infos(MultiBufferRow(1))
 955            .map(|info| info.buffer_row)
 956            .collect::<Vec<_>>(),
 957        &[]
 958    );
 959}
 960
 961#[gpui::test]
 962fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
 963    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 964    let buffer = cx.new(|cx| Buffer::local("", cx));
 965    let base_text = "a\nb\nc";
 966
 967    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
 968    multibuffer.update(cx, |multibuffer, cx| {
 969        multibuffer.push_excerpts(
 970            buffer.clone(),
 971            [ExcerptRange {
 972                context: 0..0,
 973                primary: None,
 974            }],
 975            cx,
 976        );
 977        multibuffer.set_all_diff_hunks_expanded(cx);
 978        multibuffer.add_diff(diff.clone(), cx);
 979    });
 980    cx.run_until_parked();
 981
 982    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
 983    assert_eq!(snapshot.text(), "a\nb\nc\n");
 984
 985    let hunk = snapshot
 986        .diff_hunks_in_range(Point::new(1, 1)..Point::new(1, 1))
 987        .next()
 988        .unwrap();
 989
 990    assert_eq!(hunk.diff_base_byte_range.start, 0);
 991
 992    let buf2 = cx.new(|cx| Buffer::local("X", cx));
 993    multibuffer.update(cx, |multibuffer, cx| {
 994        multibuffer.push_excerpts(
 995            buf2,
 996            [ExcerptRange {
 997                context: 0..1,
 998                primary: None,
 999            }],
1000            cx,
1001        );
1002    });
1003
1004    buffer.update(cx, |buffer, cx| {
1005        buffer.edit([(0..0, "a\nb\nc")], None, cx);
1006        diff.update(cx, |diff, cx| {
1007            diff.recalculate_diff_sync(buffer.snapshot().text, cx);
1008        });
1009        assert_eq!(buffer.text(), "a\nb\nc")
1010    });
1011    cx.run_until_parked();
1012
1013    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1014    assert_eq!(snapshot.text(), "a\nb\nc\nX");
1015
1016    buffer.update(cx, |buffer, cx| {
1017        buffer.undo(cx);
1018        diff.update(cx, |diff, cx| {
1019            diff.recalculate_diff_sync(buffer.snapshot().text, cx);
1020        });
1021        assert_eq!(buffer.text(), "")
1022    });
1023    cx.run_until_parked();
1024
1025    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1026    assert_eq!(snapshot.text(), "a\nb\nc\n\nX");
1027}
1028
1029#[gpui::test]
1030fn test_singleton_multibuffer_anchors(cx: &mut App) {
1031    let buffer = cx.new(|cx| Buffer::local("abcd", cx));
1032    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
1033    let old_snapshot = multibuffer.read(cx).snapshot(cx);
1034    buffer.update(cx, |buffer, cx| {
1035        buffer.edit([(0..0, "X")], None, cx);
1036        buffer.edit([(5..5, "Y")], None, cx);
1037    });
1038    let new_snapshot = multibuffer.read(cx).snapshot(cx);
1039
1040    assert_eq!(old_snapshot.text(), "abcd");
1041    assert_eq!(new_snapshot.text(), "XabcdY");
1042
1043    assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1044    assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1045    assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5);
1046    assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6);
1047}
1048
1049#[gpui::test]
1050fn test_multibuffer_anchors(cx: &mut App) {
1051    let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1052    let buffer_2 = cx.new(|cx| Buffer::local("efghi", cx));
1053    let multibuffer = cx.new(|cx| {
1054        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1055        multibuffer.push_excerpts(
1056            buffer_1.clone(),
1057            [ExcerptRange {
1058                context: 0..4,
1059                primary: None,
1060            }],
1061            cx,
1062        );
1063        multibuffer.push_excerpts(
1064            buffer_2.clone(),
1065            [ExcerptRange {
1066                context: 0..5,
1067                primary: None,
1068            }],
1069            cx,
1070        );
1071        multibuffer
1072    });
1073    let old_snapshot = multibuffer.read(cx).snapshot(cx);
1074
1075    assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0);
1076    assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0);
1077    assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1078    assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1079    assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1080    assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1081
1082    buffer_1.update(cx, |buffer, cx| {
1083        buffer.edit([(0..0, "W")], None, cx);
1084        buffer.edit([(5..5, "X")], None, cx);
1085    });
1086    buffer_2.update(cx, |buffer, cx| {
1087        buffer.edit([(0..0, "Y")], None, cx);
1088        buffer.edit([(6..6, "Z")], None, cx);
1089    });
1090    let new_snapshot = multibuffer.read(cx).snapshot(cx);
1091
1092    assert_eq!(old_snapshot.text(), "abcd\nefghi");
1093    assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
1094
1095    assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1096    assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1097    assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2);
1098    assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2);
1099    assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3);
1100    assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3);
1101    assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7);
1102    assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8);
1103    assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13);
1104    assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14);
1105}
1106
1107#[gpui::test]
1108fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
1109    let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1110    let buffer_2 = cx.new(|cx| Buffer::local("ABCDEFGHIJKLMNOP", cx));
1111    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1112
1113    // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
1114    // Add an excerpt from buffer 1 that spans this new insertion.
1115    buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx));
1116    let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| {
1117        multibuffer
1118            .push_excerpts(
1119                buffer_1.clone(),
1120                [ExcerptRange {
1121                    context: 0..7,
1122                    primary: None,
1123                }],
1124                cx,
1125            )
1126            .pop()
1127            .unwrap()
1128    });
1129
1130    let snapshot_1 = multibuffer.read(cx).snapshot(cx);
1131    assert_eq!(snapshot_1.text(), "abcd123");
1132
1133    // Replace the buffer 1 excerpt with new excerpts from buffer 2.
1134    let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
1135        multibuffer.remove_excerpts([excerpt_id_1], cx);
1136        let mut ids = multibuffer
1137            .push_excerpts(
1138                buffer_2.clone(),
1139                [
1140                    ExcerptRange {
1141                        context: 0..4,
1142                        primary: None,
1143                    },
1144                    ExcerptRange {
1145                        context: 6..10,
1146                        primary: None,
1147                    },
1148                    ExcerptRange {
1149                        context: 12..16,
1150                        primary: None,
1151                    },
1152                ],
1153                cx,
1154            )
1155            .into_iter();
1156        (ids.next().unwrap(), ids.next().unwrap())
1157    });
1158    let snapshot_2 = multibuffer.read(cx).snapshot(cx);
1159    assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP");
1160
1161    // The old excerpt id doesn't get reused.
1162    assert_ne!(excerpt_id_2, excerpt_id_1);
1163
1164    // Resolve some anchors from the previous snapshot in the new snapshot.
1165    // The current excerpts are from a different buffer, so we don't attempt to
1166    // resolve the old text anchor in the new buffer.
1167    assert_eq!(
1168        snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)),
1169        0
1170    );
1171    assert_eq!(
1172        snapshot_2.summaries_for_anchors::<usize, _>(&[
1173            snapshot_1.anchor_before(2),
1174            snapshot_1.anchor_after(3)
1175        ]),
1176        vec![0, 0]
1177    );
1178
1179    // Refresh anchors from the old snapshot. The return value indicates that both
1180    // anchors lost their original excerpt.
1181    let refresh =
1182        snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]);
1183    assert_eq!(
1184        refresh,
1185        &[
1186            (0, snapshot_2.anchor_before(0), false),
1187            (1, snapshot_2.anchor_after(0), false),
1188        ]
1189    );
1190
1191    // Replace the middle excerpt with a smaller excerpt in buffer 2,
1192    // that intersects the old excerpt.
1193    let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| {
1194        multibuffer.remove_excerpts([excerpt_id_3], cx);
1195        multibuffer
1196            .insert_excerpts_after(
1197                excerpt_id_2,
1198                buffer_2.clone(),
1199                [ExcerptRange {
1200                    context: 5..8,
1201                    primary: None,
1202                }],
1203                cx,
1204            )
1205            .pop()
1206            .unwrap()
1207    });
1208
1209    let snapshot_3 = multibuffer.read(cx).snapshot(cx);
1210    assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP");
1211    assert_ne!(excerpt_id_5, excerpt_id_3);
1212
1213    // Resolve some anchors from the previous snapshot in the new snapshot.
1214    // The third anchor can't be resolved, since its excerpt has been removed,
1215    // so it resolves to the same position as its predecessor.
1216    let anchors = [
1217        snapshot_2.anchor_before(0),
1218        snapshot_2.anchor_after(2),
1219        snapshot_2.anchor_after(6),
1220        snapshot_2.anchor_after(14),
1221    ];
1222    assert_eq!(
1223        snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
1224        &[0, 2, 9, 13]
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::<usize, _>(new_anchors.iter().map(|a| &a.1)),
1234        &[0, 2, 7, 13]
1235    );
1236}
1237
1238#[gpui::test]
1239fn test_basic_diff_hunks(cx: &mut TestAppContext) {
1240    let text = indoc!(
1241        "
1242        ZERO
1243        one
1244        TWO
1245        three
1246        six
1247        "
1248    );
1249    let base_text = indoc!(
1250        "
1251        one
1252        two
1253        three
1254        four
1255        five
1256        six
1257        "
1258    );
1259
1260    let buffer = cx.new(|cx| Buffer::local(text, cx));
1261    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
1262    cx.run_until_parked();
1263
1264    let multibuffer = cx.new(|cx| {
1265        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1266        multibuffer.add_diff(diff.clone(), cx);
1267        multibuffer
1268    });
1269
1270    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1271        (multibuffer.snapshot(cx), multibuffer.subscribe())
1272    });
1273    assert_eq!(
1274        snapshot.text(),
1275        indoc!(
1276            "
1277            ZERO
1278            one
1279            TWO
1280            three
1281            six
1282            "
1283        ),
1284    );
1285
1286    multibuffer.update(cx, |multibuffer, cx| {
1287        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1288    });
1289
1290    assert_new_snapshot(
1291        &multibuffer,
1292        &mut snapshot,
1293        &mut subscription,
1294        cx,
1295        indoc!(
1296            "
1297            + ZERO
1298              one
1299            - two
1300            + TWO
1301              three
1302            - four
1303            - five
1304              six
1305            "
1306        ),
1307    );
1308
1309    assert_eq!(
1310        snapshot
1311            .row_infos(MultiBufferRow(0))
1312            .map(|info| (info.buffer_row, info.diff_status))
1313            .collect::<Vec<_>>(),
1314        vec![
1315            (Some(0), Some(DiffHunkStatus::added_none())),
1316            (Some(1), None),
1317            (Some(1), Some(DiffHunkStatus::deleted_none())),
1318            (Some(2), Some(DiffHunkStatus::added_none())),
1319            (Some(3), None),
1320            (Some(3), Some(DiffHunkStatus::deleted_none())),
1321            (Some(4), Some(DiffHunkStatus::deleted_none())),
1322            (Some(4), None),
1323            (Some(5), None)
1324        ]
1325    );
1326
1327    assert_chunks_in_ranges(&snapshot);
1328    assert_consistent_line_numbers(&snapshot);
1329    assert_position_translation(&snapshot);
1330    assert_line_indents(&snapshot);
1331
1332    multibuffer.update(cx, |multibuffer, cx| {
1333        multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
1334    });
1335    assert_new_snapshot(
1336        &multibuffer,
1337        &mut snapshot,
1338        &mut subscription,
1339        cx,
1340        indoc!(
1341            "
1342            ZERO
1343            one
1344            TWO
1345            three
1346            six
1347            "
1348        ),
1349    );
1350
1351    assert_chunks_in_ranges(&snapshot);
1352    assert_consistent_line_numbers(&snapshot);
1353    assert_position_translation(&snapshot);
1354    assert_line_indents(&snapshot);
1355
1356    // Expand the first diff hunk
1357    multibuffer.update(cx, |multibuffer, cx| {
1358        let position = multibuffer.read(cx).anchor_before(Point::new(2, 2));
1359        multibuffer.expand_diff_hunks(vec![position..position], cx)
1360    });
1361    assert_new_snapshot(
1362        &multibuffer,
1363        &mut snapshot,
1364        &mut subscription,
1365        cx,
1366        indoc!(
1367            "
1368              ZERO
1369              one
1370            - two
1371            + TWO
1372              three
1373              six
1374            "
1375        ),
1376    );
1377
1378    // Expand the second diff hunk
1379    multibuffer.update(cx, |multibuffer, cx| {
1380        let start = multibuffer.read(cx).anchor_before(Point::new(4, 0));
1381        let end = multibuffer.read(cx).anchor_before(Point::new(5, 0));
1382        multibuffer.expand_diff_hunks(vec![start..end], cx)
1383    });
1384    assert_new_snapshot(
1385        &multibuffer,
1386        &mut snapshot,
1387        &mut subscription,
1388        cx,
1389        indoc!(
1390            "
1391              ZERO
1392              one
1393            - two
1394            + TWO
1395              three
1396            - four
1397            - five
1398              six
1399            "
1400        ),
1401    );
1402
1403    assert_chunks_in_ranges(&snapshot);
1404    assert_consistent_line_numbers(&snapshot);
1405    assert_position_translation(&snapshot);
1406    assert_line_indents(&snapshot);
1407
1408    // Edit the buffer before the first hunk
1409    buffer.update(cx, |buffer, cx| {
1410        buffer.edit_via_marked_text(
1411            indoc!(
1412                "
1413                ZERO
1414                one« hundred
1415                  thousand»
1416                TWO
1417                three
1418                six
1419                "
1420            ),
1421            None,
1422            cx,
1423        );
1424    });
1425    assert_new_snapshot(
1426        &multibuffer,
1427        &mut snapshot,
1428        &mut subscription,
1429        cx,
1430        indoc!(
1431            "
1432              ZERO
1433              one hundred
1434                thousand
1435            - two
1436            + TWO
1437              three
1438            - four
1439            - five
1440              six
1441            "
1442        ),
1443    );
1444
1445    assert_chunks_in_ranges(&snapshot);
1446    assert_consistent_line_numbers(&snapshot);
1447    assert_position_translation(&snapshot);
1448    assert_line_indents(&snapshot);
1449
1450    // Recalculate the diff, changing the first diff hunk.
1451    diff.update(cx, |diff, cx| {
1452        diff.recalculate_diff_sync(buffer.read(cx).text_snapshot(), cx);
1453    });
1454    cx.run_until_parked();
1455    assert_new_snapshot(
1456        &multibuffer,
1457        &mut snapshot,
1458        &mut subscription,
1459        cx,
1460        indoc!(
1461            "
1462              ZERO
1463              one hundred
1464                thousand
1465              TWO
1466              three
1467            - four
1468            - five
1469              six
1470            "
1471        ),
1472    );
1473
1474    assert_eq!(
1475        snapshot
1476            .diff_hunks_in_range(0..snapshot.len())
1477            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1478            .collect::<Vec<_>>(),
1479        &[0..4, 5..7]
1480    );
1481}
1482
1483#[gpui::test]
1484fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
1485    let text = indoc!(
1486        "
1487        one
1488        TWO
1489        THREE
1490        four
1491        FIVE
1492        six
1493        "
1494    );
1495    let base_text = indoc!(
1496        "
1497        one
1498        four
1499        six
1500        "
1501    );
1502
1503    let buffer = cx.new(|cx| Buffer::local(text, cx));
1504    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
1505    cx.run_until_parked();
1506
1507    let multibuffer = cx.new(|cx| {
1508        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1509        multibuffer.add_diff(diff.clone(), cx);
1510        multibuffer
1511    });
1512
1513    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1514        (multibuffer.snapshot(cx), multibuffer.subscribe())
1515    });
1516
1517    multibuffer.update(cx, |multibuffer, cx| {
1518        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1519    });
1520
1521    assert_new_snapshot(
1522        &multibuffer,
1523        &mut snapshot,
1524        &mut subscription,
1525        cx,
1526        indoc!(
1527            "
1528              one
1529            + TWO
1530            + THREE
1531              four
1532            + FIVE
1533              six
1534            "
1535        ),
1536    );
1537
1538    // Regression test: expanding diff hunks that are already expanded should not change anything.
1539    multibuffer.update(cx, |multibuffer, cx| {
1540        multibuffer.expand_diff_hunks(
1541            vec![
1542                snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_before(Point::new(2, 0)),
1543            ],
1544            cx,
1545        );
1546    });
1547
1548    assert_new_snapshot(
1549        &multibuffer,
1550        &mut snapshot,
1551        &mut subscription,
1552        cx,
1553        indoc!(
1554            "
1555              one
1556            + TWO
1557            + THREE
1558              four
1559            + FIVE
1560              six
1561            "
1562        ),
1563    );
1564}
1565
1566#[gpui::test]
1567fn test_set_excerpts_for_buffer_ordering(cx: &mut TestAppContext) {
1568    let buf1 = cx.new(|cx| {
1569        Buffer::local(
1570            indoc! {
1571            "zero
1572            one
1573            two
1574            two.five
1575            three
1576            four
1577            five
1578            six
1579            seven
1580            eight
1581            nine
1582            ten
1583            eleven
1584            ",
1585            },
1586            cx,
1587        )
1588    });
1589    let path1: PathKey = PathKey::namespaced("0", Path::new("/").into());
1590
1591    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1592    multibuffer.update(cx, |multibuffer, cx| {
1593        multibuffer.set_excerpts_for_path(
1594            path1.clone(),
1595            buf1.clone(),
1596            vec![
1597                Point::row_range(1..2),
1598                Point::row_range(6..7),
1599                Point::row_range(11..12),
1600            ],
1601            1,
1602            cx,
1603        );
1604    });
1605
1606    assert_excerpts_match(
1607        &multibuffer,
1608        cx,
1609        indoc! {
1610            "-----
1611            zero
1612            one
1613            two
1614            two.five
1615            -----
1616            four
1617            five
1618            six
1619            seven
1620            -----
1621            nine
1622            ten
1623            eleven
1624            "
1625        },
1626    );
1627
1628    buf1.update(cx, |buffer, cx| buffer.edit([(0..5, "")], None, cx));
1629
1630    multibuffer.update(cx, |multibuffer, cx| {
1631        multibuffer.set_excerpts_for_path(
1632            path1.clone(),
1633            buf1.clone(),
1634            vec![
1635                Point::row_range(0..2),
1636                Point::row_range(5..6),
1637                Point::row_range(10..11),
1638            ],
1639            1,
1640            cx,
1641        );
1642    });
1643
1644    assert_excerpts_match(
1645        &multibuffer,
1646        cx,
1647        indoc! {
1648            "-----
1649             one
1650             two
1651             two.five
1652             three
1653             -----
1654             four
1655             five
1656             six
1657             seven
1658             -----
1659             nine
1660             ten
1661             eleven
1662            "
1663        },
1664    );
1665}
1666
1667#[gpui::test]
1668fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
1669    let buf1 = cx.new(|cx| {
1670        Buffer::local(
1671            indoc! {
1672            "zero
1673            one
1674            two
1675            three
1676            four
1677            five
1678            six
1679            seven
1680            ",
1681            },
1682            cx,
1683        )
1684    });
1685    let path1: PathKey = PathKey::namespaced("0", Path::new("/").into());
1686    let buf2 = cx.new(|cx| {
1687        Buffer::local(
1688            indoc! {
1689            "000
1690            111
1691            222
1692            333
1693            444
1694            555
1695            666
1696            777
1697            888
1698            999
1699            "
1700            },
1701            cx,
1702        )
1703    });
1704    let path2 = PathKey::namespaced("x", Path::new("/").into());
1705
1706    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1707    multibuffer.update(cx, |multibuffer, cx| {
1708        multibuffer.set_excerpts_for_path(
1709            path1.clone(),
1710            buf1.clone(),
1711            vec![Point::row_range(0..1)],
1712            2,
1713            cx,
1714        );
1715    });
1716
1717    assert_excerpts_match(
1718        &multibuffer,
1719        cx,
1720        indoc! {
1721        "-----
1722        zero
1723        one
1724        two
1725        three
1726        "
1727        },
1728    );
1729
1730    multibuffer.update(cx, |multibuffer, cx| {
1731        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1732    });
1733
1734    assert_excerpts_match(&multibuffer, cx, "");
1735
1736    multibuffer.update(cx, |multibuffer, cx| {
1737        multibuffer.set_excerpts_for_path(
1738            path1.clone(),
1739            buf1.clone(),
1740            vec![Point::row_range(0..1), Point::row_range(7..8)],
1741            2,
1742            cx,
1743        );
1744    });
1745
1746    assert_excerpts_match(
1747        &multibuffer,
1748        cx,
1749        indoc! {"-----
1750                zero
1751                one
1752                two
1753                three
1754                -----
1755                five
1756                six
1757                seven
1758                "},
1759    );
1760
1761    multibuffer.update(cx, |multibuffer, cx| {
1762        multibuffer.set_excerpts_for_path(
1763            path1.clone(),
1764            buf1.clone(),
1765            vec![Point::row_range(0..1), Point::row_range(5..6)],
1766            2,
1767            cx,
1768        );
1769    });
1770
1771    assert_excerpts_match(
1772        &multibuffer,
1773        cx,
1774        indoc! {"-----
1775                    zero
1776                    one
1777                    two
1778                    three
1779                    four
1780                    five
1781                    six
1782                    seven
1783                    "},
1784    );
1785
1786    multibuffer.update(cx, |multibuffer, cx| {
1787        multibuffer.set_excerpts_for_path(
1788            path2.clone(),
1789            buf2.clone(),
1790            vec![Point::row_range(2..3)],
1791            2,
1792            cx,
1793        );
1794    });
1795
1796    assert_excerpts_match(
1797        &multibuffer,
1798        cx,
1799        indoc! {"-----
1800                zero
1801                one
1802                two
1803                three
1804                four
1805                five
1806                six
1807                seven
1808                -----
1809                000
1810                111
1811                222
1812                333
1813                444
1814                555
1815                "},
1816    );
1817
1818    multibuffer.update(cx, |multibuffer, cx| {
1819        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1820    });
1821
1822    multibuffer.update(cx, |multibuffer, cx| {
1823        multibuffer.set_excerpts_for_path(
1824            path1.clone(),
1825            buf1.clone(),
1826            vec![Point::row_range(3..4)],
1827            2,
1828            cx,
1829        );
1830    });
1831
1832    assert_excerpts_match(
1833        &multibuffer,
1834        cx,
1835        indoc! {"-----
1836                one
1837                two
1838                three
1839                four
1840                five
1841                six
1842                -----
1843                000
1844                111
1845                222
1846                333
1847                444
1848                555
1849                "},
1850    );
1851
1852    multibuffer.update(cx, |multibuffer, cx| {
1853        multibuffer.set_excerpts_for_path(
1854            path1.clone(),
1855            buf1.clone(),
1856            vec![Point::row_range(3..4)],
1857            2,
1858            cx,
1859        );
1860    });
1861}
1862
1863#[gpui::test]
1864fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
1865    let base_text_1 = indoc!(
1866        "
1867        one
1868        two
1869            three
1870        four
1871        five
1872        six
1873        "
1874    );
1875    let text_1 = indoc!(
1876        "
1877        ZERO
1878        one
1879        TWO
1880            three
1881        six
1882        "
1883    );
1884    let base_text_2 = indoc!(
1885        "
1886        seven
1887          eight
1888        nine
1889        ten
1890        eleven
1891        twelve
1892        "
1893    );
1894    let text_2 = indoc!(
1895        "
1896          eight
1897        nine
1898        eleven
1899        THIRTEEN
1900        FOURTEEN
1901        "
1902    );
1903
1904    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
1905    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
1906    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_1, &buffer_1, cx));
1907    let diff_2 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_2, &buffer_2, cx));
1908    cx.run_until_parked();
1909
1910    let multibuffer = cx.new(|cx| {
1911        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1912        multibuffer.push_excerpts(
1913            buffer_1.clone(),
1914            [ExcerptRange {
1915                context: text::Anchor::MIN..text::Anchor::MAX,
1916                primary: None,
1917            }],
1918            cx,
1919        );
1920        multibuffer.push_excerpts(
1921            buffer_2.clone(),
1922            [ExcerptRange {
1923                context: text::Anchor::MIN..text::Anchor::MAX,
1924                primary: None,
1925            }],
1926            cx,
1927        );
1928        multibuffer.add_diff(diff_1.clone(), cx);
1929        multibuffer.add_diff(diff_2.clone(), cx);
1930        multibuffer
1931    });
1932
1933    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1934        (multibuffer.snapshot(cx), multibuffer.subscribe())
1935    });
1936    assert_eq!(
1937        snapshot.text(),
1938        indoc!(
1939            "
1940            ZERO
1941            one
1942            TWO
1943                three
1944            six
1945
1946              eight
1947            nine
1948            eleven
1949            THIRTEEN
1950            FOURTEEN
1951            "
1952        ),
1953    );
1954
1955    multibuffer.update(cx, |multibuffer, cx| {
1956        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1957    });
1958
1959    assert_new_snapshot(
1960        &multibuffer,
1961        &mut snapshot,
1962        &mut subscription,
1963        cx,
1964        indoc!(
1965            "
1966            + ZERO
1967              one
1968            - two
1969            + TWO
1970                  three
1971            - four
1972            - five
1973              six
1974
1975            - seven
1976                eight
1977              nine
1978            - ten
1979              eleven
1980            - twelve
1981            + THIRTEEN
1982            + FOURTEEN
1983            "
1984        ),
1985    );
1986
1987    let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
1988    let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
1989    let base_id_1 = diff_1.read_with(cx, |diff, _| diff.base_text().remote_id());
1990    let base_id_2 = diff_2.read_with(cx, |diff, _| diff.base_text().remote_id());
1991
1992    let buffer_lines = (0..=snapshot.max_row().0)
1993        .map(|row| {
1994            let (buffer, range) = snapshot.buffer_line_for_row(MultiBufferRow(row))?;
1995            Some((
1996                buffer.remote_id(),
1997                buffer.text_for_range(range).collect::<String>(),
1998            ))
1999        })
2000        .collect::<Vec<_>>();
2001    pretty_assertions::assert_eq!(
2002        buffer_lines,
2003        [
2004            Some((id_1, "ZERO".into())),
2005            Some((id_1, "one".into())),
2006            Some((base_id_1, "two".into())),
2007            Some((id_1, "TWO".into())),
2008            Some((id_1, "    three".into())),
2009            Some((base_id_1, "four".into())),
2010            Some((base_id_1, "five".into())),
2011            Some((id_1, "six".into())),
2012            Some((id_1, "".into())),
2013            Some((base_id_2, "seven".into())),
2014            Some((id_2, "  eight".into())),
2015            Some((id_2, "nine".into())),
2016            Some((base_id_2, "ten".into())),
2017            Some((id_2, "eleven".into())),
2018            Some((base_id_2, "twelve".into())),
2019            Some((id_2, "THIRTEEN".into())),
2020            Some((id_2, "FOURTEEN".into())),
2021            Some((id_2, "".into())),
2022        ]
2023    );
2024
2025    let buffer_ids_by_range = [
2026        (Point::new(0, 0)..Point::new(0, 0), &[id_1] as &[_]),
2027        (Point::new(0, 0)..Point::new(2, 0), &[id_1]),
2028        (Point::new(2, 0)..Point::new(2, 0), &[id_1]),
2029        (Point::new(3, 0)..Point::new(3, 0), &[id_1]),
2030        (Point::new(8, 0)..Point::new(9, 0), &[id_1]),
2031        (Point::new(8, 0)..Point::new(10, 0), &[id_1, id_2]),
2032        (Point::new(9, 0)..Point::new(9, 0), &[id_2]),
2033    ];
2034    for (range, buffer_ids) in buffer_ids_by_range {
2035        assert_eq!(
2036            snapshot
2037                .buffer_ids_for_range(range.clone())
2038                .collect::<Vec<_>>(),
2039            buffer_ids,
2040            "buffer_ids_for_range({range:?}"
2041        );
2042    }
2043
2044    assert_position_translation(&snapshot);
2045    assert_line_indents(&snapshot);
2046
2047    assert_eq!(
2048        snapshot
2049            .diff_hunks_in_range(0..snapshot.len())
2050            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
2051            .collect::<Vec<_>>(),
2052        &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
2053    );
2054
2055    buffer_2.update(cx, |buffer, cx| {
2056        buffer.edit_via_marked_text(
2057            indoc!(
2058                "
2059                  eight
2060                «»eleven
2061                THIRTEEN
2062                FOURTEEN
2063                "
2064            ),
2065            None,
2066            cx,
2067        );
2068    });
2069
2070    assert_new_snapshot(
2071        &multibuffer,
2072        &mut snapshot,
2073        &mut subscription,
2074        cx,
2075        indoc!(
2076            "
2077            + ZERO
2078              one
2079            - two
2080            + TWO
2081                  three
2082            - four
2083            - five
2084              six
2085
2086            - seven
2087                eight
2088              eleven
2089            - twelve
2090            + THIRTEEN
2091            + FOURTEEN
2092            "
2093        ),
2094    );
2095
2096    assert_line_indents(&snapshot);
2097}
2098
2099/// A naive implementation of a multi-buffer that does not maintain
2100/// any derived state, used for comparison in a randomized test.
2101#[derive(Default)]
2102struct ReferenceMultibuffer {
2103    excerpts: Vec<ReferenceExcerpt>,
2104    diffs: HashMap<BufferId, Entity<BufferDiff>>,
2105}
2106
2107#[derive(Debug)]
2108struct ReferenceExcerpt {
2109    id: ExcerptId,
2110    buffer: Entity<Buffer>,
2111    range: Range<text::Anchor>,
2112    expanded_diff_hunks: Vec<text::Anchor>,
2113}
2114
2115#[derive(Debug)]
2116struct ReferenceRegion {
2117    buffer_id: Option<BufferId>,
2118    range: Range<usize>,
2119    buffer_start: Option<Point>,
2120    status: Option<DiffHunkStatus>,
2121}
2122
2123impl ReferenceMultibuffer {
2124    fn expand_excerpts(&mut self, excerpts: &HashSet<ExcerptId>, line_count: u32, cx: &App) {
2125        if line_count == 0 {
2126            return;
2127        }
2128
2129        for id in excerpts {
2130            let excerpt = self.excerpts.iter_mut().find(|e| e.id == *id).unwrap();
2131            let snapshot = excerpt.buffer.read(cx).snapshot();
2132            let mut point_range = excerpt.range.to_point(&snapshot);
2133            point_range.start = Point::new(point_range.start.row.saturating_sub(line_count), 0);
2134            point_range.end =
2135                snapshot.clip_point(Point::new(point_range.end.row + line_count, 0), Bias::Left);
2136            point_range.end.column = snapshot.line_len(point_range.end.row);
2137            excerpt.range =
2138                snapshot.anchor_before(point_range.start)..snapshot.anchor_after(point_range.end);
2139        }
2140    }
2141
2142    fn remove_excerpt(&mut self, id: ExcerptId, cx: &App) {
2143        let ix = self
2144            .excerpts
2145            .iter()
2146            .position(|excerpt| excerpt.id == id)
2147            .unwrap();
2148        let excerpt = self.excerpts.remove(ix);
2149        let buffer = excerpt.buffer.read(cx);
2150        let id = buffer.remote_id();
2151        log::info!(
2152            "Removing excerpt {}: {:?}",
2153            ix,
2154            buffer
2155                .text_for_range(excerpt.range.to_offset(buffer))
2156                .collect::<String>(),
2157        );
2158        if !self
2159            .excerpts
2160            .iter()
2161            .any(|excerpt| excerpt.buffer.read(cx).remote_id() == id)
2162        {
2163            self.diffs.remove(&id);
2164        }
2165    }
2166
2167    fn insert_excerpt_after(
2168        &mut self,
2169        prev_id: ExcerptId,
2170        new_excerpt_id: ExcerptId,
2171        (buffer_handle, anchor_range): (Entity<Buffer>, Range<text::Anchor>),
2172    ) {
2173        let excerpt_ix = if prev_id == ExcerptId::max() {
2174            self.excerpts.len()
2175        } else {
2176            self.excerpts
2177                .iter()
2178                .position(|excerpt| excerpt.id == prev_id)
2179                .unwrap()
2180                + 1
2181        };
2182        self.excerpts.insert(
2183            excerpt_ix,
2184            ReferenceExcerpt {
2185                id: new_excerpt_id,
2186                buffer: buffer_handle,
2187                range: anchor_range,
2188                expanded_diff_hunks: Vec::new(),
2189            },
2190        );
2191    }
2192
2193    fn expand_diff_hunks(&mut self, excerpt_id: ExcerptId, range: Range<text::Anchor>, cx: &App) {
2194        let excerpt = self
2195            .excerpts
2196            .iter_mut()
2197            .find(|e| e.id == excerpt_id)
2198            .unwrap();
2199        let buffer = excerpt.buffer.read(cx).snapshot();
2200        let buffer_id = buffer.remote_id();
2201        let Some(diff) = self.diffs.get(&buffer_id) else {
2202            return;
2203        };
2204        let excerpt_range = excerpt.range.to_offset(&buffer);
2205        for hunk in diff.read(cx).hunks_intersecting_range(range, &buffer, cx) {
2206            let hunk_range = hunk.buffer_range.to_offset(&buffer);
2207            if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end {
2208                continue;
2209            }
2210            if let Err(ix) = excerpt
2211                .expanded_diff_hunks
2212                .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
2213            {
2214                log::info!(
2215                    "expanding diff hunk {:?}. excerpt:{:?}, excerpt range:{:?}",
2216                    hunk_range,
2217                    excerpt_id,
2218                    excerpt_range
2219                );
2220                excerpt
2221                    .expanded_diff_hunks
2222                    .insert(ix, hunk.buffer_range.start);
2223            } else {
2224                log::trace!("hunk {hunk_range:?} already expanded in excerpt {excerpt_id:?}");
2225            }
2226        }
2227    }
2228
2229    fn expected_content(&self, cx: &App) -> (String, Vec<RowInfo>, HashSet<MultiBufferRow>) {
2230        let mut text = String::new();
2231        let mut regions = Vec::<ReferenceRegion>::new();
2232        let mut excerpt_boundary_rows = HashSet::default();
2233        for excerpt in &self.excerpts {
2234            excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
2235            let buffer = excerpt.buffer.read(cx);
2236            let buffer_range = excerpt.range.to_offset(buffer);
2237            let diff = self.diffs.get(&buffer.remote_id()).unwrap().read(cx);
2238            let base_buffer = diff.base_text();
2239
2240            let mut offset = buffer_range.start;
2241            let mut hunks = diff
2242                .hunks_intersecting_range(excerpt.range.clone(), buffer, cx)
2243                .peekable();
2244
2245            while let Some(hunk) = hunks.next() {
2246                // Ignore hunks that are outside the excerpt range.
2247                let mut hunk_range = hunk.buffer_range.to_offset(buffer);
2248
2249                hunk_range.end = hunk_range.end.min(buffer_range.end);
2250                if hunk_range.start > buffer_range.end || hunk_range.start < buffer_range.start {
2251                    log::trace!("skipping hunk outside excerpt range");
2252                    continue;
2253                }
2254
2255                if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| {
2256                    expanded_anchor.to_offset(&buffer).max(buffer_range.start)
2257                        == hunk_range.start.max(buffer_range.start)
2258                }) {
2259                    log::trace!("skipping a hunk that's not marked as expanded");
2260                    continue;
2261                }
2262
2263                if !hunk.buffer_range.start.is_valid(&buffer) {
2264                    log::trace!("skipping hunk with deleted start: {:?}", hunk.range);
2265                    continue;
2266                }
2267
2268                if hunk_range.start >= offset {
2269                    // Add the buffer text before the hunk
2270                    let len = text.len();
2271                    text.extend(buffer.text_for_range(offset..hunk_range.start));
2272                    regions.push(ReferenceRegion {
2273                        buffer_id: Some(buffer.remote_id()),
2274                        range: len..text.len(),
2275                        buffer_start: Some(buffer.offset_to_point(offset)),
2276                        status: None,
2277                    });
2278
2279                    // Add the deleted text for the hunk.
2280                    if !hunk.diff_base_byte_range.is_empty() {
2281                        let mut base_text = base_buffer
2282                            .text_for_range(hunk.diff_base_byte_range.clone())
2283                            .collect::<String>();
2284                        if !base_text.ends_with('\n') {
2285                            base_text.push('\n');
2286                        }
2287                        let len = text.len();
2288                        text.push_str(&base_text);
2289                        regions.push(ReferenceRegion {
2290                            buffer_id: Some(base_buffer.remote_id()),
2291                            range: len..text.len(),
2292                            buffer_start: Some(
2293                                base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
2294                            ),
2295                            status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
2296                        });
2297                    }
2298
2299                    offset = hunk_range.start;
2300                }
2301
2302                // Add the inserted text for the hunk.
2303                if hunk_range.end > offset {
2304                    let len = text.len();
2305                    text.extend(buffer.text_for_range(offset..hunk_range.end));
2306                    regions.push(ReferenceRegion {
2307                        buffer_id: Some(buffer.remote_id()),
2308                        range: len..text.len(),
2309                        buffer_start: Some(buffer.offset_to_point(offset)),
2310                        status: Some(DiffHunkStatus::added(hunk.secondary_status)),
2311                    });
2312                    offset = hunk_range.end;
2313                }
2314            }
2315
2316            // Add the buffer text for the rest of the excerpt.
2317            let len = text.len();
2318            text.extend(buffer.text_for_range(offset..buffer_range.end));
2319            text.push('\n');
2320            regions.push(ReferenceRegion {
2321                buffer_id: Some(buffer.remote_id()),
2322                range: len..text.len(),
2323                buffer_start: Some(buffer.offset_to_point(offset)),
2324                status: None,
2325            });
2326        }
2327
2328        // Remove final trailing newline.
2329        if self.excerpts.is_empty() {
2330            regions.push(ReferenceRegion {
2331                buffer_id: None,
2332                range: 0..1,
2333                buffer_start: Some(Point::new(0, 0)),
2334                status: None,
2335            });
2336        } else {
2337            text.pop();
2338        }
2339
2340        // Retrieve the row info using the region that contains
2341        // the start of each multi-buffer line.
2342        let mut ix = 0;
2343        let row_infos = text
2344            .split('\n')
2345            .map(|line| {
2346                let row_info = regions
2347                    .iter()
2348                    .find(|region| region.range.contains(&ix))
2349                    .map_or(RowInfo::default(), |region| {
2350                        let buffer_row = region.buffer_start.map(|start_point| {
2351                            start_point.row
2352                                + text[region.range.start..ix].matches('\n').count() as u32
2353                        });
2354                        RowInfo {
2355                            buffer_id: region.buffer_id,
2356                            diff_status: region.status,
2357                            buffer_row,
2358                            multibuffer_row: Some(MultiBufferRow(
2359                                text[..ix].matches('\n').count() as u32
2360                            )),
2361                        }
2362                    });
2363                ix += line.len() + 1;
2364                row_info
2365            })
2366            .collect();
2367
2368        (text, row_infos, excerpt_boundary_rows)
2369    }
2370
2371    fn diffs_updated(&mut self, cx: &App) {
2372        for excerpt in &mut self.excerpts {
2373            let buffer = excerpt.buffer.read(cx).snapshot();
2374            let excerpt_range = excerpt.range.to_offset(&buffer);
2375            let buffer_id = buffer.remote_id();
2376            let diff = self.diffs.get(&buffer_id).unwrap().read(cx);
2377            let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer, cx).peekable();
2378            excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
2379                if !hunk_anchor.is_valid(&buffer) {
2380                    return false;
2381                }
2382                while let Some(hunk) = hunks.peek() {
2383                    match hunk.buffer_range.start.cmp(&hunk_anchor, &buffer) {
2384                        cmp::Ordering::Less => {
2385                            hunks.next();
2386                        }
2387                        cmp::Ordering::Equal => {
2388                            let hunk_range = hunk.buffer_range.to_offset(&buffer);
2389                            return hunk_range.end >= excerpt_range.start
2390                                && hunk_range.start <= excerpt_range.end;
2391                        }
2392                        cmp::Ordering::Greater => break,
2393                    }
2394                }
2395                false
2396            });
2397        }
2398    }
2399
2400    fn add_diff(&mut self, diff: Entity<BufferDiff>, cx: &mut App) {
2401        let buffer_id = diff.read(cx).buffer_id;
2402        self.diffs.insert(buffer_id, diff);
2403    }
2404}
2405
2406#[gpui::test(iterations = 100)]
2407async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
2408    let operations = env::var("OPERATIONS")
2409        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2410        .unwrap_or(10);
2411
2412    let mut buffers: Vec<Entity<Buffer>> = Vec::new();
2413    let mut base_texts: HashMap<BufferId, String> = HashMap::default();
2414    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2415    let mut reference = ReferenceMultibuffer::default();
2416    let mut anchors = Vec::new();
2417    let mut old_versions = Vec::new();
2418    let mut needs_diff_calculation = false;
2419
2420    for _ in 0..operations {
2421        match rng.gen_range(0..100) {
2422            0..=14 if !buffers.is_empty() => {
2423                let buffer = buffers.choose(&mut rng).unwrap();
2424                buffer.update(cx, |buf, cx| {
2425                    let edit_count = rng.gen_range(1..5);
2426                    buf.randomly_edit(&mut rng, edit_count, cx);
2427                    log::info!("buffer text:\n{}", buf.text());
2428                    needs_diff_calculation = true;
2429                });
2430                cx.update(|cx| reference.diffs_updated(cx));
2431            }
2432            15..=19 if !reference.excerpts.is_empty() => {
2433                multibuffer.update(cx, |multibuffer, cx| {
2434                    let ids = multibuffer.excerpt_ids();
2435                    let mut excerpts = HashSet::default();
2436                    for _ in 0..rng.gen_range(0..ids.len()) {
2437                        excerpts.extend(ids.choose(&mut rng).copied());
2438                    }
2439
2440                    let line_count = rng.gen_range(0..5);
2441
2442                    let excerpt_ixs = excerpts
2443                        .iter()
2444                        .map(|id| reference.excerpts.iter().position(|e| e.id == *id).unwrap())
2445                        .collect::<Vec<_>>();
2446                    log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
2447                    multibuffer.expand_excerpts(
2448                        excerpts.iter().cloned(),
2449                        line_count,
2450                        ExpandExcerptDirection::UpAndDown,
2451                        cx,
2452                    );
2453
2454                    reference.expand_excerpts(&excerpts, line_count, cx);
2455                });
2456            }
2457            20..=29 if !reference.excerpts.is_empty() => {
2458                let mut ids_to_remove = vec![];
2459                for _ in 0..rng.gen_range(1..=3) {
2460                    let Some(excerpt) = reference.excerpts.choose(&mut rng) else {
2461                        break;
2462                    };
2463                    let id = excerpt.id;
2464                    cx.update(|cx| reference.remove_excerpt(id, cx));
2465                    ids_to_remove.push(id);
2466                }
2467                let snapshot =
2468                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2469                ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
2470                drop(snapshot);
2471                multibuffer.update(cx, |multibuffer, cx| {
2472                    multibuffer.remove_excerpts(ids_to_remove, cx)
2473                });
2474            }
2475            30..=39 if !reference.excerpts.is_empty() => {
2476                let multibuffer =
2477                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2478                let offset =
2479                    multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
2480                let bias = if rng.gen() { Bias::Left } else { Bias::Right };
2481                log::info!("Creating anchor at {} with bias {:?}", offset, bias);
2482                anchors.push(multibuffer.anchor_at(offset, bias));
2483                anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
2484            }
2485            40..=44 if !anchors.is_empty() => {
2486                let multibuffer =
2487                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2488                let prev_len = anchors.len();
2489                anchors = multibuffer
2490                    .refresh_anchors(&anchors)
2491                    .into_iter()
2492                    .map(|a| a.1)
2493                    .collect();
2494
2495                // Ensure the newly-refreshed anchors point to a valid excerpt and don't
2496                // overshoot its boundaries.
2497                assert_eq!(anchors.len(), prev_len);
2498                for anchor in &anchors {
2499                    if anchor.excerpt_id == ExcerptId::min()
2500                        || anchor.excerpt_id == ExcerptId::max()
2501                    {
2502                        continue;
2503                    }
2504
2505                    let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
2506                    assert_eq!(excerpt.id, anchor.excerpt_id);
2507                    assert!(excerpt.contains(anchor));
2508                }
2509            }
2510            45..=55 if !reference.excerpts.is_empty() => {
2511                multibuffer.update(cx, |multibuffer, cx| {
2512                    let snapshot = multibuffer.snapshot(cx);
2513                    let excerpt_ix = rng.gen_range(0..reference.excerpts.len());
2514                    let excerpt = &reference.excerpts[excerpt_ix];
2515                    let start = excerpt.range.start;
2516                    let end = excerpt.range.end;
2517                    let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
2518                        ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
2519
2520                    log::info!(
2521                        "expanding diff hunks in range {:?} (excerpt id {:?}, index {excerpt_ix:?}, buffer id {:?})",
2522                        range.to_offset(&snapshot),
2523                        excerpt.id,
2524                        excerpt.buffer.read(cx).remote_id(),
2525                    );
2526                    reference.expand_diff_hunks(excerpt.id, start..end, cx);
2527                    multibuffer.expand_diff_hunks(vec![range], cx);
2528                });
2529            }
2530            56..=85 if needs_diff_calculation => {
2531                multibuffer.update(cx, |multibuffer, cx| {
2532                    for buffer in multibuffer.all_buffers() {
2533                        let snapshot = buffer.read(cx).snapshot();
2534                        multibuffer.diff_for(snapshot.remote_id()).unwrap().update(
2535                            cx,
2536                            |diff, cx| {
2537                                log::info!(
2538                                    "recalculating diff for buffer {:?}",
2539                                    snapshot.remote_id(),
2540                                );
2541                                diff.recalculate_diff_sync(snapshot.text, cx);
2542                            },
2543                        );
2544                    }
2545                    reference.diffs_updated(cx);
2546                    needs_diff_calculation = false;
2547                });
2548            }
2549            _ => {
2550                let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
2551                    let mut base_text = util::RandomCharIter::new(&mut rng)
2552                        .take(256)
2553                        .collect::<String>();
2554
2555                    let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
2556                    text::LineEnding::normalize(&mut base_text);
2557                    base_texts.insert(
2558                        buffer.read_with(cx, |buffer, _| buffer.remote_id()),
2559                        base_text,
2560                    );
2561                    buffers.push(buffer);
2562                    buffers.last().unwrap()
2563                } else {
2564                    buffers.choose(&mut rng).unwrap()
2565                };
2566
2567                let prev_excerpt_ix = rng.gen_range(0..=reference.excerpts.len());
2568                let prev_excerpt_id = reference
2569                    .excerpts
2570                    .get(prev_excerpt_ix)
2571                    .map_or(ExcerptId::max(), |e| e.id);
2572                let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
2573
2574                let (range, anchor_range) = buffer_handle.read_with(cx, |buffer, _| {
2575                    let end_row = rng.gen_range(0..=buffer.max_point().row);
2576                    let start_row = rng.gen_range(0..=end_row);
2577                    let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
2578                    let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
2579                    let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
2580
2581                    log::info!(
2582                        "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
2583                        excerpt_ix,
2584                        reference.excerpts.len(),
2585                        buffer.remote_id(),
2586                        buffer.text(),
2587                        start_ix..end_ix,
2588                        &buffer.text()[start_ix..end_ix]
2589                    );
2590
2591                    (start_ix..end_ix, anchor_range)
2592                });
2593
2594                multibuffer.update(cx, |multibuffer, cx| {
2595                    let id = buffer_handle.read(cx).remote_id();
2596                    if multibuffer.diff_for(id).is_none() {
2597                        let base_text = base_texts.get(&id).unwrap();
2598                        let diff = cx.new(|cx| {
2599                            BufferDiff::new_with_base_text(base_text, &buffer_handle, cx)
2600                        });
2601                        reference.add_diff(diff.clone(), cx);
2602                        multibuffer.add_diff(diff, cx)
2603                    }
2604                });
2605
2606                let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
2607                    multibuffer
2608                        .insert_excerpts_after(
2609                            prev_excerpt_id,
2610                            buffer_handle.clone(),
2611                            [ExcerptRange {
2612                                context: range,
2613                                primary: None,
2614                            }],
2615                            cx,
2616                        )
2617                        .pop()
2618                        .unwrap()
2619                });
2620
2621                reference.insert_excerpt_after(
2622                    prev_excerpt_id,
2623                    excerpt_id,
2624                    (buffer_handle.clone(), anchor_range),
2625                );
2626            }
2627        }
2628
2629        if rng.gen_bool(0.3) {
2630            multibuffer.update(cx, |multibuffer, cx| {
2631                old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
2632            })
2633        }
2634
2635        let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2636        let actual_text = snapshot.text();
2637        let actual_boundary_rows = snapshot
2638            .excerpt_boundaries_in_range(0..)
2639            .filter_map(|b| if b.next.is_some() { Some(b.row) } else { None })
2640            .collect::<HashSet<_>>();
2641        let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2642
2643        let (expected_text, expected_row_infos, expected_boundary_rows) =
2644            cx.update(|cx| reference.expected_content(cx));
2645
2646        let has_diff = actual_row_infos
2647            .iter()
2648            .any(|info| info.diff_status.is_some())
2649            || expected_row_infos
2650                .iter()
2651                .any(|info| info.diff_status.is_some());
2652        let actual_diff = format_diff(
2653            &actual_text,
2654            &actual_row_infos,
2655            &actual_boundary_rows,
2656            Some(has_diff),
2657        );
2658        let expected_diff = format_diff(
2659            &expected_text,
2660            &expected_row_infos,
2661            &expected_boundary_rows,
2662            Some(has_diff),
2663        );
2664
2665        log::info!("Multibuffer content:\n{}", actual_diff);
2666
2667        assert_eq!(
2668            actual_row_infos.len(),
2669            actual_text.split('\n').count(),
2670            "line count: {}",
2671            actual_text.split('\n').count()
2672        );
2673        pretty_assertions::assert_eq!(actual_diff, expected_diff);
2674        pretty_assertions::assert_eq!(actual_text, expected_text);
2675        pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
2676
2677        for _ in 0..5 {
2678            let start_row = rng.gen_range(0..=expected_row_infos.len());
2679            assert_eq!(
2680                snapshot
2681                    .row_infos(MultiBufferRow(start_row as u32))
2682                    .collect::<Vec<_>>(),
2683                &expected_row_infos[start_row..],
2684                "buffer_rows({})",
2685                start_row
2686            );
2687        }
2688
2689        assert_eq!(
2690            snapshot.widest_line_number(),
2691            expected_row_infos
2692                .into_iter()
2693                .filter_map(|info| {
2694                    if info.diff_status.is_some_and(|status| status.is_deleted()) {
2695                        None
2696                    } else {
2697                        info.buffer_row
2698                    }
2699                })
2700                .max()
2701                .unwrap()
2702                + 1
2703        );
2704
2705        assert_consistent_line_numbers(&snapshot);
2706        assert_position_translation(&snapshot);
2707
2708        for (row, line) in expected_text.split('\n').enumerate() {
2709            assert_eq!(
2710                snapshot.line_len(MultiBufferRow(row as u32)),
2711                line.len() as u32,
2712                "line_len({}).",
2713                row
2714            );
2715        }
2716
2717        let text_rope = Rope::from(expected_text.as_str());
2718        for _ in 0..10 {
2719            let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2720            let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
2721
2722            let text_for_range = snapshot
2723                .text_for_range(start_ix..end_ix)
2724                .collect::<String>();
2725            assert_eq!(
2726                text_for_range,
2727                &expected_text[start_ix..end_ix],
2728                "incorrect text for range {:?}",
2729                start_ix..end_ix
2730            );
2731
2732            let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
2733            assert_eq!(
2734                snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
2735                expected_summary,
2736                "incorrect summary for range {:?}",
2737                start_ix..end_ix
2738            );
2739        }
2740
2741        // Anchor resolution
2742        let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
2743        assert_eq!(anchors.len(), summaries.len());
2744        for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
2745            assert!(resolved_offset <= snapshot.len());
2746            assert_eq!(
2747                snapshot.summary_for_anchor::<usize>(anchor),
2748                resolved_offset,
2749                "anchor: {:?}",
2750                anchor
2751            );
2752        }
2753
2754        for _ in 0..10 {
2755            let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2756            assert_eq!(
2757                snapshot.reversed_chars_at(end_ix).collect::<String>(),
2758                expected_text[..end_ix].chars().rev().collect::<String>(),
2759            );
2760        }
2761
2762        for _ in 0..10 {
2763            let end_ix = rng.gen_range(0..=text_rope.len());
2764            let start_ix = rng.gen_range(0..=end_ix);
2765            assert_eq!(
2766                snapshot
2767                    .bytes_in_range(start_ix..end_ix)
2768                    .flatten()
2769                    .copied()
2770                    .collect::<Vec<_>>(),
2771                expected_text.as_bytes()[start_ix..end_ix].to_vec(),
2772                "bytes_in_range({:?})",
2773                start_ix..end_ix,
2774            );
2775        }
2776    }
2777
2778    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2779    for (old_snapshot, subscription) in old_versions {
2780        let edits = subscription.consume().into_inner();
2781
2782        log::info!(
2783            "applying subscription edits to old text: {:?}: {:?}",
2784            old_snapshot.text(),
2785            edits,
2786        );
2787
2788        let mut text = old_snapshot.text();
2789        for edit in edits {
2790            let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
2791            text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
2792        }
2793        assert_eq!(text.to_string(), snapshot.text());
2794    }
2795}
2796
2797#[gpui::test]
2798fn test_history(cx: &mut App) {
2799    let test_settings = SettingsStore::test(cx);
2800    cx.set_global(test_settings);
2801    let group_interval: Duration = Duration::from_millis(1);
2802    let buffer_1 = cx.new(|cx| {
2803        let mut buf = Buffer::local("1234", cx);
2804        buf.set_group_interval(group_interval);
2805        buf
2806    });
2807    let buffer_2 = cx.new(|cx| {
2808        let mut buf = Buffer::local("5678", cx);
2809        buf.set_group_interval(group_interval);
2810        buf
2811    });
2812    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2813    multibuffer.update(cx, |this, _| {
2814        this.history.group_interval = group_interval;
2815    });
2816    multibuffer.update(cx, |multibuffer, cx| {
2817        multibuffer.push_excerpts(
2818            buffer_1.clone(),
2819            [ExcerptRange {
2820                context: 0..buffer_1.read(cx).len(),
2821                primary: None,
2822            }],
2823            cx,
2824        );
2825        multibuffer.push_excerpts(
2826            buffer_2.clone(),
2827            [ExcerptRange {
2828                context: 0..buffer_2.read(cx).len(),
2829                primary: None,
2830            }],
2831            cx,
2832        );
2833    });
2834
2835    let mut now = Instant::now();
2836
2837    multibuffer.update(cx, |multibuffer, cx| {
2838        let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
2839        multibuffer.edit(
2840            [
2841                (Point::new(0, 0)..Point::new(0, 0), "A"),
2842                (Point::new(1, 0)..Point::new(1, 0), "A"),
2843            ],
2844            None,
2845            cx,
2846        );
2847        multibuffer.edit(
2848            [
2849                (Point::new(0, 1)..Point::new(0, 1), "B"),
2850                (Point::new(1, 1)..Point::new(1, 1), "B"),
2851            ],
2852            None,
2853            cx,
2854        );
2855        multibuffer.end_transaction_at(now, cx);
2856        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2857
2858        // Verify edited ranges for transaction 1
2859        assert_eq!(
2860            multibuffer.edited_ranges_for_transaction(transaction_1, cx),
2861            &[
2862                Point::new(0, 0)..Point::new(0, 2),
2863                Point::new(1, 0)..Point::new(1, 2)
2864            ]
2865        );
2866
2867        // Edit buffer 1 through the multibuffer
2868        now += 2 * group_interval;
2869        multibuffer.start_transaction_at(now, cx);
2870        multibuffer.edit([(2..2, "C")], None, cx);
2871        multibuffer.end_transaction_at(now, cx);
2872        assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
2873
2874        // Edit buffer 1 independently
2875        buffer_1.update(cx, |buffer_1, cx| {
2876            buffer_1.start_transaction_at(now);
2877            buffer_1.edit([(3..3, "D")], None, cx);
2878            buffer_1.end_transaction_at(now, cx);
2879
2880            now += 2 * group_interval;
2881            buffer_1.start_transaction_at(now);
2882            buffer_1.edit([(4..4, "E")], None, cx);
2883            buffer_1.end_transaction_at(now, cx);
2884        });
2885        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2886
2887        // An undo in the multibuffer undoes the multibuffer transaction
2888        // and also any individual buffer edits that have occurred since
2889        // that transaction.
2890        multibuffer.undo(cx);
2891        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2892
2893        multibuffer.undo(cx);
2894        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2895
2896        multibuffer.redo(cx);
2897        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2898
2899        multibuffer.redo(cx);
2900        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2901
2902        // Undo buffer 2 independently.
2903        buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
2904        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
2905
2906        // An undo in the multibuffer undoes the components of the
2907        // the last multibuffer transaction that are not already undone.
2908        multibuffer.undo(cx);
2909        assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
2910
2911        multibuffer.undo(cx);
2912        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2913
2914        multibuffer.redo(cx);
2915        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2916
2917        buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
2918        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2919
2920        // Redo stack gets cleared after an edit.
2921        now += 2 * group_interval;
2922        multibuffer.start_transaction_at(now, cx);
2923        multibuffer.edit([(0..0, "X")], None, cx);
2924        multibuffer.end_transaction_at(now, cx);
2925        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2926        multibuffer.redo(cx);
2927        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2928        multibuffer.undo(cx);
2929        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2930        multibuffer.undo(cx);
2931        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2932
2933        // Transactions can be grouped manually.
2934        multibuffer.redo(cx);
2935        multibuffer.redo(cx);
2936        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2937        multibuffer.group_until_transaction(transaction_1, cx);
2938        multibuffer.undo(cx);
2939        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2940        multibuffer.redo(cx);
2941        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2942    });
2943}
2944
2945#[gpui::test]
2946async fn test_enclosing_indent(cx: &mut TestAppContext) {
2947    async fn enclosing_indent(
2948        text: &str,
2949        buffer_row: u32,
2950        cx: &mut TestAppContext,
2951    ) -> Option<(Range<u32>, LineIndent)> {
2952        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2953        let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
2954        let (range, indent) = snapshot
2955            .enclosing_indent(MultiBufferRow(buffer_row))
2956            .await?;
2957        Some((range.start.0..range.end.0, indent))
2958    }
2959
2960    assert_eq!(
2961        enclosing_indent(
2962            indoc!(
2963                "
2964                fn b() {
2965                    if c {
2966                        let d = 2;
2967                    }
2968                }
2969                "
2970            ),
2971            1,
2972            cx,
2973        )
2974        .await,
2975        Some((
2976            1..2,
2977            LineIndent {
2978                tabs: 0,
2979                spaces: 4,
2980                line_blank: false,
2981            }
2982        ))
2983    );
2984
2985    assert_eq!(
2986        enclosing_indent(
2987            indoc!(
2988                "
2989                fn b() {
2990                    if c {
2991                        let d = 2;
2992                    }
2993                }
2994                "
2995            ),
2996            2,
2997            cx,
2998        )
2999        .await,
3000        Some((
3001            1..2,
3002            LineIndent {
3003                tabs: 0,
3004                spaces: 4,
3005                line_blank: false,
3006            }
3007        ))
3008    );
3009
3010    assert_eq!(
3011        enclosing_indent(
3012            indoc!(
3013                "
3014                fn b() {
3015                    if c {
3016                        let d = 2;
3017
3018                        let e = 5;
3019                    }
3020                }
3021                "
3022            ),
3023            3,
3024            cx,
3025        )
3026        .await,
3027        Some((
3028            1..4,
3029            LineIndent {
3030                tabs: 0,
3031                spaces: 4,
3032                line_blank: false,
3033            }
3034        ))
3035    );
3036}
3037
3038#[gpui::test]
3039fn test_summaries_for_anchors(cx: &mut TestAppContext) {
3040    let base_text_1 = indoc!(
3041        "
3042        bar
3043        "
3044    );
3045    let text_1 = indoc!(
3046        "
3047        BAR
3048        "
3049    );
3050    let base_text_2 = indoc!(
3051        "
3052        foo
3053        "
3054    );
3055    let text_2 = indoc!(
3056        "
3057        FOO
3058        "
3059    );
3060
3061    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3062    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
3063    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_1, &buffer_1, cx));
3064    let diff_2 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_2, &buffer_2, cx));
3065    cx.run_until_parked();
3066
3067    let mut ids = vec![];
3068    let multibuffer = cx.new(|cx| {
3069        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
3070        multibuffer.set_all_diff_hunks_expanded(cx);
3071        ids.extend(multibuffer.push_excerpts(
3072            buffer_1.clone(),
3073            [ExcerptRange {
3074                context: text::Anchor::MIN..text::Anchor::MAX,
3075                primary: None,
3076            }],
3077            cx,
3078        ));
3079        ids.extend(multibuffer.push_excerpts(
3080            buffer_2.clone(),
3081            [ExcerptRange {
3082                context: text::Anchor::MIN..text::Anchor::MAX,
3083                primary: None,
3084            }],
3085            cx,
3086        ));
3087        multibuffer.add_diff(diff_1.clone(), cx);
3088        multibuffer.add_diff(diff_2.clone(), cx);
3089        multibuffer
3090    });
3091
3092    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3093        (multibuffer.snapshot(cx), multibuffer.subscribe())
3094    });
3095
3096    assert_new_snapshot(
3097        &multibuffer,
3098        &mut snapshot,
3099        &mut subscription,
3100        cx,
3101        indoc!(
3102            "
3103            - bar
3104            + BAR
3105
3106            - foo
3107            + FOO
3108            "
3109        ),
3110    );
3111
3112    let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
3113    let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
3114
3115    let anchor_1 = Anchor::in_buffer(ids[0], id_1, text::Anchor::MIN);
3116    let point_1 = snapshot.summaries_for_anchors::<Point, _>([&anchor_1])[0];
3117    assert_eq!(point_1, Point::new(0, 0));
3118
3119    let anchor_2 = Anchor::in_buffer(ids[1], id_2, text::Anchor::MIN);
3120    let point_2 = snapshot.summaries_for_anchors::<Point, _>([&anchor_2])[0];
3121    assert_eq!(point_2, Point::new(3, 0));
3122}
3123
3124#[gpui::test]
3125fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
3126    let base_text_1 = "one\ntwo".to_owned();
3127    let text_1 = "one\n".to_owned();
3128
3129    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3130    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(&base_text_1, &buffer_1, cx));
3131    cx.run_until_parked();
3132
3133    let multibuffer = cx.new(|cx| {
3134        let mut multibuffer = MultiBuffer::singleton(buffer_1.clone(), cx);
3135        multibuffer.add_diff(diff_1.clone(), cx);
3136        multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
3137        multibuffer
3138    });
3139
3140    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3141        (multibuffer.snapshot(cx), multibuffer.subscribe())
3142    });
3143
3144    assert_new_snapshot(
3145        &multibuffer,
3146        &mut snapshot,
3147        &mut subscription,
3148        cx,
3149        indoc!(
3150            "
3151              one
3152            - two
3153            "
3154        ),
3155    );
3156
3157    assert_eq!(snapshot.max_point(), Point::new(2, 0));
3158    assert_eq!(snapshot.len(), 8);
3159
3160    assert_eq!(
3161        snapshot
3162            .dimensions_from_points::<Point>([Point::new(2, 0)])
3163            .collect::<Vec<_>>(),
3164        vec![Point::new(2, 0)]
3165    );
3166
3167    let (_, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3168    assert_eq!(translated_offset, "one\n".len());
3169    let (_, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3170    assert_eq!(translated_point, Point::new(1, 0));
3171
3172    // The same, for an excerpt that's not at the end of the multibuffer.
3173
3174    let text_2 = "foo\n".to_owned();
3175    let buffer_2 = cx.new(|cx| Buffer::local(&text_2, cx));
3176    multibuffer.update(cx, |multibuffer, cx| {
3177        multibuffer.push_excerpts(
3178            buffer_2.clone(),
3179            [ExcerptRange {
3180                context: Point::new(0, 0)..Point::new(1, 0),
3181                primary: None,
3182            }],
3183            cx,
3184        );
3185    });
3186
3187    assert_new_snapshot(
3188        &multibuffer,
3189        &mut snapshot,
3190        &mut subscription,
3191        cx,
3192        indoc!(
3193            "
3194              one
3195            - two
3196
3197              foo
3198            "
3199        ),
3200    );
3201
3202    assert_eq!(
3203        snapshot
3204            .dimensions_from_points::<Point>([Point::new(2, 0)])
3205            .collect::<Vec<_>>(),
3206        vec![Point::new(2, 0)]
3207    );
3208
3209    let buffer_1_id = buffer_1.read_with(cx, |buffer_1, _| buffer_1.remote_id());
3210    let (buffer, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3211    assert_eq!(buffer.remote_id(), buffer_1_id);
3212    assert_eq!(translated_offset, "one\n".len());
3213    let (buffer, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3214    assert_eq!(buffer.remote_id(), buffer_1_id);
3215    assert_eq!(translated_point, Point::new(1, 0));
3216}
3217
3218fn format_diff(
3219    text: &str,
3220    row_infos: &Vec<RowInfo>,
3221    boundary_rows: &HashSet<MultiBufferRow>,
3222    has_diff: Option<bool>,
3223) -> String {
3224    let has_diff =
3225        has_diff.unwrap_or_else(|| row_infos.iter().any(|info| info.diff_status.is_some()));
3226    text.split('\n')
3227        .enumerate()
3228        .zip(row_infos)
3229        .map(|((ix, line), info)| {
3230            let marker = match info.diff_status.map(|status| status.kind) {
3231                Some(DiffHunkStatusKind::Added) => "+ ",
3232                Some(DiffHunkStatusKind::Deleted) => "- ",
3233                Some(DiffHunkStatusKind::Modified) => unreachable!(),
3234                None => {
3235                    if has_diff && !line.is_empty() {
3236                        "  "
3237                    } else {
3238                        ""
3239                    }
3240                }
3241            };
3242            let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
3243                if has_diff {
3244                    "  ----------\n"
3245                } else {
3246                    "---------\n"
3247                }
3248            } else {
3249                ""
3250            };
3251            format!("{boundary_row}{marker}{line}")
3252        })
3253        .collect::<Vec<_>>()
3254        .join("\n")
3255}
3256
3257#[track_caller]
3258fn assert_excerpts_match(
3259    multibuffer: &Entity<MultiBuffer>,
3260    cx: &mut TestAppContext,
3261    expected: &str,
3262) {
3263    let mut output = String::new();
3264    multibuffer.read_with(cx, |multibuffer, cx| {
3265        for (_, buffer, range) in multibuffer.snapshot(cx).excerpts() {
3266            output.push_str("-----\n");
3267            output.extend(buffer.text_for_range(range.context));
3268            if !output.ends_with('\n') {
3269                output.push('\n');
3270            }
3271        }
3272    });
3273    assert_eq!(output, expected);
3274}
3275
3276#[track_caller]
3277fn assert_new_snapshot(
3278    multibuffer: &Entity<MultiBuffer>,
3279    snapshot: &mut MultiBufferSnapshot,
3280    subscription: &mut Subscription,
3281    cx: &mut TestAppContext,
3282    expected_diff: &str,
3283) {
3284    let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3285    let actual_text = new_snapshot.text();
3286    let line_infos = new_snapshot
3287        .row_infos(MultiBufferRow(0))
3288        .collect::<Vec<_>>();
3289    let actual_diff = format_diff(&actual_text, &line_infos, &Default::default(), None);
3290    pretty_assertions::assert_eq!(actual_diff, expected_diff);
3291    check_edits(
3292        snapshot,
3293        &new_snapshot,
3294        &subscription.consume().into_inner(),
3295    );
3296    *snapshot = new_snapshot;
3297}
3298
3299#[track_caller]
3300fn check_edits(
3301    old_snapshot: &MultiBufferSnapshot,
3302    new_snapshot: &MultiBufferSnapshot,
3303    edits: &[Edit<usize>],
3304) {
3305    let mut text = old_snapshot.text();
3306    let new_text = new_snapshot.text();
3307    for edit in edits.iter().rev() {
3308        if !text.is_char_boundary(edit.old.start)
3309            || !text.is_char_boundary(edit.old.end)
3310            || !new_text.is_char_boundary(edit.new.start)
3311            || !new_text.is_char_boundary(edit.new.end)
3312        {
3313            panic!(
3314                "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
3315                edits, text, new_text
3316            );
3317        }
3318
3319        text.replace_range(
3320            edit.old.start..edit.old.end,
3321            &new_text[edit.new.start..edit.new.end],
3322        );
3323    }
3324
3325    pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
3326}
3327
3328#[track_caller]
3329fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
3330    let full_text = snapshot.text();
3331    for ix in 0..full_text.len() {
3332        let mut chunks = snapshot.chunks(0..snapshot.len(), false);
3333        chunks.seek(ix..snapshot.len());
3334        let tail = chunks.map(|chunk| chunk.text).collect::<String>();
3335        assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
3336    }
3337}
3338
3339#[track_caller]
3340fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
3341    let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3342    for start_row in 1..all_line_numbers.len() {
3343        let line_numbers = snapshot
3344            .row_infos(MultiBufferRow(start_row as u32))
3345            .collect::<Vec<_>>();
3346        assert_eq!(
3347            line_numbers,
3348            all_line_numbers[start_row..],
3349            "start_row: {start_row}"
3350        );
3351    }
3352}
3353
3354#[track_caller]
3355fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
3356    let text = Rope::from(snapshot.text());
3357
3358    let mut left_anchors = Vec::new();
3359    let mut right_anchors = Vec::new();
3360    let mut offsets = Vec::new();
3361    let mut points = Vec::new();
3362    for offset in 0..=text.len() + 1 {
3363        let clipped_left = snapshot.clip_offset(offset, Bias::Left);
3364        let clipped_right = snapshot.clip_offset(offset, Bias::Right);
3365        assert_eq!(
3366            clipped_left,
3367            text.clip_offset(offset, Bias::Left),
3368            "clip_offset({offset:?}, Left)"
3369        );
3370        assert_eq!(
3371            clipped_right,
3372            text.clip_offset(offset, Bias::Right),
3373            "clip_offset({offset:?}, Right)"
3374        );
3375        assert_eq!(
3376            snapshot.offset_to_point(clipped_left),
3377            text.offset_to_point(clipped_left),
3378            "offset_to_point({clipped_left})"
3379        );
3380        assert_eq!(
3381            snapshot.offset_to_point(clipped_right),
3382            text.offset_to_point(clipped_right),
3383            "offset_to_point({clipped_right})"
3384        );
3385        let anchor_after = snapshot.anchor_after(clipped_left);
3386        assert_eq!(
3387            anchor_after.to_offset(snapshot),
3388            clipped_left,
3389            "anchor_after({clipped_left}).to_offset {anchor_after:?}"
3390        );
3391        let anchor_before = snapshot.anchor_before(clipped_left);
3392        assert_eq!(
3393            anchor_before.to_offset(snapshot),
3394            clipped_left,
3395            "anchor_before({clipped_left}).to_offset"
3396        );
3397        left_anchors.push(anchor_before);
3398        right_anchors.push(anchor_after);
3399        offsets.push(clipped_left);
3400        points.push(text.offset_to_point(clipped_left));
3401    }
3402
3403    for row in 0..text.max_point().row {
3404        for column in 0..text.line_len(row) + 1 {
3405            let point = Point { row, column };
3406            let clipped_left = snapshot.clip_point(point, Bias::Left);
3407            let clipped_right = snapshot.clip_point(point, Bias::Right);
3408            assert_eq!(
3409                clipped_left,
3410                text.clip_point(point, Bias::Left),
3411                "clip_point({point:?}, Left)"
3412            );
3413            assert_eq!(
3414                clipped_right,
3415                text.clip_point(point, Bias::Right),
3416                "clip_point({point:?}, Right)"
3417            );
3418            assert_eq!(
3419                snapshot.point_to_offset(clipped_left),
3420                text.point_to_offset(clipped_left),
3421                "point_to_offset({clipped_left:?})"
3422            );
3423            assert_eq!(
3424                snapshot.point_to_offset(clipped_right),
3425                text.point_to_offset(clipped_right),
3426                "point_to_offset({clipped_right:?})"
3427            );
3428        }
3429    }
3430
3431    assert_eq!(
3432        snapshot.summaries_for_anchors::<usize, _>(&left_anchors),
3433        offsets,
3434        "left_anchors <-> offsets"
3435    );
3436    assert_eq!(
3437        snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
3438        points,
3439        "left_anchors <-> points"
3440    );
3441    assert_eq!(
3442        snapshot.summaries_for_anchors::<usize, _>(&right_anchors),
3443        offsets,
3444        "right_anchors <-> offsets"
3445    );
3446    assert_eq!(
3447        snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
3448        points,
3449        "right_anchors <-> points"
3450    );
3451
3452    for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
3453        for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
3454            if ix > 0 {
3455                if *offset == 252 {
3456                    if offset > &offsets[ix - 1] {
3457                        let prev_anchor = left_anchors[ix - 1];
3458                        assert!(
3459                            anchor.cmp(&prev_anchor, snapshot).is_gt(),
3460                            "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
3461                            offsets[ix],
3462                            offsets[ix - 1],
3463                        );
3464                        assert!(
3465                            prev_anchor.cmp(&anchor, snapshot).is_lt(),
3466                            "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
3467                            offsets[ix - 1],
3468                            offsets[ix],
3469                        );
3470                    }
3471                }
3472            }
3473        }
3474    }
3475
3476    if let Some((buffer, offset)) = snapshot.point_to_buffer_offset(snapshot.max_point()) {
3477        assert!(offset <= buffer.len());
3478    }
3479    if let Some((buffer, point, _)) = snapshot.point_to_buffer_point(snapshot.max_point()) {
3480        assert!(point <= buffer.max_point());
3481    }
3482}
3483
3484fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
3485    let max_row = snapshot.max_point().row;
3486    let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
3487    let text = text::Buffer::new(0, buffer_id, snapshot.text());
3488    let mut line_indents = text
3489        .line_indents_in_row_range(0..max_row + 1)
3490        .collect::<Vec<_>>();
3491    for start_row in 0..snapshot.max_point().row {
3492        pretty_assertions::assert_eq!(
3493            snapshot
3494                .line_indents(MultiBufferRow(start_row), |_| true)
3495                .map(|(row, indent, _)| (row.0, indent))
3496                .collect::<Vec<_>>(),
3497            &line_indents[(start_row as usize)..],
3498            "line_indents({start_row})"
3499        );
3500    }
3501
3502    line_indents.reverse();
3503    pretty_assertions::assert_eq!(
3504        snapshot
3505            .reversed_line_indents(MultiBufferRow(max_row), |_| true)
3506            .map(|(row, indent, _)| (row.0, indent))
3507            .collect::<Vec<_>>(),
3508        &line_indents[..],
3509        "reversed_line_indents({max_row})"
3510    );
3511}