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