1use super::*;
2use git::diff::DiffHunkStatus;
3use gpui::{App, TestAppContext};
4use indoc::indoc;
5use language::{Buffer, Rope};
6use parking_lot::RwLock;
7use rand::prelude::*;
8use settings::SettingsStore;
9use std::env;
10use util::test::sample_text;
11
12#[ctor::ctor]
13fn init_logger() {
14 if std::env::var("RUST_LOG").is_ok() {
15 env_logger::init();
16 }
17}
18
19#[gpui::test]
20fn test_empty_singleton(cx: &mut App) {
21 let buffer = cx.new(|cx| Buffer::local("", cx));
22 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
23 let snapshot = multibuffer.read(cx).snapshot(cx);
24 assert_eq!(snapshot.text(), "");
25 assert_eq!(
26 snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
27 [RowInfo {
28 buffer_row: Some(0),
29 multibuffer_row: Some(MultiBufferRow(0)),
30 diff_status: None
31 }]
32 );
33}
34
35#[gpui::test]
36fn test_singleton(cx: &mut App) {
37 let buffer = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
38 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
39
40 let snapshot = multibuffer.read(cx).snapshot(cx);
41 assert_eq!(snapshot.text(), buffer.read(cx).text());
42
43 assert_eq!(
44 snapshot
45 .row_infos(MultiBufferRow(0))
46 .map(|info| info.buffer_row)
47 .collect::<Vec<_>>(),
48 (0..buffer.read(cx).row_count())
49 .map(Some)
50 .collect::<Vec<_>>()
51 );
52 assert_consistent_line_numbers(&snapshot);
53
54 buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx));
55 let snapshot = multibuffer.read(cx).snapshot(cx);
56
57 assert_eq!(snapshot.text(), buffer.read(cx).text());
58 assert_eq!(
59 snapshot
60 .row_infos(MultiBufferRow(0))
61 .map(|info| info.buffer_row)
62 .collect::<Vec<_>>(),
63 (0..buffer.read(cx).row_count())
64 .map(Some)
65 .collect::<Vec<_>>()
66 );
67 assert_consistent_line_numbers(&snapshot);
68}
69
70#[gpui::test]
71fn test_remote(cx: &mut App) {
72 let host_buffer = cx.new(|cx| Buffer::local("a", cx));
73 let guest_buffer = cx.new(|cx| {
74 let state = host_buffer.read(cx).to_proto(cx);
75 let ops = cx
76 .background_executor()
77 .block(host_buffer.read(cx).serialize_ops(None, cx));
78 let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
79 buffer.apply_ops(
80 ops.into_iter()
81 .map(|op| language::proto::deserialize_operation(op).unwrap()),
82 cx,
83 );
84 buffer
85 });
86 let multibuffer = cx.new(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx));
87 let snapshot = multibuffer.read(cx).snapshot(cx);
88 assert_eq!(snapshot.text(), "a");
89
90 guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx));
91 let snapshot = multibuffer.read(cx).snapshot(cx);
92 assert_eq!(snapshot.text(), "ab");
93
94 guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx));
95 let snapshot = multibuffer.read(cx).snapshot(cx);
96 assert_eq!(snapshot.text(), "abc");
97}
98
99#[gpui::test]
100fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
101 let buffer_1 = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
102 let buffer_2 = cx.new(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
103 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
104
105 let events = Arc::new(RwLock::new(Vec::<Event>::new()));
106 multibuffer.update(cx, |_, cx| {
107 let events = events.clone();
108 cx.subscribe(&multibuffer, move |_, _, event, _| {
109 if let Event::Edited { .. } = event {
110 events.write().push(event.clone())
111 }
112 })
113 .detach();
114 });
115
116 let subscription = multibuffer.update(cx, |multibuffer, cx| {
117 let subscription = multibuffer.subscribe();
118 multibuffer.push_excerpts(
119 buffer_1.clone(),
120 [ExcerptRange {
121 context: Point::new(1, 2)..Point::new(2, 5),
122 primary: None,
123 }],
124 cx,
125 );
126 assert_eq!(
127 subscription.consume().into_inner(),
128 [Edit {
129 old: 0..0,
130 new: 0..10
131 }]
132 );
133
134 multibuffer.push_excerpts(
135 buffer_1.clone(),
136 [ExcerptRange {
137 context: Point::new(3, 3)..Point::new(4, 4),
138 primary: None,
139 }],
140 cx,
141 );
142 multibuffer.push_excerpts(
143 buffer_2.clone(),
144 [ExcerptRange {
145 context: Point::new(3, 1)..Point::new(3, 3),
146 primary: None,
147 }],
148 cx,
149 );
150 assert_eq!(
151 subscription.consume().into_inner(),
152 [Edit {
153 old: 10..10,
154 new: 10..22
155 }]
156 );
157
158 subscription
159 });
160
161 // Adding excerpts emits an edited event.
162 assert_eq!(
163 events.read().as_slice(),
164 &[
165 Event::Edited {
166 singleton_buffer_edited: false,
167 edited_buffer: None,
168 },
169 Event::Edited {
170 singleton_buffer_edited: false,
171 edited_buffer: None,
172 },
173 Event::Edited {
174 singleton_buffer_edited: false,
175 edited_buffer: None,
176 }
177 ]
178 );
179
180 let snapshot = multibuffer.read(cx).snapshot(cx);
181 assert_eq!(
182 snapshot.text(),
183 indoc!(
184 "
185 bbbb
186 ccccc
187 ddd
188 eeee
189 jj"
190 ),
191 );
192 assert_eq!(
193 snapshot
194 .row_infos(MultiBufferRow(0))
195 .map(|info| info.buffer_row)
196 .collect::<Vec<_>>(),
197 [Some(1), Some(2), Some(3), Some(4), Some(3)]
198 );
199 assert_eq!(
200 snapshot
201 .row_infos(MultiBufferRow(2))
202 .map(|info| info.buffer_row)
203 .collect::<Vec<_>>(),
204 [Some(3), Some(4), Some(3)]
205 );
206 assert_eq!(
207 snapshot
208 .row_infos(MultiBufferRow(4))
209 .map(|info| info.buffer_row)
210 .collect::<Vec<_>>(),
211 [Some(3)]
212 );
213 assert_eq!(
214 snapshot
215 .row_infos(MultiBufferRow(5))
216 .map(|info| info.buffer_row)
217 .collect::<Vec<_>>(),
218 []
219 );
220
221 assert_eq!(
222 boundaries_in_range(Point::new(0, 0)..Point::new(4, 2), &snapshot),
223 &[
224 (MultiBufferRow(0), "bbbb\nccccc".to_string(), true),
225 (MultiBufferRow(2), "ddd\neeee".to_string(), false),
226 (MultiBufferRow(4), "jj".to_string(), true),
227 ]
228 );
229 assert_eq!(
230 boundaries_in_range(Point::new(0, 0)..Point::new(2, 0), &snapshot),
231 &[(MultiBufferRow(0), "bbbb\nccccc".to_string(), true)]
232 );
233 assert_eq!(
234 boundaries_in_range(Point::new(1, 0)..Point::new(1, 5), &snapshot),
235 &[]
236 );
237 assert_eq!(
238 boundaries_in_range(Point::new(1, 0)..Point::new(2, 0), &snapshot),
239 &[]
240 );
241 assert_eq!(
242 boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
243 &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
244 );
245 assert_eq!(
246 boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
247 &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
248 );
249 assert_eq!(
250 boundaries_in_range(Point::new(2, 0)..Point::new(3, 0), &snapshot),
251 &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
252 );
253 assert_eq!(
254 boundaries_in_range(Point::new(4, 0)..Point::new(4, 2), &snapshot),
255 &[(MultiBufferRow(4), "jj".to_string(), true)]
256 );
257 assert_eq!(
258 boundaries_in_range(Point::new(4, 2)..Point::new(4, 2), &snapshot),
259 &[]
260 );
261
262 buffer_1.update(cx, |buffer, cx| {
263 let text = "\n";
264 buffer.edit(
265 [
266 (Point::new(0, 0)..Point::new(0, 0), text),
267 (Point::new(2, 1)..Point::new(2, 3), text),
268 ],
269 None,
270 cx,
271 );
272 });
273
274 let snapshot = multibuffer.read(cx).snapshot(cx);
275 assert_eq!(
276 snapshot.text(),
277 concat!(
278 "bbbb\n", // Preserve newlines
279 "c\n", //
280 "cc\n", //
281 "ddd\n", //
282 "eeee\n", //
283 "jj" //
284 )
285 );
286
287 assert_eq!(
288 subscription.consume().into_inner(),
289 [Edit {
290 old: 6..8,
291 new: 6..7
292 }]
293 );
294
295 let snapshot = multibuffer.read(cx).snapshot(cx);
296 assert_eq!(
297 snapshot.clip_point(Point::new(0, 5), Bias::Left),
298 Point::new(0, 4)
299 );
300 assert_eq!(
301 snapshot.clip_point(Point::new(0, 5), Bias::Right),
302 Point::new(0, 4)
303 );
304 assert_eq!(
305 snapshot.clip_point(Point::new(5, 1), Bias::Right),
306 Point::new(5, 1)
307 );
308 assert_eq!(
309 snapshot.clip_point(Point::new(5, 2), Bias::Right),
310 Point::new(5, 2)
311 );
312 assert_eq!(
313 snapshot.clip_point(Point::new(5, 3), Bias::Right),
314 Point::new(5, 2)
315 );
316
317 let snapshot = multibuffer.update(cx, |multibuffer, cx| {
318 let (buffer_2_excerpt_id, _) = multibuffer.excerpts_for_buffer(&buffer_2, cx)[0].clone();
319 multibuffer.remove_excerpts([buffer_2_excerpt_id], cx);
320 multibuffer.snapshot(cx)
321 });
322
323 assert_eq!(
324 snapshot.text(),
325 concat!(
326 "bbbb\n", // Preserve newlines
327 "c\n", //
328 "cc\n", //
329 "ddd\n", //
330 "eeee", //
331 )
332 );
333
334 fn boundaries_in_range(
335 range: Range<Point>,
336 snapshot: &MultiBufferSnapshot,
337 ) -> Vec<(MultiBufferRow, String, bool)> {
338 snapshot
339 .excerpt_boundaries_in_range(range)
340 .filter_map(|boundary| {
341 let starts_new_buffer = boundary.starts_new_buffer();
342 boundary.next.map(|next| {
343 (
344 boundary.row,
345 next.buffer
346 .text_for_range(next.range.context)
347 .collect::<String>(),
348 starts_new_buffer,
349 )
350 })
351 })
352 .collect::<Vec<_>>()
353 }
354}
355
356#[gpui::test]
357fn test_diff_boundary_anchors(cx: &mut App) {
358 let base_text = "one\ntwo\nthree\n";
359 let text = "one\nthree\n";
360 let buffer = cx.new(|cx| Buffer::local(text, cx));
361 let snapshot = buffer.read(cx).snapshot();
362 let change_set = cx.new(|cx| {
363 let mut change_set = BufferChangeSet::new(&buffer, cx);
364 change_set.recalculate_diff_sync(base_text.into(), snapshot.text, true, cx);
365 change_set
366 });
367 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
368 multibuffer.update(cx, |multibuffer, cx| {
369 multibuffer.add_change_set(change_set, cx)
370 });
371
372 let (before, after) = multibuffer.update(cx, |multibuffer, cx| {
373 let before = multibuffer.snapshot(cx).anchor_before(Point::new(1, 0));
374 let after = multibuffer.snapshot(cx).anchor_after(Point::new(1, 0));
375 multibuffer.set_all_diff_hunks_expanded(cx);
376 (before, after)
377 });
378 cx.background_executor().run_until_parked();
379
380 let snapshot = multibuffer.read(cx).snapshot(cx);
381 let actual_text = snapshot.text();
382 let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
383 let actual_diff = format_diff(&actual_text, &actual_row_infos, &Default::default());
384 pretty_assertions::assert_eq!(
385 actual_diff,
386 indoc! {
387 " one
388 - two
389 three
390 "
391 },
392 );
393
394 multibuffer.update(cx, |multibuffer, cx| {
395 let snapshot = multibuffer.snapshot(cx);
396 assert_eq!(before.to_point(&snapshot), Point::new(1, 0));
397 assert_eq!(after.to_point(&snapshot), Point::new(2, 0));
398 assert_eq!(
399 vec![Point::new(1, 0), Point::new(2, 0),],
400 snapshot.summaries_for_anchors::<Point, _>(&[before, after]),
401 )
402 })
403}
404
405#[gpui::test]
406fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
407 let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n";
408 let text = "one\nfour\nseven\n";
409 let buffer = cx.new(|cx| Buffer::local(text, cx));
410 let change_set = cx.new(|cx| {
411 let mut change_set = BufferChangeSet::new(&buffer, cx);
412 let snapshot = buffer.read(cx).snapshot();
413 change_set.recalculate_diff_sync(base_text.into(), snapshot.text, true, cx);
414 change_set
415 });
416 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
417 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
418 (multibuffer.snapshot(cx), multibuffer.subscribe())
419 });
420
421 multibuffer.update(cx, |multibuffer, cx| {
422 multibuffer.add_change_set(change_set, cx);
423 multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
424 });
425
426 assert_new_snapshot(
427 &multibuffer,
428 &mut snapshot,
429 &mut subscription,
430 cx,
431 indoc! {
432 " one
433 - two
434 - three
435 four
436 - five
437 - six
438 seven
439 - eight
440 "
441 },
442 );
443
444 assert_eq!(
445 snapshot
446 .diff_hunks_in_range(Point::new(1, 0)..Point::MAX)
447 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
448 .collect::<Vec<_>>(),
449 vec![1..3, 4..6, 7..8]
450 );
451
452 assert_eq!(
453 snapshot
454 .diff_hunk_before(Point::new(1, 1))
455 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
456 None,
457 );
458 assert_eq!(
459 snapshot
460 .diff_hunk_before(Point::new(7, 0))
461 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
462 Some(4..6)
463 );
464 assert_eq!(
465 snapshot
466 .diff_hunk_before(Point::new(4, 0))
467 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
468 Some(1..3)
469 );
470
471 multibuffer.update(cx, |multibuffer, cx| {
472 multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
473 });
474
475 assert_new_snapshot(
476 &multibuffer,
477 &mut snapshot,
478 &mut subscription,
479 cx,
480 indoc! {
481 "
482 one
483 four
484 seven
485 "
486 },
487 );
488
489 assert_eq!(
490 snapshot
491 .diff_hunk_before(Point::new(2, 0))
492 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
493 Some(1..1),
494 );
495 assert_eq!(
496 snapshot
497 .diff_hunk_before(Point::new(4, 0))
498 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
499 Some(2..2)
500 );
501}
502
503#[gpui::test]
504fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) {
505 let base_text = "one\ntwo\nfour\nfive\nsix\nseven\n";
506 let text = "one\ntwo\nTHREE\nfour\nfive\nseven\n";
507 let buffer = cx.new(|cx| Buffer::local(text, cx));
508 let change_set = cx.new(|cx| {
509 let mut change_set = BufferChangeSet::new(&buffer, cx);
510 let snapshot = buffer.read(cx).snapshot();
511 change_set.recalculate_diff_sync(base_text.into(), snapshot.text, true, cx);
512 change_set
513 });
514 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
515
516 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
517 multibuffer.add_change_set(change_set.clone(), cx);
518 (multibuffer.snapshot(cx), multibuffer.subscribe())
519 });
520
521 cx.executor().run_until_parked();
522 multibuffer.update(cx, |multibuffer, cx| {
523 multibuffer.set_all_diff_hunks_expanded(cx);
524 });
525
526 assert_new_snapshot(
527 &multibuffer,
528 &mut snapshot,
529 &mut subscription,
530 cx,
531 indoc! {
532 "
533 one
534 two
535 + THREE
536 four
537 five
538 - six
539 seven
540 "
541 },
542 );
543
544 // Insert a newline within an insertion hunk
545 multibuffer.update(cx, |multibuffer, cx| {
546 multibuffer.edit([(Point::new(2, 0)..Point::new(2, 0), "__\n__")], None, cx);
547 });
548 assert_new_snapshot(
549 &multibuffer,
550 &mut snapshot,
551 &mut subscription,
552 cx,
553 indoc! {
554 "
555 one
556 two
557 + __
558 + __THREE
559 four
560 five
561 - six
562 seven
563 "
564 },
565 );
566
567 // Delete the newline before a deleted hunk.
568 multibuffer.update(cx, |multibuffer, cx| {
569 multibuffer.edit([(Point::new(5, 4)..Point::new(6, 0), "")], None, cx);
570 });
571 assert_new_snapshot(
572 &multibuffer,
573 &mut snapshot,
574 &mut subscription,
575 cx,
576 indoc! {
577 "
578 one
579 two
580 + __
581 + __THREE
582 four
583 fiveseven
584 "
585 },
586 );
587
588 multibuffer.update(cx, |multibuffer, cx| multibuffer.undo(cx));
589 change_set.update(cx, |change_set, cx| {
590 change_set.recalculate_diff_sync(
591 base_text.into(),
592 buffer.read(cx).text_snapshot(),
593 true,
594 cx,
595 );
596 });
597 assert_new_snapshot(
598 &multibuffer,
599 &mut snapshot,
600 &mut subscription,
601 cx,
602 indoc! {
603 "
604 one
605 two
606 + __
607 + __THREE
608 four
609 five
610 - six
611 seven
612 "
613 },
614 );
615
616 // Cannot (yet) insert at the beginning of a deleted hunk.
617 // (because it would put the newline in the wrong place)
618 multibuffer.update(cx, |multibuffer, cx| {
619 multibuffer.edit([(Point::new(6, 0)..Point::new(6, 0), "\n")], None, cx);
620 });
621 assert_new_snapshot(
622 &multibuffer,
623 &mut snapshot,
624 &mut subscription,
625 cx,
626 indoc! {
627 "
628 one
629 two
630 + __
631 + __THREE
632 four
633 five
634 - six
635 seven
636 "
637 },
638 );
639
640 // Replace a range that ends in a deleted hunk.
641 multibuffer.update(cx, |multibuffer, cx| {
642 multibuffer.edit([(Point::new(5, 2)..Point::new(6, 2), "fty-")], None, cx);
643 });
644 assert_new_snapshot(
645 &multibuffer,
646 &mut snapshot,
647 &mut subscription,
648 cx,
649 indoc! {
650 "
651 one
652 two
653 + __
654 + __THREE
655 four
656 fifty-seven
657 "
658 },
659 );
660}
661
662#[gpui::test]
663fn test_excerpt_events(cx: &mut App) {
664 let buffer_1 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'a'), cx));
665 let buffer_2 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'm'), cx));
666
667 let leader_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
668 let follower_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
669 let follower_edit_event_count = Arc::new(RwLock::new(0));
670
671 follower_multibuffer.update(cx, |_, cx| {
672 let follower_edit_event_count = follower_edit_event_count.clone();
673 cx.subscribe(
674 &leader_multibuffer,
675 move |follower, _, event, cx| match event.clone() {
676 Event::ExcerptsAdded {
677 buffer,
678 predecessor,
679 excerpts,
680 } => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx),
681 Event::ExcerptsRemoved { ids } => follower.remove_excerpts(ids, cx),
682 Event::Edited { .. } => {
683 *follower_edit_event_count.write() += 1;
684 }
685 _ => {}
686 },
687 )
688 .detach();
689 });
690
691 leader_multibuffer.update(cx, |leader, cx| {
692 leader.push_excerpts(
693 buffer_1.clone(),
694 [
695 ExcerptRange {
696 context: 0..8,
697 primary: None,
698 },
699 ExcerptRange {
700 context: 12..16,
701 primary: None,
702 },
703 ],
704 cx,
705 );
706 leader.insert_excerpts_after(
707 leader.excerpt_ids()[0],
708 buffer_2.clone(),
709 [
710 ExcerptRange {
711 context: 0..5,
712 primary: None,
713 },
714 ExcerptRange {
715 context: 10..15,
716 primary: None,
717 },
718 ],
719 cx,
720 )
721 });
722 assert_eq!(
723 leader_multibuffer.read(cx).snapshot(cx).text(),
724 follower_multibuffer.read(cx).snapshot(cx).text(),
725 );
726 assert_eq!(*follower_edit_event_count.read(), 2);
727
728 leader_multibuffer.update(cx, |leader, cx| {
729 let excerpt_ids = leader.excerpt_ids();
730 leader.remove_excerpts([excerpt_ids[1], excerpt_ids[3]], cx);
731 });
732 assert_eq!(
733 leader_multibuffer.read(cx).snapshot(cx).text(),
734 follower_multibuffer.read(cx).snapshot(cx).text(),
735 );
736 assert_eq!(*follower_edit_event_count.read(), 3);
737
738 // Removing an empty set of excerpts is a noop.
739 leader_multibuffer.update(cx, |leader, cx| {
740 leader.remove_excerpts([], cx);
741 });
742 assert_eq!(
743 leader_multibuffer.read(cx).snapshot(cx).text(),
744 follower_multibuffer.read(cx).snapshot(cx).text(),
745 );
746 assert_eq!(*follower_edit_event_count.read(), 3);
747
748 // Adding an empty set of excerpts is a noop.
749 leader_multibuffer.update(cx, |leader, cx| {
750 leader.push_excerpts::<usize>(buffer_2.clone(), [], cx);
751 });
752 assert_eq!(
753 leader_multibuffer.read(cx).snapshot(cx).text(),
754 follower_multibuffer.read(cx).snapshot(cx).text(),
755 );
756 assert_eq!(*follower_edit_event_count.read(), 3);
757
758 leader_multibuffer.update(cx, |leader, cx| {
759 leader.clear(cx);
760 });
761 assert_eq!(
762 leader_multibuffer.read(cx).snapshot(cx).text(),
763 follower_multibuffer.read(cx).snapshot(cx).text(),
764 );
765 assert_eq!(*follower_edit_event_count.read(), 4);
766}
767
768#[gpui::test]
769fn test_expand_excerpts(cx: &mut App) {
770 let buffer = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
771 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
772
773 multibuffer.update(cx, |multibuffer, cx| {
774 multibuffer.push_excerpts_with_context_lines(
775 buffer.clone(),
776 vec![
777 // Note that in this test, this first excerpt
778 // does not contain a new line
779 Point::new(3, 2)..Point::new(3, 3),
780 Point::new(7, 1)..Point::new(7, 3),
781 Point::new(15, 0)..Point::new(15, 0),
782 ],
783 1,
784 cx,
785 )
786 });
787
788 let snapshot = multibuffer.read(cx).snapshot(cx);
789
790 assert_eq!(
791 snapshot.text(),
792 concat!(
793 "ccc\n", //
794 "ddd\n", //
795 "eee", //
796 "\n", // End of excerpt
797 "ggg\n", //
798 "hhh\n", //
799 "iii", //
800 "\n", // End of excerpt
801 "ooo\n", //
802 "ppp\n", //
803 "qqq", // End of excerpt
804 )
805 );
806 drop(snapshot);
807
808 multibuffer.update(cx, |multibuffer, cx| {
809 multibuffer.expand_excerpts(
810 multibuffer.excerpt_ids(),
811 1,
812 ExpandExcerptDirection::UpAndDown,
813 cx,
814 )
815 });
816
817 let snapshot = multibuffer.read(cx).snapshot(cx);
818
819 // Expanding context lines causes the line containing 'fff' to appear in two different excerpts.
820 // We don't attempt to merge them, because removing the excerpt could create inconsistency with other layers
821 // that are tracking excerpt ids.
822 assert_eq!(
823 snapshot.text(),
824 concat!(
825 "bbb\n", //
826 "ccc\n", //
827 "ddd\n", //
828 "eee\n", //
829 "fff\n", // End of excerpt
830 "fff\n", //
831 "ggg\n", //
832 "hhh\n", //
833 "iii\n", //
834 "jjj\n", // End of excerpt
835 "nnn\n", //
836 "ooo\n", //
837 "ppp\n", //
838 "qqq\n", //
839 "rrr", // End of excerpt
840 )
841 );
842}
843
844#[gpui::test]
845fn test_push_excerpts_with_context_lines(cx: &mut App) {
846 let buffer = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
847 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
848 let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
849 multibuffer.push_excerpts_with_context_lines(
850 buffer.clone(),
851 vec![
852 // Note that in this test, this first excerpt
853 // does contain a new line
854 Point::new(3, 2)..Point::new(4, 2),
855 Point::new(7, 1)..Point::new(7, 3),
856 Point::new(15, 0)..Point::new(15, 0),
857 ],
858 2,
859 cx,
860 )
861 });
862
863 let snapshot = multibuffer.read(cx).snapshot(cx);
864 assert_eq!(
865 snapshot.text(),
866 concat!(
867 "bbb\n", // Preserve newlines
868 "ccc\n", //
869 "ddd\n", //
870 "eee\n", //
871 "fff\n", //
872 "ggg\n", //
873 "hhh\n", //
874 "iii\n", //
875 "jjj\n", //
876 "nnn\n", //
877 "ooo\n", //
878 "ppp\n", //
879 "qqq\n", //
880 "rrr", //
881 )
882 );
883
884 assert_eq!(
885 anchor_ranges
886 .iter()
887 .map(|range| range.to_point(&snapshot))
888 .collect::<Vec<_>>(),
889 vec![
890 Point::new(2, 2)..Point::new(3, 2),
891 Point::new(6, 1)..Point::new(6, 3),
892 Point::new(11, 0)..Point::new(11, 0)
893 ]
894 );
895}
896
897#[gpui::test(iterations = 100)]
898async fn test_push_multiple_excerpts_with_context_lines(cx: &mut TestAppContext) {
899 let buffer_1 = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
900 let buffer_2 = cx.new(|cx| Buffer::local(sample_text(15, 4, 'a'), cx));
901 let snapshot_1 = buffer_1.update(cx, |buffer, _| buffer.snapshot());
902 let snapshot_2 = buffer_2.update(cx, |buffer, _| buffer.snapshot());
903 let ranges_1 = vec![
904 snapshot_1.anchor_before(Point::new(3, 2))..snapshot_1.anchor_before(Point::new(4, 2)),
905 snapshot_1.anchor_before(Point::new(7, 1))..snapshot_1.anchor_before(Point::new(7, 3)),
906 snapshot_1.anchor_before(Point::new(15, 0))..snapshot_1.anchor_before(Point::new(15, 0)),
907 ];
908 let ranges_2 = vec![
909 snapshot_2.anchor_before(Point::new(2, 1))..snapshot_2.anchor_before(Point::new(3, 1)),
910 snapshot_2.anchor_before(Point::new(10, 0))..snapshot_2.anchor_before(Point::new(10, 2)),
911 ];
912
913 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
914 let anchor_ranges = multibuffer
915 .update(cx, |multibuffer, cx| {
916 multibuffer.push_multiple_excerpts_with_context_lines(
917 vec![(buffer_1.clone(), ranges_1), (buffer_2.clone(), ranges_2)],
918 2,
919 cx,
920 )
921 })
922 .await;
923
924 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
925 assert_eq!(
926 snapshot.text(),
927 concat!(
928 "bbb\n", // buffer_1
929 "ccc\n", //
930 "ddd\n", // <-- excerpt 1
931 "eee\n", // <-- excerpt 1
932 "fff\n", //
933 "ggg\n", //
934 "hhh\n", // <-- excerpt 2
935 "iii\n", //
936 "jjj\n", //
937 //
938 "nnn\n", //
939 "ooo\n", //
940 "ppp\n", // <-- excerpt 3
941 "qqq\n", //
942 "rrr\n", //
943 //
944 "aaaa\n", // buffer 2
945 "bbbb\n", //
946 "cccc\n", // <-- excerpt 4
947 "dddd\n", // <-- excerpt 4
948 "eeee\n", //
949 "ffff\n", //
950 //
951 "iiii\n", //
952 "jjjj\n", //
953 "kkkk\n", // <-- excerpt 5
954 "llll\n", //
955 "mmmm", //
956 )
957 );
958
959 assert_eq!(
960 anchor_ranges
961 .iter()
962 .map(|range| range.to_point(&snapshot))
963 .collect::<Vec<_>>(),
964 vec![
965 Point::new(2, 2)..Point::new(3, 2),
966 Point::new(6, 1)..Point::new(6, 3),
967 Point::new(11, 0)..Point::new(11, 0),
968 Point::new(16, 1)..Point::new(17, 1),
969 Point::new(22, 0)..Point::new(22, 2)
970 ]
971 );
972}
973
974#[gpui::test]
975fn test_empty_multibuffer(cx: &mut App) {
976 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
977
978 let snapshot = multibuffer.read(cx).snapshot(cx);
979 assert_eq!(snapshot.text(), "");
980 assert_eq!(
981 snapshot
982 .row_infos(MultiBufferRow(0))
983 .map(|info| info.buffer_row)
984 .collect::<Vec<_>>(),
985 &[Some(0)]
986 );
987 assert_eq!(
988 snapshot
989 .row_infos(MultiBufferRow(1))
990 .map(|info| info.buffer_row)
991 .collect::<Vec<_>>(),
992 &[]
993 );
994}
995
996#[gpui::test]
997fn test_singleton_multibuffer_anchors(cx: &mut App) {
998 let buffer = cx.new(|cx| Buffer::local("abcd", cx));
999 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
1000 let old_snapshot = multibuffer.read(cx).snapshot(cx);
1001 buffer.update(cx, |buffer, cx| {
1002 buffer.edit([(0..0, "X")], None, cx);
1003 buffer.edit([(5..5, "Y")], None, cx);
1004 });
1005 let new_snapshot = multibuffer.read(cx).snapshot(cx);
1006
1007 assert_eq!(old_snapshot.text(), "abcd");
1008 assert_eq!(new_snapshot.text(), "XabcdY");
1009
1010 assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1011 assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1012 assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5);
1013 assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6);
1014}
1015
1016#[gpui::test]
1017fn test_multibuffer_anchors(cx: &mut App) {
1018 let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1019 let buffer_2 = cx.new(|cx| Buffer::local("efghi", cx));
1020 let multibuffer = cx.new(|cx| {
1021 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1022 multibuffer.push_excerpts(
1023 buffer_1.clone(),
1024 [ExcerptRange {
1025 context: 0..4,
1026 primary: None,
1027 }],
1028 cx,
1029 );
1030 multibuffer.push_excerpts(
1031 buffer_2.clone(),
1032 [ExcerptRange {
1033 context: 0..5,
1034 primary: None,
1035 }],
1036 cx,
1037 );
1038 multibuffer
1039 });
1040 let old_snapshot = multibuffer.read(cx).snapshot(cx);
1041
1042 assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0);
1043 assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0);
1044 assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1045 assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1046 assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1047 assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1048
1049 buffer_1.update(cx, |buffer, cx| {
1050 buffer.edit([(0..0, "W")], None, cx);
1051 buffer.edit([(5..5, "X")], None, cx);
1052 });
1053 buffer_2.update(cx, |buffer, cx| {
1054 buffer.edit([(0..0, "Y")], None, cx);
1055 buffer.edit([(6..6, "Z")], None, cx);
1056 });
1057 let new_snapshot = multibuffer.read(cx).snapshot(cx);
1058
1059 assert_eq!(old_snapshot.text(), "abcd\nefghi");
1060 assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
1061
1062 assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1063 assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1064 assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2);
1065 assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2);
1066 assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3);
1067 assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3);
1068 assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7);
1069 assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8);
1070 assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13);
1071 assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14);
1072}
1073
1074#[gpui::test]
1075fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
1076 let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1077 let buffer_2 = cx.new(|cx| Buffer::local("ABCDEFGHIJKLMNOP", cx));
1078 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1079
1080 // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
1081 // Add an excerpt from buffer 1 that spans this new insertion.
1082 buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx));
1083 let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| {
1084 multibuffer
1085 .push_excerpts(
1086 buffer_1.clone(),
1087 [ExcerptRange {
1088 context: 0..7,
1089 primary: None,
1090 }],
1091 cx,
1092 )
1093 .pop()
1094 .unwrap()
1095 });
1096
1097 let snapshot_1 = multibuffer.read(cx).snapshot(cx);
1098 assert_eq!(snapshot_1.text(), "abcd123");
1099
1100 // Replace the buffer 1 excerpt with new excerpts from buffer 2.
1101 let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
1102 multibuffer.remove_excerpts([excerpt_id_1], cx);
1103 let mut ids = multibuffer
1104 .push_excerpts(
1105 buffer_2.clone(),
1106 [
1107 ExcerptRange {
1108 context: 0..4,
1109 primary: None,
1110 },
1111 ExcerptRange {
1112 context: 6..10,
1113 primary: None,
1114 },
1115 ExcerptRange {
1116 context: 12..16,
1117 primary: None,
1118 },
1119 ],
1120 cx,
1121 )
1122 .into_iter();
1123 (ids.next().unwrap(), ids.next().unwrap())
1124 });
1125 let snapshot_2 = multibuffer.read(cx).snapshot(cx);
1126 assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP");
1127
1128 // The old excerpt id doesn't get reused.
1129 assert_ne!(excerpt_id_2, excerpt_id_1);
1130
1131 // Resolve some anchors from the previous snapshot in the new snapshot.
1132 // The current excerpts are from a different buffer, so we don't attempt to
1133 // resolve the old text anchor in the new buffer.
1134 assert_eq!(
1135 snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)),
1136 0
1137 );
1138 assert_eq!(
1139 snapshot_2.summaries_for_anchors::<usize, _>(&[
1140 snapshot_1.anchor_before(2),
1141 snapshot_1.anchor_after(3)
1142 ]),
1143 vec![0, 0]
1144 );
1145
1146 // Refresh anchors from the old snapshot. The return value indicates that both
1147 // anchors lost their original excerpt.
1148 let refresh =
1149 snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]);
1150 assert_eq!(
1151 refresh,
1152 &[
1153 (0, snapshot_2.anchor_before(0), false),
1154 (1, snapshot_2.anchor_after(0), false),
1155 ]
1156 );
1157
1158 // Replace the middle excerpt with a smaller excerpt in buffer 2,
1159 // that intersects the old excerpt.
1160 let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| {
1161 multibuffer.remove_excerpts([excerpt_id_3], cx);
1162 multibuffer
1163 .insert_excerpts_after(
1164 excerpt_id_2,
1165 buffer_2.clone(),
1166 [ExcerptRange {
1167 context: 5..8,
1168 primary: None,
1169 }],
1170 cx,
1171 )
1172 .pop()
1173 .unwrap()
1174 });
1175
1176 let snapshot_3 = multibuffer.read(cx).snapshot(cx);
1177 assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP");
1178 assert_ne!(excerpt_id_5, excerpt_id_3);
1179
1180 // Resolve some anchors from the previous snapshot in the new snapshot.
1181 // The third anchor can't be resolved, since its excerpt has been removed,
1182 // so it resolves to the same position as its predecessor.
1183 let anchors = [
1184 snapshot_2.anchor_before(0),
1185 snapshot_2.anchor_after(2),
1186 snapshot_2.anchor_after(6),
1187 snapshot_2.anchor_after(14),
1188 ];
1189 assert_eq!(
1190 snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
1191 &[0, 2, 9, 13]
1192 );
1193
1194 let new_anchors = snapshot_3.refresh_anchors(&anchors);
1195 assert_eq!(
1196 new_anchors.iter().map(|a| (a.0, a.2)).collect::<Vec<_>>(),
1197 &[(0, true), (1, true), (2, true), (3, true)]
1198 );
1199 assert_eq!(
1200 snapshot_3.summaries_for_anchors::<usize, _>(new_anchors.iter().map(|a| &a.1)),
1201 &[0, 2, 7, 13]
1202 );
1203}
1204
1205#[gpui::test]
1206fn test_basic_diff_hunks(cx: &mut TestAppContext) {
1207 let text = indoc!(
1208 "
1209 ZERO
1210 one
1211 TWO
1212 three
1213 six
1214 "
1215 );
1216 let base_text = indoc!(
1217 "
1218 one
1219 two
1220 three
1221 four
1222 five
1223 six
1224 "
1225 );
1226
1227 let buffer = cx.new(|cx| Buffer::local(text, cx));
1228 let change_set =
1229 cx.new(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
1230 cx.run_until_parked();
1231
1232 let multibuffer = cx.new(|cx| {
1233 let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1234 multibuffer.add_change_set(change_set.clone(), cx);
1235 multibuffer
1236 });
1237
1238 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1239 (multibuffer.snapshot(cx), multibuffer.subscribe())
1240 });
1241 assert_eq!(
1242 snapshot.text(),
1243 indoc!(
1244 "
1245 ZERO
1246 one
1247 TWO
1248 three
1249 six
1250 "
1251 ),
1252 );
1253
1254 multibuffer.update(cx, |multibuffer, cx| {
1255 multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1256 });
1257
1258 assert_new_snapshot(
1259 &multibuffer,
1260 &mut snapshot,
1261 &mut subscription,
1262 cx,
1263 indoc!(
1264 "
1265 + ZERO
1266 one
1267 - two
1268 + TWO
1269 three
1270 - four
1271 - five
1272 six
1273 "
1274 ),
1275 );
1276
1277 assert_eq!(
1278 snapshot
1279 .row_infos(MultiBufferRow(0))
1280 .map(|info| (info.buffer_row, info.diff_status))
1281 .collect::<Vec<_>>(),
1282 vec![
1283 (Some(0), Some(DiffHunkStatus::Added)),
1284 (Some(1), None),
1285 (Some(1), Some(DiffHunkStatus::Removed)),
1286 (Some(2), Some(DiffHunkStatus::Added)),
1287 (Some(3), None),
1288 (Some(3), Some(DiffHunkStatus::Removed)),
1289 (Some(4), Some(DiffHunkStatus::Removed)),
1290 (Some(4), None),
1291 (Some(5), None)
1292 ]
1293 );
1294
1295 assert_chunks_in_ranges(&snapshot);
1296 assert_consistent_line_numbers(&snapshot);
1297 assert_position_translation(&snapshot);
1298 assert_line_indents(&snapshot);
1299
1300 multibuffer.update(cx, |multibuffer, cx| {
1301 multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
1302 });
1303 assert_new_snapshot(
1304 &multibuffer,
1305 &mut snapshot,
1306 &mut subscription,
1307 cx,
1308 indoc!(
1309 "
1310 ZERO
1311 one
1312 TWO
1313 three
1314 six
1315 "
1316 ),
1317 );
1318
1319 assert_chunks_in_ranges(&snapshot);
1320 assert_consistent_line_numbers(&snapshot);
1321 assert_position_translation(&snapshot);
1322 assert_line_indents(&snapshot);
1323
1324 // Expand the first diff hunk
1325 multibuffer.update(cx, |multibuffer, cx| {
1326 let position = multibuffer.read(cx).anchor_before(Point::new(2, 2));
1327 multibuffer.expand_diff_hunks(vec![position..position], cx)
1328 });
1329 assert_new_snapshot(
1330 &multibuffer,
1331 &mut snapshot,
1332 &mut subscription,
1333 cx,
1334 indoc!(
1335 "
1336 ZERO
1337 one
1338 - two
1339 + TWO
1340 three
1341 six
1342 "
1343 ),
1344 );
1345
1346 // Expand the second diff hunk
1347 multibuffer.update(cx, |multibuffer, cx| {
1348 let start = multibuffer.read(cx).anchor_before(Point::new(4, 0));
1349 let end = multibuffer.read(cx).anchor_before(Point::new(5, 0));
1350 multibuffer.expand_diff_hunks(vec![start..end], cx)
1351 });
1352 assert_new_snapshot(
1353 &multibuffer,
1354 &mut snapshot,
1355 &mut subscription,
1356 cx,
1357 indoc!(
1358 "
1359 ZERO
1360 one
1361 - two
1362 + TWO
1363 three
1364 - four
1365 - five
1366 six
1367 "
1368 ),
1369 );
1370
1371 assert_chunks_in_ranges(&snapshot);
1372 assert_consistent_line_numbers(&snapshot);
1373 assert_position_translation(&snapshot);
1374 assert_line_indents(&snapshot);
1375
1376 // Edit the buffer before the first hunk
1377 buffer.update(cx, |buffer, cx| {
1378 buffer.edit_via_marked_text(
1379 indoc!(
1380 "
1381 ZERO
1382 one« hundred
1383 thousand»
1384 TWO
1385 three
1386 six
1387 "
1388 ),
1389 None,
1390 cx,
1391 );
1392 });
1393 assert_new_snapshot(
1394 &multibuffer,
1395 &mut snapshot,
1396 &mut subscription,
1397 cx,
1398 indoc!(
1399 "
1400 ZERO
1401 one hundred
1402 thousand
1403 - two
1404 + TWO
1405 three
1406 - four
1407 - five
1408 six
1409 "
1410 ),
1411 );
1412
1413 assert_chunks_in_ranges(&snapshot);
1414 assert_consistent_line_numbers(&snapshot);
1415 assert_position_translation(&snapshot);
1416 assert_line_indents(&snapshot);
1417
1418 // Recalculate the diff, changing the first diff hunk.
1419 let _ = change_set.update(cx, |change_set, cx| {
1420 change_set.recalculate_diff(buffer.read(cx).text_snapshot(), cx)
1421 });
1422 cx.run_until_parked();
1423 assert_new_snapshot(
1424 &multibuffer,
1425 &mut snapshot,
1426 &mut subscription,
1427 cx,
1428 indoc!(
1429 "
1430 ZERO
1431 one hundred
1432 thousand
1433 TWO
1434 three
1435 - four
1436 - five
1437 six
1438 "
1439 ),
1440 );
1441
1442 assert_eq!(
1443 snapshot
1444 .diff_hunks_in_range(0..snapshot.len())
1445 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1446 .collect::<Vec<_>>(),
1447 &[0..4, 5..7]
1448 );
1449}
1450
1451#[gpui::test]
1452fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
1453 let text = indoc!(
1454 "
1455 one
1456 TWO
1457 THREE
1458 four
1459 FIVE
1460 six
1461 "
1462 );
1463 let base_text = indoc!(
1464 "
1465 one
1466 four
1467 six
1468 "
1469 );
1470
1471 let buffer = cx.new(|cx| Buffer::local(text, cx));
1472 let change_set =
1473 cx.new(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
1474 cx.run_until_parked();
1475
1476 let multibuffer = cx.new(|cx| {
1477 let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1478 multibuffer.add_change_set(change_set.clone(), cx);
1479 multibuffer
1480 });
1481
1482 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1483 (multibuffer.snapshot(cx), multibuffer.subscribe())
1484 });
1485
1486 multibuffer.update(cx, |multibuffer, cx| {
1487 multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1488 });
1489
1490 assert_new_snapshot(
1491 &multibuffer,
1492 &mut snapshot,
1493 &mut subscription,
1494 cx,
1495 indoc!(
1496 "
1497 one
1498 + TWO
1499 + THREE
1500 four
1501 + FIVE
1502 six
1503 "
1504 ),
1505 );
1506
1507 // Regression test: expanding diff hunks that are already expanded should not change anything.
1508 multibuffer.update(cx, |multibuffer, cx| {
1509 multibuffer.expand_diff_hunks(
1510 vec![
1511 snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_before(Point::new(2, 0)),
1512 ],
1513 cx,
1514 );
1515 });
1516
1517 assert_new_snapshot(
1518 &multibuffer,
1519 &mut snapshot,
1520 &mut subscription,
1521 cx,
1522 indoc!(
1523 "
1524 one
1525 + TWO
1526 + THREE
1527 four
1528 + FIVE
1529 six
1530 "
1531 ),
1532 );
1533}
1534
1535#[gpui::test]
1536fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
1537 let base_text_1 = indoc!(
1538 "
1539 one
1540 two
1541 three
1542 four
1543 five
1544 six
1545 "
1546 );
1547 let text_1 = indoc!(
1548 "
1549 ZERO
1550 one
1551 TWO
1552 three
1553 six
1554 "
1555 );
1556 let base_text_2 = indoc!(
1557 "
1558 seven
1559 eight
1560 nine
1561 ten
1562 eleven
1563 twelve
1564 "
1565 );
1566 let text_2 = indoc!(
1567 "
1568 eight
1569 nine
1570 eleven
1571 THIRTEEN
1572 FOURTEEN
1573 "
1574 );
1575
1576 let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
1577 let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
1578 let change_set_1 =
1579 cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_1.to_string(), &buffer_1, cx));
1580 let change_set_2 =
1581 cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_2.to_string(), &buffer_2, cx));
1582 cx.run_until_parked();
1583
1584 let multibuffer = cx.new(|cx| {
1585 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1586 multibuffer.push_excerpts(
1587 buffer_1.clone(),
1588 [ExcerptRange {
1589 context: text::Anchor::MIN..text::Anchor::MAX,
1590 primary: None,
1591 }],
1592 cx,
1593 );
1594 multibuffer.push_excerpts(
1595 buffer_2.clone(),
1596 [ExcerptRange {
1597 context: text::Anchor::MIN..text::Anchor::MAX,
1598 primary: None,
1599 }],
1600 cx,
1601 );
1602 multibuffer.add_change_set(change_set_1.clone(), cx);
1603 multibuffer.add_change_set(change_set_2.clone(), cx);
1604 multibuffer
1605 });
1606
1607 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1608 (multibuffer.snapshot(cx), multibuffer.subscribe())
1609 });
1610 assert_eq!(
1611 snapshot.text(),
1612 indoc!(
1613 "
1614 ZERO
1615 one
1616 TWO
1617 three
1618 six
1619
1620 eight
1621 nine
1622 eleven
1623 THIRTEEN
1624 FOURTEEN
1625 "
1626 ),
1627 );
1628
1629 multibuffer.update(cx, |multibuffer, cx| {
1630 multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1631 });
1632
1633 assert_new_snapshot(
1634 &multibuffer,
1635 &mut snapshot,
1636 &mut subscription,
1637 cx,
1638 indoc!(
1639 "
1640 + ZERO
1641 one
1642 - two
1643 + TWO
1644 three
1645 - four
1646 - five
1647 six
1648
1649 - seven
1650 eight
1651 nine
1652 - ten
1653 eleven
1654 - twelve
1655 + THIRTEEN
1656 + FOURTEEN
1657 "
1658 ),
1659 );
1660
1661 let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
1662 let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
1663 let base_id_1 = change_set_1.read_with(cx, |change_set, _| {
1664 change_set.base_text.as_ref().unwrap().remote_id()
1665 });
1666 let base_id_2 = change_set_2.read_with(cx, |change_set, _| {
1667 change_set.base_text.as_ref().unwrap().remote_id()
1668 });
1669
1670 let buffer_lines = (0..=snapshot.max_row().0)
1671 .map(|row| {
1672 let (buffer, range) = snapshot.buffer_line_for_row(MultiBufferRow(row))?;
1673 Some((
1674 buffer.remote_id(),
1675 buffer.text_for_range(range).collect::<String>(),
1676 ))
1677 })
1678 .collect::<Vec<_>>();
1679 pretty_assertions::assert_eq!(
1680 buffer_lines,
1681 [
1682 Some((id_1, "ZERO".into())),
1683 Some((id_1, "one".into())),
1684 Some((base_id_1, "two".into())),
1685 Some((id_1, "TWO".into())),
1686 Some((id_1, " three".into())),
1687 Some((base_id_1, "four".into())),
1688 Some((base_id_1, "five".into())),
1689 Some((id_1, "six".into())),
1690 Some((id_1, "".into())),
1691 Some((base_id_2, "seven".into())),
1692 Some((id_2, " eight".into())),
1693 Some((id_2, "nine".into())),
1694 Some((base_id_2, "ten".into())),
1695 Some((id_2, "eleven".into())),
1696 Some((base_id_2, "twelve".into())),
1697 Some((id_2, "THIRTEEN".into())),
1698 Some((id_2, "FOURTEEN".into())),
1699 Some((id_2, "".into())),
1700 ]
1701 );
1702
1703 assert_position_translation(&snapshot);
1704 assert_line_indents(&snapshot);
1705
1706 assert_eq!(
1707 snapshot
1708 .diff_hunks_in_range(0..snapshot.len())
1709 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1710 .collect::<Vec<_>>(),
1711 &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
1712 );
1713
1714 buffer_2.update(cx, |buffer, cx| {
1715 buffer.edit_via_marked_text(
1716 indoc!(
1717 "
1718 eight
1719 «»eleven
1720 THIRTEEN
1721 FOURTEEN
1722 "
1723 ),
1724 None,
1725 cx,
1726 );
1727 });
1728
1729 assert_new_snapshot(
1730 &multibuffer,
1731 &mut snapshot,
1732 &mut subscription,
1733 cx,
1734 indoc!(
1735 "
1736 + ZERO
1737 one
1738 - two
1739 + TWO
1740 three
1741 - four
1742 - five
1743 six
1744
1745 - seven
1746 eight
1747 eleven
1748 - twelve
1749 + THIRTEEN
1750 + FOURTEEN
1751 "
1752 ),
1753 );
1754
1755 assert_line_indents(&snapshot);
1756}
1757
1758/// A naive implementation of a multi-buffer that does not maintain
1759/// any derived state, used for comparison in a randomized test.
1760#[derive(Default)]
1761struct ReferenceMultibuffer {
1762 excerpts: Vec<ReferenceExcerpt>,
1763 change_sets: HashMap<BufferId, Entity<BufferChangeSet>>,
1764}
1765
1766struct ReferenceExcerpt {
1767 id: ExcerptId,
1768 buffer: Entity<Buffer>,
1769 range: Range<text::Anchor>,
1770 expanded_diff_hunks: Vec<text::Anchor>,
1771}
1772
1773#[derive(Debug)]
1774struct ReferenceRegion {
1775 range: Range<usize>,
1776 buffer_start: Option<Point>,
1777 status: Option<DiffHunkStatus>,
1778}
1779
1780impl ReferenceMultibuffer {
1781 fn expand_excerpts(&mut self, excerpts: &HashSet<ExcerptId>, line_count: u32, cx: &App) {
1782 if line_count == 0 {
1783 return;
1784 }
1785
1786 for id in excerpts {
1787 let excerpt = self.excerpts.iter_mut().find(|e| e.id == *id).unwrap();
1788 let snapshot = excerpt.buffer.read(cx).snapshot();
1789 let mut point_range = excerpt.range.to_point(&snapshot);
1790 point_range.start = Point::new(point_range.start.row.saturating_sub(line_count), 0);
1791 point_range.end =
1792 snapshot.clip_point(Point::new(point_range.end.row + line_count, 0), Bias::Left);
1793 point_range.end.column = snapshot.line_len(point_range.end.row);
1794 excerpt.range =
1795 snapshot.anchor_before(point_range.start)..snapshot.anchor_after(point_range.end);
1796 }
1797 }
1798
1799 fn remove_excerpt(&mut self, id: ExcerptId, cx: &App) {
1800 let ix = self
1801 .excerpts
1802 .iter()
1803 .position(|excerpt| excerpt.id == id)
1804 .unwrap();
1805 let excerpt = self.excerpts.remove(ix);
1806 let buffer = excerpt.buffer.read(cx);
1807 log::info!(
1808 "Removing excerpt {}: {:?}",
1809 ix,
1810 buffer
1811 .text_for_range(excerpt.range.to_offset(buffer))
1812 .collect::<String>(),
1813 );
1814 }
1815
1816 fn insert_excerpt_after(
1817 &mut self,
1818 prev_id: ExcerptId,
1819 new_excerpt_id: ExcerptId,
1820 (buffer_handle, anchor_range): (Entity<Buffer>, Range<text::Anchor>),
1821 ) {
1822 let excerpt_ix = if prev_id == ExcerptId::max() {
1823 self.excerpts.len()
1824 } else {
1825 self.excerpts
1826 .iter()
1827 .position(|excerpt| excerpt.id == prev_id)
1828 .unwrap()
1829 + 1
1830 };
1831 self.excerpts.insert(
1832 excerpt_ix,
1833 ReferenceExcerpt {
1834 id: new_excerpt_id,
1835 buffer: buffer_handle,
1836 range: anchor_range,
1837 expanded_diff_hunks: Vec::new(),
1838 },
1839 );
1840 }
1841
1842 fn expand_diff_hunks(&mut self, excerpt_id: ExcerptId, range: Range<text::Anchor>, cx: &App) {
1843 let excerpt = self
1844 .excerpts
1845 .iter_mut()
1846 .find(|e| e.id == excerpt_id)
1847 .unwrap();
1848 let buffer = excerpt.buffer.read(cx).snapshot();
1849 let buffer_id = buffer.remote_id();
1850 let Some(change_set) = self.change_sets.get(&buffer_id) else {
1851 return;
1852 };
1853 let diff = change_set.read(cx).diff_to_buffer.clone();
1854 let excerpt_range = excerpt.range.to_offset(&buffer);
1855 if excerpt_range.is_empty() {
1856 return;
1857 }
1858 for hunk in diff.hunks_intersecting_range(range, &buffer) {
1859 let hunk_range = hunk.buffer_range.to_offset(&buffer);
1860 let hunk_precedes_excerpt = hunk
1861 .buffer_range
1862 .end
1863 .cmp(&excerpt.range.start, &buffer)
1864 .is_le();
1865 let hunk_follows_excerpt = hunk
1866 .buffer_range
1867 .start
1868 .cmp(&excerpt.range.end, &buffer)
1869 .is_ge();
1870 if hunk_precedes_excerpt || hunk_follows_excerpt {
1871 continue;
1872 }
1873
1874 if let Err(ix) = excerpt
1875 .expanded_diff_hunks
1876 .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
1877 {
1878 log::info!(
1879 "expanding diff hunk {:?}. excerpt: {:?}",
1880 hunk_range,
1881 excerpt_range
1882 );
1883 excerpt
1884 .expanded_diff_hunks
1885 .insert(ix, hunk.buffer_range.start);
1886 }
1887 }
1888 }
1889
1890 fn expected_content(&self, cx: &App) -> (String, Vec<RowInfo>, HashSet<MultiBufferRow>) {
1891 let mut text = String::new();
1892 let mut regions = Vec::<ReferenceRegion>::new();
1893 let mut excerpt_boundary_rows = HashSet::default();
1894 for excerpt in &self.excerpts {
1895 excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
1896 let buffer = excerpt.buffer.read(cx);
1897 let buffer_range = excerpt.range.to_offset(buffer);
1898 let change_set = self.change_sets.get(&buffer.remote_id()).unwrap().read(cx);
1899 let diff = change_set.diff_to_buffer.clone();
1900 let base_buffer = change_set.base_text.as_ref().unwrap();
1901
1902 let mut offset = buffer_range.start;
1903 let mut hunks = diff
1904 .hunks_intersecting_range(excerpt.range.clone(), buffer)
1905 .peekable();
1906
1907 while let Some(hunk) = hunks.next() {
1908 if !hunk.buffer_range.start.is_valid(&buffer) {
1909 continue;
1910 }
1911
1912 // Ignore hunks that are outside the excerpt range.
1913 let mut hunk_range = hunk.buffer_range.to_offset(buffer);
1914 hunk_range.end = hunk_range.end.min(buffer_range.end);
1915 if hunk_range.start > buffer_range.end
1916 || hunk_range.end < buffer_range.start
1917 || buffer_range.is_empty()
1918 {
1919 continue;
1920 }
1921
1922 if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| {
1923 expanded_anchor.to_offset(&buffer).max(buffer_range.start)
1924 == hunk_range.start.max(buffer_range.start)
1925 }) {
1926 continue;
1927 }
1928
1929 if hunk_range.start >= offset {
1930 // Add the buffer text before the hunk
1931 let len = text.len();
1932 text.extend(buffer.text_for_range(offset..hunk_range.start));
1933 regions.push(ReferenceRegion {
1934 range: len..text.len(),
1935 buffer_start: Some(buffer.offset_to_point(offset)),
1936 status: None,
1937 });
1938
1939 // Add the deleted text for the hunk.
1940 if !hunk.diff_base_byte_range.is_empty() {
1941 let mut base_text = base_buffer
1942 .text_for_range(hunk.diff_base_byte_range.clone())
1943 .collect::<String>();
1944 if !base_text.ends_with('\n') {
1945 base_text.push('\n');
1946 }
1947 let len = text.len();
1948 text.push_str(&base_text);
1949 regions.push(ReferenceRegion {
1950 range: len..text.len(),
1951 buffer_start: Some(
1952 base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
1953 ),
1954 status: Some(DiffHunkStatus::Removed),
1955 });
1956 }
1957
1958 offset = hunk_range.start;
1959 }
1960
1961 // Add the inserted text for the hunk.
1962 if hunk_range.end > offset {
1963 let len = text.len();
1964 text.extend(buffer.text_for_range(offset..hunk_range.end));
1965 regions.push(ReferenceRegion {
1966 range: len..text.len(),
1967 buffer_start: Some(buffer.offset_to_point(offset)),
1968 status: Some(DiffHunkStatus::Added),
1969 });
1970 offset = hunk_range.end;
1971 }
1972 }
1973
1974 // Add the buffer text for the rest of the excerpt.
1975 let len = text.len();
1976 text.extend(buffer.text_for_range(offset..buffer_range.end));
1977 text.push('\n');
1978 regions.push(ReferenceRegion {
1979 range: len..text.len(),
1980 buffer_start: Some(buffer.offset_to_point(offset)),
1981 status: None,
1982 });
1983 }
1984
1985 // Remove final trailing newline.
1986 if self.excerpts.is_empty() {
1987 regions.push(ReferenceRegion {
1988 range: 0..1,
1989 buffer_start: Some(Point::new(0, 0)),
1990 status: None,
1991 });
1992 } else {
1993 text.pop();
1994 }
1995
1996 // Retrieve the row info using the region that contains
1997 // the start of each multi-buffer line.
1998 let mut ix = 0;
1999 let row_infos = text
2000 .split('\n')
2001 .map(|line| {
2002 let row_info = regions
2003 .iter()
2004 .find(|region| region.range.contains(&ix))
2005 .map_or(RowInfo::default(), |region| {
2006 let buffer_row = region.buffer_start.map(|start_point| {
2007 start_point.row
2008 + text[region.range.start..ix].matches('\n').count() as u32
2009 });
2010 RowInfo {
2011 diff_status: region.status,
2012 buffer_row,
2013 multibuffer_row: Some(MultiBufferRow(
2014 text[..ix].matches('\n').count() as u32
2015 )),
2016 }
2017 });
2018 ix += line.len() + 1;
2019 row_info
2020 })
2021 .collect();
2022
2023 (text, row_infos, excerpt_boundary_rows)
2024 }
2025
2026 fn diffs_updated(&mut self, cx: &App) {
2027 for excerpt in &mut self.excerpts {
2028 let buffer = excerpt.buffer.read(cx).snapshot();
2029 let excerpt_range = excerpt.range.to_offset(&buffer);
2030 let buffer_id = buffer.remote_id();
2031 let diff = &self
2032 .change_sets
2033 .get(&buffer_id)
2034 .unwrap()
2035 .read(cx)
2036 .diff_to_buffer;
2037 let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer).peekable();
2038 excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
2039 if !hunk_anchor.is_valid(&buffer) {
2040 return false;
2041 }
2042 while let Some(hunk) = hunks.peek() {
2043 match hunk.buffer_range.start.cmp(&hunk_anchor, &buffer) {
2044 cmp::Ordering::Less => {
2045 hunks.next();
2046 }
2047 cmp::Ordering::Equal => {
2048 let hunk_range = hunk.buffer_range.to_offset(&buffer);
2049 return hunk_range.end >= excerpt_range.start
2050 && hunk_range.start <= excerpt_range.end;
2051 }
2052 cmp::Ordering::Greater => break,
2053 }
2054 }
2055 false
2056 });
2057 }
2058 }
2059
2060 fn add_change_set(&mut self, change_set: Entity<BufferChangeSet>, cx: &mut App) {
2061 let buffer_id = change_set.read(cx).buffer_id;
2062 self.change_sets.insert(buffer_id, change_set);
2063 }
2064}
2065
2066#[gpui::test(iterations = 100)]
2067fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
2068 let operations = env::var("OPERATIONS")
2069 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2070 .unwrap_or(10);
2071
2072 let mut buffers: Vec<Entity<Buffer>> = Vec::new();
2073 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2074 let mut reference = ReferenceMultibuffer::default();
2075 let mut anchors = Vec::new();
2076 let mut old_versions = Vec::new();
2077 let mut needs_diff_calculation = false;
2078
2079 for _ in 0..operations {
2080 match rng.gen_range(0..100) {
2081 0..=14 if !buffers.is_empty() => {
2082 let buffer = buffers.choose(&mut rng).unwrap();
2083 buffer.update(cx, |buf, cx| {
2084 let edit_count = rng.gen_range(1..5);
2085 buf.randomly_edit(&mut rng, edit_count, cx);
2086 needs_diff_calculation = true;
2087 });
2088 reference.diffs_updated(cx);
2089 }
2090 15..=19 if !reference.excerpts.is_empty() => {
2091 multibuffer.update(cx, |multibuffer, cx| {
2092 let ids = multibuffer.excerpt_ids();
2093 let mut excerpts = HashSet::default();
2094 for _ in 0..rng.gen_range(0..ids.len()) {
2095 excerpts.extend(ids.choose(&mut rng).copied());
2096 }
2097
2098 let line_count = rng.gen_range(0..5);
2099
2100 let excerpt_ixs = excerpts
2101 .iter()
2102 .map(|id| reference.excerpts.iter().position(|e| e.id == *id).unwrap())
2103 .collect::<Vec<_>>();
2104 log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
2105 multibuffer.expand_excerpts(
2106 excerpts.iter().cloned(),
2107 line_count,
2108 ExpandExcerptDirection::UpAndDown,
2109 cx,
2110 );
2111
2112 reference.expand_excerpts(&excerpts, line_count, cx);
2113 });
2114 }
2115 20..=29 if !reference.excerpts.is_empty() => {
2116 let mut ids_to_remove = vec![];
2117 for _ in 0..rng.gen_range(1..=3) {
2118 let Some(excerpt) = reference.excerpts.choose(&mut rng) else {
2119 break;
2120 };
2121 let id = excerpt.id;
2122 reference.remove_excerpt(id, cx);
2123 ids_to_remove.push(id);
2124 }
2125 let snapshot = multibuffer.read(cx).read(cx);
2126 ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
2127 drop(snapshot);
2128 multibuffer.update(cx, |multibuffer, cx| {
2129 multibuffer.remove_excerpts(ids_to_remove, cx)
2130 });
2131 }
2132 30..=39 if !reference.excerpts.is_empty() => {
2133 let multibuffer = multibuffer.read(cx).read(cx);
2134 let offset =
2135 multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
2136 let bias = if rng.gen() { Bias::Left } else { Bias::Right };
2137 log::info!("Creating anchor at {} with bias {:?}", offset, bias);
2138 anchors.push(multibuffer.anchor_at(offset, bias));
2139 anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
2140 }
2141 40..=44 if !anchors.is_empty() => {
2142 let multibuffer = multibuffer.read(cx).read(cx);
2143 let prev_len = anchors.len();
2144 anchors = multibuffer
2145 .refresh_anchors(&anchors)
2146 .into_iter()
2147 .map(|a| a.1)
2148 .collect();
2149
2150 // Ensure the newly-refreshed anchors point to a valid excerpt and don't
2151 // overshoot its boundaries.
2152 assert_eq!(anchors.len(), prev_len);
2153 for anchor in &anchors {
2154 if anchor.excerpt_id == ExcerptId::min()
2155 || anchor.excerpt_id == ExcerptId::max()
2156 {
2157 continue;
2158 }
2159
2160 let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
2161 assert_eq!(excerpt.id, anchor.excerpt_id);
2162 assert!(excerpt.contains(anchor));
2163 }
2164 }
2165 45..=55 if !reference.excerpts.is_empty() => {
2166 multibuffer.update(cx, |multibuffer, cx| {
2167 let snapshot = multibuffer.snapshot(cx);
2168 let excerpt_ix = rng.gen_range(0..reference.excerpts.len());
2169 let excerpt = &reference.excerpts[excerpt_ix];
2170 let start = excerpt.range.start;
2171 let end = excerpt.range.end;
2172 let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
2173 ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
2174
2175 log::info!("expanding diff hunks for excerpt {:?}", excerpt_ix);
2176 reference.expand_diff_hunks(excerpt.id, start..end, cx);
2177 multibuffer.expand_diff_hunks(vec![range], cx);
2178 });
2179 }
2180 56..=85 if needs_diff_calculation => {
2181 multibuffer.update(cx, |multibuffer, cx| {
2182 for buffer in multibuffer.all_buffers() {
2183 let snapshot = buffer.read(cx).snapshot();
2184 let _ = multibuffer
2185 .change_set_for(snapshot.remote_id())
2186 .unwrap()
2187 .update(cx, |change_set, cx| {
2188 log::info!(
2189 "recalculating diff for buffer {:?}",
2190 snapshot.remote_id(),
2191 );
2192 change_set.recalculate_diff_sync(
2193 change_set.base_text.clone().unwrap().text(),
2194 snapshot.text,
2195 false,
2196 cx,
2197 )
2198 });
2199 }
2200 reference.diffs_updated(cx);
2201 needs_diff_calculation = false;
2202 });
2203 }
2204 _ => {
2205 let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
2206 let base_text = util::RandomCharIter::new(&mut rng)
2207 .take(256)
2208 .collect::<String>();
2209
2210 let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
2211 let snapshot = buffer.read(cx).snapshot();
2212 let change_set = cx.new(|cx| {
2213 let mut change_set = BufferChangeSet::new(&buffer, cx);
2214 change_set.recalculate_diff_sync(base_text, snapshot.text, true, cx);
2215 change_set
2216 });
2217
2218 reference.add_change_set(change_set.clone(), cx);
2219 multibuffer.update(cx, |multibuffer, cx| {
2220 multibuffer.add_change_set(change_set, cx)
2221 });
2222 buffers.push(buffer);
2223 buffers.last().unwrap()
2224 } else {
2225 buffers.choose(&mut rng).unwrap()
2226 };
2227
2228 let buffer = buffer_handle.read(cx);
2229 let end_row = rng.gen_range(0..=buffer.max_point().row);
2230 let start_row = rng.gen_range(0..=end_row);
2231 let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
2232 let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
2233 let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
2234 let prev_excerpt_ix = rng.gen_range(0..=reference.excerpts.len());
2235 let prev_excerpt_id = reference
2236 .excerpts
2237 .get(prev_excerpt_ix)
2238 .map_or(ExcerptId::max(), |e| e.id);
2239 let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
2240
2241 log::info!(
2242 "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
2243 excerpt_ix,
2244 reference.excerpts.len(),
2245 buffer_handle.read(cx).remote_id(),
2246 buffer.text(),
2247 start_ix..end_ix,
2248 &buffer.text()[start_ix..end_ix]
2249 );
2250
2251 let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
2252 multibuffer
2253 .insert_excerpts_after(
2254 prev_excerpt_id,
2255 buffer_handle.clone(),
2256 [ExcerptRange {
2257 context: start_ix..end_ix,
2258 primary: None,
2259 }],
2260 cx,
2261 )
2262 .pop()
2263 .unwrap()
2264 });
2265
2266 reference.insert_excerpt_after(
2267 prev_excerpt_id,
2268 excerpt_id,
2269 (buffer_handle.clone(), anchor_range),
2270 );
2271 }
2272 }
2273
2274 if rng.gen_bool(0.3) {
2275 multibuffer.update(cx, |multibuffer, cx| {
2276 old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
2277 })
2278 }
2279
2280 let snapshot = multibuffer.read(cx).snapshot(cx);
2281 let actual_text = snapshot.text();
2282 let actual_boundary_rows = snapshot
2283 .excerpt_boundaries_in_range(0..)
2284 .filter_map(|b| if b.next.is_some() { Some(b.row) } else { None })
2285 .collect::<HashSet<_>>();
2286 let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2287 let actual_diff = format_diff(&actual_text, &actual_row_infos, &actual_boundary_rows);
2288
2289 let (expected_text, expected_row_infos, expected_boundary_rows) =
2290 reference.expected_content(cx);
2291 let expected_diff =
2292 format_diff(&expected_text, &expected_row_infos, &expected_boundary_rows);
2293
2294 log::info!("Multibuffer content:\n{}", actual_diff);
2295
2296 assert_eq!(
2297 actual_row_infos.len(),
2298 actual_text.split('\n').count(),
2299 "line count: {}",
2300 actual_text.split('\n').count()
2301 );
2302 pretty_assertions::assert_eq!(actual_diff, expected_diff);
2303 pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
2304 pretty_assertions::assert_eq!(actual_text, expected_text);
2305
2306 for _ in 0..5 {
2307 let start_row = rng.gen_range(0..=expected_row_infos.len());
2308 assert_eq!(
2309 snapshot
2310 .row_infos(MultiBufferRow(start_row as u32))
2311 .collect::<Vec<_>>(),
2312 &expected_row_infos[start_row..],
2313 "buffer_rows({})",
2314 start_row
2315 );
2316 }
2317
2318 assert_eq!(
2319 snapshot.widest_line_number(),
2320 expected_row_infos
2321 .into_iter()
2322 .filter_map(
2323 |info| if info.diff_status == Some(DiffHunkStatus::Removed) {
2324 None
2325 } else {
2326 info.buffer_row
2327 }
2328 )
2329 .max()
2330 .unwrap()
2331 + 1
2332 );
2333
2334 assert_consistent_line_numbers(&snapshot);
2335 assert_position_translation(&snapshot);
2336
2337 for (row, line) in expected_text.split('\n').enumerate() {
2338 assert_eq!(
2339 snapshot.line_len(MultiBufferRow(row as u32)),
2340 line.len() as u32,
2341 "line_len({}).",
2342 row
2343 );
2344 }
2345
2346 let text_rope = Rope::from(expected_text.as_str());
2347 for _ in 0..10 {
2348 let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2349 let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
2350
2351 let text_for_range = snapshot
2352 .text_for_range(start_ix..end_ix)
2353 .collect::<String>();
2354 assert_eq!(
2355 text_for_range,
2356 &expected_text[start_ix..end_ix],
2357 "incorrect text for range {:?}",
2358 start_ix..end_ix
2359 );
2360
2361 let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
2362 assert_eq!(
2363 snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
2364 expected_summary,
2365 "incorrect summary for range {:?}",
2366 start_ix..end_ix
2367 );
2368 }
2369
2370 // Anchor resolution
2371 let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
2372 assert_eq!(anchors.len(), summaries.len());
2373 for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
2374 assert!(resolved_offset <= snapshot.len());
2375 assert_eq!(
2376 snapshot.summary_for_anchor::<usize>(anchor),
2377 resolved_offset,
2378 "anchor: {:?}",
2379 anchor
2380 );
2381 }
2382
2383 for _ in 0..10 {
2384 let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2385 assert_eq!(
2386 snapshot.reversed_chars_at(end_ix).collect::<String>(),
2387 expected_text[..end_ix].chars().rev().collect::<String>(),
2388 );
2389 }
2390
2391 for _ in 0..10 {
2392 let end_ix = rng.gen_range(0..=text_rope.len());
2393 let start_ix = rng.gen_range(0..=end_ix);
2394 assert_eq!(
2395 snapshot
2396 .bytes_in_range(start_ix..end_ix)
2397 .flatten()
2398 .copied()
2399 .collect::<Vec<_>>(),
2400 expected_text.as_bytes()[start_ix..end_ix].to_vec(),
2401 "bytes_in_range({:?})",
2402 start_ix..end_ix,
2403 );
2404 }
2405 }
2406
2407 let snapshot = multibuffer.read(cx).snapshot(cx);
2408 for (old_snapshot, subscription) in old_versions {
2409 let edits = subscription.consume().into_inner();
2410
2411 log::info!(
2412 "applying subscription edits to old text: {:?}: {:?}",
2413 old_snapshot.text(),
2414 edits,
2415 );
2416
2417 let mut text = old_snapshot.text();
2418 for edit in edits {
2419 let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
2420 text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
2421 }
2422 assert_eq!(text.to_string(), snapshot.text());
2423 }
2424}
2425
2426#[gpui::test]
2427fn test_history(cx: &mut App) {
2428 let test_settings = SettingsStore::test(cx);
2429 cx.set_global(test_settings);
2430 let group_interval: Duration = Duration::from_millis(1);
2431 let buffer_1 = cx.new(|cx| {
2432 let mut buf = Buffer::local("1234", cx);
2433 buf.set_group_interval(group_interval);
2434 buf
2435 });
2436 let buffer_2 = cx.new(|cx| {
2437 let mut buf = Buffer::local("5678", cx);
2438 buf.set_group_interval(group_interval);
2439 buf
2440 });
2441 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2442 multibuffer.update(cx, |this, _| {
2443 this.history.group_interval = group_interval;
2444 });
2445 multibuffer.update(cx, |multibuffer, cx| {
2446 multibuffer.push_excerpts(
2447 buffer_1.clone(),
2448 [ExcerptRange {
2449 context: 0..buffer_1.read(cx).len(),
2450 primary: None,
2451 }],
2452 cx,
2453 );
2454 multibuffer.push_excerpts(
2455 buffer_2.clone(),
2456 [ExcerptRange {
2457 context: 0..buffer_2.read(cx).len(),
2458 primary: None,
2459 }],
2460 cx,
2461 );
2462 });
2463
2464 let mut now = Instant::now();
2465
2466 multibuffer.update(cx, |multibuffer, cx| {
2467 let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
2468 multibuffer.edit(
2469 [
2470 (Point::new(0, 0)..Point::new(0, 0), "A"),
2471 (Point::new(1, 0)..Point::new(1, 0), "A"),
2472 ],
2473 None,
2474 cx,
2475 );
2476 multibuffer.edit(
2477 [
2478 (Point::new(0, 1)..Point::new(0, 1), "B"),
2479 (Point::new(1, 1)..Point::new(1, 1), "B"),
2480 ],
2481 None,
2482 cx,
2483 );
2484 multibuffer.end_transaction_at(now, cx);
2485 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2486
2487 // Verify edited ranges for transaction 1
2488 assert_eq!(
2489 multibuffer.edited_ranges_for_transaction(transaction_1, cx),
2490 &[
2491 Point::new(0, 0)..Point::new(0, 2),
2492 Point::new(1, 0)..Point::new(1, 2)
2493 ]
2494 );
2495
2496 // Edit buffer 1 through the multibuffer
2497 now += 2 * group_interval;
2498 multibuffer.start_transaction_at(now, cx);
2499 multibuffer.edit([(2..2, "C")], None, cx);
2500 multibuffer.end_transaction_at(now, cx);
2501 assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
2502
2503 // Edit buffer 1 independently
2504 buffer_1.update(cx, |buffer_1, cx| {
2505 buffer_1.start_transaction_at(now);
2506 buffer_1.edit([(3..3, "D")], None, cx);
2507 buffer_1.end_transaction_at(now, cx);
2508
2509 now += 2 * group_interval;
2510 buffer_1.start_transaction_at(now);
2511 buffer_1.edit([(4..4, "E")], None, cx);
2512 buffer_1.end_transaction_at(now, cx);
2513 });
2514 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2515
2516 // An undo in the multibuffer undoes the multibuffer transaction
2517 // and also any individual buffer edits that have occurred since
2518 // that transaction.
2519 multibuffer.undo(cx);
2520 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2521
2522 multibuffer.undo(cx);
2523 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2524
2525 multibuffer.redo(cx);
2526 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2527
2528 multibuffer.redo(cx);
2529 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2530
2531 // Undo buffer 2 independently.
2532 buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
2533 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
2534
2535 // An undo in the multibuffer undoes the components of the
2536 // the last multibuffer transaction that are not already undone.
2537 multibuffer.undo(cx);
2538 assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
2539
2540 multibuffer.undo(cx);
2541 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2542
2543 multibuffer.redo(cx);
2544 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2545
2546 buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
2547 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2548
2549 // Redo stack gets cleared after an edit.
2550 now += 2 * group_interval;
2551 multibuffer.start_transaction_at(now, cx);
2552 multibuffer.edit([(0..0, "X")], None, cx);
2553 multibuffer.end_transaction_at(now, cx);
2554 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2555 multibuffer.redo(cx);
2556 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2557 multibuffer.undo(cx);
2558 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2559 multibuffer.undo(cx);
2560 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2561
2562 // Transactions can be grouped manually.
2563 multibuffer.redo(cx);
2564 multibuffer.redo(cx);
2565 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2566 multibuffer.group_until_transaction(transaction_1, cx);
2567 multibuffer.undo(cx);
2568 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2569 multibuffer.redo(cx);
2570 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2571 });
2572}
2573
2574#[gpui::test]
2575async fn test_enclosing_indent(cx: &mut TestAppContext) {
2576 async fn enclosing_indent(
2577 text: &str,
2578 buffer_row: u32,
2579 cx: &mut TestAppContext,
2580 ) -> Option<(Range<u32>, LineIndent)> {
2581 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2582 let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
2583 let (range, indent) = snapshot
2584 .enclosing_indent(MultiBufferRow(buffer_row))
2585 .await?;
2586 Some((range.start.0..range.end.0, indent))
2587 }
2588
2589 assert_eq!(
2590 enclosing_indent(
2591 indoc!(
2592 "
2593 fn b() {
2594 if c {
2595 let d = 2;
2596 }
2597 }
2598 "
2599 ),
2600 1,
2601 cx,
2602 )
2603 .await,
2604 Some((
2605 1..2,
2606 LineIndent {
2607 tabs: 0,
2608 spaces: 4,
2609 line_blank: false,
2610 }
2611 ))
2612 );
2613
2614 assert_eq!(
2615 enclosing_indent(
2616 indoc!(
2617 "
2618 fn b() {
2619 if c {
2620 let d = 2;
2621 }
2622 }
2623 "
2624 ),
2625 2,
2626 cx,
2627 )
2628 .await,
2629 Some((
2630 1..2,
2631 LineIndent {
2632 tabs: 0,
2633 spaces: 4,
2634 line_blank: false,
2635 }
2636 ))
2637 );
2638
2639 assert_eq!(
2640 enclosing_indent(
2641 indoc!(
2642 "
2643 fn b() {
2644 if c {
2645 let d = 2;
2646
2647 let e = 5;
2648 }
2649 }
2650 "
2651 ),
2652 3,
2653 cx,
2654 )
2655 .await,
2656 Some((
2657 1..4,
2658 LineIndent {
2659 tabs: 0,
2660 spaces: 4,
2661 line_blank: false,
2662 }
2663 ))
2664 );
2665}
2666
2667fn format_diff(
2668 text: &str,
2669 row_infos: &Vec<RowInfo>,
2670 boundary_rows: &HashSet<MultiBufferRow>,
2671) -> String {
2672 let has_diff = row_infos.iter().any(|info| info.diff_status.is_some());
2673 text.split('\n')
2674 .enumerate()
2675 .zip(row_infos)
2676 .map(|((ix, line), info)| {
2677 let marker = match info.diff_status {
2678 Some(DiffHunkStatus::Added) => "+ ",
2679 Some(DiffHunkStatus::Removed) => "- ",
2680 Some(DiffHunkStatus::Modified) => unreachable!(),
2681 None => {
2682 if has_diff && !line.is_empty() {
2683 " "
2684 } else {
2685 ""
2686 }
2687 }
2688 };
2689 let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
2690 if has_diff {
2691 " ----------\n"
2692 } else {
2693 "---------\n"
2694 }
2695 } else {
2696 ""
2697 };
2698 format!("{boundary_row}{marker}{line}")
2699 })
2700 .collect::<Vec<_>>()
2701 .join("\n")
2702}
2703
2704#[track_caller]
2705fn assert_new_snapshot(
2706 multibuffer: &Entity<MultiBuffer>,
2707 snapshot: &mut MultiBufferSnapshot,
2708 subscription: &mut Subscription,
2709 cx: &mut TestAppContext,
2710 expected_diff: &str,
2711) {
2712 let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2713 let actual_text = new_snapshot.text();
2714 let line_infos = new_snapshot
2715 .row_infos(MultiBufferRow(0))
2716 .collect::<Vec<_>>();
2717 let actual_diff = format_diff(&actual_text, &line_infos, &Default::default());
2718 pretty_assertions::assert_eq!(actual_diff, expected_diff);
2719 check_edits(
2720 snapshot,
2721 &new_snapshot,
2722 &subscription.consume().into_inner(),
2723 );
2724 *snapshot = new_snapshot;
2725}
2726
2727#[track_caller]
2728fn check_edits(
2729 old_snapshot: &MultiBufferSnapshot,
2730 new_snapshot: &MultiBufferSnapshot,
2731 edits: &[Edit<usize>],
2732) {
2733 let mut text = old_snapshot.text();
2734 let new_text = new_snapshot.text();
2735 for edit in edits.iter().rev() {
2736 if !text.is_char_boundary(edit.old.start)
2737 || !text.is_char_boundary(edit.old.end)
2738 || !new_text.is_char_boundary(edit.new.start)
2739 || !new_text.is_char_boundary(edit.new.end)
2740 {
2741 panic!(
2742 "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
2743 edits, text, new_text
2744 );
2745 }
2746
2747 text.replace_range(
2748 edit.old.start..edit.old.end,
2749 &new_text[edit.new.start..edit.new.end],
2750 );
2751 }
2752
2753 pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
2754}
2755
2756#[track_caller]
2757fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
2758 let full_text = snapshot.text();
2759 for ix in 0..full_text.len() {
2760 let mut chunks = snapshot.chunks(0..snapshot.len(), false);
2761 chunks.seek(ix..snapshot.len());
2762 let tail = chunks.map(|chunk| chunk.text).collect::<String>();
2763 assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
2764 }
2765}
2766
2767#[track_caller]
2768fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
2769 let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2770 for start_row in 1..all_line_numbers.len() {
2771 let line_numbers = snapshot
2772 .row_infos(MultiBufferRow(start_row as u32))
2773 .collect::<Vec<_>>();
2774 assert_eq!(
2775 line_numbers,
2776 all_line_numbers[start_row..],
2777 "start_row: {start_row}"
2778 );
2779 }
2780}
2781
2782#[track_caller]
2783fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
2784 let text = Rope::from(snapshot.text());
2785
2786 let mut left_anchors = Vec::new();
2787 let mut right_anchors = Vec::new();
2788 let mut offsets = Vec::new();
2789 let mut points = Vec::new();
2790 for offset in 0..=text.len() + 1 {
2791 let clipped_left = snapshot.clip_offset(offset, Bias::Left);
2792 let clipped_right = snapshot.clip_offset(offset, Bias::Right);
2793 assert_eq!(
2794 clipped_left,
2795 text.clip_offset(offset, Bias::Left),
2796 "clip_offset({offset:?}, Left)"
2797 );
2798 assert_eq!(
2799 clipped_right,
2800 text.clip_offset(offset, Bias::Right),
2801 "clip_offset({offset:?}, Right)"
2802 );
2803 assert_eq!(
2804 snapshot.offset_to_point(clipped_left),
2805 text.offset_to_point(clipped_left),
2806 "offset_to_point({clipped_left})"
2807 );
2808 assert_eq!(
2809 snapshot.offset_to_point(clipped_right),
2810 text.offset_to_point(clipped_right),
2811 "offset_to_point({clipped_right})"
2812 );
2813 let anchor_after = snapshot.anchor_after(clipped_left);
2814 assert_eq!(
2815 anchor_after.to_offset(snapshot),
2816 clipped_left,
2817 "anchor_after({clipped_left}).to_offset {anchor_after:?}"
2818 );
2819 let anchor_before = snapshot.anchor_before(clipped_left);
2820 assert_eq!(
2821 anchor_before.to_offset(snapshot),
2822 clipped_left,
2823 "anchor_before({clipped_left}).to_offset"
2824 );
2825 left_anchors.push(anchor_before);
2826 right_anchors.push(anchor_after);
2827 offsets.push(clipped_left);
2828 points.push(text.offset_to_point(clipped_left));
2829 }
2830
2831 for row in 0..text.max_point().row {
2832 for column in 0..text.line_len(row) + 1 {
2833 let point = Point { row, column };
2834 let clipped_left = snapshot.clip_point(point, Bias::Left);
2835 let clipped_right = snapshot.clip_point(point, Bias::Right);
2836 assert_eq!(
2837 clipped_left,
2838 text.clip_point(point, Bias::Left),
2839 "clip_point({point:?}, Left)"
2840 );
2841 assert_eq!(
2842 clipped_right,
2843 text.clip_point(point, Bias::Right),
2844 "clip_point({point:?}, Right)"
2845 );
2846 assert_eq!(
2847 snapshot.point_to_offset(clipped_left),
2848 text.point_to_offset(clipped_left),
2849 "point_to_offset({clipped_left:?})"
2850 );
2851 assert_eq!(
2852 snapshot.point_to_offset(clipped_right),
2853 text.point_to_offset(clipped_right),
2854 "point_to_offset({clipped_right:?})"
2855 );
2856 }
2857 }
2858
2859 assert_eq!(
2860 snapshot.summaries_for_anchors::<usize, _>(&left_anchors),
2861 offsets,
2862 "left_anchors <-> offsets"
2863 );
2864 assert_eq!(
2865 snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
2866 points,
2867 "left_anchors <-> points"
2868 );
2869 assert_eq!(
2870 snapshot.summaries_for_anchors::<usize, _>(&right_anchors),
2871 offsets,
2872 "right_anchors <-> offsets"
2873 );
2874 assert_eq!(
2875 snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
2876 points,
2877 "right_anchors <-> points"
2878 );
2879
2880 for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
2881 for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
2882 if ix > 0 {
2883 if *offset == 252 {
2884 if offset > &offsets[ix - 1] {
2885 let prev_anchor = left_anchors[ix - 1];
2886 assert!(
2887 anchor.cmp(&prev_anchor, snapshot).is_gt(),
2888 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
2889 offsets[ix],
2890 offsets[ix - 1],
2891 );
2892 assert!(
2893 prev_anchor.cmp(&anchor, snapshot).is_lt(),
2894 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
2895 offsets[ix - 1],
2896 offsets[ix],
2897 );
2898 }
2899 }
2900 }
2901 }
2902 }
2903}
2904
2905fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
2906 let max_row = snapshot.max_point().row;
2907 let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
2908 let text = text::Buffer::new(0, buffer_id, snapshot.text());
2909 let mut line_indents = text
2910 .line_indents_in_row_range(0..max_row + 1)
2911 .collect::<Vec<_>>();
2912 for start_row in 0..snapshot.max_point().row {
2913 pretty_assertions::assert_eq!(
2914 snapshot
2915 .line_indents(MultiBufferRow(start_row), |_| true)
2916 .map(|(row, indent, _)| (row.0, indent))
2917 .collect::<Vec<_>>(),
2918 &line_indents[(start_row as usize)..],
2919 "line_indents({start_row})"
2920 );
2921 }
2922
2923 line_indents.reverse();
2924 pretty_assertions::assert_eq!(
2925 snapshot
2926 .reversed_line_indents(MultiBufferRow(max_row), |_| true)
2927 .map(|(row, indent, _)| (row.0, indent))
2928 .collect::<Vec<_>>(),
2929 &line_indents[..],
2930 "reversed_line_indents({max_row})"
2931 );
2932}