Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:35:08.325Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

multi_buffer_tests.rs

6277 lines · 199.1 KB · rust
1use super::*;
2use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
3use gpui::{App, Entity, TestAppContext};
4use indoc::indoc;
5use language::{Buffer, Rope};
6use parking_lot::RwLock;
7use rand::prelude::*;
8use settings::SettingsStore;
9use std::env;
10use std::sync::Arc;
11use std::time::{Duration, Instant};
12use util::RandomCharIter;
13use util::rel_path::rel_path;
14use util::test::sample_text;
15
16#[ctor::ctor(unsafe)]
17fn init_logger() {
18    zlog::init_test();
19}
20
21#[gpui::test]
22fn test_empty_singleton(cx: &mut App) {
23    let buffer = cx.new(|cx| Buffer::local("", cx));
24    let buffer_id = buffer.read(cx).remote_id();
25    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
26    let snapshot = multibuffer.read(cx).snapshot(cx);
27    assert_eq!(snapshot.text(), "");
28    assert_eq!(
29        snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
30        [RowInfo {
31            buffer_id: Some(buffer_id),
32            buffer_row: Some(0),
33            multibuffer_row: Some(MultiBufferRow(0)),
34            diff_status: None,
35            expand_info: None,
36            wrapped_buffer_row: None,
37        }]
38    );
39}
40
41#[gpui::test]
42fn test_singleton(cx: &mut App) {
43    let buffer = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
44    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
45
46    let snapshot = multibuffer.read(cx).snapshot(cx);
47    assert_eq!(snapshot.text(), buffer.read(cx).text());
48
49    assert_eq!(
50        snapshot
51            .row_infos(MultiBufferRow(0))
52            .map(|info| info.buffer_row)
53            .collect::<Vec<_>>(),
54        (0..buffer.read(cx).row_count())
55            .map(Some)
56            .collect::<Vec<_>>()
57    );
58    assert_consistent_line_numbers(&snapshot);
59
60    buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx));
61    let snapshot = multibuffer.read(cx).snapshot(cx);
62
63    assert_eq!(snapshot.text(), buffer.read(cx).text());
64    assert_eq!(
65        snapshot
66            .row_infos(MultiBufferRow(0))
67            .map(|info| info.buffer_row)
68            .collect::<Vec<_>>(),
69        (0..buffer.read(cx).row_count())
70            .map(Some)
71            .collect::<Vec<_>>()
72    );
73    assert_consistent_line_numbers(&snapshot);
74}
75
76#[gpui::test]
77fn test_buffer_point_to_anchor_at_end_of_singleton_buffer(cx: &mut App) {
78    let buffer = cx.new(|cx| Buffer::local("abc", cx));
79    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
80
81    let anchor = multibuffer
82        .read(cx)
83        .buffer_point_to_anchor(&buffer, Point::new(0, 3), cx)
84        .unwrap();
85    let (anchor, _) = multibuffer
86        .read(cx)
87        .snapshot(cx)
88        .anchor_to_buffer_anchor(anchor)
89        .unwrap();
90
91    assert_eq!(
92        anchor,
93        buffer.read(cx).snapshot().anchor_after(Point::new(0, 3)),
94    );
95}
96
97#[gpui::test]
98fn test_remote(cx: &mut App) {
99    let host_buffer = cx.new(|cx| Buffer::local("a", cx));
100    let guest_buffer = cx.new(|cx| {
101        let state = host_buffer.read(cx).to_proto(cx);
102        let ops = cx
103            .foreground_executor()
104            .block_on(host_buffer.read(cx).serialize_ops(None, cx));
105        let mut buffer =
106            Buffer::from_proto(ReplicaId::REMOTE_SERVER, Capability::ReadWrite, state, None)
107                .unwrap();
108        buffer.apply_ops(
109            ops.into_iter()
110                .map(|op| language::proto::deserialize_operation(op).unwrap()),
111            cx,
112        );
113        buffer
114    });
115    let multibuffer = cx.new(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx));
116    let snapshot = multibuffer.read(cx).snapshot(cx);
117    assert_eq!(snapshot.text(), "a");
118
119    guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx));
120    let snapshot = multibuffer.read(cx).snapshot(cx);
121    assert_eq!(snapshot.text(), "ab");
122
123    guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx));
124    let snapshot = multibuffer.read(cx).snapshot(cx);
125    assert_eq!(snapshot.text(), "abc");
126}
127
128#[gpui::test]
129fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
130    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(7, 6, 'a'), cx));
131    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(7, 6, 'g'), cx));
132    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
133
134    let events = Arc::new(RwLock::new(Vec::<Event>::new()));
135    multibuffer.update(cx, |_, cx| {
136        let events = events.clone();
137        cx.subscribe(&multibuffer, move |_, _, event, _| {
138            if let Event::Edited { .. } = event {
139                events.write().push(event.clone())
140            }
141        })
142        .detach();
143    });
144
145    let subscription = multibuffer.update(cx, |multibuffer, cx| {
146        let subscription = multibuffer.subscribe();
147        multibuffer.set_excerpt_ranges_for_path(
148            PathKey::sorted(0),
149            buffer_1.clone(),
150            &buffer_1.read(cx).snapshot(),
151            vec![ExcerptRange::new(Point::new(1, 2)..Point::new(2, 5))],
152            cx,
153        );
154        assert_eq!(
155            subscription.consume().into_inner(),
156            [Edit {
157                old: MultiBufferOffset(0)..MultiBufferOffset(0),
158                new: MultiBufferOffset(0)..MultiBufferOffset(10)
159            }]
160        );
161
162        multibuffer.set_excerpt_ranges_for_path(
163            PathKey::sorted(0),
164            buffer_1.clone(),
165            &buffer_1.read(cx).snapshot(),
166            vec![
167                ExcerptRange::new(Point::new(1, 2)..Point::new(2, 5)),
168                ExcerptRange::new(Point::new(5, 3)..Point::new(6, 4)),
169            ],
170            cx,
171        );
172        multibuffer.set_excerpt_ranges_for_path(
173            PathKey::sorted(1),
174            buffer_2.clone(),
175            &buffer_2.read(cx).snapshot(),
176            vec![ExcerptRange::new(Point::new(3, 1)..Point::new(3, 3))],
177            cx,
178        );
179        assert_eq!(
180            subscription.consume().into_inner(),
181            [Edit {
182                old: MultiBufferOffset(10)..MultiBufferOffset(10),
183                new: MultiBufferOffset(10)..MultiBufferOffset(22)
184            }]
185        );
186
187        subscription
188    });
189
190    // Adding excerpts emits an edited event.
191    assert_eq!(
192        events.read().as_slice(),
193        &[
194            Event::Edited {
195                edited_buffer: None,
196                source: language::BufferEditSource::User,
197            },
198            Event::Edited {
199                edited_buffer: None,
200                source: language::BufferEditSource::User,
201            },
202            Event::Edited {
203                edited_buffer: None,
204                source: language::BufferEditSource::User,
205            }
206        ]
207    );
208
209    let snapshot = multibuffer.read(cx).snapshot(cx);
210    assert_eq!(
211        snapshot.text(),
212        indoc!(
213            "
214            bbbb
215            ccccc
216            fff
217            gggg
218            jj"
219        ),
220    );
221    assert_eq!(
222        snapshot
223            .row_infos(MultiBufferRow(0))
224            .map(|info| info.buffer_row)
225            .collect::<Vec<_>>(),
226        [Some(1), Some(2), Some(5), Some(6), Some(3)]
227    );
228    assert_eq!(
229        snapshot
230            .row_infos(MultiBufferRow(2))
231            .map(|info| info.buffer_row)
232            .collect::<Vec<_>>(),
233        [Some(5), Some(6), Some(3)]
234    );
235    assert_eq!(
236        snapshot
237            .row_infos(MultiBufferRow(4))
238            .map(|info| info.buffer_row)
239            .collect::<Vec<_>>(),
240        [Some(3)]
241    );
242    assert!(
243        snapshot
244            .row_infos(MultiBufferRow(5))
245            .map(|info| info.buffer_row)
246            .collect::<Vec<_>>()
247            .is_empty()
248    );
249
250    assert_eq!(
251        boundaries_in_range(Point::new(0, 0)..Point::new(4, 2), &snapshot),
252        &[
253            (MultiBufferRow(0), "bbbb\nccccc".to_string(), true),
254            (MultiBufferRow(2), "fff\ngggg".to_string(), false),
255            (MultiBufferRow(4), "jj".to_string(), true),
256        ]
257    );
258    assert_eq!(
259        boundaries_in_range(Point::new(0, 0)..Point::new(2, 0), &snapshot),
260        &[(MultiBufferRow(0), "bbbb\nccccc".to_string(), true)]
261    );
262    assert_eq!(
263        boundaries_in_range(Point::new(1, 0)..Point::new(1, 5), &snapshot),
264        &[]
265    );
266    assert_eq!(
267        boundaries_in_range(Point::new(1, 0)..Point::new(2, 0), &snapshot),
268        &[]
269    );
270    assert_eq!(
271        boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
272        &[(MultiBufferRow(2), "fff\ngggg".to_string(), false)]
273    );
274    assert_eq!(
275        boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
276        &[(MultiBufferRow(2), "fff\ngggg".to_string(), false)]
277    );
278    assert_eq!(
279        boundaries_in_range(Point::new(2, 0)..Point::new(3, 0), &snapshot),
280        &[(MultiBufferRow(2), "fff\ngggg".to_string(), false)]
281    );
282    assert_eq!(
283        boundaries_in_range(Point::new(4, 0)..Point::new(4, 2), &snapshot),
284        &[(MultiBufferRow(4), "jj".to_string(), true)]
285    );
286    assert_eq!(
287        boundaries_in_range(Point::new(4, 2)..Point::new(4, 2), &snapshot),
288        &[]
289    );
290
291    buffer_1.update(cx, |buffer, cx| {
292        let text = "\n";
293        buffer.edit(
294            [
295                (Point::new(0, 0)..Point::new(0, 0), text),
296                (Point::new(2, 1)..Point::new(2, 3), text),
297            ],
298            None,
299            cx,
300        );
301    });
302
303    let snapshot = multibuffer.read(cx).snapshot(cx);
304    assert_eq!(
305        snapshot.text(),
306        concat!(
307            "bbbb\n", // Preserve newlines
308            "c\n",    //
309            "cc\n",   //
310            "fff\n",  //
311            "gggg\n", //
312            "jj"      //
313        )
314    );
315
316    assert_eq!(
317        subscription.consume().into_inner(),
318        [Edit {
319            old: MultiBufferOffset(6)..MultiBufferOffset(8),
320            new: MultiBufferOffset(6)..MultiBufferOffset(7)
321        }]
322    );
323
324    let snapshot = multibuffer.read(cx).snapshot(cx);
325    assert_eq!(
326        snapshot.clip_point(Point::new(0, 5), Bias::Left),
327        Point::new(0, 4)
328    );
329    assert_eq!(
330        snapshot.clip_point(Point::new(0, 5), Bias::Right),
331        Point::new(0, 4)
332    );
333    assert_eq!(
334        snapshot.clip_point(Point::new(5, 1), Bias::Right),
335        Point::new(5, 1)
336    );
337    assert_eq!(
338        snapshot.clip_point(Point::new(5, 2), Bias::Right),
339        Point::new(5, 2)
340    );
341    assert_eq!(
342        snapshot.clip_point(Point::new(5, 3), Bias::Right),
343        Point::new(5, 2)
344    );
345
346    let snapshot = multibuffer.update(cx, |multibuffer, cx| {
347        multibuffer.remove_excerpts(PathKey::sorted(1), cx);
348        multibuffer.snapshot(cx)
349    });
350
351    assert_eq!(
352        snapshot.text(),
353        concat!(
354            "bbbb\n", // Preserve newlines
355            "c\n",    //
356            "cc\n",   //
357            "fff\n",  //
358            "gggg",   //
359        )
360    );
361
362    fn boundaries_in_range(
363        range: Range<Point>,
364        snapshot: &MultiBufferSnapshot,
365    ) -> Vec<(MultiBufferRow, String, bool)> {
366        snapshot
367            .excerpt_boundaries_in_range(range)
368            .map(|boundary| {
369                let starts_new_buffer = boundary.starts_new_buffer();
370                (
371                    boundary.row,
372                    boundary
373                        .next
374                        .buffer(snapshot)
375                        .text_for_range(boundary.next.range.context)
376                        .collect::<String>(),
377                    starts_new_buffer,
378                )
379            })
380            .collect::<Vec<_>>()
381    }
382}
383
384#[gpui::test]
385async fn test_diff_boundary_anchors(cx: &mut TestAppContext) {
386    let base_text = "one\ntwo\nthree\n";
387    let text = "one\nthree\n";
388    let buffer = cx.new(|cx| Buffer::local(text, cx));
389    let diff = cx
390        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
391    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
392    multibuffer.update(cx, |multibuffer, cx| multibuffer.add_diff(diff, cx));
393
394    let (before, after) = multibuffer.update(cx, |multibuffer, cx| {
395        let before = multibuffer.snapshot(cx).anchor_before(Point::new(1, 0));
396        let after = multibuffer.snapshot(cx).anchor_after(Point::new(1, 0));
397        multibuffer.set_all_diff_hunks_expanded(cx);
398        (before, after)
399    });
400    cx.run_until_parked();
401
402    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
403    let actual_text = snapshot.text();
404    let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
405    let actual_diff = format_diff(&actual_text, &actual_row_infos, &Default::default(), None);
406    pretty_assertions::assert_eq!(
407        actual_diff,
408        indoc! {
409            "  one
410             - two
411               three
412             "
413        },
414    );
415
416    multibuffer.update(cx, |multibuffer, cx| {
417        let snapshot = multibuffer.snapshot(cx);
418        assert_eq!(before.to_point(&snapshot), Point::new(1, 0));
419        assert_eq!(after.to_point(&snapshot), Point::new(2, 0));
420        assert_eq!(
421            vec![Point::new(1, 0), Point::new(2, 0),],
422            snapshot.summaries_for_anchors::<Point, _>(&[before, after]),
423        )
424    })
425}
426
427#[gpui::test]
428async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
429    let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n";
430    let text = "one\nfour\nseven\n";
431    let buffer = cx.new(|cx| Buffer::local(text, cx));
432    let diff = cx
433        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
434    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
435    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
436        (multibuffer.snapshot(cx), multibuffer.subscribe())
437    });
438
439    multibuffer.update(cx, |multibuffer, cx| {
440        multibuffer.add_diff(diff, cx);
441        multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
442    });
443
444    assert_new_snapshot(
445        &multibuffer,
446        &mut snapshot,
447        &mut subscription,
448        cx,
449        indoc! {
450            "  one
451             - two
452             - three
453               four
454             - five
455             - six
456               seven
457             - eight
458            "
459        },
460    );
461
462    assert_eq!(
463        snapshot
464            .diff_hunks_in_range(Point::new(1, 0)..Point::MAX)
465            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
466            .collect::<Vec<_>>(),
467        vec![1..3, 4..6, 7..8]
468    );
469
470    assert_eq!(snapshot.diff_hunk_before(Point::new(1, 1)), None,);
471    assert_eq!(
472        snapshot.diff_hunk_before(Point::new(7, 0)),
473        Some(MultiBufferRow(4))
474    );
475    assert_eq!(
476        snapshot.diff_hunk_before(Point::new(4, 0)),
477        Some(MultiBufferRow(1))
478    );
479
480    multibuffer.update(cx, |multibuffer, cx| {
481        multibuffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
482    });
483
484    assert_new_snapshot(
485        &multibuffer,
486        &mut snapshot,
487        &mut subscription,
488        cx,
489        indoc! {
490            "
491            one
492            four
493            seven
494            "
495        },
496    );
497
498    assert_eq!(
499        snapshot.diff_hunk_before(Point::new(2, 0)),
500        Some(MultiBufferRow(1)),
501    );
502    assert_eq!(
503        snapshot.diff_hunk_before(Point::new(4, 0)),
504        Some(MultiBufferRow(2))
505    );
506}
507
508#[gpui::test]
509async fn test_diff_hunks_in_range_query_starting_at_added_row(cx: &mut TestAppContext) {
510    let base_text = "one\ntwo\nthree\n";
511    let text = "one\nTWO\nthree\n";
512    let buffer = cx.new(|cx| Buffer::local(text, cx));
513    let diff = cx
514        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
515    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
516    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
517        (multibuffer.snapshot(cx), multibuffer.subscribe())
518    });
519
520    multibuffer.update(cx, |multibuffer, cx| {
521        multibuffer.add_diff(diff, cx);
522        multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
523    });
524
525    assert_new_snapshot(
526        &multibuffer,
527        &mut snapshot,
528        &mut subscription,
529        cx,
530        indoc! {
531            "  one
532             - two
533             + TWO
534               three
535            "
536        },
537    );
538
539    assert_eq!(
540        snapshot
541            .diff_hunks_in_range(Point::new(2, 0)..Point::MAX)
542            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
543            .collect::<Vec<_>>(),
544        vec![1..3],
545        "querying starting at the added row should still return the full hunk including deleted lines"
546    );
547}
548
549#[gpui::test]
550async fn test_inverted_diff_hunks_in_range(cx: &mut TestAppContext) {
551    let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n";
552    let text = "ZERO\none\nTHREE\nfour\nseven\nEIGHT\nNINE\n";
553    let buffer = cx.new(|cx| Buffer::local(text, cx));
554    let diff = cx
555        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
556    let base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
557    let multibuffer = cx.new(|cx| MultiBuffer::singleton(base_text_buffer.clone(), cx));
558    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
559        (multibuffer.snapshot(cx), multibuffer.subscribe())
560    });
561
562    multibuffer.update(cx, |multibuffer, cx| {
563        multibuffer.add_inverted_diff(diff, buffer.clone(), cx);
564    });
565
566    assert_new_snapshot(
567        &multibuffer,
568        &mut snapshot,
569        &mut subscription,
570        cx,
571        indoc! {
572            "  one
573             - two
574             - three
575               four
576             - five
577             - six
578               seven
579             - eight
580            "
581        },
582    );
583
584    assert_eq!(
585        snapshot
586            .diff_hunks_in_range(Point::new(0, 0)..Point::MAX)
587            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
588            .collect::<Vec<_>>(),
589        vec![0..0, 1..3, 4..6, 7..8]
590    );
591
592    assert_eq!(
593        snapshot.diff_hunk_before(Point::new(1, 1)),
594        Some(MultiBufferRow(0))
595    );
596    assert_eq!(
597        snapshot.diff_hunk_before(Point::new(7, 0)),
598        Some(MultiBufferRow(4))
599    );
600    assert_eq!(
601        snapshot.diff_hunk_before(Point::new(4, 0)),
602        Some(MultiBufferRow(1))
603    );
604}
605
606#[gpui::test]
607async fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) {
608    let base_text = "one\ntwo\nfour\nfive\nsix\nseven\n";
609    let text = "one\ntwo\nTHREE\nfour\nfive\nseven\n";
610    let buffer = cx.new(|cx| Buffer::local(text, cx));
611    let diff = cx
612        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
613    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
614
615    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
616        multibuffer.add_diff(diff.clone(), cx);
617        (multibuffer.snapshot(cx), multibuffer.subscribe())
618    });
619
620    cx.executor().run_until_parked();
621    multibuffer.update(cx, |multibuffer, cx| {
622        multibuffer.set_all_diff_hunks_expanded(cx);
623    });
624
625    assert_new_snapshot(
626        &multibuffer,
627        &mut snapshot,
628        &mut subscription,
629        cx,
630        indoc! {
631            "
632              one
633              two
634            + THREE
635              four
636              five
637            - six
638              seven
639            "
640        },
641    );
642
643    // Insert a newline within an insertion hunk
644    multibuffer.update(cx, |multibuffer, cx| {
645        multibuffer.edit([(Point::new(2, 0)..Point::new(2, 0), "__\n__")], None, cx);
646    });
647    assert_new_snapshot(
648        &multibuffer,
649        &mut snapshot,
650        &mut subscription,
651        cx,
652        indoc! {
653            "
654              one
655              two
656            + __
657            + __THREE
658              four
659              five
660            - six
661              seven
662            "
663        },
664    );
665
666    // Delete the newline before a deleted hunk.
667    multibuffer.update(cx, |multibuffer, cx| {
668        multibuffer.edit([(Point::new(5, 4)..Point::new(6, 0), "")], None, cx);
669    });
670    assert_new_snapshot(
671        &multibuffer,
672        &mut snapshot,
673        &mut subscription,
674        cx,
675        indoc! {
676            "
677              one
678              two
679            + __
680            + __THREE
681              four
682              fiveseven
683            "
684        },
685    );
686
687    multibuffer.update(cx, |multibuffer, cx| multibuffer.undo(cx));
688    assert_new_snapshot(
689        &multibuffer,
690        &mut snapshot,
691        &mut subscription,
692        cx,
693        indoc! {
694            "
695              one
696              two
697            + __
698            + __THREE
699              four
700              five
701            - six
702              seven
703            "
704        },
705    );
706
707    // Cannot (yet) insert at the beginning of a deleted hunk.
708    // (because it would put the newline in the wrong place)
709    multibuffer.update(cx, |multibuffer, cx| {
710        multibuffer.edit([(Point::new(6, 0)..Point::new(6, 0), "\n")], None, cx);
711    });
712    assert_new_snapshot(
713        &multibuffer,
714        &mut snapshot,
715        &mut subscription,
716        cx,
717        indoc! {
718            "
719              one
720              two
721            + __
722            + __THREE
723              four
724              five
725            - six
726              seven
727            "
728        },
729    );
730
731    // Replace a range that ends in a deleted hunk.
732    multibuffer.update(cx, |multibuffer, cx| {
733        multibuffer.edit([(Point::new(5, 2)..Point::new(6, 2), "fty-")], None, cx);
734    });
735    assert_new_snapshot(
736        &multibuffer,
737        &mut snapshot,
738        &mut subscription,
739        cx,
740        indoc! {
741            "
742              one
743              two
744            + __
745            + __THREE
746              four
747              fifty-seven
748            "
749        },
750    );
751}
752
753#[gpui::test]
754fn test_excerpt_events(cx: &mut App) {
755    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'a'), cx));
756    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'm'), cx));
757
758    let leader_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
759    let follower_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
760    let follower_edit_event_count = Arc::new(RwLock::new(0));
761
762    follower_multibuffer.update(cx, |_, cx| {
763        let follower_edit_event_count = follower_edit_event_count.clone();
764        cx.subscribe(
765            &leader_multibuffer,
766            move |follower, _, event, cx| match event.clone() {
767                Event::BufferRangesUpdated {
768                    buffer,
769                    path_key,
770                    ranges,
771                } => {
772                    let buffer_snapshot = buffer.read(cx).snapshot();
773                    follower.set_merged_excerpt_ranges_for_path(
774                        path_key,
775                        buffer,
776                        &buffer_snapshot,
777                        ranges,
778                        cx,
779                    );
780                }
781                Event::BuffersRemoved {
782                    removed_buffer_ids, ..
783                } => {
784                    for id in removed_buffer_ids {
785                        follower.remove_excerpts_for_buffer(id, cx);
786                    }
787                }
788                Event::Edited { .. } => {
789                    *follower_edit_event_count.write() += 1;
790                }
791                _ => {}
792            },
793        )
794        .detach();
795    });
796
797    let buffer_1_snapshot = buffer_1.read(cx).snapshot();
798    let buffer_2_snapshot = buffer_2.read(cx).snapshot();
799    leader_multibuffer.update(cx, |leader, cx| {
800        leader.set_excerpt_ranges_for_path(
801            PathKey::sorted(0),
802            buffer_1.clone(),
803            &buffer_1_snapshot,
804            vec![
805                ExcerptRange::new((0..8).to_point(&buffer_1_snapshot)),
806                ExcerptRange::new((22..26).to_point(&buffer_1_snapshot)),
807            ],
808            cx,
809        );
810        leader.set_excerpt_ranges_for_path(
811            PathKey::sorted(1),
812            buffer_2.clone(),
813            &buffer_2_snapshot,
814            vec![
815                ExcerptRange::new((0..5).to_point(&buffer_2_snapshot)),
816                ExcerptRange::new((20..25).to_point(&buffer_2_snapshot)),
817            ],
818            cx,
819        );
820    });
821    assert_eq!(
822        leader_multibuffer.read(cx).snapshot(cx).text(),
823        follower_multibuffer.read(cx).snapshot(cx).text(),
824    );
825    assert_eq!(*follower_edit_event_count.read(), 2);
826
827    leader_multibuffer.update(cx, |leader, cx| {
828        leader.set_excerpt_ranges_for_path(
829            PathKey::sorted(0),
830            buffer_1.clone(),
831            &buffer_1_snapshot,
832            vec![ExcerptRange::new((0..8).to_point(&buffer_1_snapshot))],
833            cx,
834        );
835        leader.set_excerpt_ranges_for_path(
836            PathKey::sorted(1),
837            buffer_2,
838            &buffer_2_snapshot,
839            vec![ExcerptRange::new((0..5).to_point(&buffer_2_snapshot))],
840            cx,
841        );
842    });
843    assert_eq!(
844        leader_multibuffer.read(cx).snapshot(cx).text(),
845        follower_multibuffer.read(cx).snapshot(cx).text(),
846    );
847    assert_eq!(*follower_edit_event_count.read(), 4);
848
849    leader_multibuffer.update(cx, |leader, cx| {
850        leader.clear(cx);
851    });
852    assert_eq!(
853        leader_multibuffer.read(cx).snapshot(cx).text(),
854        follower_multibuffer.read(cx).snapshot(cx).text(),
855    );
856    assert_eq!(*follower_edit_event_count.read(), 5);
857}
858
859#[gpui::test]
860fn test_expand_excerpts(cx: &mut App) {
861    let buffer = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
862    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
863
864    multibuffer.update(cx, |multibuffer, cx| {
865        multibuffer.set_excerpts_for_path(
866            PathKey::for_buffer(&buffer, cx),
867            buffer,
868            vec![
869                // Note that in this test, this first excerpt
870                // does not contain a new line
871                Point::new(3, 2)..Point::new(3, 3),
872                Point::new(7, 1)..Point::new(7, 3),
873                Point::new(15, 0)..Point::new(15, 0),
874            ],
875            1,
876            cx,
877        )
878    });
879
880    let snapshot = multibuffer.read(cx).snapshot(cx);
881
882    assert_eq!(
883        snapshot.text(),
884        concat!(
885            "ccc\n", //
886            "ddd\n", //
887            "eee",   //
888            "\n",    // End of excerpt
889            "ggg\n", //
890            "hhh\n", //
891            "iii",   //
892            "\n",    // End of excerpt
893            "ooo\n", //
894            "ppp\n", //
895            "qqq",   // End of excerpt
896        )
897    );
898    drop(snapshot);
899
900    multibuffer.update(cx, |multibuffer, cx| {
901        let multibuffer_snapshot = multibuffer.snapshot(cx);
902        let line_zero = multibuffer_snapshot.anchor_before(Point::new(0, 0));
903        multibuffer.expand_excerpts(
904            multibuffer.snapshot(cx).excerpts().map(|excerpt| {
905                multibuffer_snapshot
906                    .anchor_in_excerpt(excerpt.context.start)
907                    .unwrap()
908            }),
909            1,
910            ExpandExcerptDirection::UpAndDown,
911            cx,
912        );
913        let snapshot = multibuffer.snapshot(cx);
914        let line_two = snapshot.anchor_before(Point::new(2, 0));
915        assert_eq!(line_two.cmp(&line_zero, &snapshot), cmp::Ordering::Greater);
916    });
917
918    let snapshot = multibuffer.read(cx).snapshot(cx);
919
920    assert_eq!(
921        snapshot.text(),
922        concat!(
923            "bbb\n", //
924            "ccc\n", //
925            "ddd\n", //
926            "eee\n", //
927            "fff\n", //
928            "ggg\n", //
929            "hhh\n", //
930            "iii\n", //
931            "jjj\n", // End of excerpt
932            "nnn\n", //
933            "ooo\n", //
934            "ppp\n", //
935            "qqq\n", //
936            "rrr",   // End of excerpt
937        )
938    );
939}
940
941#[gpui::test(iterations = 100)]
942async fn test_set_anchored_excerpts_for_path(cx: &mut TestAppContext) {
943    let buffer_1 = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
944    let buffer_2 = cx.new(|cx| Buffer::local(sample_text(15, 4, 'a'), cx));
945    let snapshot_1 = buffer_1.update(cx, |buffer, _| buffer.snapshot());
946    let snapshot_2 = buffer_2.update(cx, |buffer, _| buffer.snapshot());
947    let ranges_1 = vec![
948        snapshot_1.anchor_before(Point::new(3, 2))..snapshot_1.anchor_before(Point::new(4, 2)),
949        snapshot_1.anchor_before(Point::new(7, 1))..snapshot_1.anchor_before(Point::new(7, 3)),
950        snapshot_1.anchor_before(Point::new(15, 0))..snapshot_1.anchor_before(Point::new(15, 0)),
951    ];
952    let ranges_2 = vec![
953        snapshot_2.anchor_before(Point::new(2, 1))..snapshot_2.anchor_before(Point::new(3, 1)),
954        snapshot_2.anchor_before(Point::new(10, 0))..snapshot_2.anchor_before(Point::new(10, 2)),
955    ];
956
957    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
958    let anchor_ranges_1 = multibuffer
959        .update(cx, |multibuffer, cx| {
960            multibuffer.set_anchored_excerpts_for_path(
961                PathKey::for_buffer(&buffer_1, cx),
962                buffer_1.clone(),
963                ranges_1,
964                2,
965                cx,
966            )
967        })
968        .await;
969    let snapshot_1 = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
970    assert_eq!(
971        anchor_ranges_1
972            .iter()
973            .map(|range| range.to_point(&snapshot_1))
974            .collect::<Vec<_>>(),
975        vec![
976            Point::new(2, 2)..Point::new(3, 2),
977            Point::new(6, 1)..Point::new(6, 3),
978            Point::new(11, 0)..Point::new(11, 0),
979        ]
980    );
981    let anchor_ranges_2 = multibuffer
982        .update(cx, |multibuffer, cx| {
983            multibuffer.set_anchored_excerpts_for_path(
984                PathKey::for_buffer(&buffer_2, cx),
985                buffer_2.clone(),
986                ranges_2,
987                2,
988                cx,
989            )
990        })
991        .await;
992    let snapshot_2 = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
993    assert_eq!(
994        anchor_ranges_2
995            .iter()
996            .map(|range| range.to_point(&snapshot_2))
997            .collect::<Vec<_>>(),
998        vec![
999            Point::new(16, 1)..Point::new(17, 1),
1000            Point::new(22, 0)..Point::new(22, 2)
1001        ]
1002    );
1003
1004    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1005    assert_eq!(
1006        snapshot.text(),
1007        concat!(
1008            "bbb\n", // buffer_1
1009            "ccc\n", //
1010            "ddd\n", // <-- excerpt 1
1011            "eee\n", // <-- excerpt 1
1012            "fff\n", //
1013            "ggg\n", //
1014            "hhh\n", // <-- excerpt 2
1015            "iii\n", //
1016            "jjj\n", //
1017            //
1018            "nnn\n", //
1019            "ooo\n", //
1020            "ppp\n", // <-- excerpt 3
1021            "qqq\n", //
1022            "rrr\n", //
1023            //
1024            "aaaa\n", // buffer 2
1025            "bbbb\n", //
1026            "cccc\n", // <-- excerpt 4
1027            "dddd\n", // <-- excerpt 4
1028            "eeee\n", //
1029            "ffff\n", //
1030            //
1031            "iiii\n", //
1032            "jjjj\n", //
1033            "kkkk\n", // <-- excerpt 5
1034            "llll\n", //
1035            "mmmm",   //
1036        )
1037    );
1038}
1039
1040#[gpui::test]
1041fn test_empty_multibuffer(cx: &mut App) {
1042    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1043
1044    let snapshot = multibuffer.read(cx).snapshot(cx);
1045    assert_eq!(snapshot.text(), "");
1046    assert_eq!(
1047        snapshot
1048            .row_infos(MultiBufferRow(0))
1049            .map(|info| info.buffer_row)
1050            .collect::<Vec<_>>(),
1051        &[Some(0)]
1052    );
1053    assert!(
1054        snapshot
1055            .row_infos(MultiBufferRow(1))
1056            .map(|info| info.buffer_row)
1057            .collect::<Vec<_>>()
1058            .is_empty(),
1059    );
1060}
1061
1062#[gpui::test]
1063async fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
1064    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1065    let buffer = cx.new(|cx| Buffer::local("", cx));
1066    let base_text = "a\nb\nc";
1067
1068    let diff = cx
1069        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
1070    multibuffer.update(cx, |multibuffer, cx| {
1071        multibuffer.set_excerpt_ranges_for_path(
1072            PathKey::sorted(0),
1073            buffer.clone(),
1074            &buffer.read(cx).snapshot(),
1075            vec![ExcerptRange::new(Point::zero()..Point::zero())],
1076            cx,
1077        );
1078        multibuffer.set_all_diff_hunks_expanded(cx);
1079        multibuffer.add_diff(diff.clone(), cx);
1080    });
1081    cx.run_until_parked();
1082
1083    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1084    assert_eq!(snapshot.text(), "a\nb\nc\n");
1085
1086    let hunk = snapshot
1087        .diff_hunks_in_range(Point::new(1, 1)..Point::new(1, 1))
1088        .next()
1089        .unwrap();
1090
1091    assert_eq!(hunk.diff_base_byte_range.start, BufferOffset(0));
1092
1093    let buf2 = cx.new(|cx| Buffer::local("X", cx));
1094    multibuffer.update(cx, |multibuffer, cx| {
1095        multibuffer.set_excerpts_for_path(
1096            PathKey::sorted(1),
1097            buf2,
1098            [Point::new(0, 0)..Point::new(0, 1)],
1099            0,
1100            cx,
1101        );
1102    });
1103
1104    buffer.update(cx, |buffer, cx| {
1105        buffer.edit([(0..0, "a\nb\nc")], None, cx);
1106        diff.update(cx, |diff, cx| {
1107            diff.recalculate_diff_sync(&buffer.text_snapshot(), cx);
1108        });
1109        assert_eq!(buffer.text(), "a\nb\nc")
1110    });
1111    cx.run_until_parked();
1112
1113    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1114    assert_eq!(snapshot.text(), "a\nb\nc\nX");
1115
1116    buffer.update(cx, |buffer, cx| {
1117        buffer.undo(cx);
1118        diff.update(cx, |diff, cx| {
1119            diff.recalculate_diff_sync(&buffer.text_snapshot(), cx);
1120        });
1121        assert_eq!(buffer.text(), "")
1122    });
1123    cx.run_until_parked();
1124
1125    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1126    assert_eq!(snapshot.text(), "a\nb\nc\n\nX");
1127}
1128
1129#[gpui::test]
1130fn test_singleton_multibuffer_anchors(cx: &mut App) {
1131    let buffer = cx.new(|cx| Buffer::local("abcd", cx));
1132    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
1133    let old_snapshot = multibuffer.read(cx).snapshot(cx);
1134    buffer.update(cx, |buffer, cx| {
1135        buffer.edit([(0..0, "X")], None, cx);
1136        buffer.edit([(5..5, "Y")], None, cx);
1137    });
1138    let new_snapshot = multibuffer.read(cx).snapshot(cx);
1139
1140    assert_eq!(old_snapshot.text(), "abcd");
1141    assert_eq!(new_snapshot.text(), "XabcdY");
1142
1143    assert_eq!(
1144        old_snapshot
1145            .anchor_before(MultiBufferOffset(0))
1146            .to_offset(&new_snapshot),
1147        MultiBufferOffset(0)
1148    );
1149    assert_eq!(
1150        old_snapshot
1151            .anchor_after(MultiBufferOffset(0))
1152            .to_offset(&new_snapshot),
1153        MultiBufferOffset(1)
1154    );
1155    assert_eq!(
1156        old_snapshot
1157            .anchor_before(MultiBufferOffset(4))
1158            .to_offset(&new_snapshot),
1159        MultiBufferOffset(5)
1160    );
1161    assert_eq!(
1162        old_snapshot
1163            .anchor_after(MultiBufferOffset(4))
1164            .to_offset(&new_snapshot),
1165        MultiBufferOffset(6)
1166    );
1167}
1168
1169#[gpui::test]
1170fn test_multibuffer_anchors(cx: &mut App) {
1171    let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1172    let buffer_2 = cx.new(|cx| Buffer::local("efghi", cx));
1173    let multibuffer = cx.new(|cx| {
1174        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1175        multibuffer.set_excerpts_for_path(
1176            PathKey::sorted(0),
1177            buffer_1.clone(),
1178            [Point::new(0, 0)..Point::new(0, 4)],
1179            0,
1180            cx,
1181        );
1182        multibuffer.set_excerpts_for_path(
1183            PathKey::sorted(1),
1184            buffer_2.clone(),
1185            [Point::new(0, 0)..Point::new(0, 5)],
1186            0,
1187            cx,
1188        );
1189        multibuffer
1190    });
1191    let old_snapshot = multibuffer.read(cx).snapshot(cx);
1192
1193    assert_eq!(
1194        old_snapshot
1195            .anchor_before(MultiBufferOffset(0))
1196            .to_offset(&old_snapshot),
1197        MultiBufferOffset(0)
1198    );
1199    assert_eq!(
1200        old_snapshot
1201            .anchor_after(MultiBufferOffset(0))
1202            .to_offset(&old_snapshot),
1203        MultiBufferOffset(0)
1204    );
1205    assert_eq!(Anchor::Min.to_offset(&old_snapshot), MultiBufferOffset(0));
1206    assert_eq!(Anchor::Min.to_offset(&old_snapshot), MultiBufferOffset(0));
1207    assert_eq!(Anchor::Max.to_offset(&old_snapshot), MultiBufferOffset(10));
1208    assert_eq!(Anchor::Max.to_offset(&old_snapshot), MultiBufferOffset(10));
1209
1210    buffer_1.update(cx, |buffer, cx| {
1211        buffer.edit([(0..0, "W")], None, cx);
1212        buffer.edit([(5..5, "X")], None, cx);
1213    });
1214    buffer_2.update(cx, |buffer, cx| {
1215        buffer.edit([(0..0, "Y")], None, cx);
1216        buffer.edit([(6..6, "Z")], None, cx);
1217    });
1218    let new_snapshot = multibuffer.read(cx).snapshot(cx);
1219
1220    assert_eq!(old_snapshot.text(), "abcd\nefghi");
1221    assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
1222
1223    assert_eq!(
1224        old_snapshot
1225            .anchor_before(MultiBufferOffset(0))
1226            .to_offset(&new_snapshot),
1227        MultiBufferOffset(0)
1228    );
1229    assert_eq!(
1230        old_snapshot
1231            .anchor_after(MultiBufferOffset(0))
1232            .to_offset(&new_snapshot),
1233        MultiBufferOffset(1)
1234    );
1235    assert_eq!(
1236        old_snapshot
1237            .anchor_before(MultiBufferOffset(1))
1238            .to_offset(&new_snapshot),
1239        MultiBufferOffset(2)
1240    );
1241    assert_eq!(
1242        old_snapshot
1243            .anchor_after(MultiBufferOffset(1))
1244            .to_offset(&new_snapshot),
1245        MultiBufferOffset(2)
1246    );
1247    assert_eq!(
1248        old_snapshot
1249            .anchor_before(MultiBufferOffset(2))
1250            .to_offset(&new_snapshot),
1251        MultiBufferOffset(3)
1252    );
1253    assert_eq!(
1254        old_snapshot
1255            .anchor_after(MultiBufferOffset(2))
1256            .to_offset(&new_snapshot),
1257        MultiBufferOffset(3)
1258    );
1259    assert_eq!(
1260        old_snapshot
1261            .anchor_before(MultiBufferOffset(5))
1262            .to_offset(&new_snapshot),
1263        MultiBufferOffset(7)
1264    );
1265    assert_eq!(
1266        old_snapshot
1267            .anchor_after(MultiBufferOffset(5))
1268            .to_offset(&new_snapshot),
1269        MultiBufferOffset(8)
1270    );
1271    assert_eq!(
1272        old_snapshot
1273            .anchor_before(MultiBufferOffset(10))
1274            .to_offset(&new_snapshot),
1275        MultiBufferOffset(13)
1276    );
1277    assert_eq!(
1278        old_snapshot
1279            .anchor_after(MultiBufferOffset(10))
1280            .to_offset(&new_snapshot),
1281        MultiBufferOffset(14)
1282    );
1283}
1284
1285#[gpui::test]
1286async fn test_basic_diff_hunks(cx: &mut TestAppContext) {
1287    let text = indoc!(
1288        "
1289        ZERO
1290        one
1291        TWO
1292        three
1293        six
1294        "
1295    );
1296    let base_text = indoc!(
1297        "
1298        one
1299        two
1300        three
1301        four
1302        five
1303        six
1304        "
1305    );
1306
1307    let buffer = cx.new(|cx| Buffer::local(text, cx));
1308    let diff = cx
1309        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
1310    cx.run_until_parked();
1311
1312    let multibuffer = cx.new(|cx| {
1313        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1314        multibuffer.add_diff(diff.clone(), cx);
1315        multibuffer
1316    });
1317
1318    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1319        (multibuffer.snapshot(cx), multibuffer.subscribe())
1320    });
1321    assert_eq!(
1322        snapshot.text(),
1323        indoc!(
1324            "
1325            ZERO
1326            one
1327            TWO
1328            three
1329            six
1330            "
1331        ),
1332    );
1333
1334    multibuffer.update(cx, |multibuffer, cx| {
1335        multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
1336    });
1337
1338    assert_new_snapshot(
1339        &multibuffer,
1340        &mut snapshot,
1341        &mut subscription,
1342        cx,
1343        indoc!(
1344            "
1345            + ZERO
1346              one
1347            - two
1348            + TWO
1349              three
1350            - four
1351            - five
1352              six
1353            "
1354        ),
1355    );
1356
1357    assert_eq!(
1358        snapshot
1359            .row_infos(MultiBufferRow(0))
1360            .map(|info| (info.buffer_row, info.diff_status))
1361            .collect::<Vec<_>>(),
1362        vec![
1363            (Some(0), Some(DiffHunkStatus::added_none())),
1364            (Some(1), None),
1365            (Some(1), Some(DiffHunkStatus::deleted_none())),
1366            (Some(2), Some(DiffHunkStatus::added_none())),
1367            (Some(3), None),
1368            (Some(3), Some(DiffHunkStatus::deleted_none())),
1369            (Some(4), Some(DiffHunkStatus::deleted_none())),
1370            (Some(4), None),
1371            (Some(5), None)
1372        ]
1373    );
1374
1375    assert_chunks_in_ranges(&snapshot);
1376    assert_consistent_line_numbers(&snapshot);
1377    assert_position_translation(&snapshot);
1378    assert_line_indents(&snapshot);
1379
1380    multibuffer.update(cx, |multibuffer, cx| {
1381        multibuffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx)
1382    });
1383    assert_new_snapshot(
1384        &multibuffer,
1385        &mut snapshot,
1386        &mut subscription,
1387        cx,
1388        indoc!(
1389            "
1390            ZERO
1391            one
1392            TWO
1393            three
1394            six
1395            "
1396        ),
1397    );
1398
1399    assert_chunks_in_ranges(&snapshot);
1400    assert_consistent_line_numbers(&snapshot);
1401    assert_position_translation(&snapshot);
1402    assert_line_indents(&snapshot);
1403
1404    // Expand the first diff hunk
1405    multibuffer.update(cx, |multibuffer, cx| {
1406        let position = multibuffer.read(cx).anchor_before(Point::new(2, 2));
1407        multibuffer.expand_diff_hunks(vec![position..position], cx)
1408    });
1409    assert_new_snapshot(
1410        &multibuffer,
1411        &mut snapshot,
1412        &mut subscription,
1413        cx,
1414        indoc!(
1415            "
1416              ZERO
1417              one
1418            - two
1419            + TWO
1420              three
1421              six
1422            "
1423        ),
1424    );
1425
1426    // Expand the second diff hunk
1427    multibuffer.update(cx, |multibuffer, cx| {
1428        let start = multibuffer.read(cx).anchor_before(Point::new(4, 0));
1429        let end = multibuffer.read(cx).anchor_before(Point::new(5, 0));
1430        multibuffer.expand_diff_hunks(vec![start..end], cx)
1431    });
1432    assert_new_snapshot(
1433        &multibuffer,
1434        &mut snapshot,
1435        &mut subscription,
1436        cx,
1437        indoc!(
1438            "
1439              ZERO
1440              one
1441            - two
1442            + TWO
1443              three
1444            - four
1445            - five
1446              six
1447            "
1448        ),
1449    );
1450
1451    assert_chunks_in_ranges(&snapshot);
1452    assert_consistent_line_numbers(&snapshot);
1453    assert_position_translation(&snapshot);
1454    assert_line_indents(&snapshot);
1455
1456    // Edit the buffer before the first hunk
1457    buffer.update(cx, |buffer, cx| {
1458        buffer.edit_via_marked_text(
1459            indoc!(
1460                "
1461                ZERO
1462                one« hundred
1463                  thousand»
1464                TWO
1465                three
1466                six
1467                "
1468            ),
1469            None,
1470            cx,
1471        );
1472    });
1473    assert_new_snapshot(
1474        &multibuffer,
1475        &mut snapshot,
1476        &mut subscription,
1477        cx,
1478        indoc!(
1479            "
1480              ZERO
1481              one hundred
1482                thousand
1483            - two
1484            + TWO
1485              three
1486            - four
1487            - five
1488              six
1489            "
1490        ),
1491    );
1492
1493    assert_chunks_in_ranges(&snapshot);
1494    assert_consistent_line_numbers(&snapshot);
1495    assert_position_translation(&snapshot);
1496    assert_line_indents(&snapshot);
1497
1498    // Recalculate the diff, changing the first diff hunk.
1499    diff.update(cx, |diff, cx| {
1500        diff.recalculate_diff_sync(&buffer.read(cx).text_snapshot(), cx);
1501    });
1502    cx.run_until_parked();
1503    assert_new_snapshot(
1504        &multibuffer,
1505        &mut snapshot,
1506        &mut subscription,
1507        cx,
1508        indoc!(
1509            "
1510              ZERO
1511              one hundred
1512                thousand
1513              TWO
1514              three
1515            - four
1516            - five
1517              six
1518            "
1519        ),
1520    );
1521
1522    assert_eq!(
1523        snapshot
1524            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.len())
1525            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1526            .collect::<Vec<_>>(),
1527        &[0..4, 5..7]
1528    );
1529}
1530
1531#[gpui::test]
1532fn test_text_for_range_with_diff_transform_boundary_inside_multibyte_character(cx: &mut App) {
1533    let buffer = cx.new(|cx| Buffer::local("タx", cx));
1534    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1535    let mut snapshot = multibuffer.read(cx).snapshot(cx);
1536
1537    fn ascii_summary_with_byte_len(byte_len: usize) -> MBTextSummary {
1538        let text = "x".repeat(byte_len);
1539        MBTextSummary::from(TextSummary::from(text.as_str()))
1540    }
1541
1542    // FR-16 shown a diff transform boundary two bytes into the leading 'タ'.
1543    // Build that transform tree directly so this test stays focused on chunk iteration.
1544    let mut diff_transforms = SumTree::default();
1545    diff_transforms.push(
1546        DiffTransform::BufferContent {
1547            summary: ascii_summary_with_byte_len(2),
1548            inserted_hunk_info: None,
1549        },
1550        (),
1551    );
1552    diff_transforms.push(
1553        DiffTransform::BufferContent {
1554            summary: ascii_summary_with_byte_len("タx".len() - 2),
1555            inserted_hunk_info: None,
1556        },
1557        (),
1558    );
1559    snapshot.diff_transforms = diff_transforms;
1560
1561    let text = snapshot
1562        .text_for_range(MultiBufferOffset(0)..snapshot.len())
1563        .collect::<String>();
1564    assert_eq!(text, "タx");
1565}
1566
1567#[gpui::test]
1568async fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
1569    let text = indoc!(
1570        "
1571        one
1572        TWO
1573        THREE
1574        four
1575        FIVE
1576        six
1577        "
1578    );
1579    let base_text = indoc!(
1580        "
1581        one
1582        four
1583        five
1584        six
1585        "
1586    );
1587
1588    let buffer = cx.new(|cx| Buffer::local(text, cx));
1589    let diff = cx
1590        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
1591    cx.run_until_parked();
1592
1593    let multibuffer = cx.new(|cx| {
1594        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1595        multibuffer.add_diff(diff.clone(), cx);
1596        multibuffer
1597    });
1598
1599    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1600        (multibuffer.snapshot(cx), multibuffer.subscribe())
1601    });
1602
1603    multibuffer.update(cx, |multibuffer, cx| {
1604        multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
1605    });
1606
1607    assert_new_snapshot(
1608        &multibuffer,
1609        &mut snapshot,
1610        &mut subscription,
1611        cx,
1612        indoc!(
1613            "
1614              one
1615            + TWO
1616            + THREE
1617              four
1618            - five
1619            + FIVE
1620              six
1621            "
1622        ),
1623    );
1624
1625    // Regression test: expanding diff hunks that are already expanded should not change anything.
1626    multibuffer.update(cx, |multibuffer, cx| {
1627        multibuffer.expand_diff_hunks(
1628            vec![
1629                snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_before(Point::new(2, 0)),
1630            ],
1631            cx,
1632        );
1633    });
1634
1635    assert_new_snapshot(
1636        &multibuffer,
1637        &mut snapshot,
1638        &mut subscription,
1639        cx,
1640        indoc!(
1641            "
1642              one
1643            + TWO
1644            + THREE
1645              four
1646            - five
1647            + FIVE
1648              six
1649            "
1650        ),
1651    );
1652
1653    // Now collapse all diff hunks
1654    multibuffer.update(cx, |multibuffer, cx| {
1655        multibuffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
1656    });
1657
1658    assert_new_snapshot(
1659        &multibuffer,
1660        &mut snapshot,
1661        &mut subscription,
1662        cx,
1663        indoc!(
1664            "
1665            one
1666            TWO
1667            THREE
1668            four
1669            FIVE
1670            six
1671            "
1672        ),
1673    );
1674
1675    // Expand the hunks again, but this time provide two ranges that are both within the same hunk
1676    // Target the first hunk which is between "one" and "four"
1677    multibuffer.update(cx, |multibuffer, cx| {
1678        multibuffer.expand_diff_hunks(
1679            vec![
1680                snapshot.anchor_before(Point::new(4, 0))..snapshot.anchor_before(Point::new(4, 0)),
1681                snapshot.anchor_before(Point::new(4, 2))..snapshot.anchor_before(Point::new(4, 2)),
1682            ],
1683            cx,
1684        );
1685    });
1686    assert_new_snapshot(
1687        &multibuffer,
1688        &mut snapshot,
1689        &mut subscription,
1690        cx,
1691        indoc!(
1692            "
1693              one
1694              TWO
1695              THREE
1696              four
1697            - five
1698            + FIVE
1699              six
1700            "
1701        ),
1702    );
1703}
1704
1705#[gpui::test]
1706fn test_set_excerpts_for_buffer_ordering(cx: &mut TestAppContext) {
1707    let buf1 = cx.new(|cx| {
1708        Buffer::local(
1709            indoc! {
1710            "zero
1711            one
1712            two
1713            two.five
1714            three
1715            four
1716            five
1717            six
1718            seven
1719            eight
1720            nine
1721            ten
1722            eleven
1723            ",
1724            },
1725            cx,
1726        )
1727    });
1728    let path1: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
1729
1730    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1731    multibuffer.update(cx, |multibuffer, cx| {
1732        multibuffer.set_excerpts_for_path(
1733            path1.clone(),
1734            buf1.clone(),
1735            vec![
1736                Point::row_range(1..2),
1737                Point::row_range(6..7),
1738                Point::row_range(11..12),
1739            ],
1740            1,
1741            cx,
1742        );
1743    });
1744
1745    assert_excerpts_match(
1746        &multibuffer,
1747        cx,
1748        indoc! {
1749            "-----
1750            zero
1751            one
1752            two
1753            two.five
1754            -----
1755            four
1756            five
1757            six
1758            seven
1759            -----
1760            nine
1761            ten
1762            eleven
1763            "
1764        },
1765    );
1766
1767    buf1.update(cx, |buffer, cx| buffer.edit([(0..5, "")], None, cx));
1768
1769    multibuffer.update(cx, |multibuffer, cx| {
1770        multibuffer.set_excerpts_for_path(
1771            path1.clone(),
1772            buf1.clone(),
1773            vec![
1774                Point::row_range(0..3),
1775                Point::row_range(5..7),
1776                Point::row_range(10..11),
1777            ],
1778            1,
1779            cx,
1780        );
1781    });
1782
1783    assert_excerpts_match(
1784        &multibuffer,
1785        cx,
1786        indoc! {
1787            "-----
1788             one
1789             two
1790             two.five
1791             three
1792             four
1793             five
1794             six
1795             seven
1796             eight
1797             nine
1798             ten
1799             eleven
1800            "
1801        },
1802    );
1803}
1804
1805#[gpui::test]
1806fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
1807    let buf1 = cx.new(|cx| {
1808        Buffer::local(
1809            indoc! {
1810            "zero
1811            one
1812            two
1813            three
1814            four
1815            five
1816            six
1817            seven
1818            ",
1819            },
1820            cx,
1821        )
1822    });
1823    let path1: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
1824    let buf2 = cx.new(|cx| {
1825        Buffer::local(
1826            indoc! {
1827            "000
1828            111
1829            222
1830            333
1831            444
1832            555
1833            666
1834            777
1835            888
1836            999
1837            "
1838            },
1839            cx,
1840        )
1841    });
1842    let path2 = PathKey::with_sort_prefix(1, rel_path("root").into_arc());
1843
1844    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1845    multibuffer.update(cx, |multibuffer, cx| {
1846        multibuffer.set_excerpts_for_path(
1847            path1.clone(),
1848            buf1.clone(),
1849            vec![Point::row_range(0..1)],
1850            2,
1851            cx,
1852        );
1853    });
1854
1855    assert_excerpts_match(
1856        &multibuffer,
1857        cx,
1858        indoc! {
1859        "-----
1860        zero
1861        one
1862        two
1863        three
1864        "
1865        },
1866    );
1867
1868    multibuffer.update(cx, |multibuffer, cx| {
1869        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1870    });
1871
1872    assert_excerpts_match(&multibuffer, cx, "");
1873
1874    multibuffer.update(cx, |multibuffer, cx| {
1875        multibuffer.set_excerpts_for_path(
1876            path1.clone(),
1877            buf1.clone(),
1878            vec![Point::row_range(0..1), Point::row_range(7..8)],
1879            2,
1880            cx,
1881        );
1882    });
1883
1884    assert_excerpts_match(
1885        &multibuffer,
1886        cx,
1887        indoc! {"-----
1888                zero
1889                one
1890                two
1891                three
1892                -----
1893                five
1894                six
1895                seven
1896                "},
1897    );
1898
1899    multibuffer.update(cx, |multibuffer, cx| {
1900        multibuffer.set_excerpts_for_path(
1901            path1.clone(),
1902            buf1.clone(),
1903            vec![Point::row_range(0..1), Point::row_range(5..6)],
1904            2,
1905            cx,
1906        );
1907    });
1908
1909    assert_excerpts_match(
1910        &multibuffer,
1911        cx,
1912        indoc! {"-----
1913                    zero
1914                    one
1915                    two
1916                    three
1917                    four
1918                    five
1919                    six
1920                    seven
1921                    "},
1922    );
1923
1924    multibuffer.update(cx, |multibuffer, cx| {
1925        multibuffer.set_excerpts_for_path(
1926            path2.clone(),
1927            buf2.clone(),
1928            vec![Point::row_range(2..3)],
1929            2,
1930            cx,
1931        );
1932    });
1933
1934    assert_excerpts_match(
1935        &multibuffer,
1936        cx,
1937        indoc! {"-----
1938                zero
1939                one
1940                two
1941                three
1942                four
1943                five
1944                six
1945                seven
1946                -----
1947                000
1948                111
1949                222
1950                333
1951                444
1952                555
1953                "},
1954    );
1955
1956    multibuffer.update(cx, |multibuffer, cx| {
1957        multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1958    });
1959
1960    multibuffer.update(cx, |multibuffer, cx| {
1961        multibuffer.set_excerpts_for_path(
1962            path1.clone(),
1963            buf1.clone(),
1964            vec![Point::row_range(3..4)],
1965            2,
1966            cx,
1967        );
1968    });
1969
1970    assert_excerpts_match(
1971        &multibuffer,
1972        cx,
1973        indoc! {"-----
1974                one
1975                two
1976                three
1977                four
1978                five
1979                six
1980                -----
1981                000
1982                111
1983                222
1984                333
1985                444
1986                555
1987                "},
1988    );
1989
1990    multibuffer.update(cx, |multibuffer, cx| {
1991        multibuffer.set_excerpts_for_path(
1992            path1.clone(),
1993            buf1.clone(),
1994            vec![Point::row_range(3..4)],
1995            2,
1996            cx,
1997        );
1998    });
1999}
2000
2001#[gpui::test]
2002fn test_update_excerpt_ranges_for_path(cx: &mut TestAppContext) {
2003    let buffer = cx.new(|cx| {
2004        Buffer::local(
2005            indoc! {
2006            "row 0
2007            row 1
2008            row 2
2009            row 3
2010            row 4
2011            row 5
2012            row 6
2013            row 7
2014            row 8
2015            row 9
2016            row 10
2017            row 11
2018            row 12
2019            row 13
2020            row 14
2021            "},
2022            cx,
2023        )
2024    });
2025    let path = PathKey::with_sort_prefix(0, rel_path("test.rs").into_arc());
2026
2027    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2028    multibuffer.update(cx, |multibuffer, cx| {
2029        multibuffer.set_excerpts_for_path(
2030            path.clone(),
2031            buffer.clone(),
2032            vec![Point::row_range(2..4), Point::row_range(8..10)],
2033            0,
2034            cx,
2035        );
2036    });
2037    assert_excerpts_match(
2038        &multibuffer,
2039        cx,
2040        indoc! {"-----
2041            row 2
2042            row 3
2043            row 4
2044            -----
2045            row 8
2046            row 9
2047            row 10
2048            "},
2049    );
2050
2051    multibuffer.update(cx, |multibuffer, cx| {
2052        multibuffer.update_excerpts_for_path(
2053            path.clone(),
2054            buffer.clone(),
2055            vec![Point::row_range(12..13)],
2056            0,
2057            cx,
2058        );
2059    });
2060    assert_excerpts_match(
2061        &multibuffer,
2062        cx,
2063        indoc! {"-----
2064            row 12
2065            row 13
2066            "},
2067    );
2068
2069    multibuffer.update(cx, |multibuffer, cx| {
2070        multibuffer.set_excerpts_for_path(
2071            path.clone(),
2072            buffer.clone(),
2073            vec![Point::row_range(2..4)],
2074            0,
2075            cx,
2076        );
2077    });
2078    assert_excerpts_match(
2079        &multibuffer,
2080        cx,
2081        indoc! {"-----
2082            row 2
2083            row 3
2084            row 4
2085            "},
2086    );
2087    multibuffer.update(cx, |multibuffer, cx| {
2088        multibuffer.update_excerpts_for_path(
2089            path.clone(),
2090            buffer.clone(),
2091            vec![Point::row_range(3..5)],
2092            0,
2093            cx,
2094        );
2095    });
2096    assert_excerpts_match(
2097        &multibuffer,
2098        cx,
2099        indoc! {"-----
2100            row 2
2101            row 3
2102            row 4
2103            row 5
2104            "},
2105    );
2106
2107    multibuffer.update(cx, |multibuffer, cx| {
2108        multibuffer.set_excerpts_for_path(
2109            path.clone(),
2110            buffer.clone(),
2111            vec![
2112                Point::row_range(0..1),
2113                Point::row_range(6..8),
2114                Point::row_range(12..13),
2115            ],
2116            0,
2117            cx,
2118        );
2119    });
2120    assert_excerpts_match(
2121        &multibuffer,
2122        cx,
2123        indoc! {"-----
2124            row 0
2125            row 1
2126            -----
2127            row 6
2128            row 7
2129            row 8
2130            -----
2131            row 12
2132            row 13
2133            "},
2134    );
2135    multibuffer.update(cx, |multibuffer, cx| {
2136        multibuffer.update_excerpts_for_path(
2137            path.clone(),
2138            buffer.clone(),
2139            vec![Point::row_range(7..9)],
2140            0,
2141            cx,
2142        );
2143    });
2144    assert_excerpts_match(
2145        &multibuffer,
2146        cx,
2147        indoc! {"-----
2148            row 6
2149            row 7
2150            row 8
2151            row 9
2152            "},
2153    );
2154
2155    multibuffer.update(cx, |multibuffer, cx| {
2156        multibuffer.set_excerpts_for_path(
2157            path.clone(),
2158            buffer.clone(),
2159            vec![Point::row_range(2..3), Point::row_range(6..7)],
2160            0,
2161            cx,
2162        );
2163    });
2164    assert_excerpts_match(
2165        &multibuffer,
2166        cx,
2167        indoc! {"-----
2168            row 2
2169            row 3
2170            -----
2171            row 6
2172            row 7
2173            "},
2174    );
2175    multibuffer.update(cx, |multibuffer, cx| {
2176        multibuffer.update_excerpts_for_path(
2177            path.clone(),
2178            buffer.clone(),
2179            vec![Point::row_range(3..6)],
2180            0,
2181            cx,
2182        );
2183    });
2184    assert_excerpts_match(
2185        &multibuffer,
2186        cx,
2187        indoc! {"-----
2188            row 2
2189            row 3
2190            row 4
2191            row 5
2192            row 6
2193            row 7
2194            "},
2195    );
2196}
2197
2198#[gpui::test]
2199fn test_set_excerpts_for_buffer_rename(cx: &mut TestAppContext) {
2200    let buf1 = cx.new(|cx| {
2201        Buffer::local(
2202            indoc! {
2203            "zero
2204            one
2205            two
2206            three
2207            four
2208            five
2209            six
2210            seven
2211            ",
2212            },
2213            cx,
2214        )
2215    });
2216    let path: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
2217    let buf2 = cx.new(|cx| {
2218        Buffer::local(
2219            indoc! {
2220            "000
2221            111
2222            222
2223            333
2224            "
2225            },
2226            cx,
2227        )
2228    });
2229
2230    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2231    multibuffer.update(cx, |multibuffer, cx| {
2232        multibuffer.set_excerpts_for_path(
2233            path.clone(),
2234            buf1.clone(),
2235            vec![Point::row_range(1..1), Point::row_range(4..5)],
2236            1,
2237            cx,
2238        );
2239    });
2240
2241    assert_excerpts_match(
2242        &multibuffer,
2243        cx,
2244        indoc! {
2245        "-----
2246        zero
2247        one
2248        two
2249        three
2250        four
2251        five
2252        six
2253        "
2254        },
2255    );
2256
2257    multibuffer.update(cx, |multibuffer, cx| {
2258        multibuffer.set_excerpts_for_path(
2259            path.clone(),
2260            buf2.clone(),
2261            vec![Point::row_range(0..1)],
2262            2,
2263            cx,
2264        );
2265    });
2266
2267    assert_excerpts_match(
2268        &multibuffer,
2269        cx,
2270        indoc! {"-----
2271                000
2272                111
2273                222
2274                333
2275                "},
2276    );
2277}
2278
2279#[gpui::test]
2280fn test_set_excerpts_for_path_replaces_previous_buffer(cx: &mut TestAppContext) {
2281    let buffer_a = cx.new(|cx| {
2282        Buffer::local(
2283            indoc! {
2284            "alpha
2285            beta
2286            gamma
2287            delta
2288            epsilon
2289            ",
2290            },
2291            cx,
2292        )
2293    });
2294    let buffer_b = cx.new(|cx| {
2295        Buffer::local(
2296            indoc! {
2297            "one
2298            two
2299            three
2300            four
2301            ",
2302            },
2303            cx,
2304        )
2305    });
2306    let path: PathKey = PathKey::with_sort_prefix(0, rel_path("shared/path").into_arc());
2307
2308    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2309    let removed_buffer_ids: Arc<RwLock<Vec<BufferId>>> = Default::default();
2310    multibuffer.update(cx, |_, cx| {
2311        let removed_buffer_ids = removed_buffer_ids.clone();
2312        cx.subscribe(&multibuffer, move |_, _, event, _| {
2313            if let Event::BuffersRemoved {
2314                removed_buffer_ids: ids,
2315            } = event
2316            {
2317                removed_buffer_ids.write().extend(ids.iter().copied());
2318            }
2319        })
2320        .detach();
2321    });
2322
2323    let ranges_a = vec![Point::row_range(0..1), Point::row_range(3..4)];
2324    multibuffer.update(cx, |multibuffer, cx| {
2325        multibuffer.set_excerpts_for_path(path.clone(), buffer_a.clone(), ranges_a.clone(), 0, cx);
2326    });
2327    let (anchor_a1, anchor_a2) = multibuffer.read_with(cx, |multibuffer, cx| {
2328        let snapshot = multibuffer.snapshot(cx);
2329        let buffer_snapshot = buffer_a.read(cx).snapshot();
2330        let mut anchors = ranges_a.into_iter().filter_map(|range| {
2331            let text_range = buffer_snapshot.anchor_range_inside(range);
2332            let start = snapshot.anchor_in_buffer(text_range.start)?;
2333            let end = snapshot.anchor_in_buffer(text_range.end)?;
2334            Some(start..end)
2335        });
2336        (
2337            anchors.next().expect("should have first anchor"),
2338            anchors.next().expect("should have second anchor"),
2339        )
2340    });
2341
2342    assert_excerpts_match(
2343        &multibuffer,
2344        cx,
2345        indoc! {
2346        "-----
2347        alpha
2348        beta
2349        -----
2350        delta
2351        epsilon
2352        "
2353        },
2354    );
2355
2356    let buffer_a_id = buffer_a.read_with(cx, |buffer, _| buffer.remote_id());
2357    multibuffer.read_with(cx, |multibuffer, cx| {
2358        let snapshot = multibuffer.snapshot(cx);
2359        assert!(
2360            snapshot
2361                .excerpts()
2362                .any(|excerpt| excerpt.context.start.buffer_id == buffer_a_id),
2363        );
2364    });
2365
2366    let ranges_b = vec![Point::row_range(1..2)];
2367    multibuffer.update(cx, |multibuffer, cx| {
2368        multibuffer.set_excerpts_for_path(path.clone(), buffer_b.clone(), ranges_b.clone(), 1, cx);
2369    });
2370    let anchor_b = multibuffer.read_with(cx, |multibuffer, cx| {
2371        let snapshot = multibuffer.snapshot(cx);
2372        let buffer_snapshot = buffer_b.read(cx).snapshot();
2373        ranges_b
2374            .into_iter()
2375            .filter_map(|range| {
2376                let text_range = buffer_snapshot.anchor_range_inside(range);
2377                let start = snapshot.anchor_in_buffer(text_range.start)?;
2378                let end = snapshot.anchor_in_buffer(text_range.end)?;
2379                Some(start..end)
2380            })
2381            .next()
2382            .expect("should have an anchor")
2383    });
2384
2385    let buffer_b_id = buffer_b.read_with(cx, |buffer, _| buffer.remote_id());
2386    multibuffer.read_with(cx, |multibuffer, cx| {
2387        let snapshot = multibuffer.snapshot(cx);
2388        assert!(
2389            !snapshot
2390                .excerpts()
2391                .any(|excerpt| excerpt.context.start.buffer_id == buffer_a_id),
2392        );
2393        assert!(
2394            snapshot
2395                .excerpts()
2396                .any(|excerpt| excerpt.context.start.buffer_id == buffer_b_id),
2397        );
2398        assert!(
2399            multibuffer.buffer(buffer_a_id).is_none(),
2400            "old buffer should be fully removed from the multibuffer"
2401        );
2402        assert!(
2403            multibuffer.buffer(buffer_b_id).is_some(),
2404            "new buffer should be present in the multibuffer"
2405        );
2406    });
2407    assert!(
2408        removed_buffer_ids.read().contains(&buffer_a_id),
2409        "BuffersRemoved event should have been emitted for the old buffer"
2410    );
2411
2412    assert_excerpts_match(
2413        &multibuffer,
2414        cx,
2415        indoc! {
2416        "-----
2417        one
2418        two
2419        three
2420        four
2421        "
2422        },
2423    );
2424
2425    multibuffer.read_with(cx, |multibuffer, cx| {
2426        let snapshot = multibuffer.snapshot(cx);
2427        anchor_a1.start.cmp(&anchor_b.start, &snapshot);
2428        anchor_a1.end.cmp(&anchor_b.end, &snapshot);
2429        anchor_a1.start.cmp(&anchor_a2.start, &snapshot);
2430        anchor_a1.end.cmp(&anchor_a2.end, &snapshot);
2431    });
2432}
2433
2434#[gpui::test]
2435fn test_stale_anchor_after_buffer_removal_and_path_reuse(cx: &mut TestAppContext) {
2436    let buffer_a = cx.new(|cx| Buffer::local("aaa\nbbb\nccc\n", cx));
2437    let buffer_b = cx.new(|cx| Buffer::local("xxx\nyyy\nzzz\n", cx));
2438    let buffer_other = cx.new(|cx| Buffer::local("111\n222\n333\n", cx));
2439    let path = PathKey::with_sort_prefix(0, rel_path("the/path").into_arc());
2440    let other_path = PathKey::with_sort_prefix(1, rel_path("other/path").into_arc());
2441
2442    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2443
2444    multibuffer.update(cx, |multibuffer, cx| {
2445        multibuffer.set_excerpts_for_path(
2446            path.clone(),
2447            buffer_a.clone(),
2448            [Point::new(0, 0)..Point::new(2, 3)],
2449            0,
2450            cx,
2451        );
2452        multibuffer.set_excerpts_for_path(
2453            other_path.clone(),
2454            buffer_other.clone(),
2455            [Point::new(0, 0)..Point::new(2, 3)],
2456            0,
2457            cx,
2458        );
2459    });
2460
2461    buffer_a.update(cx, |buffer, cx| {
2462        buffer.edit(
2463            [(Point::new(1, 0)..Point::new(1, 0), "INSERTED ")],
2464            None,
2465            cx,
2466        );
2467    });
2468
2469    let stale_anchor = multibuffer.read_with(cx, |multibuffer, cx| {
2470        let snapshot = multibuffer.snapshot(cx);
2471        snapshot.anchor_before(Point::new(1, 5))
2472    });
2473
2474    multibuffer.update(cx, |multibuffer, cx| {
2475        multibuffer.remove_excerpts(path.clone(), cx);
2476    });
2477
2478    multibuffer.read_with(cx, |multibuffer, cx| {
2479        let snapshot = multibuffer.snapshot(cx);
2480        let offset = stale_anchor.to_offset(&snapshot);
2481        assert!(
2482            offset.0 <= snapshot.len().0,
2483            "stale anchor resolved to offset {offset:?} but multibuffer len is {:?}",
2484            snapshot.len()
2485        );
2486    });
2487
2488    multibuffer.update(cx, |multibuffer, cx| {
2489        multibuffer.set_excerpts_for_path(
2490            path.clone(),
2491            buffer_b.clone(),
2492            [Point::new(0, 0)..Point::new(2, 3)],
2493            0,
2494            cx,
2495        );
2496    });
2497
2498    multibuffer.read_with(cx, |multibuffer, cx| {
2499        let snapshot = multibuffer.snapshot(cx);
2500        let offset = stale_anchor.to_offset(&snapshot);
2501        assert!(
2502            offset.0 <= snapshot.len().0,
2503            "stale anchor resolved to offset {offset:?} but multibuffer len is {:?}",
2504            snapshot.len()
2505        );
2506    });
2507}
2508
2509#[gpui::test]
2510async fn test_map_excerpt_ranges(cx: &mut TestAppContext) {
2511    let base_text = indoc!(
2512        "
2513        {
2514          (aaa)
2515          (bbb)
2516          (ccc)
2517        }
2518        xxx
2519        yyy
2520        zzz
2521        [
2522          (ddd)
2523          (EEE)
2524        ]
2525        "
2526    );
2527    let text = indoc!(
2528        "
2529        {
2530          (aaa)
2531          (CCC)
2532        }
2533        xxx
2534        yyy
2535        zzz
2536        [
2537          (ddd)
2538          (EEE)
2539        ]
2540        "
2541    );
2542
2543    let buffer = cx.new(|cx| Buffer::local(text, cx));
2544    let diff = cx
2545        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
2546    cx.run_until_parked();
2547
2548    let multibuffer = cx.new(|cx| {
2549        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
2550        multibuffer.set_excerpts_for_path(
2551            PathKey::sorted(0),
2552            buffer.clone(),
2553            [
2554                Point::new(0, 0)..Point::new(3, 1),
2555                Point::new(7, 0)..Point::new(10, 1),
2556            ],
2557            0,
2558            cx,
2559        );
2560        multibuffer.add_diff(diff.clone(), cx);
2561        multibuffer
2562    });
2563
2564    multibuffer.update(cx, |multibuffer, cx| {
2565        multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
2566    });
2567    cx.run_until_parked();
2568
2569    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2570
2571    let actual_diff = format_diff(
2572        &snapshot.text(),
2573        &snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
2574        &Default::default(),
2575        None,
2576    );
2577    pretty_assertions::assert_eq!(
2578        actual_diff,
2579        indoc!(
2580            "
2581              {
2582                (aaa)
2583            -   (bbb)
2584            -   (ccc)
2585            +   (CCC)
2586              } [\u{2193}]
2587              [ [\u{2191}]
2588                (ddd)
2589                (EEE)
2590              ] [\u{2193}]"
2591        )
2592    );
2593
2594    assert_eq!(
2595        snapshot.map_excerpt_ranges(
2596            snapshot.point_to_offset(Point::new(1, 3))..snapshot.point_to_offset(Point::new(1, 3)),
2597            |buffer, excerpt_range, input_range| {
2598                assert_eq!(
2599                    buffer.offset_to_point(input_range.start.0)
2600                        ..buffer.offset_to_point(input_range.end.0),
2601                    Point::new(1, 3)..Point::new(1, 3),
2602                );
2603                assert_eq!(
2604                    buffer.offset_to_point(excerpt_range.context.start.0)
2605                        ..buffer.offset_to_point(excerpt_range.context.end.0),
2606                    Point::new(0, 0)..Point::new(3, 1),
2607                );
2608                vec![
2609                    (input_range.start..BufferOffset(input_range.start.0 + 3), ()),
2610                    (excerpt_range.context, ()),
2611                    (
2612                        BufferOffset(text::ToOffset::to_offset(&Point::new(2, 2), buffer))
2613                            ..BufferOffset(text::ToOffset::to_offset(&Point::new(2, 7), buffer)),
2614                        (),
2615                    ),
2616                    (
2617                        BufferOffset(text::ToOffset::to_offset(&Point::new(0, 0), buffer))
2618                            ..BufferOffset(text::ToOffset::to_offset(&Point::new(2, 0), buffer)),
2619                        (),
2620                    ),
2621                ]
2622            },
2623        ),
2624        Some(vec![
2625            (
2626                snapshot.point_to_offset(Point::new(1, 3))
2627                    ..snapshot.point_to_offset(Point::new(1, 6)),
2628                (),
2629            ),
2630            (
2631                snapshot.point_to_offset(Point::zero())..snapshot.point_to_offset(Point::new(5, 1)),
2632                ()
2633            ),
2634            (
2635                snapshot.point_to_offset(Point::new(4, 2))
2636                    ..snapshot.point_to_offset(Point::new(4, 7)),
2637                (),
2638            ),
2639            (
2640                snapshot.point_to_offset(Point::zero())..snapshot.point_to_offset(Point::new(4, 0)),
2641                ()
2642            ),
2643        ]),
2644    );
2645
2646    assert_eq!(
2647        snapshot.map_excerpt_ranges(
2648            snapshot.point_to_offset(Point::new(5, 0))..snapshot.point_to_offset(Point::new(7, 0)),
2649            |_, _, range| vec![(range, ())],
2650        ),
2651        None,
2652    );
2653
2654    assert_eq!(
2655        snapshot.map_excerpt_ranges(
2656            snapshot.point_to_offset(Point::new(7, 3))..snapshot.point_to_offset(Point::new(7, 6)),
2657            |buffer, excerpt_range, input_range| {
2658                assert_eq!(
2659                    buffer.offset_to_point(input_range.start.0)
2660                        ..buffer.offset_to_point(input_range.end.0),
2661                    Point::new(8, 3)..Point::new(8, 6),
2662                );
2663                assert_eq!(
2664                    buffer.offset_to_point(excerpt_range.context.start.0)
2665                        ..buffer.offset_to_point(excerpt_range.context.end.0),
2666                    Point::new(7, 0)..Point::new(10, 1),
2667                );
2668                vec![(input_range, ())]
2669            },
2670        ),
2671        Some(vec![(
2672            snapshot.point_to_offset(Point::new(7, 3))..snapshot.point_to_offset(Point::new(7, 6)),
2673            (),
2674        )]),
2675    );
2676}
2677
2678#[gpui::test]
2679async fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
2680    let base_text_1 = indoc!(
2681        "
2682        one
2683        two
2684            three
2685        four
2686        five
2687        six
2688        "
2689    );
2690    let text_1 = indoc!(
2691        "
2692        ZERO
2693        one
2694        TWO
2695            three
2696        six
2697        "
2698    );
2699    let base_text_2 = indoc!(
2700        "
2701        seven
2702          eight
2703        nine
2704        ten
2705        eleven
2706        twelve
2707        "
2708    );
2709    let text_2 = indoc!(
2710        "
2711          eight
2712        nine
2713        eleven
2714        THIRTEEN
2715        FOURTEEN
2716        "
2717    );
2718
2719    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
2720    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
2721    let diff_1 = cx.new(|cx| {
2722        BufferDiff::new_with_base_text(base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
2723    });
2724    let diff_2 = cx.new(|cx| {
2725        BufferDiff::new_with_base_text(base_text_2, &buffer_2.read(cx).text_snapshot(), cx)
2726    });
2727    cx.run_until_parked();
2728
2729    let multibuffer = cx.new(|cx| {
2730        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
2731        multibuffer.set_excerpts_for_path(
2732            PathKey::sorted(0),
2733            buffer_1.clone(),
2734            [Point::zero()..buffer_1.read(cx).max_point()],
2735            0,
2736            cx,
2737        );
2738        multibuffer.set_excerpts_for_path(
2739            PathKey::sorted(1),
2740            buffer_2.clone(),
2741            [Point::zero()..buffer_2.read(cx).max_point()],
2742            0,
2743            cx,
2744        );
2745        multibuffer.add_diff(diff_1.clone(), cx);
2746        multibuffer.add_diff(diff_2.clone(), cx);
2747        multibuffer
2748    });
2749
2750    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
2751        (multibuffer.snapshot(cx), multibuffer.subscribe())
2752    });
2753    assert_eq!(
2754        snapshot.text(),
2755        indoc!(
2756            "
2757            ZERO
2758            one
2759            TWO
2760                three
2761            six
2762
2763              eight
2764            nine
2765            eleven
2766            THIRTEEN
2767            FOURTEEN
2768            "
2769        ),
2770    );
2771
2772    multibuffer.update(cx, |multibuffer, cx| {
2773        multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
2774    });
2775
2776    assert_new_snapshot(
2777        &multibuffer,
2778        &mut snapshot,
2779        &mut subscription,
2780        cx,
2781        indoc!(
2782            "
2783            + ZERO
2784              one
2785            - two
2786            + TWO
2787                  three
2788            - four
2789            - five
2790              six
2791
2792            - seven
2793                eight
2794              nine
2795            - ten
2796              eleven
2797            - twelve
2798            + THIRTEEN
2799            + FOURTEEN
2800            "
2801        ),
2802    );
2803
2804    let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
2805    let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
2806    let base_id_1 = diff_1.read_with(cx, |diff, cx| diff.base_text(cx).remote_id());
2807    let base_id_2 = diff_2.read_with(cx, |diff, cx| diff.base_text(cx).remote_id());
2808
2809    let buffer_lines = (0..=snapshot.max_row().0)
2810        .map(|row| {
2811            let (buffer, range) = snapshot.buffer_line_for_row(MultiBufferRow(row))?;
2812            Some((
2813                buffer.remote_id(),
2814                buffer.text_for_range(range).collect::<String>(),
2815            ))
2816        })
2817        .collect::<Vec<_>>();
2818    pretty_assertions::assert_eq!(
2819        buffer_lines,
2820        [
2821            Some((id_1, "ZERO".into())),
2822            Some((id_1, "one".into())),
2823            Some((base_id_1, "two".into())),
2824            Some((id_1, "TWO".into())),
2825            Some((id_1, "    three".into())),
2826            Some((base_id_1, "four".into())),
2827            Some((base_id_1, "five".into())),
2828            Some((id_1, "six".into())),
2829            Some((id_1, "".into())),
2830            Some((base_id_2, "seven".into())),
2831            Some((id_2, "  eight".into())),
2832            Some((id_2, "nine".into())),
2833            Some((base_id_2, "ten".into())),
2834            Some((id_2, "eleven".into())),
2835            Some((base_id_2, "twelve".into())),
2836            Some((id_2, "THIRTEEN".into())),
2837            Some((id_2, "FOURTEEN".into())),
2838            Some((id_2, "".into())),
2839        ]
2840    );
2841
2842    let buffer_ids_by_range = [
2843        (Point::new(0, 0)..Point::new(0, 0), &[id_1] as &[_]),
2844        (Point::new(0, 0)..Point::new(2, 0), &[id_1]),
2845        (Point::new(2, 0)..Point::new(2, 0), &[id_1]),
2846        (Point::new(3, 0)..Point::new(3, 0), &[id_1]),
2847        (Point::new(8, 0)..Point::new(9, 0), &[id_1]),
2848        (Point::new(8, 0)..Point::new(10, 0), &[id_1, id_2]),
2849        (Point::new(9, 0)..Point::new(9, 0), &[id_2]),
2850    ];
2851    for (range, buffer_ids) in buffer_ids_by_range {
2852        assert_eq!(
2853            snapshot
2854                .buffer_ids_for_range(range.clone())
2855                .collect::<Vec<_>>(),
2856            buffer_ids,
2857            "buffer_ids_for_range({range:?}"
2858        );
2859    }
2860
2861    assert_position_translation(&snapshot);
2862    assert_line_indents(&snapshot);
2863
2864    assert_eq!(
2865        snapshot
2866            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.len())
2867            .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
2868            .collect::<Vec<_>>(),
2869        &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
2870    );
2871
2872    buffer_2.update(cx, |buffer, cx| {
2873        buffer.edit_via_marked_text(
2874            indoc!(
2875                "
2876                  eight
2877                «»eleven
2878                THIRTEEN
2879                FOURTEEN
2880                "
2881            ),
2882            None,
2883            cx,
2884        );
2885    });
2886
2887    assert_new_snapshot(
2888        &multibuffer,
2889        &mut snapshot,
2890        &mut subscription,
2891        cx,
2892        indoc!(
2893            "
2894            + ZERO
2895              one
2896            - two
2897            + TWO
2898                  three
2899            - four
2900            - five
2901              six
2902
2903            - seven
2904                eight
2905              eleven
2906            - twelve
2907            + THIRTEEN
2908            + FOURTEEN
2909            "
2910        ),
2911    );
2912
2913    assert_line_indents(&snapshot);
2914}
2915
2916/// A naive implementation of a multi-buffer that does not maintain
2917/// any derived state, used for comparison in a randomized test.
2918#[derive(Default)]
2919struct ReferenceMultibuffer {
2920    excerpts: Vec<ReferenceExcerpt>,
2921    diffs: HashMap<BufferId, Entity<BufferDiff>>,
2922    inverted_diffs: HashMap<BufferId, (Entity<BufferDiff>, Entity<language::Buffer>)>,
2923    expanded_diff_hunks_by_buffer: HashMap<BufferId, Vec<text::Anchor>>,
2924}
2925
2926#[derive(Clone, Debug)]
2927struct ReferenceExcerpt {
2928    path_key: PathKey,
2929    path_key_index: PathKeyIndex,
2930    buffer: Entity<Buffer>,
2931    range: Range<text::Anchor>,
2932}
2933
2934#[derive(Clone, Debug)]
2935struct ReferenceRegion {
2936    buffer_id: Option<BufferId>,
2937    range: Range<usize>,
2938    buffer_range: Range<Point>,
2939    // if this is a deleted hunk, the main buffer anchor to which the deleted content is attached
2940    deleted_hunk_anchor: Option<text::Anchor>,
2941    status: Option<DiffHunkStatus>,
2942    excerpt: Option<ReferenceExcerpt>,
2943}
2944
2945impl ReferenceMultibuffer {
2946    fn expand_excerpts(
2947        &mut self,
2948        excerpts: &HashSet<ExcerptRange<text::Anchor>>,
2949        line_count: u32,
2950        cx: &mut App,
2951    ) {
2952        use text::AnchorRangeExt as _;
2953
2954        if line_count == 0 || excerpts.is_empty() {
2955            return;
2956        }
2957
2958        let mut excerpts_by_buffer: HashMap<BufferId, Vec<ExcerptRange<text::Anchor>>> =
2959            HashMap::default();
2960        for excerpt in excerpts {
2961            excerpts_by_buffer
2962                .entry(excerpt.context.start.buffer_id)
2963                .or_default()
2964                .push(excerpt.clone())
2965        }
2966
2967        for (buffer_id, excerpts_to_expand) in excerpts_by_buffer {
2968            let mut buffer = None;
2969            let mut buffer_snapshot = None;
2970            let mut path = None;
2971            let mut path_key_index = None;
2972            let mut new_ranges =
2973                self.excerpts
2974                    .iter()
2975                    .filter(|excerpt| excerpt.range.start.buffer_id == buffer_id)
2976                    .map(|excerpt| {
2977                        let snapshot = excerpt.buffer.read(cx).snapshot();
2978                        let mut range = excerpt.range.to_point(&snapshot);
2979                        if excerpts_to_expand.iter().any(|info| {
2980                            excerpt.range.contains_anchor(info.context.start, &snapshot)
2981                        }) {
2982                            range.start = Point::new(range.start.row.saturating_sub(line_count), 0);
2983                            range.end = snapshot
2984                                .clip_point(Point::new(range.end.row + line_count, 0), Bias::Left);
2985                            range.end.column = snapshot.line_len(range.end.row);
2986                        }
2987                        buffer = Some(excerpt.buffer.clone());
2988                        buffer_snapshot = Some(snapshot);
2989                        path = Some(excerpt.path_key.clone());
2990                        path_key_index = Some(excerpt.path_key_index);
2991                        ExcerptRange::new(range)
2992                    })
2993                    .collect::<Vec<_>>();
2994
2995            new_ranges.sort_by_key(|nr| nr.context.start);
2996
2997            self.set_excerpts(
2998                path.unwrap(),
2999                path_key_index.unwrap(),
3000                buffer.unwrap(),
3001                &buffer_snapshot.unwrap(),
3002                new_ranges,
3003                cx,
3004            );
3005        }
3006    }
3007
3008    fn set_excerpts(
3009        &mut self,
3010        path_key: PathKey,
3011        path_key_index: PathKeyIndex,
3012        buffer: Entity<Buffer>,
3013        buffer_snapshot: &BufferSnapshot,
3014        ranges: Vec<ExcerptRange<Point>>,
3015        cx: &mut App,
3016    ) {
3017        self.excerpts.retain(|excerpt| {
3018            excerpt.path_key != path_key && excerpt.buffer.entity_id() != buffer.entity_id()
3019        });
3020
3021        let ranges = MultiBuffer::merge_excerpt_ranges(&ranges);
3022
3023        let (Ok(ix) | Err(ix)) = self
3024            .excerpts
3025            .binary_search_by(|probe| probe.path_key.cmp(&path_key));
3026        self.excerpts.splice(
3027            ix..ix,
3028            ranges.into_iter().map(|range| ReferenceExcerpt {
3029                path_key: path_key.clone(),
3030                path_key_index,
3031                buffer: buffer.clone(),
3032                range: buffer_snapshot.anchor_before(range.context.start)
3033                    ..buffer_snapshot.anchor_after(range.context.end),
3034            }),
3035        );
3036        self.update_expanded_diff_hunks_for_buffer(buffer_snapshot.remote_id(), cx);
3037    }
3038
3039    fn expand_diff_hunks(&mut self, path_key: PathKey, range: Range<text::Anchor>, cx: &App) {
3040        let excerpt = self
3041            .excerpts
3042            .iter_mut()
3043            .find(|e| {
3044                e.path_key == path_key
3045                    && e.range
3046                        .start
3047                        .cmp(&range.start, &e.buffer.read(cx).snapshot())
3048                        .is_le()
3049                    && e.range
3050                        .end
3051                        .cmp(&range.end, &e.buffer.read(cx).snapshot())
3052                        .is_ge()
3053            })
3054            .unwrap();
3055        let buffer = excerpt.buffer.read(cx).snapshot();
3056        let buffer_id = buffer.remote_id();
3057
3058        // Skip inverted excerpts - hunks are always expanded
3059        if self.inverted_diffs.contains_key(&buffer_id) {
3060            return;
3061        }
3062
3063        let Some(diff) = self.diffs.get(&buffer_id) else {
3064            return;
3065        };
3066        let excerpt_range = excerpt.range.to_point(&buffer);
3067        let expanded_diff_hunks = self
3068            .expanded_diff_hunks_by_buffer
3069            .entry(buffer_id)
3070            .or_default();
3071        for hunk in diff
3072            .read(cx)
3073            .snapshot(cx)
3074            .hunks_intersecting_range(range, &buffer)
3075        {
3076            let hunk_range = hunk.buffer_range.to_point(&buffer);
3077            if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end {
3078                continue;
3079            }
3080            if let Err(ix) = expanded_diff_hunks
3081                .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
3082            {
3083                log::info!(
3084                    "expanding diff hunk {:?}. excerpt range: {:?}, buffer {:?}",
3085                    hunk_range,
3086                    excerpt_range,
3087                    buffer.remote_id()
3088                );
3089                expanded_diff_hunks.insert(ix, hunk.buffer_range.start);
3090            } else {
3091                log::trace!("hunk {hunk_range:?} already expanded in excerpt");
3092            }
3093        }
3094    }
3095
3096    fn expected_content(
3097        &self,
3098        cx: &App,
3099    ) -> (
3100        String,
3101        Vec<RowInfo>,
3102        HashSet<MultiBufferRow>,
3103        Vec<ReferenceRegion>,
3104    ) {
3105        use util::maybe;
3106
3107        let mut text = String::new();
3108        let mut regions = Vec::<ReferenceRegion>::new();
3109        let mut excerpt_boundary_rows = HashSet::default();
3110        for excerpt in &self.excerpts {
3111            excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
3112            let buffer = excerpt.buffer.read(cx);
3113            let buffer_id = buffer.remote_id();
3114            let buffer_range = excerpt.range.to_offset(buffer);
3115
3116            if let Some((diff, main_buffer)) = self.inverted_diffs.get(&buffer_id) {
3117                let diff_snapshot = diff.read(cx).snapshot(cx);
3118                let main_buffer_snapshot = main_buffer.read(cx).snapshot();
3119
3120                let mut offset = buffer_range.start;
3121                for hunk in diff_snapshot.hunks_intersecting_base_text_range(
3122                    buffer_range.clone(),
3123                    &main_buffer_snapshot.text,
3124                ) {
3125                    let mut hunk_base_range = hunk.diff_base_byte_range.clone();
3126
3127                    hunk_base_range.end = hunk_base_range.end.min(buffer_range.end);
3128                    if hunk_base_range.start > buffer_range.end
3129                        || hunk_base_range.start < buffer_range.start
3130                    {
3131                        continue;
3132                    }
3133
3134                    // Add the text before the hunk
3135                    if hunk_base_range.start >= offset {
3136                        let len = text.len();
3137                        text.extend(buffer.text_for_range(offset..hunk_base_range.start));
3138                        if text.len() > len {
3139                            regions.push(ReferenceRegion {
3140                                buffer_id: Some(buffer_id),
3141                                range: len..text.len(),
3142                                buffer_range: (offset..hunk_base_range.start).to_point(&buffer),
3143                                status: None,
3144                                excerpt: Some(excerpt.clone()),
3145                                deleted_hunk_anchor: None,
3146                            });
3147                        }
3148                    }
3149
3150                    // Add the "deleted" region (base text that's not in main)
3151                    if !hunk_base_range.is_empty() {
3152                        let len = text.len();
3153                        text.extend(buffer.text_for_range(hunk_base_range.clone()));
3154                        regions.push(ReferenceRegion {
3155                            buffer_id: Some(buffer_id),
3156                            range: len..text.len(),
3157                            buffer_range: hunk_base_range.to_point(&buffer),
3158                            status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
3159                            excerpt: Some(excerpt.clone()),
3160                            deleted_hunk_anchor: None,
3161                        });
3162                    }
3163
3164                    offset = hunk_base_range.end;
3165                }
3166
3167                // Add remaining buffer text
3168                let len = text.len();
3169                text.extend(buffer.text_for_range(offset..buffer_range.end));
3170                text.push('\n');
3171                regions.push(ReferenceRegion {
3172                    buffer_id: Some(buffer_id),
3173                    range: len..text.len(),
3174                    buffer_range: (offset..buffer_range.end).to_point(&buffer),
3175                    status: None,
3176                    excerpt: Some(excerpt.clone()),
3177                    deleted_hunk_anchor: None,
3178                });
3179            } else {
3180                let diff = self.diffs.get(&buffer_id).unwrap().read(cx).snapshot(cx);
3181                let base_buffer = diff.base_text();
3182
3183                let mut offset = buffer_range.start;
3184                let hunks = diff
3185                    .hunks_intersecting_range(excerpt.range.clone(), buffer)
3186                    .peekable();
3187
3188                for hunk in hunks {
3189                    // Ignore hunks that are outside the excerpt range.
3190                    let mut hunk_range = hunk.buffer_range.to_offset(buffer);
3191
3192                    hunk_range.end = hunk_range.end.min(buffer_range.end);
3193                    if hunk_range.start > buffer_range.end || hunk_range.start < buffer_range.start
3194                    {
3195                        log::trace!("skipping hunk outside excerpt range");
3196                        continue;
3197                    }
3198
3199                    if !self
3200                        .expanded_diff_hunks_by_buffer
3201                        .get(&buffer_id)
3202                        .cloned()
3203                        .into_iter()
3204                        .flatten()
3205                        .any(|expanded_anchor| {
3206                            expanded_anchor
3207                                .cmp(&hunk.buffer_range.start, buffer)
3208                                .is_eq()
3209                        })
3210                    {
3211                        log::trace!("skipping a hunk that's not marked as expanded");
3212                        continue;
3213                    }
3214
3215                    if !hunk.buffer_range.start.is_valid(buffer) {
3216                        log::trace!("skipping hunk with deleted start: {:?}", hunk.range);
3217                        continue;
3218                    }
3219
3220                    if hunk_range.start >= offset {
3221                        // Add the buffer text before the hunk
3222                        let len = text.len();
3223                        text.extend(buffer.text_for_range(offset..hunk_range.start));
3224                        if text.len() > len {
3225                            regions.push(ReferenceRegion {
3226                                buffer_id: Some(buffer_id),
3227                                range: len..text.len(),
3228                                buffer_range: (offset..hunk_range.start).to_point(&buffer),
3229                                status: None,
3230                                excerpt: Some(excerpt.clone()),
3231                                deleted_hunk_anchor: None,
3232                            });
3233                        }
3234
3235                        // Add the deleted text for the hunk.
3236                        if !hunk.diff_base_byte_range.is_empty() {
3237                            let mut base_text = base_buffer
3238                                .text_for_range(hunk.diff_base_byte_range.clone())
3239                                .collect::<String>();
3240                            if !base_text.ends_with('\n') {
3241                                base_text.push('\n');
3242                            }
3243                            let len = text.len();
3244                            text.push_str(&base_text);
3245                            regions.push(ReferenceRegion {
3246                                buffer_id: Some(base_buffer.remote_id()),
3247                                range: len..text.len(),
3248                                buffer_range: hunk.diff_base_byte_range.to_point(&base_buffer),
3249                                status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
3250                                excerpt: Some(excerpt.clone()),
3251                                deleted_hunk_anchor: Some(hunk.buffer_range.start),
3252                            });
3253                        }
3254
3255                        offset = hunk_range.start;
3256                    }
3257
3258                    // Add the inserted text for the hunk.
3259                    if hunk_range.end > offset {
3260                        let len = text.len();
3261                        text.extend(buffer.text_for_range(offset..hunk_range.end));
3262                        let range = len..text.len();
3263                        let region = ReferenceRegion {
3264                            buffer_id: Some(buffer_id),
3265                            range,
3266                            buffer_range: (offset..hunk_range.end).to_point(&buffer),
3267                            status: Some(DiffHunkStatus::added(hunk.secondary_status)),
3268                            excerpt: Some(excerpt.clone()),
3269                            deleted_hunk_anchor: None,
3270                        };
3271                        offset = hunk_range.end;
3272                        regions.push(region);
3273                    }
3274                }
3275
3276                // Add the buffer text for the rest of the excerpt.
3277                let len = text.len();
3278                text.extend(buffer.text_for_range(offset..buffer_range.end));
3279                text.push('\n');
3280                regions.push(ReferenceRegion {
3281                    buffer_id: Some(buffer_id),
3282                    range: len..text.len(),
3283                    buffer_range: (offset..buffer_range.end).to_point(&buffer),
3284                    status: None,
3285                    excerpt: Some(excerpt.clone()),
3286                    deleted_hunk_anchor: None,
3287                });
3288            }
3289        }
3290
3291        // Remove final trailing newline.
3292        if self.excerpts.is_empty() {
3293            regions.push(ReferenceRegion {
3294                buffer_id: None,
3295                range: 0..1,
3296                buffer_range: Point::new(0, 0)..Point::new(0, 1),
3297                status: None,
3298                excerpt: None,
3299                deleted_hunk_anchor: None,
3300            });
3301        } else {
3302            text.pop();
3303            let region = regions.last_mut().unwrap();
3304            assert!(region.deleted_hunk_anchor.is_none());
3305            region.range.end -= 1;
3306        }
3307
3308        // Retrieve the row info using the region that contains
3309        // the start of each multi-buffer line.
3310        let mut ix = 0;
3311        let row_infos = text
3312            .split('\n')
3313            .map(|line| {
3314                let row_info = regions
3315                    .iter()
3316                    .rposition(|region| {
3317                        region.range.contains(&ix) || (ix == text.len() && ix == region.range.end)
3318                    })
3319                    .map_or(RowInfo::default(), |region_ix| {
3320                        let region = regions[region_ix].clone();
3321                        let buffer_row = region.buffer_range.start.row
3322                            + text[region.range.start..ix].matches('\n').count() as u32;
3323                        let main_buffer = region.excerpt.as_ref().map(|e| e.buffer.clone());
3324                        let excerpt_range = region.excerpt.as_ref().map(|e| &e.range);
3325                        let is_excerpt_start = region_ix == 0
3326                            || regions[region_ix - 1].excerpt.as_ref().map(|e| &e.range)
3327                                != excerpt_range
3328                            || regions[region_ix - 1].range.is_empty();
3329                        let mut is_excerpt_end = region_ix == regions.len() - 1
3330                            || regions[region_ix + 1].excerpt.as_ref().map(|e| &e.range)
3331                                != excerpt_range;
3332                        let is_start = !text[region.range.start..ix].contains('\n');
3333                        let is_last_region = region_ix == regions.len() - 1;
3334                        let mut is_end = if region.range.end > text.len() {
3335                            !text[ix..].contains('\n')
3336                        } else {
3337                            let remaining_newlines = text[ix..region.range.end.min(text.len())]
3338                                .matches('\n')
3339                                .count();
3340                            remaining_newlines == if is_last_region { 0 } else { 1 }
3341                        };
3342                        if region_ix < regions.len() - 1
3343                            && !text[ix..].contains("\n")
3344                            && (region.status == Some(DiffHunkStatus::added_none())
3345                                || region.status.is_some_and(|s| s.is_deleted()))
3346                            && regions[region_ix + 1].excerpt.as_ref().map(|e| &e.range)
3347                                == excerpt_range
3348                            && regions[region_ix + 1].range.start == text.len()
3349                        {
3350                            is_end = true;
3351                            is_excerpt_end = true;
3352                        }
3353                        let multibuffer_row =
3354                            MultiBufferRow(text[..ix].matches('\n').count() as u32);
3355                        let mut expand_direction = None;
3356                        if let Some(buffer) = &main_buffer {
3357                            let needs_expand_up = is_excerpt_start && is_start && buffer_row > 0;
3358                            let needs_expand_down = is_excerpt_end
3359                                && is_end
3360                                && buffer.read(cx).max_point().row > buffer_row;
3361                            expand_direction = if needs_expand_up && needs_expand_down {
3362                                Some(ExpandExcerptDirection::UpAndDown)
3363                            } else if needs_expand_up {
3364                                Some(ExpandExcerptDirection::Up)
3365                            } else if needs_expand_down {
3366                                Some(ExpandExcerptDirection::Down)
3367                            } else {
3368                                None
3369                            };
3370                        }
3371                        RowInfo {
3372                            buffer_id: region.buffer_id,
3373                            diff_status: region.status,
3374                            buffer_row: Some(buffer_row),
3375                            wrapped_buffer_row: None,
3376
3377                            multibuffer_row: Some(multibuffer_row),
3378                            expand_info: maybe!({
3379                                let direction = expand_direction?;
3380                                let excerpt = region.excerpt.as_ref()?;
3381                                Some(ExpandInfo {
3382                                    direction,
3383                                    start_anchor: Anchor::in_buffer(
3384                                        excerpt.path_key_index,
3385                                        excerpt.range.start,
3386                                    ),
3387                                })
3388                            }),
3389                        }
3390                    });
3391                ix += line.len() + 1;
3392                row_info
3393            })
3394            .collect();
3395
3396        (text, row_infos, excerpt_boundary_rows, regions)
3397    }
3398
3399    fn diffs_updated(&mut self, cx: &mut App) {
3400        let buffer_ids = self.diffs.keys().copied().collect::<Vec<_>>();
3401        for buffer_id in buffer_ids {
3402            self.update_expanded_diff_hunks_for_buffer(buffer_id, cx);
3403        }
3404    }
3405
3406    fn add_diff(&mut self, diff: Entity<BufferDiff>, cx: &mut App) {
3407        let buffer_id = diff.read(cx).buffer_id;
3408        self.diffs.insert(buffer_id, diff);
3409    }
3410
3411    fn add_inverted_diff(
3412        &mut self,
3413        diff: Entity<BufferDiff>,
3414        main_buffer: Entity<language::Buffer>,
3415        cx: &App,
3416    ) {
3417        let base_text_buffer_id = diff.read(cx).base_text(cx).remote_id();
3418        self.inverted_diffs
3419            .insert(base_text_buffer_id, (diff, main_buffer));
3420    }
3421
3422    fn update_expanded_diff_hunks_for_buffer(&mut self, buffer_id: BufferId, cx: &mut App) {
3423        let excerpts = self
3424            .excerpts
3425            .iter()
3426            .filter(|excerpt| excerpt.buffer.read(cx).remote_id() == buffer_id)
3427            .collect::<Vec<_>>();
3428        let Some(buffer) = excerpts.first().map(|excerpt| excerpt.buffer.clone()) else {
3429            self.expanded_diff_hunks_by_buffer.remove(&buffer_id);
3430            return;
3431        };
3432        let buffer_snapshot = buffer.read(cx).snapshot();
3433        let Some(diff) = self.diffs.get(&buffer_id) else {
3434            self.expanded_diff_hunks_by_buffer.remove(&buffer_id);
3435            return;
3436        };
3437        let diff = diff.read(cx).snapshot(cx);
3438        let hunks = diff
3439            .hunks_in_row_range(0..u32::MAX, &buffer_snapshot)
3440            .collect::<Vec<_>>();
3441        self.expanded_diff_hunks_by_buffer
3442            .entry(buffer_id)
3443            .or_default()
3444            .retain(|hunk_anchor| {
3445                if !hunk_anchor.is_valid(&buffer_snapshot) {
3446                    return false;
3447                }
3448
3449                let Ok(ix) = hunks.binary_search_by(|hunk| {
3450                    hunk.buffer_range.start.cmp(hunk_anchor, &buffer_snapshot)
3451                }) else {
3452                    return false;
3453                };
3454                let hunk_range = hunks[ix].buffer_range.to_point(&buffer_snapshot);
3455                excerpts.iter().any(|excerpt| {
3456                    let excerpt_range = excerpt.range.to_point(&buffer_snapshot);
3457                    hunk_range.start >= excerpt_range.start && hunk_range.start <= excerpt_range.end
3458                })
3459            });
3460    }
3461
3462    fn anchor_to_offset(&self, anchor: &Anchor, cx: &App) -> Option<MultiBufferOffset> {
3463        if anchor.diff_base_anchor().is_some() {
3464            panic!("reference multibuffer cannot yet resolve anchors inside deleted hunks");
3465        }
3466        let (anchor, snapshot, path_key) = self.anchor_to_buffer_anchor(anchor, cx)?;
3467        // TODO(cole) can maybe make this and expected content call a common function instead
3468        let (text, _, _, regions) = self.expected_content(cx);
3469
3470        // Locate the first region that contains or is past the putative location of the buffer anchor
3471        let ix = regions.partition_point(|region| {
3472            let excerpt = region
3473                .excerpt
3474                .as_ref()
3475                .expect("should have no buffers in empty reference multibuffer");
3476            excerpt
3477                .path_key
3478                .cmp(&path_key)
3479                .then_with(|| {
3480                    if excerpt.range.end.cmp(&anchor, &snapshot).is_lt() {
3481                        Ordering::Less
3482                    } else if excerpt.range.start.cmp(&anchor, &snapshot).is_gt() {
3483                        Ordering::Greater
3484                    } else {
3485                        Ordering::Equal
3486                    }
3487                })
3488                .then_with(|| {
3489                    if let Some(deleted_hunk_anchor) = region.deleted_hunk_anchor {
3490                        deleted_hunk_anchor.cmp(&anchor, &snapshot)
3491                    } else {
3492                        let point = anchor.to_point(&snapshot);
3493                        assert_eq!(region.buffer_id, Some(snapshot.remote_id()));
3494                        if region.buffer_range.end < point {
3495                            Ordering::Less
3496                        } else if region.buffer_range.start > point {
3497                            Ordering::Greater
3498                        } else {
3499                            Ordering::Equal
3500                        }
3501                    }
3502                })
3503                .is_lt()
3504        });
3505
3506        let Some(region) = regions.get(ix) else {
3507            return Some(MultiBufferOffset(text.len()));
3508        };
3509
3510        let offset = if region.buffer_id == Some(snapshot.remote_id()) {
3511            let buffer_offset = anchor.to_offset(&snapshot);
3512            let buffer_range = region.buffer_range.to_offset(&snapshot);
3513            assert!(buffer_offset <= buffer_range.end);
3514            let overshoot = buffer_offset.saturating_sub(buffer_range.start);
3515            region.range.start + overshoot
3516        } else {
3517            region.range.start
3518        };
3519        Some(MultiBufferOffset(offset))
3520    }
3521
3522    fn anchor_to_buffer_anchor(
3523        &self,
3524        anchor: &Anchor,
3525        cx: &App,
3526    ) -> Option<(text::Anchor, BufferSnapshot, PathKey)> {
3527        let (excerpt, anchor) = match anchor {
3528            Anchor::Min => {
3529                let excerpt = self.excerpts.first()?;
3530                (excerpt, excerpt.range.start)
3531            }
3532            Anchor::Excerpt(excerpt_anchor) => (
3533                self.excerpts.iter().find(|excerpt| {
3534                    excerpt.buffer.read(cx).remote_id() == excerpt_anchor.buffer_id()
3535                })?,
3536                excerpt_anchor.text_anchor,
3537            ),
3538            Anchor::Max => {
3539                let excerpt = self.excerpts.last()?;
3540                (excerpt, excerpt.range.end)
3541            }
3542        };
3543
3544        Some((
3545            anchor,
3546            excerpt.buffer.read(cx).snapshot(),
3547            excerpt.path_key.clone(),
3548        ))
3549    }
3550}
3551
3552#[gpui::test(iterations = 100)]
3553async fn test_random_set_ranges(cx: &mut TestAppContext, mut rng: StdRng) {
3554    let base_text = "a\n".repeat(100);
3555    let buf = cx.update(|cx| cx.new(|cx| Buffer::local(base_text, cx)));
3556    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
3557
3558    let operations = env::var("OPERATIONS")
3559        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3560        .unwrap_or(10);
3561
3562    fn row_ranges(ranges: &Vec<Range<Point>>) -> Vec<Range<u32>> {
3563        ranges
3564            .iter()
3565            .map(|range| range.start.row..range.end.row)
3566            .collect()
3567    }
3568
3569    for _ in 0..operations {
3570        let snapshot = buf.update(cx, |buf, _| buf.snapshot());
3571        let num_ranges = rng.random_range(0..=10);
3572        let max_row = snapshot.max_point().row;
3573        let mut ranges = (0..num_ranges)
3574            .map(|_| {
3575                let start = rng.random_range(0..max_row);
3576                let end = rng.random_range(start + 1..max_row + 1);
3577                Point::row_range(start..end)
3578            })
3579            .collect::<Vec<_>>();
3580        ranges.sort_by_key(|range| range.start);
3581        log::info!("Setting ranges: {:?}", row_ranges(&ranges));
3582        multibuffer.update(cx, |multibuffer, cx| {
3583            multibuffer.set_excerpts_for_path(
3584                PathKey::for_buffer(&buf, cx),
3585                buf.clone(),
3586                ranges.clone(),
3587                2,
3588                cx,
3589            )
3590        });
3591
3592        let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3593        let mut last_end = None;
3594        let mut seen_ranges = Vec::default();
3595
3596        for info in snapshot.excerpts() {
3597            let buffer_snapshot = snapshot
3598                .buffer_for_id(info.context.start.buffer_id)
3599                .unwrap();
3600            let start = info.context.start.to_point(buffer_snapshot);
3601            let end = info.context.end.to_point(buffer_snapshot);
3602            seen_ranges.push(start..end);
3603
3604            if let Some(last_end) = last_end.take() {
3605                assert!(
3606                    start > last_end,
3607                    "multibuffer has out-of-order ranges: {:?}; {:?} <= {:?}",
3608                    row_ranges(&seen_ranges),
3609                    start,
3610                    last_end
3611                )
3612            }
3613
3614            ranges.retain(|range| range.start < start || range.end > end);
3615
3616            last_end = Some(end)
3617        }
3618
3619        assert!(
3620            ranges.is_empty(),
3621            "multibuffer {:?} did not include all ranges: {:?}",
3622            row_ranges(&seen_ranges),
3623            row_ranges(&ranges)
3624        );
3625    }
3626}
3627
3628#[gpui::test(iterations = 100)]
3629async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
3630    let operations = env::var("OPERATIONS")
3631        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3632        .unwrap_or(10);
3633    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
3634    let mut buffers: Vec<Entity<Buffer>> = Vec::new();
3635    let mut base_texts: HashMap<BufferId, String> = HashMap::default();
3636    let mut reference = ReferenceMultibuffer::default();
3637    let mut anchors = Vec::new();
3638    let mut old_versions = Vec::new();
3639    let mut needs_diff_calculation = false;
3640    let mut inverted_diff_main_buffers: HashMap<BufferId, Entity<BufferDiff>> = HashMap::default();
3641    for _ in 0..operations {
3642        match rng.random_range(0..100) {
3643            0..=14 if !buffers.is_empty() => {
3644                let buffer = buffers.choose(&mut rng).unwrap();
3645                buffer.update(cx, |buf, cx| {
3646                    let edit_count = rng.random_range(1..5);
3647                    buf.randomly_edit(&mut rng, edit_count, cx);
3648                    log::info!("buffer text:\n{}", buf.text());
3649                    needs_diff_calculation = true;
3650                });
3651                cx.update(|cx| reference.diffs_updated(cx));
3652            }
3653            15..=24 if !reference.excerpts.is_empty() => {
3654                multibuffer.update(cx, |multibuffer, cx| {
3655                    let snapshot = multibuffer.snapshot(cx);
3656                    let infos = snapshot.excerpts().collect::<Vec<_>>();
3657                    let mut excerpts = HashSet::default();
3658                    for _ in 0..rng.random_range(0..infos.len()) {
3659                        excerpts.extend(infos.choose(&mut rng).cloned());
3660                    }
3661
3662                    let line_count = rng.random_range(0..5);
3663
3664                    let excerpt_ixs = excerpts
3665                        .iter()
3666                        .map(|info| {
3667                            reference
3668                                .excerpts
3669                                .iter()
3670                                .position(|e| e.range == info.context)
3671                                .unwrap()
3672                        })
3673                        .collect::<Vec<_>>();
3674                    log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
3675                    multibuffer.expand_excerpts(
3676                        excerpts
3677                            .iter()
3678                            .map(|info| snapshot.anchor_in_excerpt(info.context.end).unwrap()),
3679                        line_count,
3680                        ExpandExcerptDirection::UpAndDown,
3681                        cx,
3682                    );
3683
3684                    reference.expand_excerpts(&excerpts, line_count, cx);
3685                });
3686            }
3687            25..=34 if !reference.excerpts.is_empty() => {
3688                let multibuffer =
3689                    multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3690                let offset = multibuffer.clip_offset(
3691                    MultiBufferOffset(rng.random_range(0..=multibuffer.len().0)),
3692                    Bias::Left,
3693                );
3694                let bias = if rng.random() {
3695                    Bias::Left
3696                } else {
3697                    Bias::Right
3698                };
3699                log::info!("Creating anchor at {} with bias {:?}", offset.0, bias);
3700                anchors.push(multibuffer.anchor_at(offset, bias));
3701                anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
3702            }
3703            35..=45 if !reference.excerpts.is_empty() => {
3704                multibuffer.update(cx, |multibuffer, cx| {
3705                    let snapshot = multibuffer.snapshot(cx);
3706                    let excerpt_ix = rng.random_range(0..reference.excerpts.len());
3707                    let excerpt = &reference.excerpts[excerpt_ix];
3708
3709                    // Skip inverted excerpts - hunks can't be collapsed
3710                    let buffer_id = excerpt.buffer.read(cx).remote_id();
3711                    if reference.inverted_diffs.contains_key(&buffer_id) {
3712                        return;
3713                    }
3714
3715                    let start = excerpt.range.start;
3716                    let end = excerpt.range.end;
3717                    let range = snapshot.anchor_in_excerpt(start).unwrap()
3718                        ..snapshot.anchor_in_excerpt(end).unwrap();
3719
3720                    log::info!(
3721                        "expanding diff hunks in range {:?} (excerpt index {excerpt_ix:?}, buffer id {:?})",
3722                        range.to_point(&snapshot),
3723                        buffer_id,
3724                    );
3725                    reference.expand_diff_hunks(excerpt.path_key.clone(), start..end, cx);
3726                    multibuffer.expand_diff_hunks(vec![range], cx);
3727                });
3728            }
3729            46..=75 if needs_diff_calculation => {
3730                multibuffer.update(cx, |multibuffer, cx| {
3731                    for buffer in multibuffer.all_buffers() {
3732                        let snapshot = buffer.read(cx).snapshot();
3733                        let buffer_id = snapshot.remote_id();
3734
3735                        if let Some(diff) = multibuffer.diff_for(buffer_id) {
3736                            diff.update(cx, |diff, cx| {
3737                                log::info!("recalculating diff for buffer {:?}", buffer_id,);
3738                                diff.recalculate_diff_sync(&snapshot.text, cx);
3739                            });
3740                        }
3741
3742                        if let Some(inverted_diff) = inverted_diff_main_buffers.get(&buffer_id) {
3743                            inverted_diff.update(cx, |diff, cx| {
3744                                log::info!(
3745                                    "recalculating inverted diff for main buffer {:?}",
3746                                    buffer_id,
3747                                );
3748                                diff.recalculate_diff_sync(&snapshot.text, cx);
3749                            });
3750                        }
3751                    }
3752                    reference.diffs_updated(cx);
3753                    needs_diff_calculation = false;
3754                });
3755            }
3756            _ => {
3757                // Decide if we're creating a new buffer or reusing an existing one
3758                let create_new_buffer = buffers.is_empty() || rng.random_bool(0.4);
3759
3760                let (excerpt_buffer, diff, inverted_main_buffer) = if create_new_buffer {
3761                    let create_inverted = rng.random_bool(0.3);
3762
3763                    if create_inverted {
3764                        let mut main_buffer_text = util::RandomCharIter::new(&mut rng)
3765                            .take(256)
3766                            .collect::<String>();
3767                        let main_buffer = cx.new(|cx| Buffer::local(main_buffer_text.clone(), cx));
3768                        text::LineEnding::normalize(&mut main_buffer_text);
3769                        let main_buffer_id =
3770                            main_buffer.read_with(cx, |buffer, _| buffer.remote_id());
3771                        base_texts.insert(main_buffer_id, main_buffer_text.clone());
3772                        buffers.push(main_buffer.clone());
3773
3774                        let diff = cx.new(|cx| {
3775                            BufferDiff::new_with_base_text(
3776                                &main_buffer_text,
3777                                &main_buffer.read(cx).text_snapshot(),
3778                                cx,
3779                            )
3780                        });
3781
3782                        let base_text_buffer =
3783                            diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
3784
3785                        // Track for recalculation when main buffer is edited
3786                        inverted_diff_main_buffers.insert(main_buffer_id, diff.clone());
3787
3788                        (base_text_buffer, diff, Some(main_buffer))
3789                    } else {
3790                        let mut base_text = util::RandomCharIter::new(&mut rng)
3791                            .take(256)
3792                            .collect::<String>();
3793
3794                        let buffer_handle = cx.new(|cx| Buffer::local(base_text.clone(), cx));
3795                        text::LineEnding::normalize(&mut base_text);
3796                        let buffer_id = buffer_handle.read_with(cx, |buffer, _| buffer.remote_id());
3797                        base_texts.insert(buffer_id, base_text.clone());
3798                        buffers.push(buffer_handle.clone());
3799
3800                        let diff = cx.new(|cx| {
3801                            BufferDiff::new_with_base_text(
3802                                &base_text,
3803                                &buffer_handle.read(cx).text_snapshot(),
3804                                cx,
3805                            )
3806                        });
3807
3808                        (buffer_handle, diff, None)
3809                    }
3810                } else {
3811                    // Reuse an existing buffer
3812                    let buffer_handle = buffers.choose(&mut rng).unwrap().clone();
3813                    let buffer_id = buffer_handle.read_with(cx, |buffer, _| buffer.remote_id());
3814
3815                    if let Some(diff) = inverted_diff_main_buffers.get(&buffer_id) {
3816                        let base_text_buffer =
3817                            diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
3818                        (base_text_buffer, diff.clone(), Some(buffer_handle))
3819                    } else {
3820                        // Get existing diff or create new one for regular buffer
3821                        let diff = multibuffer
3822                            .read_with(cx, |mb, _| mb.diff_for(buffer_id))
3823                            .unwrap_or_else(|| {
3824                                let base_text = base_texts.get(&buffer_id).unwrap();
3825                                cx.new(|cx| {
3826                                    BufferDiff::new_with_base_text(
3827                                        base_text,
3828                                        &buffer_handle.read(cx).text_snapshot(),
3829                                        cx,
3830                                    )
3831                                })
3832                            });
3833                        (buffer_handle, diff, None)
3834                    }
3835                };
3836
3837                let excerpt_buffer_snapshot =
3838                    excerpt_buffer.read_with(cx, |excerpt_buffer, _| excerpt_buffer.snapshot());
3839                let mut ranges = reference
3840                    .excerpts
3841                    .iter()
3842                    .filter(|excerpt| excerpt.buffer == excerpt_buffer)
3843                    .map(|excerpt| excerpt.range.to_point(&excerpt_buffer_snapshot))
3844                    .collect::<Vec<_>>();
3845                mutate_excerpt_ranges(&mut rng, &mut ranges, &excerpt_buffer_snapshot, 1);
3846                let ranges = ranges
3847                    .iter()
3848                    .cloned()
3849                    .map(ExcerptRange::new)
3850                    .collect::<Vec<_>>();
3851                let path = cx.update(|cx| PathKey::for_buffer(&excerpt_buffer, cx));
3852                let path_key_index = multibuffer.update(cx, |multibuffer, _| {
3853                    multibuffer.get_or_create_path_key_index(&path)
3854                });
3855
3856                multibuffer.update(cx, |multibuffer, cx| {
3857                    multibuffer.set_excerpt_ranges_for_path(
3858                        path.clone(),
3859                        excerpt_buffer.clone(),
3860                        &excerpt_buffer_snapshot,
3861                        ranges.clone(),
3862                        cx,
3863                    )
3864                });
3865
3866                cx.update(|cx| {
3867                    reference.set_excerpts(
3868                        path,
3869                        path_key_index,
3870                        excerpt_buffer.clone(),
3871                        &excerpt_buffer_snapshot,
3872                        ranges,
3873                        cx,
3874                    )
3875                });
3876
3877                let excerpt_buffer_id =
3878                    excerpt_buffer.read_with(cx, |buffer, _| buffer.remote_id());
3879                multibuffer.update(cx, |multibuffer, cx| {
3880                    if multibuffer.diff_for(excerpt_buffer_id).is_none() {
3881                        if let Some(main_buffer) = inverted_main_buffer {
3882                            reference.add_inverted_diff(diff.clone(), main_buffer.clone(), cx);
3883                            multibuffer.add_inverted_diff(diff, main_buffer, cx);
3884                        } else {
3885                            reference.add_diff(diff.clone(), cx);
3886                            multibuffer.add_diff(diff, cx);
3887                        }
3888                    }
3889                });
3890            }
3891        }
3892
3893        if rng.random_bool(0.3) {
3894            multibuffer.update(cx, |multibuffer, cx| {
3895                old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
3896            })
3897        }
3898
3899        multibuffer.read_with(cx, |multibuffer, cx| {
3900            check_multibuffer(multibuffer, &reference, &anchors, cx, &mut rng);
3901        });
3902    }
3903    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3904    for (old_snapshot, subscription) in old_versions {
3905        check_multibuffer_edits(&snapshot, &old_snapshot, subscription);
3906    }
3907}
3908
3909fn mutate_excerpt_ranges(
3910    rng: &mut StdRng,
3911    existing_ranges: &mut Vec<Range<Point>>,
3912    buffer: &BufferSnapshot,
3913    operations: u32,
3914) {
3915    let mut ranges_to_add = Vec::new();
3916
3917    for _ in 0..operations {
3918        match rng.random_range(0..5) {
3919            0..=1 if !existing_ranges.is_empty() => {
3920                let index = rng.random_range(0..existing_ranges.len());
3921                log::info!("Removing excerpt at index {index}");
3922                existing_ranges.remove(index);
3923            }
3924            _ => {
3925                let end_row = rng.random_range(0..=buffer.max_point().row);
3926                let start_row = rng.random_range(0..=end_row);
3927                let end_col = buffer.line_len(end_row);
3928                log::info!(
3929                    "Inserting excerpt for buffer {:?}, row range {:?}",
3930                    buffer.remote_id(),
3931                    start_row..end_row
3932                );
3933                ranges_to_add.push(Point::new(start_row, 0)..Point::new(end_row, end_col));
3934            }
3935        }
3936    }
3937
3938    existing_ranges.extend(ranges_to_add);
3939    existing_ranges.sort_by_key(|r| r.start);
3940}
3941
3942fn check_multibuffer(
3943    multibuffer: &MultiBuffer,
3944    reference: &ReferenceMultibuffer,
3945    anchors: &[Anchor],
3946    cx: &App,
3947    rng: &mut StdRng,
3948) {
3949    let snapshot = multibuffer.snapshot(cx);
3950    let actual_text = snapshot.text();
3951    let actual_boundary_rows = snapshot
3952        .excerpt_boundaries_in_range(MultiBufferOffset(0)..)
3953        .map(|b| b.row)
3954        .collect::<HashSet<_>>();
3955    let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3956
3957    let anchors_to_check = anchors
3958        .iter()
3959        .filter_map(|anchor| {
3960            snapshot
3961                .anchor_to_buffer_anchor(*anchor)
3962                .map(|(anchor, _)| anchor)
3963        })
3964        // Intentionally mix in some anchors that are (in general) not contained in any excerpt
3965        .chain(
3966            reference
3967                .excerpts
3968                .iter()
3969                .map(|excerpt| excerpt.buffer.read(cx).remote_id())
3970                .dedup()
3971                .flat_map(|buffer_id| {
3972                    [
3973                        text::Anchor::min_for_buffer(buffer_id),
3974                        text::Anchor::max_for_buffer(buffer_id),
3975                    ]
3976                }),
3977        )
3978        .map(|anchor| snapshot.anchor_in_buffer(anchor).unwrap())
3979        .collect::<Vec<_>>();
3980
3981    let (expected_text, expected_row_infos, expected_boundary_rows, _) =
3982        reference.expected_content(cx);
3983    let expected_anchor_offsets = anchors_to_check
3984        .iter()
3985        .map(|anchor| reference.anchor_to_offset(anchor, cx).unwrap())
3986        .collect::<Vec<_>>();
3987
3988    let has_diff = actual_row_infos
3989        .iter()
3990        .any(|info| info.diff_status.is_some())
3991        || expected_row_infos
3992            .iter()
3993            .any(|info| info.diff_status.is_some());
3994    let actual_diff = format_diff(
3995        &actual_text,
3996        &actual_row_infos,
3997        &actual_boundary_rows,
3998        Some(has_diff),
3999    );
4000    let expected_diff = format_diff(
4001        &expected_text,
4002        &expected_row_infos,
4003        &expected_boundary_rows,
4004        Some(has_diff),
4005    );
4006
4007    log::info!("Multibuffer content:\n{}", actual_diff);
4008
4009    assert_eq!(
4010        actual_row_infos.len(),
4011        actual_text.split('\n').count(),
4012        "line count: {}",
4013        actual_text.split('\n').count()
4014    );
4015    pretty_assertions::assert_eq!(actual_diff, expected_diff);
4016    pretty_assertions::assert_eq!(actual_text, expected_text);
4017    pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
4018
4019    for _ in 0..5 {
4020        let start_row = rng.random_range(0..=expected_row_infos.len());
4021        assert_eq!(
4022            snapshot
4023                .row_infos(MultiBufferRow(start_row as u32))
4024                .collect::<Vec<_>>(),
4025            &expected_row_infos[start_row..],
4026            "buffer_rows({})",
4027            start_row
4028        );
4029    }
4030
4031    assert_eq!(
4032        snapshot.widest_line_number(),
4033        expected_row_infos
4034            .into_iter()
4035            .filter_map(|info| {
4036                // For inverted diffs, deleted rows are visible and should be counted.
4037                // Only filter out deleted rows that are NOT from inverted diffs.
4038                let is_inverted_diff = info
4039                    .buffer_id
4040                    .is_some_and(|id| reference.inverted_diffs.contains_key(&id));
4041                if info.diff_status.is_some_and(|status| status.is_deleted()) && !is_inverted_diff {
4042                    None
4043                } else {
4044                    info.buffer_row
4045                }
4046            })
4047            .max()
4048            .unwrap()
4049            + 1
4050    );
4051    for i in 0..snapshot.len().0 {
4052        let (_, excerpt_range) = snapshot
4053            .excerpt_containing(MultiBufferOffset(i)..MultiBufferOffset(i))
4054            .unwrap();
4055        reference
4056            .excerpts
4057            .iter()
4058            .find(|reference_excerpt| reference_excerpt.range == excerpt_range.context)
4059            .expect("corresponding excerpt should exist in reference multibuffer");
4060    }
4061
4062    assert_consistent_line_numbers(&snapshot);
4063    assert_position_translation(&snapshot);
4064
4065    for (row, line) in expected_text.split('\n').enumerate() {
4066        assert_eq!(
4067            snapshot.line_len(MultiBufferRow(row as u32)),
4068            line.len() as u32,
4069            "line_len({}).",
4070            row
4071        );
4072    }
4073
4074    let text_rope = Rope::from(expected_text.as_str());
4075    for _ in 0..10 {
4076        let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
4077        let start_ix = text_rope.clip_offset(rng.random_range(0..=end_ix), Bias::Left);
4078
4079        let text_for_range = snapshot
4080            .text_for_range(MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix))
4081            .collect::<String>();
4082        assert_eq!(
4083            text_for_range,
4084            &expected_text[start_ix..end_ix],
4085            "incorrect text for range {:?}",
4086            start_ix..end_ix
4087        );
4088
4089        let expected_summary =
4090            MBTextSummary::from(TextSummary::from(&expected_text[start_ix..end_ix]));
4091        assert_eq!(
4092            snapshot.text_summary_for_range::<MBTextSummary, _>(
4093                MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix)
4094            ),
4095            expected_summary,
4096            "incorrect summary for range {:?}",
4097            start_ix..end_ix
4098        );
4099    }
4100
4101    // Anchor resolution
4102    let summaries = snapshot.summaries_for_anchors::<MultiBufferOffset, _>(anchors);
4103    assert_eq!(anchors.len(), summaries.len());
4104    for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
4105        assert!(resolved_offset <= snapshot.len());
4106        assert_eq!(
4107            snapshot.summary_for_anchor::<MultiBufferOffset>(anchor),
4108            resolved_offset,
4109            "anchor: {:?}",
4110            anchor
4111        );
4112    }
4113
4114    let actual_anchor_offsets = anchors_to_check
4115        .into_iter()
4116        .map(|anchor| anchor.to_offset(&snapshot))
4117        .collect::<Vec<_>>();
4118    assert_eq!(
4119        actual_anchor_offsets, expected_anchor_offsets,
4120        "buffer anchor resolves to wrong offset"
4121    );
4122
4123    for _ in 0..10 {
4124        let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
4125        assert_eq!(
4126            snapshot
4127                .reversed_chars_at(MultiBufferOffset(end_ix))
4128                .collect::<String>(),
4129            expected_text[..end_ix].chars().rev().collect::<String>(),
4130        );
4131    }
4132
4133    for _ in 0..10 {
4134        let end_ix = rng.random_range(0..=text_rope.len());
4135        let end_ix = text_rope.floor_char_boundary(end_ix);
4136        let start_ix = rng.random_range(0..=end_ix);
4137        let start_ix = text_rope.floor_char_boundary(start_ix);
4138        assert_eq!(
4139            snapshot
4140                .bytes_in_range(MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix))
4141                .flatten()
4142                .copied()
4143                .collect::<Vec<_>>(),
4144            expected_text.as_bytes()[start_ix..end_ix].to_vec(),
4145            "bytes_in_range({:?})",
4146            start_ix..end_ix,
4147        );
4148    }
4149}
4150
4151fn check_multibuffer_edits(
4152    snapshot: &MultiBufferSnapshot,
4153    old_snapshot: &MultiBufferSnapshot,
4154    subscription: Subscription<MultiBufferOffset>,
4155) {
4156    let edits = subscription.consume().into_inner();
4157
4158    log::info!(
4159        "applying subscription edits to old text: {:?}: {:#?}",
4160        old_snapshot.text(),
4161        edits,
4162    );
4163
4164    let mut text = old_snapshot.text();
4165    for edit in edits {
4166        let new_text: String = snapshot
4167            .text_for_range(edit.new.start..edit.new.end)
4168            .collect();
4169        text.replace_range(
4170            (edit.new.start.0..edit.new.start.0 + (edit.old.end.0 - edit.old.start.0)).clone(),
4171            &new_text,
4172        );
4173        pretty_assertions::assert_eq!(
4174            &text[0..edit.new.end.0],
4175            snapshot
4176                .text_for_range(MultiBufferOffset(0)..edit.new.end)
4177                .collect::<String>()
4178        );
4179    }
4180    pretty_assertions::assert_eq!(text, snapshot.text());
4181}
4182
4183#[gpui::test]
4184fn test_history(cx: &mut App) {
4185    let test_settings = SettingsStore::test(cx);
4186    cx.set_global(test_settings);
4187
4188    let group_interval: Duration = Duration::from_millis(1);
4189    let buffer_1 = cx.new(|cx| {
4190        let mut buf = Buffer::local("1234", cx);
4191        buf.set_group_interval(group_interval);
4192        buf
4193    });
4194    let buffer_2 = cx.new(|cx| {
4195        let mut buf = Buffer::local("5678", cx);
4196        buf.set_group_interval(group_interval);
4197        buf
4198    });
4199    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
4200    multibuffer.update(cx, |this, cx| {
4201        this.set_group_interval(group_interval, cx);
4202    });
4203    multibuffer.update(cx, |multibuffer, cx| {
4204        multibuffer.set_excerpts_for_path(
4205            PathKey::sorted(0),
4206            buffer_1.clone(),
4207            [Point::zero()..buffer_1.read(cx).max_point()],
4208            0,
4209            cx,
4210        );
4211        multibuffer.set_excerpts_for_path(
4212            PathKey::sorted(1),
4213            buffer_2.clone(),
4214            [Point::zero()..buffer_2.read(cx).max_point()],
4215            0,
4216            cx,
4217        );
4218    });
4219
4220    let mut now = Instant::now();
4221
4222    multibuffer.update(cx, |multibuffer, cx| {
4223        let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
4224        multibuffer.edit(
4225            [
4226                (Point::new(0, 0)..Point::new(0, 0), "A"),
4227                (Point::new(1, 0)..Point::new(1, 0), "A"),
4228            ],
4229            None,
4230            cx,
4231        );
4232        multibuffer.edit(
4233            [
4234                (Point::new(0, 1)..Point::new(0, 1), "B"),
4235                (Point::new(1, 1)..Point::new(1, 1), "B"),
4236            ],
4237            None,
4238            cx,
4239        );
4240        multibuffer.end_transaction_at(now, cx);
4241        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4242
4243        // Verify edited ranges for transaction 1
4244        assert_eq!(
4245            multibuffer.edited_ranges_for_transaction(transaction_1, cx),
4246            &[
4247                MultiBufferOffset(0)..MultiBufferOffset(2),
4248                MultiBufferOffset(7)..MultiBufferOffset(9),
4249            ]
4250        );
4251
4252        // Edit buffer 1 through the multibuffer
4253        now += 2 * group_interval;
4254        multibuffer.start_transaction_at(now, cx);
4255        multibuffer.edit(
4256            [(MultiBufferOffset(2)..MultiBufferOffset(2), "C")],
4257            None,
4258            cx,
4259        );
4260        multibuffer.end_transaction_at(now, cx);
4261        assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
4262
4263        // Edit buffer 1 independently
4264        buffer_1.update(cx, |buffer_1, cx| {
4265            buffer_1.start_transaction_at(now);
4266            buffer_1.edit([(3..3, "D")], None, cx);
4267            buffer_1.end_transaction_at(now, cx);
4268
4269            now += 2 * group_interval;
4270            buffer_1.start_transaction_at(now);
4271            buffer_1.edit([(4..4, "E")], None, cx);
4272            buffer_1.end_transaction_at(now, cx);
4273        });
4274        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
4275
4276        // An undo in the multibuffer undoes the multibuffer transaction
4277        // and also any individual buffer edits that have occurred since
4278        // that transaction.
4279        multibuffer.undo(cx);
4280        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4281
4282        multibuffer.undo(cx);
4283        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4284
4285        multibuffer.redo(cx);
4286        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4287
4288        multibuffer.redo(cx);
4289        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
4290
4291        // Undo buffer 2 independently.
4292        buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
4293        assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
4294
4295        // An undo in the multibuffer undoes the components of the
4296        // the last multibuffer transaction that are not already undone.
4297        multibuffer.undo(cx);
4298        assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
4299
4300        multibuffer.undo(cx);
4301        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4302
4303        multibuffer.redo(cx);
4304        assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4305
4306        buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
4307        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
4308
4309        // Redo stack gets cleared after an edit.
4310        now += 2 * group_interval;
4311        multibuffer.start_transaction_at(now, cx);
4312        multibuffer.edit(
4313            [(MultiBufferOffset(0)..MultiBufferOffset(0), "X")],
4314            None,
4315            cx,
4316        );
4317        multibuffer.end_transaction_at(now, cx);
4318        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4319        multibuffer.redo(cx);
4320        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4321        multibuffer.undo(cx);
4322        assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
4323        multibuffer.undo(cx);
4324        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4325
4326        // Transactions can be grouped manually.
4327        multibuffer.redo(cx);
4328        multibuffer.redo(cx);
4329        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4330        multibuffer.group_until_transaction(transaction_1, cx);
4331        multibuffer.undo(cx);
4332        assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4333        multibuffer.redo(cx);
4334        assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4335    });
4336}
4337
4338#[gpui::test]
4339async fn test_enclosing_indent(cx: &mut TestAppContext) {
4340    async fn enclosing_indent(
4341        text: &str,
4342        buffer_row: u32,
4343        cx: &mut TestAppContext,
4344    ) -> Option<(Range<u32>, LineIndent)> {
4345        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
4346        let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
4347        let (range, indent) = snapshot
4348            .enclosing_indent(MultiBufferRow(buffer_row))
4349            .await?;
4350        Some((range.start.0..range.end.0, indent))
4351    }
4352
4353    assert_eq!(
4354        enclosing_indent(
4355            indoc!(
4356                "
4357                fn b() {
4358                    if c {
4359                        let d = 2;
4360                    }
4361                }
4362                "
4363            ),
4364            1,
4365            cx,
4366        )
4367        .await,
4368        Some((
4369            1..2,
4370            LineIndent {
4371                tabs: 0,
4372                spaces: 4,
4373                line_blank: false,
4374            }
4375        ))
4376    );
4377
4378    assert_eq!(
4379        enclosing_indent(
4380            indoc!(
4381                "
4382                fn b() {
4383                    if c {
4384                        let d = 2;
4385                    }
4386                }
4387                "
4388            ),
4389            2,
4390            cx,
4391        )
4392        .await,
4393        Some((
4394            1..2,
4395            LineIndent {
4396                tabs: 0,
4397                spaces: 4,
4398                line_blank: false,
4399            }
4400        ))
4401    );
4402
4403    assert_eq!(
4404        enclosing_indent(
4405            indoc!(
4406                "
4407                fn b() {
4408                    if c {
4409                        let d = 2;
4410
4411                        let e = 5;
4412                    }
4413                }
4414                "
4415            ),
4416            3,
4417            cx,
4418        )
4419        .await,
4420        Some((
4421            1..4,
4422            LineIndent {
4423                tabs: 0,
4424                spaces: 4,
4425                line_blank: false,
4426            }
4427        ))
4428    );
4429}
4430
4431#[gpui::test]
4432async fn test_summaries_for_anchors(cx: &mut TestAppContext) {
4433    let base_text_1 = indoc!(
4434        "
4435        bar
4436        "
4437    );
4438    let text_1 = indoc!(
4439        "
4440        BAR
4441        "
4442    );
4443    let base_text_2 = indoc!(
4444        "
4445        foo
4446        "
4447    );
4448    let text_2 = indoc!(
4449        "
4450        FOO
4451        "
4452    );
4453
4454    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
4455    let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
4456    let diff_1 = cx.new(|cx| {
4457        BufferDiff::new_with_base_text(base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
4458    });
4459    let diff_2 = cx.new(|cx| {
4460        BufferDiff::new_with_base_text(base_text_2, &buffer_2.read(cx).text_snapshot(), cx)
4461    });
4462    cx.run_until_parked();
4463
4464    let multibuffer = cx.new(|cx| {
4465        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
4466        multibuffer.set_all_diff_hunks_expanded(cx);
4467        multibuffer.set_excerpts_for_path(
4468            PathKey::sorted(0),
4469            buffer_1.clone(),
4470            [Point::zero()..buffer_1.read(cx).max_point()],
4471            0,
4472            cx,
4473        );
4474        multibuffer.set_excerpts_for_path(
4475            PathKey::sorted(1),
4476            buffer_2.clone(),
4477            [Point::zero()..buffer_2.read(cx).max_point()],
4478            0,
4479            cx,
4480        );
4481        multibuffer.add_diff(diff_1.clone(), cx);
4482        multibuffer.add_diff(diff_2.clone(), cx);
4483        multibuffer
4484    });
4485
4486    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4487        (multibuffer.snapshot(cx), multibuffer.subscribe())
4488    });
4489
4490    assert_new_snapshot(
4491        &multibuffer,
4492        &mut snapshot,
4493        &mut subscription,
4494        cx,
4495        indoc!(
4496            "
4497            - bar
4498            + BAR
4499
4500            - foo
4501            + FOO
4502            "
4503        ),
4504    );
4505
4506    let anchor_1 = multibuffer.read_with(cx, |multibuffer, cx| {
4507        multibuffer
4508            .snapshot(cx)
4509            .anchor_in_excerpt(text::Anchor::min_for_buffer(buffer_1.read(cx).remote_id()))
4510            .unwrap()
4511    });
4512    let point_1 = snapshot.summaries_for_anchors::<Point, _>([&anchor_1])[0];
4513    assert_eq!(point_1, Point::new(0, 0));
4514
4515    let anchor_2 = multibuffer.read_with(cx, |multibuffer, cx| {
4516        multibuffer
4517            .snapshot(cx)
4518            .anchor_in_excerpt(text::Anchor::min_for_buffer(buffer_2.read(cx).remote_id()))
4519            .unwrap()
4520    });
4521    let point_2 = snapshot.summaries_for_anchors::<Point, _>([&anchor_2])[0];
4522    assert_eq!(point_2, Point::new(3, 0));
4523}
4524
4525#[gpui::test]
4526async fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
4527    let base_text_1 = "one\ntwo".to_owned();
4528    let text_1 = "one\n".to_owned();
4529
4530    let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
4531    let diff_1 = cx.new(|cx| {
4532        BufferDiff::new_with_base_text(&base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
4533    });
4534    cx.run_until_parked();
4535
4536    let multibuffer = cx.new(|cx| {
4537        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
4538        multibuffer.set_excerpts_for_path(
4539            PathKey::sorted(0),
4540            buffer_1.clone(),
4541            [Point::zero()..buffer_1.read(cx).max_point()],
4542            0,
4543            cx,
4544        );
4545        multibuffer.add_diff(diff_1.clone(), cx);
4546        multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
4547        multibuffer
4548    });
4549
4550    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4551        (multibuffer.snapshot(cx), multibuffer.subscribe())
4552    });
4553
4554    assert_new_snapshot(
4555        &multibuffer,
4556        &mut snapshot,
4557        &mut subscription,
4558        cx,
4559        indoc!(
4560            "
4561              one
4562            - two
4563            "
4564        ),
4565    );
4566
4567    assert_eq!(snapshot.max_point(), Point::new(2, 0));
4568    assert_eq!(snapshot.len().0, 8);
4569
4570    assert_eq!(
4571        snapshot
4572            .dimensions_from_points::<Point>([Point::new(2, 0)])
4573            .collect::<Vec<_>>(),
4574        vec![Point::new(2, 0)]
4575    );
4576
4577    let (_, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
4578    assert_eq!(translated_offset.0, "one\n".len());
4579    let (_, translated_point) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
4580    assert_eq!(translated_point, Point::new(1, 0));
4581
4582    // The same, for an excerpt that's not at the end of the multibuffer.
4583
4584    let text_2 = "foo\n".to_owned();
4585    let buffer_2 = cx.new(|cx| Buffer::local(&text_2, cx));
4586    multibuffer.update(cx, |multibuffer, cx| {
4587        multibuffer.set_excerpt_ranges_for_path(
4588            PathKey::sorted(1),
4589            buffer_2.clone(),
4590            &buffer_2.read(cx).snapshot(),
4591            vec![ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
4592            cx,
4593        );
4594    });
4595
4596    assert_new_snapshot(
4597        &multibuffer,
4598        &mut snapshot,
4599        &mut subscription,
4600        cx,
4601        indoc!(
4602            "
4603              one
4604            - two
4605
4606              foo
4607            "
4608        ),
4609    );
4610
4611    assert_eq!(
4612        snapshot
4613            .dimensions_from_points::<Point>([Point::new(2, 0)])
4614            .collect::<Vec<_>>(),
4615        vec![Point::new(2, 0)]
4616    );
4617
4618    let buffer_1_id = buffer_1.read_with(cx, |buffer_1, _| buffer_1.remote_id());
4619    let (buffer, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
4620    assert_eq!(buffer.remote_id(), buffer_1_id);
4621    assert_eq!(translated_offset.0, "one\n".len());
4622    let (buffer, translated_point) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
4623    assert_eq!(buffer.remote_id(), buffer_1_id);
4624    assert_eq!(translated_point, Point::new(1, 0));
4625}
4626
4627fn format_diff(
4628    text: &str,
4629    row_infos: &Vec<RowInfo>,
4630    boundary_rows: &HashSet<MultiBufferRow>,
4631    has_diff: Option<bool>,
4632) -> String {
4633    let has_diff =
4634        has_diff.unwrap_or_else(|| row_infos.iter().any(|info| info.diff_status.is_some()));
4635    text.split('\n')
4636        .enumerate()
4637        .zip(row_infos)
4638        .map(|((ix, line), info)| {
4639            let marker = match info.diff_status.map(|status| status.kind) {
4640                Some(DiffHunkStatusKind::Added) => "+ ",
4641                Some(DiffHunkStatusKind::Deleted) => "- ",
4642                Some(DiffHunkStatusKind::Modified) => unreachable!(),
4643                None => {
4644                    if has_diff && !line.is_empty() {
4645                        "  "
4646                    } else {
4647                        ""
4648                    }
4649                }
4650            };
4651            let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
4652                if has_diff {
4653                    "  ----------\n"
4654                } else {
4655                    "---------\n"
4656                }
4657            } else {
4658                ""
4659            };
4660            let expand = info
4661                .expand_info
4662                .as_ref()
4663                .map(|expand_info| match expand_info.direction {
4664                    ExpandExcerptDirection::Up => " [↑]",
4665                    ExpandExcerptDirection::Down => " [↓]",
4666                    ExpandExcerptDirection::UpAndDown => " [↕]",
4667                })
4668                .unwrap_or_default();
4669
4670            format!("{boundary_row}{marker}{line}{expand}")
4671            // let mbr = info
4672            //     .multibuffer_row
4673            //     .map(|row| format!("{:0>3}", row.0))
4674            //     .unwrap_or_else(|| "???".to_string());
4675            // let byte_range = format!("{byte_range_start:0>3}..{byte_range_end:0>3}");
4676            // format!("{boundary_row}Row: {mbr}, Bytes: {byte_range} | {marker}{line}{expand}")
4677        })
4678        .collect::<Vec<_>>()
4679        .join("\n")
4680}
4681
4682// fn format_transforms(snapshot: &MultiBufferSnapshot) -> String {
4683//     snapshot
4684//         .diff_transforms
4685//         .iter()
4686//         .map(|transform| {
4687//             let (kind, summary) = match transform {
4688//                 DiffTransform::DeletedHunk { summary, .. } => ("   Deleted", (*summary).into()),
4689//                 DiffTransform::FilteredInsertedHunk { summary, .. } => ("  Filtered", *summary),
4690//                 DiffTransform::InsertedHunk { summary, .. } => ("  Inserted", *summary),
4691//                 DiffTransform::Unmodified { summary, .. } => ("Unmodified", *summary),
4692//             };
4693//             format!("{kind}(len: {}, lines: {:?})", summary.len, summary.lines)
4694//         })
4695//         .join("\n")
4696// }
4697
4698// fn format_excerpts(snapshot: &MultiBufferSnapshot) -> String {
4699//     snapshot
4700//         .excerpts
4701//         .iter()
4702//         .map(|excerpt| {
4703//             format!(
4704//                 "Excerpt(buffer_range = {:?}, lines = {:?}, has_trailing_newline = {:?})",
4705//                 excerpt.range.context.to_point(&excerpt.buffer),
4706//                 excerpt.text_summary.lines,
4707//                 excerpt.has_trailing_newline
4708//             )
4709//         })
4710//         .join("\n")
4711// }
4712
4713#[gpui::test]
4714async fn test_singleton_with_inverted_diff(cx: &mut TestAppContext) {
4715    let text = indoc!(
4716        "
4717        ZERO
4718        one
4719        TWO
4720        three
4721        six
4722        "
4723    );
4724    let base_text = indoc!(
4725        "
4726        one
4727        two
4728        three
4729        four
4730        five
4731        six
4732        "
4733    );
4734
4735    let buffer = cx.new(|cx| Buffer::local(text, cx));
4736    let diff = cx
4737        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
4738    cx.run_until_parked();
4739
4740    let base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4741
4742    let multibuffer = cx.new(|cx| {
4743        let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4744        multibuffer.set_all_diff_hunks_expanded(cx);
4745        multibuffer.add_inverted_diff(diff.clone(), buffer.clone(), cx);
4746        multibuffer
4747    });
4748
4749    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4750        (multibuffer.snapshot(cx), multibuffer.subscribe())
4751    });
4752
4753    assert_eq!(snapshot.text(), base_text);
4754    assert_new_snapshot(
4755        &multibuffer,
4756        &mut snapshot,
4757        &mut subscription,
4758        cx,
4759        indoc!(
4760            "
4761              one
4762            - two
4763              three
4764            - four
4765            - five
4766              six
4767            "
4768        ),
4769    );
4770
4771    buffer.update(cx, |buffer, cx| {
4772        buffer.edit_via_marked_text(
4773            indoc!(
4774                "
4775                ZERO
4776                one
4777                «<inserted>»W«O
4778                T»hree
4779                six
4780                "
4781            ),
4782            None,
4783            cx,
4784        );
4785    });
4786    cx.run_until_parked();
4787    let base_text_snapshot = diff.read_with(cx, |diff, cx| diff.base_text(cx));
4788    let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
4789    let update = diff
4790        .update(cx, |diff, cx| {
4791            diff.update_diff(
4792                buffer_snapshot,
4793                &base_text_snapshot,
4794                Some(Arc::from(base_text)),
4795                cx,
4796            )
4797        })
4798        .await;
4799    diff.update(cx, |diff, cx| diff.set_snapshot(update, cx));
4800    cx.run_until_parked();
4801
4802    assert_new_snapshot(
4803        &multibuffer,
4804        &mut snapshot,
4805        &mut subscription,
4806        cx,
4807        indoc! {
4808            "
4809              one
4810            - two
4811            - three
4812            - four
4813            - five
4814              six
4815            "
4816        },
4817    );
4818
4819    buffer.update(cx, |buffer, cx| {
4820        buffer.set_text("ZERO\nONE\nTWO\n", cx);
4821    });
4822    cx.run_until_parked();
4823    let base_text_snapshot = diff.read_with(cx, |diff, cx| diff.base_text(cx));
4824    let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
4825    let update = diff
4826        .update(cx, |diff, cx| {
4827            diff.update_diff(
4828                buffer_snapshot,
4829                &base_text_snapshot,
4830                Some(Arc::from(base_text)),
4831                cx,
4832            )
4833        })
4834        .await;
4835    diff.update(cx, |diff, cx| diff.set_snapshot(update, cx));
4836    cx.run_until_parked();
4837
4838    assert_new_snapshot(
4839        &multibuffer,
4840        &mut snapshot,
4841        &mut subscription,
4842        cx,
4843        indoc! {
4844            "
4845            - one
4846            - two
4847            - three
4848            - four
4849            - five
4850            - six
4851            "
4852        },
4853    );
4854
4855    diff.update(cx, |diff, cx| {
4856        diff.set_base_text(
4857            Some("new base\n".into()),
4858            buffer.read(cx).text_snapshot(),
4859            cx,
4860        )
4861    })
4862    .await;
4863    cx.run_until_parked();
4864
4865    assert_new_snapshot(
4866        &multibuffer,
4867        &mut snapshot,
4868        &mut subscription,
4869        cx,
4870        indoc! {"
4871            - new base
4872        "},
4873    );
4874}
4875
4876#[gpui::test]
4877async fn test_inverted_diff_base_text_change(cx: &mut TestAppContext) {
4878    let base_text = "aaa\nbbb\nccc\n";
4879    let text = "ddd\n";
4880    let buffer = cx.new(|cx| Buffer::local(text, cx));
4881    let diff = cx
4882        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
4883    cx.run_until_parked();
4884
4885    let base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4886
4887    let multibuffer = cx.new(|cx| {
4888        let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4889        multibuffer.set_all_diff_hunks_expanded(cx);
4890        multibuffer.add_inverted_diff(diff.clone(), buffer.clone(), cx);
4891        multibuffer
4892    });
4893
4894    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4895        (multibuffer.snapshot(cx), multibuffer.subscribe())
4896    });
4897
4898    assert_eq!(snapshot.text(), base_text);
4899    assert_new_snapshot(
4900        &multibuffer,
4901        &mut snapshot,
4902        &mut subscription,
4903        cx,
4904        indoc!(
4905            "
4906            - aaa
4907            - bbb
4908            - ccc
4909            "
4910        ),
4911    );
4912
4913    diff.update(cx, |diff, cx| {
4914        diff.set_base_text(Some("ddd\n".into()), buffer.read(cx).text_snapshot(), cx)
4915    })
4916    .await;
4917
4918    let _hunks: Vec<_> = multibuffer
4919        .read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx))
4920        .diff_hunks()
4921        .collect();
4922}
4923
4924#[gpui::test]
4925async fn test_inverted_diff_secondary_version_mismatch(cx: &mut TestAppContext) {
4926    let base_text = "one\ntwo\nthree\nfour\nfive\n";
4927    let index_text = "one\nTWO\nthree\nfour\nfive\n";
4928    let buffer_text = "one\nTWO\nthree\nFOUR\nfive\n";
4929
4930    let buffer = cx.new(|cx| Buffer::local(buffer_text, cx));
4931
4932    let unstaged_diff = cx
4933        .new(|cx| BufferDiff::new_with_base_text(index_text, &buffer.read(cx).text_snapshot(), cx));
4934    cx.run_until_parked();
4935
4936    let uncommitted_diff = cx.new(|cx| {
4937        let mut diff =
4938            BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx);
4939        diff.set_secondary_diff(unstaged_diff.clone());
4940        diff
4941    });
4942    cx.run_until_parked();
4943
4944    buffer.update(cx, |buffer, cx| {
4945        buffer.edit([(0..0, "ZERO\n")], None, cx);
4946    });
4947
4948    let base_text_snapshot = unstaged_diff.read_with(cx, |diff, cx| diff.base_text(cx));
4949    let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
4950    let update = unstaged_diff
4951        .update(cx, |diff, cx| {
4952            diff.update_diff(
4953                buffer_snapshot,
4954                &base_text_snapshot,
4955                Some(Arc::from(index_text)),
4956                cx,
4957            )
4958        })
4959        .await;
4960    unstaged_diff.update(cx, |diff, cx| diff.set_snapshot(update, cx));
4961
4962    let base_text_buffer =
4963        uncommitted_diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4964
4965    let multibuffer = cx.new(|cx| {
4966        let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4967        multibuffer.set_all_diff_hunks_expanded(cx);
4968        multibuffer.add_inverted_diff(uncommitted_diff.clone(), buffer.clone(), cx);
4969        multibuffer
4970    });
4971
4972    let _hunks: Vec<_> = multibuffer
4973        .read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx))
4974        .diff_hunks()
4975        .collect();
4976}
4977
4978#[track_caller]
4979fn assert_excerpts_match(
4980    multibuffer: &Entity<MultiBuffer>,
4981    cx: &mut TestAppContext,
4982    expected: &str,
4983) {
4984    let mut output = String::new();
4985    multibuffer.read_with(cx, |multibuffer, cx| {
4986        let snapshot = multibuffer.snapshot(cx);
4987        for excerpt in multibuffer.snapshot(cx).excerpts() {
4988            output.push_str("-----\n");
4989            output.extend(
4990                snapshot
4991                    .buffer_for_id(excerpt.context.start.buffer_id)
4992                    .unwrap()
4993                    .text_for_range(excerpt.context),
4994            );
4995            if !output.ends_with('\n') {
4996                output.push('\n');
4997            }
4998        }
4999    });
5000    assert_eq!(output, expected);
5001}
5002
5003#[track_caller]
5004fn assert_new_snapshot(
5005    multibuffer: &Entity<MultiBuffer>,
5006    snapshot: &mut MultiBufferSnapshot,
5007    subscription: &mut Subscription<MultiBufferOffset>,
5008    cx: &mut TestAppContext,
5009    expected_diff: &str,
5010) {
5011    let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
5012    let actual_text = new_snapshot.text();
5013    let line_infos = new_snapshot
5014        .row_infos(MultiBufferRow(0))
5015        .collect::<Vec<_>>();
5016    let actual_diff = format_diff(&actual_text, &line_infos, &Default::default(), None);
5017    pretty_assertions::assert_eq!(actual_diff, expected_diff);
5018    check_edits(
5019        snapshot,
5020        &new_snapshot,
5021        &subscription.consume().into_inner(),
5022    );
5023    *snapshot = new_snapshot;
5024}
5025
5026#[track_caller]
5027fn check_edits(
5028    old_snapshot: &MultiBufferSnapshot,
5029    new_snapshot: &MultiBufferSnapshot,
5030    edits: &[Edit<MultiBufferOffset>],
5031) {
5032    let mut text = old_snapshot.text();
5033    let new_text = new_snapshot.text();
5034    for edit in edits.iter().rev() {
5035        if !text.is_char_boundary(edit.old.start.0)
5036            || !text.is_char_boundary(edit.old.end.0)
5037            || !new_text.is_char_boundary(edit.new.start.0)
5038            || !new_text.is_char_boundary(edit.new.end.0)
5039        {
5040            panic!(
5041                "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
5042                edits, text, new_text
5043            );
5044        }
5045
5046        text.replace_range(
5047            edit.old.start.0..edit.old.end.0,
5048            &new_text[edit.new.start.0..edit.new.end.0],
5049        );
5050    }
5051
5052    pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
5053}
5054
5055#[track_caller]
5056fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
5057    let full_text = snapshot.text();
5058    for ix in 0..full_text.len() {
5059        let mut chunks = snapshot.chunks(
5060            MultiBufferOffset(0)..snapshot.len(),
5061            LanguageAwareStyling {
5062                tree_sitter: false,
5063                diagnostics: false,
5064            },
5065        );
5066        chunks.seek(MultiBufferOffset(ix)..snapshot.len());
5067        let tail = chunks.map(|chunk| chunk.text).collect::<String>();
5068        assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
5069    }
5070}
5071
5072#[track_caller]
5073fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
5074    let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
5075    for start_row in 1..all_line_numbers.len() {
5076        let line_numbers = snapshot
5077            .row_infos(MultiBufferRow(start_row as u32))
5078            .collect::<Vec<_>>();
5079        assert_eq!(
5080            line_numbers,
5081            all_line_numbers[start_row..],
5082            "start_row: {start_row}"
5083        );
5084    }
5085}
5086
5087#[track_caller]
5088fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
5089    let text = Rope::from(snapshot.text());
5090
5091    let mut left_anchors = Vec::new();
5092    let mut right_anchors = Vec::new();
5093    let mut offsets = Vec::new();
5094    let mut points = Vec::new();
5095    for offset in 0..=text.len() + 1 {
5096        let offset = MultiBufferOffset(offset);
5097        let clipped_left = snapshot.clip_offset(offset, Bias::Left);
5098        let clipped_right = snapshot.clip_offset(offset, Bias::Right);
5099        assert_eq!(
5100            clipped_left.0,
5101            text.clip_offset(offset.0, Bias::Left),
5102            "clip_offset({offset:?}, Left)"
5103        );
5104        assert_eq!(
5105            clipped_right.0,
5106            text.clip_offset(offset.0, Bias::Right),
5107            "clip_offset({offset:?}, Right)"
5108        );
5109        assert_eq!(
5110            snapshot.offset_to_point(clipped_left),
5111            text.offset_to_point(clipped_left.0),
5112            "offset_to_point({})",
5113            clipped_left.0
5114        );
5115        assert_eq!(
5116            snapshot.offset_to_point(clipped_right),
5117            text.offset_to_point(clipped_right.0),
5118            "offset_to_point({})",
5119            clipped_right.0
5120        );
5121        let anchor_after = snapshot.anchor_after(clipped_left);
5122        assert_eq!(
5123            anchor_after.to_offset(snapshot),
5124            clipped_left,
5125            "anchor_after({}).to_offset {anchor_after:?}",
5126            clipped_left.0
5127        );
5128        let anchor_before = snapshot.anchor_before(clipped_left);
5129        assert_eq!(
5130            anchor_before.to_offset(snapshot),
5131            clipped_left,
5132            "anchor_before({}).to_offset",
5133            clipped_left.0
5134        );
5135        left_anchors.push(anchor_before);
5136        right_anchors.push(anchor_after);
5137        offsets.push(clipped_left);
5138        points.push(text.offset_to_point(clipped_left.0));
5139    }
5140
5141    for row in 0..text.max_point().row {
5142        for column in 0..text.line_len(row) + 1 {
5143            let point = Point { row, column };
5144            let clipped_left = snapshot.clip_point(point, Bias::Left);
5145            let clipped_right = snapshot.clip_point(point, Bias::Right);
5146            assert_eq!(
5147                clipped_left,
5148                text.clip_point(point, Bias::Left),
5149                "clip_point({point:?}, Left)"
5150            );
5151            assert_eq!(
5152                clipped_right,
5153                text.clip_point(point, Bias::Right),
5154                "clip_point({point:?}, Right)"
5155            );
5156            assert_eq!(
5157                snapshot.point_to_offset(clipped_left).0,
5158                text.point_to_offset(clipped_left),
5159                "point_to_offset({clipped_left:?})"
5160            );
5161            assert_eq!(
5162                snapshot.point_to_offset(clipped_right).0,
5163                text.point_to_offset(clipped_right),
5164                "point_to_offset({clipped_right:?})"
5165            );
5166        }
5167    }
5168
5169    assert_eq!(
5170        snapshot.summaries_for_anchors::<MultiBufferOffset, _>(&left_anchors),
5171        offsets,
5172        "left_anchors <-> offsets"
5173    );
5174    assert_eq!(
5175        snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
5176        points,
5177        "left_anchors <-> points"
5178    );
5179    assert_eq!(
5180        snapshot.summaries_for_anchors::<MultiBufferOffset, _>(&right_anchors),
5181        offsets,
5182        "right_anchors <-> offsets"
5183    );
5184    assert_eq!(
5185        snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
5186        points,
5187        "right_anchors <-> points"
5188    );
5189
5190    for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
5191        for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
5192            if ix > 0 && *offset == MultiBufferOffset(252) && offset > &offsets[ix - 1] {
5193                let prev_anchor = left_anchors[ix - 1];
5194                assert!(
5195                    anchor.cmp(&prev_anchor, snapshot).is_gt(),
5196                    "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
5197                    offsets[ix],
5198                    offsets[ix - 1],
5199                );
5200                assert!(
5201                    prev_anchor.cmp(anchor, snapshot).is_lt(),
5202                    "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
5203                    offsets[ix - 1],
5204                    offsets[ix],
5205                );
5206            }
5207        }
5208    }
5209
5210    if let Some((buffer, offset)) = snapshot.point_to_buffer_offset(snapshot.max_point()) {
5211        assert!(offset.0 <= buffer.len());
5212    }
5213    if let Some((buffer, point)) = snapshot.point_to_buffer_point(snapshot.max_point()) {
5214        assert!(point <= buffer.max_point());
5215    }
5216}
5217
5218fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
5219    let max_row = snapshot.max_point().row;
5220    let buffer_id = snapshot.excerpts().next().unwrap().context.start.buffer_id;
5221    let text = text::Buffer::new(ReplicaId::LOCAL, buffer_id, snapshot.text());
5222    let mut line_indents = text
5223        .line_indents_in_row_range(0..max_row + 1)
5224        .collect::<Vec<_>>();
5225    for start_row in 0..snapshot.max_point().row {
5226        pretty_assertions::assert_eq!(
5227            snapshot
5228                .line_indents(MultiBufferRow(start_row), |_| true)
5229                .map(|(row, indent, _)| (row.0, indent))
5230                .collect::<Vec<_>>(),
5231            &line_indents[(start_row as usize)..],
5232            "line_indents({start_row})"
5233        );
5234    }
5235
5236    line_indents.reverse();
5237    pretty_assertions::assert_eq!(
5238        snapshot
5239            .reversed_line_indents(MultiBufferRow(max_row), |_| true)
5240            .map(|(row, indent, _)| (row.0, indent))
5241            .collect::<Vec<_>>(),
5242        &line_indents[..],
5243        "reversed_line_indents({max_row})"
5244    );
5245}
5246
5247#[gpui::test]
5248fn test_new_empty_buffer_uses_untitled_title(cx: &mut App) {
5249    let buffer = cx.new(|cx| Buffer::local("", cx));
5250    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5251
5252    assert_eq!(multibuffer.read(cx).title(cx), "untitled");
5253}
5254
5255#[gpui::test]
5256fn test_new_empty_buffer_uses_untitled_title_when_only_contains_whitespace(cx: &mut App) {
5257    let buffer = cx.new(|cx| Buffer::local("\n ", cx));
5258    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5259
5260    assert_eq!(multibuffer.read(cx).title(cx), "untitled");
5261}
5262
5263#[gpui::test]
5264fn test_new_empty_buffer_takes_first_line_for_title(cx: &mut App) {
5265    let buffer = cx.new(|cx| Buffer::local("Hello World\nSecond line", cx));
5266    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5267
5268    assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
5269}
5270
5271#[gpui::test]
5272fn test_new_empty_buffer_takes_trimmed_first_line_for_title(cx: &mut App) {
5273    let buffer = cx.new(|cx| Buffer::local("\nHello, World ", cx));
5274    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5275
5276    assert_eq!(multibuffer.read(cx).title(cx), "Hello, World");
5277}
5278
5279#[gpui::test]
5280fn test_new_empty_buffer_uses_truncated_first_line_for_title(cx: &mut App) {
5281    let title = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee";
5282    let title_after = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd";
5283    let buffer = cx.new(|cx| Buffer::local(title, cx));
5284    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5285
5286    assert_eq!(multibuffer.read(cx).title(cx), title_after);
5287}
5288
5289#[gpui::test]
5290fn test_new_empty_buffer_uses_truncated_first_line_for_title_after_merging_adjacent_spaces(
5291    cx: &mut App,
5292) {
5293    let title = "aaaaaaaaaabbbbbbbbbb    ccccccccccddddddddddeeeeeeeeee";
5294    let title_after = "aaaaaaaaaabbbbbbbbbb ccccccccccddddddddd";
5295    let buffer = cx.new(|cx| Buffer::local(title, cx));
5296    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5297
5298    assert_eq!(multibuffer.read(cx).title(cx), title_after);
5299}
5300
5301#[gpui::test]
5302fn test_new_empty_buffers_title_can_be_set(cx: &mut App) {
5303    let buffer = cx.new(|cx| Buffer::local("Hello World", cx));
5304    let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5305    assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
5306
5307    multibuffer.update(cx, |multibuffer, cx| {
5308        multibuffer.set_title("Hey".into(), cx)
5309    });
5310    assert_eq!(multibuffer.read(cx).title(cx), "Hey");
5311}
5312
5313#[gpui::test(iterations = 100)]
5314fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) {
5315    let multibuffer = if rng.random() {
5316        let len = rng.random_range(0..10000);
5317        let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
5318        let buffer = cx.new(|cx| Buffer::local(text, cx));
5319        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
5320    } else {
5321        MultiBuffer::build_random(&mut rng, cx)
5322    };
5323
5324    let snapshot = multibuffer.read(cx).snapshot(cx);
5325
5326    let chunks = snapshot.chunks(
5327        MultiBufferOffset(0)..snapshot.len(),
5328        LanguageAwareStyling {
5329            tree_sitter: false,
5330            diagnostics: false,
5331        },
5332    );
5333
5334    for chunk in chunks {
5335        let chunk_text = chunk.text;
5336        let chars_bitmap = chunk.chars;
5337        let tabs_bitmap = chunk.tabs;
5338
5339        if chunk_text.is_empty() {
5340            assert_eq!(
5341                chars_bitmap, 0,
5342                "Empty chunk should have empty chars bitmap"
5343            );
5344            assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
5345            continue;
5346        }
5347
5348        assert!(
5349            chunk_text.len() <= 128,
5350            "Chunk text length {} exceeds 128 bytes",
5351            chunk_text.len()
5352        );
5353
5354        // Verify chars bitmap
5355        let char_indices = chunk_text
5356            .char_indices()
5357            .map(|(i, _)| i)
5358            .collect::<Vec<_>>();
5359
5360        for byte_idx in 0..chunk_text.len() {
5361            let should_have_bit = char_indices.contains(&byte_idx);
5362            let has_bit = chars_bitmap & (1 << byte_idx) != 0;
5363
5364            if has_bit != should_have_bit {
5365                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5366                eprintln!("Char indices: {:?}", char_indices);
5367                eprintln!("Chars bitmap: {:#b}", chars_bitmap);
5368            }
5369
5370            assert_eq!(
5371                has_bit, should_have_bit,
5372                "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
5373                byte_idx, chunk_text, should_have_bit, has_bit
5374            );
5375        }
5376
5377        for (byte_idx, byte) in chunk_text.bytes().enumerate() {
5378            let is_tab = byte == b'\t';
5379            let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
5380
5381            if has_bit != is_tab {
5382                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5383                eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
5384                assert_eq!(
5385                    has_bit, is_tab,
5386                    "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
5387                    byte_idx, chunk_text, byte as char, is_tab, has_bit
5388                );
5389            }
5390        }
5391    }
5392}
5393
5394#[gpui::test(iterations = 10)]
5395fn test_random_chunk_bitmaps_with_diffs(cx: &mut App, mut rng: StdRng) {
5396    let settings_store = SettingsStore::test(cx);
5397    cx.set_global(settings_store);
5398    use buffer_diff::BufferDiff;
5399    use util::RandomCharIter;
5400
5401    let multibuffer = if rng.random() {
5402        let len = rng.random_range(100..10000);
5403        let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
5404        let buffer = cx.new(|cx| Buffer::local(text, cx));
5405        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
5406    } else {
5407        MultiBuffer::build_random(&mut rng, cx)
5408    };
5409
5410    let _diff_count = rng.random_range(1..5);
5411    let mut diffs = Vec::new();
5412
5413    multibuffer.update(cx, |multibuffer, cx| {
5414        let snapshot = multibuffer.snapshot(cx);
5415        for buffer_id in snapshot.all_buffer_ids() {
5416            if rng.random_bool(0.7) {
5417                if let Some(buffer_handle) = multibuffer.buffer(buffer_id) {
5418                    let buffer_text = buffer_handle.read(cx).text();
5419                    let mut base_text = String::new();
5420
5421                    for line in buffer_text.lines() {
5422                        if rng.random_bool(0.3) {
5423                            continue;
5424                        } else if rng.random_bool(0.3) {
5425                            let line_len = rng.random_range(0..50);
5426                            let modified_line = RandomCharIter::new(&mut rng)
5427                                .take(line_len)
5428                                .collect::<String>();
5429                            base_text.push_str(&modified_line);
5430                            base_text.push('\n');
5431                        } else {
5432                            base_text.push_str(line);
5433                            base_text.push('\n');
5434                        }
5435                    }
5436
5437                    if rng.random_bool(0.5) {
5438                        let extra_lines = rng.random_range(1..5);
5439                        for _ in 0..extra_lines {
5440                            let line_len = rng.random_range(0..50);
5441                            let extra_line = RandomCharIter::new(&mut rng)
5442                                .take(line_len)
5443                                .collect::<String>();
5444                            base_text.push_str(&extra_line);
5445                            base_text.push('\n');
5446                        }
5447                    }
5448
5449                    let diff = cx.new(|cx| {
5450                        BufferDiff::new_with_base_text(
5451                            &base_text,
5452                            &buffer_handle.read(cx).text_snapshot(),
5453                            cx,
5454                        )
5455                    });
5456                    diffs.push(diff.clone());
5457                    multibuffer.add_diff(diff, cx);
5458                }
5459            }
5460        }
5461    });
5462
5463    multibuffer.update(cx, |multibuffer, cx| {
5464        if rng.random_bool(0.5) {
5465            multibuffer.set_all_diff_hunks_expanded(cx);
5466        } else {
5467            let snapshot = multibuffer.snapshot(cx);
5468            let text = snapshot.text();
5469
5470            let mut ranges = Vec::new();
5471            for _ in 0..rng.random_range(1..5) {
5472                if snapshot.len().0 == 0 {
5473                    break;
5474                }
5475
5476                let diff_size = rng.random_range(5..1000);
5477                let mut start = rng.random_range(0..snapshot.len().0);
5478
5479                while !text.is_char_boundary(start) {
5480                    start = start.saturating_sub(1);
5481                }
5482
5483                let mut end = rng.random_range(start..snapshot.len().0.min(start + diff_size));
5484
5485                while !text.is_char_boundary(end) {
5486                    end = end.saturating_add(1);
5487                }
5488                let start_anchor = snapshot.anchor_after(MultiBufferOffset(start));
5489                let end_anchor = snapshot.anchor_before(MultiBufferOffset(end));
5490                ranges.push(start_anchor..end_anchor);
5491            }
5492            multibuffer.expand_diff_hunks(ranges, cx);
5493        }
5494    });
5495
5496    let snapshot = multibuffer.read(cx).snapshot(cx);
5497
5498    let chunks = snapshot.chunks(
5499        MultiBufferOffset(0)..snapshot.len(),
5500        LanguageAwareStyling {
5501            tree_sitter: false,
5502            diagnostics: false,
5503        },
5504    );
5505
5506    for chunk in chunks {
5507        let chunk_text = chunk.text;
5508        let chars_bitmap = chunk.chars;
5509        let tabs_bitmap = chunk.tabs;
5510
5511        if chunk_text.is_empty() {
5512            assert_eq!(
5513                chars_bitmap, 0,
5514                "Empty chunk should have empty chars bitmap"
5515            );
5516            assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
5517            continue;
5518        }
5519
5520        assert!(
5521            chunk_text.len() <= 128,
5522            "Chunk text length {} exceeds 128 bytes",
5523            chunk_text.len()
5524        );
5525
5526        let char_indices = chunk_text
5527            .char_indices()
5528            .map(|(i, _)| i)
5529            .collect::<Vec<_>>();
5530
5531        for byte_idx in 0..chunk_text.len() {
5532            let should_have_bit = char_indices.contains(&byte_idx);
5533            let has_bit = chars_bitmap & (1 << byte_idx) != 0;
5534
5535            if has_bit != should_have_bit {
5536                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5537                eprintln!("Char indices: {:?}", char_indices);
5538                eprintln!("Chars bitmap: {:#b}", chars_bitmap);
5539            }
5540
5541            assert_eq!(
5542                has_bit, should_have_bit,
5543                "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
5544                byte_idx, chunk_text, should_have_bit, has_bit
5545            );
5546        }
5547
5548        for (byte_idx, byte) in chunk_text.bytes().enumerate() {
5549            let is_tab = byte == b'\t';
5550            let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
5551
5552            if has_bit != is_tab {
5553                eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5554                eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
5555                assert_eq!(
5556                    has_bit, is_tab,
5557                    "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
5558                    byte_idx, chunk_text, byte as char, is_tab, has_bit
5559                );
5560            }
5561        }
5562    }
5563}
5564
5565fn collect_word_diffs(
5566    base_text: &str,
5567    modified_text: &str,
5568    cx: &mut TestAppContext,
5569) -> Vec<String> {
5570    let buffer = cx.new(|cx| Buffer::local(modified_text, cx));
5571    let diff = cx
5572        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
5573    cx.run_until_parked();
5574
5575    let multibuffer = cx.new(|cx| {
5576        let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
5577        multibuffer.add_diff(diff.clone(), cx);
5578        multibuffer
5579    });
5580
5581    multibuffer.update(cx, |multibuffer, cx| {
5582        multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
5583    });
5584
5585    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
5586    let text = snapshot.text();
5587
5588    snapshot
5589        .diff_hunks()
5590        .flat_map(|hunk| hunk.word_diffs)
5591        .map(|range| text[range.start.0..range.end.0].to_string())
5592        .collect()
5593}
5594
5595#[gpui::test]
5596async fn test_word_diff_simple_replacement(cx: &mut TestAppContext) {
5597    let settings_store = cx.update(|cx| SettingsStore::test(cx));
5598    cx.set_global(settings_store);
5599
5600    let base_text = "hello world foo bar\n";
5601    let modified_text = "hello WORLD foo BAR\n";
5602
5603    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5604
5605    assert_eq!(word_diffs, vec!["world", "bar", "WORLD", "BAR"]);
5606}
5607
5608#[gpui::test]
5609async fn test_word_diff_white_space(cx: &mut TestAppContext) {
5610    let settings_store = cx.update(|cx| SettingsStore::test(cx));
5611    cx.set_global(settings_store);
5612
5613    let base_text = "hello world foo bar\n";
5614    let modified_text = "    hello world foo bar\n";
5615
5616    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5617
5618    assert_eq!(word_diffs, vec!["    "]);
5619}
5620
5621#[gpui::test]
5622async fn test_word_diff_consecutive_modified_lines(cx: &mut TestAppContext) {
5623    let settings_store = cx.update(|cx| SettingsStore::test(cx));
5624    cx.set_global(settings_store);
5625
5626    let base_text = "aaa bbb\nccc ddd\n";
5627    let modified_text = "aaa BBB\nccc DDD\n";
5628
5629    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5630
5631    assert_eq!(
5632        word_diffs,
5633        vec!["bbb", "ddd", "BBB", "DDD"],
5634        "consecutive modified lines should produce word diffs when line counts match"
5635    );
5636}
5637
5638#[gpui::test]
5639async fn test_word_diff_modified_lines_with_deletion_between(cx: &mut TestAppContext) {
5640    let settings_store = cx.update(|cx| SettingsStore::test(cx));
5641    cx.set_global(settings_store);
5642
5643    let base_text = "aaa bbb\ndeleted line\nccc ddd\n";
5644    let modified_text = "aaa BBB\nccc DDD\n";
5645
5646    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5647
5648    assert_eq!(
5649        word_diffs,
5650        Vec::<String>::new(),
5651        "modified lines with a deleted line between should not produce word diffs"
5652    );
5653}
5654
5655#[gpui::test]
5656async fn test_word_diff_disabled(cx: &mut TestAppContext) {
5657    let settings_store = cx.update(|cx| {
5658        let mut settings_store = SettingsStore::test(cx);
5659        settings_store.update_user_settings(cx, |settings| {
5660            settings.project.all_languages.defaults.word_diff_enabled = Some(false);
5661        });
5662        settings_store
5663    });
5664    cx.set_global(settings_store);
5665
5666    let base_text = "hello world\n";
5667    let modified_text = "hello WORLD\n";
5668
5669    let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5670
5671    assert_eq!(
5672        word_diffs,
5673        Vec::<String>::new(),
5674        "word diffs should be empty when disabled"
5675    );
5676}
5677
5678/// Tests `excerpt_containing` and `excerpts_for_range` (functions mapping multi-buffer text-coordinates to excerpts)
5679#[gpui::test]
5680fn test_excerpts_containment_functions(cx: &mut App) {
5681    // Multibuffer content for these tests:
5682    //    0123
5683    // 0: aa0
5684    // 1: aa1
5685    //    -----
5686    // 2: bb0
5687    // 3: bb1
5688    //    -----MultiBufferOffset(0)..
5689    // 4: cc0
5690
5691    let buffer_1 = cx.new(|cx| Buffer::local("aa0\naa1", cx));
5692    let buffer_2 = cx.new(|cx| Buffer::local("bb0\nbb1", cx));
5693    let buffer_3 = cx.new(|cx| Buffer::local("cc0", cx));
5694
5695    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5696
5697    let (excerpt_1_info, excerpt_2_info, excerpt_3_info) =
5698        multibuffer.update(cx, |multibuffer, cx| {
5699            multibuffer.set_excerpts_for_path(
5700                PathKey::sorted(0),
5701                buffer_1.clone(),
5702                [Point::new(0, 0)..Point::new(1, 3)],
5703                0,
5704                cx,
5705            );
5706
5707            multibuffer.set_excerpts_for_path(
5708                PathKey::sorted(1),
5709                buffer_2.clone(),
5710                [Point::new(0, 0)..Point::new(1, 3)],
5711                0,
5712                cx,
5713            );
5714
5715            multibuffer.set_excerpts_for_path(
5716                PathKey::sorted(2),
5717                buffer_3.clone(),
5718                [Point::new(0, 0)..Point::new(0, 3)],
5719                0,
5720                cx,
5721            );
5722
5723            let snapshot = multibuffer.snapshot(cx);
5724            let mut excerpts = snapshot.excerpts();
5725            (
5726                excerpts.next().unwrap(),
5727                excerpts.next().unwrap(),
5728                excerpts.next().unwrap(),
5729            )
5730        });
5731
5732    let snapshot = multibuffer.read(cx).snapshot(cx);
5733
5734    assert_eq!(snapshot.text(), "aa0\naa1\nbb0\nbb1\ncc0");
5735
5736    //// Test `excerpts_for_range`
5737
5738    let p00 = snapshot.point_to_offset(Point::new(0, 0));
5739    let p10 = snapshot.point_to_offset(Point::new(1, 0));
5740    let p20 = snapshot.point_to_offset(Point::new(2, 0));
5741    let p23 = snapshot.point_to_offset(Point::new(2, 3));
5742    let p13 = snapshot.point_to_offset(Point::new(1, 3));
5743    let p40 = snapshot.point_to_offset(Point::new(4, 0));
5744    let p43 = snapshot.point_to_offset(Point::new(4, 3));
5745
5746    let excerpts: Vec<_> = snapshot.excerpts_for_range(p00..p00).collect();
5747    assert_eq!(excerpts.len(), 1);
5748    assert_eq!(excerpts[0].range, excerpt_1_info);
5749
5750    // Cursor at very end of excerpt 3
5751    let excerpts: Vec<_> = snapshot.excerpts_for_range(p43..p43).collect();
5752    assert_eq!(excerpts.len(), 1);
5753    assert_eq!(excerpts[0].range, excerpt_3_info);
5754
5755    let excerpts: Vec<_> = snapshot.excerpts_for_range(p00..p23).collect();
5756    assert_eq!(excerpts.len(), 2);
5757    assert_eq!(excerpts[0].range, excerpt_1_info);
5758    assert_eq!(excerpts[1].range, excerpt_2_info);
5759
5760    // This range represent an selection with end-point just inside excerpt_2
5761    // Today we only expand the first excerpt, but another interpretation that
5762    // we could consider is expanding both here
5763    let excerpts: Vec<_> = snapshot.excerpts_for_range(p10..p20).collect();
5764    assert_eq!(excerpts.len(), 1);
5765    assert_eq!(excerpts[0].range, excerpt_1_info);
5766
5767    //// Test that `excerpts_for_range` and `excerpt_containing` agree for all single offsets (cursor positions)
5768    for offset in 0..=snapshot.len().0 {
5769        let offset = MultiBufferOffset(offset);
5770        let excerpts_for_range: Vec<_> = snapshot.excerpts_for_range(offset..offset).collect();
5771        assert_eq!(
5772            excerpts_for_range.len(),
5773            1,
5774            "Expected exactly one excerpt for offset {offset}",
5775        );
5776
5777        let (_, excerpt_containing) =
5778            snapshot
5779                .excerpt_containing(offset..offset)
5780                .unwrap_or_else(|| {
5781                    panic!("Expected excerpt_containing to find excerpt for offset {offset}")
5782                });
5783
5784        assert_eq!(
5785            excerpts_for_range[0].range, excerpt_containing,
5786            "excerpts_for_range and excerpt_containing should agree for offset {offset}",
5787        );
5788    }
5789
5790    //// Test `excerpt_containing` behavior with ranges:
5791
5792    // Ranges intersecting a single-excerpt
5793    let (_, containing) = snapshot.excerpt_containing(p00..p13).unwrap();
5794    assert_eq!(containing, excerpt_1_info);
5795
5796    // Ranges intersecting multiple excerpts (should return None)
5797    let containing = snapshot.excerpt_containing(p20..p40);
5798    assert!(
5799        containing.is_none(),
5800        "excerpt_containing should return None for ranges spanning multiple excerpts"
5801    );
5802}
5803
5804#[gpui::test]
5805fn test_range_to_buffer_ranges(cx: &mut App) {
5806    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb", cx));
5807    let buffer_2 = cx.new(|cx| Buffer::local("ccc", cx));
5808
5809    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5810    multibuffer.update(cx, |multibuffer, cx| {
5811        multibuffer.set_excerpts_for_path(
5812            PathKey::sorted(0),
5813            buffer_1.clone(),
5814            [Point::new(0, 0)..Point::new(1, 3)],
5815            0,
5816            cx,
5817        );
5818
5819        multibuffer.set_excerpts_for_path(
5820            PathKey::sorted(1),
5821            buffer_2.clone(),
5822            [Point::new(0, 0)..Point::new(0, 3)],
5823            0,
5824            cx,
5825        );
5826    });
5827
5828    let snapshot = multibuffer.read(cx).snapshot(cx);
5829    assert_eq!(snapshot.text(), "aaa\nbbb\nccc");
5830
5831    let excerpt_2_start = Point::new(2, 0);
5832
5833    let ranges_half_open = snapshot.range_to_buffer_ranges(Point::zero()..excerpt_2_start);
5834    assert_eq!(
5835        ranges_half_open.len(),
5836        1,
5837        "Half-open range ending at excerpt start should EXCLUDE that excerpt"
5838    );
5839    assert_eq!(ranges_half_open[0].1, BufferOffset(0)..BufferOffset(7));
5840    assert_eq!(
5841        ranges_half_open[0].0.remote_id(),
5842        buffer_1.read(cx).remote_id()
5843    );
5844
5845    let buffer_empty = cx.new(|cx| Buffer::local("", cx));
5846    let multibuffer_trailing_empty = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5847    let (_te_excerpt_1_info, _te_excerpt_2_info) =
5848        multibuffer_trailing_empty.update(cx, |multibuffer, cx| {
5849            multibuffer.set_excerpts_for_path(
5850                PathKey::sorted(0),
5851                buffer_1.clone(),
5852                [Point::new(0, 0)..Point::new(1, 3)],
5853                0,
5854                cx,
5855            );
5856
5857            multibuffer.set_excerpts_for_path(
5858                PathKey::sorted(1),
5859                buffer_empty.clone(),
5860                [Point::new(0, 0)..Point::new(0, 0)],
5861                0,
5862                cx,
5863            );
5864
5865            let snapshot = multibuffer.snapshot(cx);
5866            let mut infos = snapshot.excerpts();
5867            (infos.next().unwrap(), infos.next().unwrap())
5868        });
5869
5870    let snapshot_trailing = multibuffer_trailing_empty.read(cx).snapshot(cx);
5871    assert_eq!(snapshot_trailing.text(), "aaa\nbbb\n");
5872
5873    let max_point = snapshot_trailing.max_point();
5874
5875    let ranges_half_open_max = snapshot_trailing.range_to_buffer_ranges(Point::zero()..max_point);
5876    assert_eq!(
5877        ranges_half_open_max.len(),
5878        2,
5879        "Should include trailing empty excerpts"
5880    );
5881    assert_eq!(ranges_half_open_max[1].1, BufferOffset(0)..BufferOffset(0));
5882}
5883
5884#[gpui::test]
5885fn test_range_to_buffer_ranges_zero_length_at_excerpt_boundary(cx: &mut App) {
5886    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb", cx));
5887    let buffer_2 = cx.new(|cx| Buffer::local("ccc\nddd", cx));
5888
5889    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5890    multibuffer.update(cx, |multibuffer, cx| {
5891        multibuffer.set_excerpts_for_path(
5892            PathKey::sorted(0),
5893            buffer_1.clone(),
5894            [Point::new(0, 0)..Point::new(1, 3)],
5895            0,
5896            cx,
5897        );
5898        multibuffer.set_excerpts_for_path(
5899            PathKey::sorted(1),
5900            buffer_2.clone(),
5901            [Point::new(0, 0)..Point::new(1, 3)],
5902            0,
5903            cx,
5904        );
5905    });
5906
5907    let snapshot = multibuffer.read(cx).snapshot(cx);
5908    assert_eq!(snapshot.text(), "aaa\nbbb\nccc\nddd");
5909
5910    // This point is right at the start of the very first excerpt, so if we get
5911    // a buffer range, we should get `0..0`
5912    let excerpt_2_start = Point::new(2, 0);
5913    let expected_ranges = vec![BufferOffset(0)..BufferOffset(0)];
5914    let ranges = snapshot
5915        .range_to_buffer_ranges(excerpt_2_start..excerpt_2_start)
5916        .into_iter()
5917        .map(|tup| tup.1)
5918        .collect_vec();
5919
5920    assert_eq!(
5921        ranges, expected_ranges,
5922        "Zero-length range at excerpt boundary should return the excerpt at that point"
5923    );
5924}
5925
5926#[gpui::test]
5927async fn test_buffer_range_to_excerpt_ranges(cx: &mut TestAppContext) {
5928    let base_text = indoc!(
5929        "
5930        aaa
5931        bbb
5932        ccc
5933        ddd
5934        eee
5935        ppp
5936        qqq
5937        rrr
5938        fff
5939        ggg
5940        hhh
5941        "
5942    );
5943    let text = indoc!(
5944        "
5945        aaa
5946        BBB
5947        ddd
5948        eee
5949        ppp
5950        qqq
5951        rrr
5952        FFF
5953        ggg
5954        hhh
5955        "
5956    );
5957
5958    let buffer = cx.new(|cx| Buffer::local(text, cx));
5959    let diff = cx
5960        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
5961    cx.run_until_parked();
5962
5963    let multibuffer = cx.new(|cx| {
5964        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
5965        multibuffer.set_excerpts_for_path(
5966            PathKey::sorted(0),
5967            buffer.clone(),
5968            [
5969                Point::new(0, 0)..Point::new(3, 3),
5970                Point::new(7, 0)..Point::new(9, 3),
5971            ],
5972            0,
5973            cx,
5974        );
5975        multibuffer.add_diff(diff.clone(), cx);
5976        multibuffer
5977    });
5978
5979    multibuffer.update(cx, |multibuffer, cx| {
5980        multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
5981    });
5982    cx.run_until_parked();
5983
5984    let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
5985
5986    let actual_diff = format_diff(
5987        &snapshot.text(),
5988        &snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
5989        &Default::default(),
5990        None,
5991    );
5992    let expected_diff = indoc!(
5993        "
5994          aaa
5995        - bbb
5996        - ccc
5997        + BBB
5998          ddd
5999          eee [\u{2193}]
6000        - fff [\u{2191}]
6001        + FFF
6002          ggg
6003          hhh [\u{2193}]"
6004    );
6005    pretty_assertions::assert_eq!(actual_diff, expected_diff);
6006
6007    let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
6008
6009    let query_spanning_deleted_hunk = buffer_snapshot.anchor_after(Point::new(0, 0))
6010        ..buffer_snapshot.anchor_before(Point::new(1, 3));
6011    assert_eq!(
6012        snapshot
6013            .buffer_range_to_excerpt_ranges(query_spanning_deleted_hunk)
6014            .map(|range| range.to_point(&snapshot))
6015            .collect::<Vec<_>>(),
6016        vec![
6017            Point::new(0, 0)..Point::new(1, 0),
6018            Point::new(3, 0)..Point::new(3, 3),
6019        ],
6020    );
6021
6022    let query_within_contiguous_main_buffer = buffer_snapshot.anchor_after(Point::new(1, 0))
6023        ..buffer_snapshot.anchor_before(Point::new(2, 3));
6024    assert_eq!(
6025        snapshot
6026            .buffer_range_to_excerpt_ranges(query_within_contiguous_main_buffer)
6027            .map(|range| range.to_point(&snapshot))
6028            .collect::<Vec<_>>(),
6029        vec![Point::new(3, 0)..Point::new(4, 3)],
6030    );
6031
6032    let query_spanning_both_excerpts = buffer_snapshot.anchor_after(Point::new(2, 0))
6033        ..buffer_snapshot.anchor_before(Point::new(8, 3));
6034    assert_eq!(
6035        snapshot
6036            .buffer_range_to_excerpt_ranges(query_spanning_both_excerpts)
6037            .map(|range| range.to_point(&snapshot))
6038            .collect::<Vec<_>>(),
6039        vec![
6040            Point::new(4, 0)..Point::new(5, 3),
6041            Point::new(7, 0)..Point::new(8, 3),
6042        ],
6043    );
6044}
6045
6046#[gpui::test]
6047fn test_cannot_seek_backward_after_excerpt_replacement(cx: &mut TestAppContext) {
6048    let buffer_b_text: String = (0..50).map(|i| format!("line_b {i}\n")).collect();
6049    let buffer_b = cx.new(|cx| Buffer::local(buffer_b_text, cx));
6050
6051    let buffer_c_text: String = (0..10).map(|i| format!("line_c {i}\n")).collect();
6052    let buffer_c = cx.new(|cx| Buffer::local(buffer_c_text, cx));
6053
6054    let buffer_d_text: String = (0..10).map(|i| format!("line_d {i}\n")).collect();
6055    let buffer_d = cx.new(|cx| Buffer::local(buffer_d_text, cx));
6056
6057    let path_b = PathKey::with_sort_prefix(0, rel_path("bbb.rs").into_arc());
6058    let path_c = PathKey::with_sort_prefix(0, rel_path("ddd.rs").into_arc());
6059    let path_d = PathKey::with_sort_prefix(0, rel_path("ccc.rs").into_arc());
6060
6061    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
6062
6063    multibuffer.update(cx, |multibuffer, cx| {
6064        multibuffer.set_excerpts_for_path(
6065            path_b.clone(),
6066            buffer_b.clone(),
6067            vec![
6068                Point::row_range(0..3),
6069                Point::row_range(15..18),
6070                Point::row_range(30..33),
6071            ],
6072            0,
6073            cx,
6074        );
6075    });
6076
6077    multibuffer.update(cx, |multibuffer, cx| {
6078        multibuffer.set_excerpts_for_path(
6079            path_c.clone(),
6080            buffer_c.clone(),
6081            vec![Point::row_range(0..3)],
6082            0,
6083            cx,
6084        );
6085    });
6086
6087    let (anchor_in_e_b2, anchor_in_e_b3) = multibuffer.read_with(cx, |multibuffer, cx| {
6088        let snapshot = multibuffer.snapshot(cx);
6089        let excerpt_infos = snapshot.excerpts().collect::<Vec<_>>();
6090        assert_eq!(excerpt_infos.len(), 4, "expected 4 excerpts (3×B + 1×C)");
6091
6092        let e_b2_info = excerpt_infos[1].clone();
6093        let e_b3_info = excerpt_infos[2].clone();
6094
6095        let anchor_b2 = snapshot.anchor_in_excerpt(e_b2_info.context.start).unwrap();
6096        let anchor_b3 = snapshot.anchor_in_excerpt(e_b3_info.context.start).unwrap();
6097        (anchor_b2, anchor_b3)
6098    });
6099
6100    multibuffer.update(cx, |multibuffer, cx| {
6101        multibuffer.set_excerpts_for_path(
6102            path_b.clone(),
6103            buffer_b.clone(),
6104            vec![Point::row_range(0..3), Point::row_range(28..36)],
6105            0,
6106            cx,
6107        );
6108    });
6109
6110    multibuffer.update(cx, |multibuffer, cx| {
6111        multibuffer.set_excerpts_for_path(
6112            path_d.clone(),
6113            buffer_d.clone(),
6114            vec![Point::row_range(0..3)],
6115            0,
6116            cx,
6117        );
6118    });
6119
6120    multibuffer.read_with(cx, |multibuffer, cx| {
6121        let snapshot = multibuffer.snapshot(cx);
6122        snapshot.summaries_for_anchors::<Point, _>(&[anchor_in_e_b2, anchor_in_e_b3]);
6123    });
6124}
6125
6126#[gpui::test]
6127fn test_resolving_max_anchor_for_buffer(cx: &mut TestAppContext) {
6128    let dock_base_text = indoc! {"
6129        0
6130        1
6131        2
6132        3
6133        4
6134        5
6135        6
6136        7
6137        8
6138        9
6139        10
6140        11
6141        12
6142    "};
6143
6144    let dock_text = indoc! {"
6145        0
6146        4
6147        5
6148        6
6149        10
6150        11
6151        12
6152    "};
6153
6154    let dock_buffer = cx.new(|cx| Buffer::local(dock_text, cx));
6155    let diff = cx.new(|cx| {
6156        BufferDiff::new_with_base_text(dock_base_text, &dock_buffer.read(cx).snapshot(), cx)
6157    });
6158
6159    let workspace_text = "second buffer\n";
6160    let workspace_buffer = cx.new(|cx| Buffer::local(workspace_text, cx));
6161
6162    let dock_path = PathKey::with_sort_prefix(0, rel_path("").into_arc());
6163    let workspace_path = PathKey::with_sort_prefix(1, rel_path("").into_arc());
6164
6165    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
6166
6167    multibuffer.update(cx, |multibuffer, cx| {
6168        multibuffer.set_excerpt_ranges_for_path(
6169            dock_path,
6170            dock_buffer.clone(),
6171            &dock_buffer.read(cx).snapshot(),
6172            vec![
6173                ExcerptRange::new(Point::zero()..Point::new(1, 1)),
6174                ExcerptRange::new(Point::new(3, 0)..Point::new(4, 2)),
6175            ],
6176            cx,
6177        );
6178        multibuffer.set_excerpt_ranges_for_path(
6179            workspace_path,
6180            workspace_buffer.clone(),
6181            &workspace_buffer.read(cx).snapshot(),
6182            vec![ExcerptRange::new(
6183                Point::zero()..workspace_buffer.read(cx).max_point(),
6184            )],
6185            cx,
6186        );
6187        multibuffer.add_diff(diff, cx);
6188        multibuffer.set_all_diff_hunks_expanded(cx);
6189    });
6190
6191    let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
6192    let diff = format_diff(
6193        &snapshot.text(),
6194        &snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
6195        &Default::default(),
6196        None,
6197    );
6198    assert_eq!(
6199        diff,
6200        indoc! {"
6201            0
6202          - 1
6203          - 2
6204          - 3
6205            4 [↓]
6206            6 [↑]
6207          - 7
6208          - 8
6209          - 9
6210            10 [↓]
6211            second buffer
6212        "}
6213    );
6214
6215    multibuffer.update(cx, |multibuffer, cx| {
6216        let snapshot = multibuffer.snapshot(cx);
6217        let point = snapshot
6218            .anchor_in_buffer(text::Anchor::max_for_buffer(
6219                dock_buffer.read(cx).remote_id(),
6220            ))
6221            .unwrap()
6222            .to_point(&snapshot);
6223        assert_eq!(point, Point::new(10, 0));
6224    })
6225}
6226
6227#[gpui::test]
6228fn test_is_valid_anchor_past_last_excerpt_for_buffer(cx: &mut TestAppContext) {
6229    let buffer_a = cx.new(|cx| Buffer::local("aaa\nbbb\nccc\n", cx));
6230    buffer_a.update(cx, |buffer, cx| {
6231        let len = buffer.len();
6232        buffer.edit([(len..len, "ddd\neee\n")], None, cx);
6233    });
6234    let buffer_b = cx.new(|cx| Buffer::local("xxx\n", cx));
6235    for line in ["yyy\n", "zzz\n", "www\n", "vvv\n"] {
6236        buffer_b.update(cx, |buffer, cx| {
6237            let len = buffer.len();
6238            buffer.edit([(len..len, line)], None, cx);
6239        });
6240    }
6241
6242    let path_a = PathKey::with_sort_prefix(0, rel_path("aaa.rs").into_arc());
6243    let path_b = PathKey::with_sort_prefix(1, rel_path("bbb.rs").into_arc());
6244
6245    let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
6246
6247    multibuffer.update(cx, |multibuffer, cx| {
6248        multibuffer.set_excerpts_for_path(
6249            path_a.clone(),
6250            buffer_a.clone(),
6251            vec![Point::new(1, 0)..Point::new(2, 3)],
6252            0,
6253            cx,
6254        );
6255        multibuffer.set_excerpts_for_path(
6256            path_b.clone(),
6257            buffer_b.clone(),
6258            vec![Point::new(1, 0)..Point::new(3, 3)],
6259            0,
6260            cx,
6261        );
6262    });
6263
6264    multibuffer.read_with(cx, |multibuffer, cx| {
6265        let snapshot = multibuffer.snapshot(cx);
6266
6267        let buffer_a_snapshot = buffer_a.read(cx).snapshot();
6268        let anchor_past_excerpt = buffer_a_snapshot.anchor_after(Point::new(4, 0));
6269        let mb_anchor = snapshot.anchor_in_buffer(anchor_past_excerpt).unwrap();
6270
6271        assert!(
6272            !mb_anchor.is_valid(&snapshot),
6273            "anchor past the last excerpt for its buffer should not be valid"
6274        );
6275    });
6276}
6277
Served at tenant.openagents/omega Member data and write actions are omitted.