1use super::*;
2use git::diff::DiffHunkStatus;
3use gpui::{AppContext, Context, 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 AppContext) {
21 let buffer = cx.new_model(|cx| Buffer::local("", cx));
22 let multibuffer = cx.new_model(|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 AppContext) {
37 let buffer = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
38 let multibuffer = cx.new_model(|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 AppContext) {
72 let host_buffer = cx.new_model(|cx| Buffer::local("a", cx));
73 let guest_buffer = cx.new_model(|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_model(|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 AppContext) {
101 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
102 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
103 let multibuffer = cx.new_model(|_| 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 AppContext) {
358 let base_text = "one\ntwo\nthree\n";
359 let text = "one\nthree\n";
360 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
361 let snapshot = buffer.read(cx).snapshot();
362 let change_set = cx.new_model(|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_model(|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_model(|cx| Buffer::local(text, cx));
410 let change_set = cx.new_model(|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_model(|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_model(|cx| Buffer::local(text, cx));
508 let change_set = cx.new_model(|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_model(|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 AppContext) {
664 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(10, 3, 'a'), cx));
665 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text(10, 3, 'm'), cx));
666
667 let leader_multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
668 let follower_multibuffer = cx.new_model(|_| 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 AppContext) {
770 let buffer = cx.new_model(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
771 let multibuffer = cx.new_model(|_| 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 AppContext) {
846 let buffer = cx.new_model(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
847 let multibuffer = cx.new_model(|_| 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_model(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
900 let buffer_2 = cx.new_model(|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_model(|_| 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 AppContext) {
976 let multibuffer = cx.new_model(|_| 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 AppContext) {
998 let buffer = cx.new_model(|cx| Buffer::local("abcd", cx));
999 let multibuffer = cx.new_model(|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 AppContext) {
1018 let buffer_1 = cx.new_model(|cx| Buffer::local("abcd", cx));
1019 let buffer_2 = cx.new_model(|cx| Buffer::local("efghi", cx));
1020 let multibuffer = cx.new_model(|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 AppContext) {
1076 let buffer_1 = cx.new_model(|cx| Buffer::local("abcd", cx));
1077 let buffer_2 = cx.new_model(|cx| Buffer::local("ABCDEFGHIJKLMNOP", cx));
1078 let multibuffer = cx.new_model(|_| 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_model(|cx| Buffer::local(text, cx));
1228 let change_set =
1229 cx.new_model(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
1230 cx.run_until_parked();
1231
1232 let multibuffer = cx.new_model(|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_model(|cx| Buffer::local(text, cx));
1472 let change_set =
1473 cx.new_model(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
1474 cx.run_until_parked();
1475
1476 let multibuffer = cx.new_model(|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_model(|cx| Buffer::local(text_1, cx));
1577 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2, cx));
1578 let change_set_1 = cx.new_model(|cx| {
1579 BufferChangeSet::new_with_base_text(base_text_1.to_string(), &buffer_1, cx)
1580 });
1581 let change_set_2 = cx.new_model(|cx| {
1582 BufferChangeSet::new_with_base_text(base_text_2.to_string(), &buffer_2, cx)
1583 });
1584 cx.run_until_parked();
1585
1586 let multibuffer = cx.new_model(|cx| {
1587 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1588 multibuffer.push_excerpts(
1589 buffer_1.clone(),
1590 [ExcerptRange {
1591 context: text::Anchor::MIN..text::Anchor::MAX,
1592 primary: None,
1593 }],
1594 cx,
1595 );
1596 multibuffer.push_excerpts(
1597 buffer_2.clone(),
1598 [ExcerptRange {
1599 context: text::Anchor::MIN..text::Anchor::MAX,
1600 primary: None,
1601 }],
1602 cx,
1603 );
1604 multibuffer.add_change_set(change_set_1.clone(), cx);
1605 multibuffer.add_change_set(change_set_2.clone(), cx);
1606 multibuffer
1607 });
1608
1609 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1610 (multibuffer.snapshot(cx), multibuffer.subscribe())
1611 });
1612 assert_eq!(
1613 snapshot.text(),
1614 indoc!(
1615 "
1616 ZERO
1617 one
1618 TWO
1619 three
1620 six
1621
1622 eight
1623 nine
1624 eleven
1625 THIRTEEN
1626 FOURTEEN
1627 "
1628 ),
1629 );
1630
1631 multibuffer.update(cx, |multibuffer, cx| {
1632 multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1633 });
1634
1635 assert_new_snapshot(
1636 &multibuffer,
1637 &mut snapshot,
1638 &mut subscription,
1639 cx,
1640 indoc!(
1641 "
1642 + ZERO
1643 one
1644 - two
1645 + TWO
1646 three
1647 - four
1648 - five
1649 six
1650
1651 - seven
1652 eight
1653 nine
1654 - ten
1655 eleven
1656 - twelve
1657 + THIRTEEN
1658 + FOURTEEN
1659 "
1660 ),
1661 );
1662
1663 let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
1664 let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
1665 let base_id_1 = change_set_1.read_with(cx, |change_set, _| {
1666 change_set.base_text.as_ref().unwrap().remote_id()
1667 });
1668 let base_id_2 = change_set_2.read_with(cx, |change_set, _| {
1669 change_set.base_text.as_ref().unwrap().remote_id()
1670 });
1671
1672 let buffer_lines = (0..=snapshot.max_row().0)
1673 .map(|row| {
1674 let (buffer, range) = snapshot.buffer_line_for_row(MultiBufferRow(row))?;
1675 Some((
1676 buffer.remote_id(),
1677 buffer.text_for_range(range).collect::<String>(),
1678 ))
1679 })
1680 .collect::<Vec<_>>();
1681 pretty_assertions::assert_eq!(
1682 buffer_lines,
1683 [
1684 Some((id_1, "ZERO".into())),
1685 Some((id_1, "one".into())),
1686 Some((base_id_1, "two".into())),
1687 Some((id_1, "TWO".into())),
1688 Some((id_1, " three".into())),
1689 Some((base_id_1, "four".into())),
1690 Some((base_id_1, "five".into())),
1691 Some((id_1, "six".into())),
1692 Some((id_1, "".into())),
1693 Some((base_id_2, "seven".into())),
1694 Some((id_2, " eight".into())),
1695 Some((id_2, "nine".into())),
1696 Some((base_id_2, "ten".into())),
1697 Some((id_2, "eleven".into())),
1698 Some((base_id_2, "twelve".into())),
1699 Some((id_2, "THIRTEEN".into())),
1700 Some((id_2, "FOURTEEN".into())),
1701 Some((id_2, "".into())),
1702 ]
1703 );
1704
1705 assert_position_translation(&snapshot);
1706 assert_line_indents(&snapshot);
1707
1708 assert_eq!(
1709 snapshot
1710 .diff_hunks_in_range(0..snapshot.len())
1711 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1712 .collect::<Vec<_>>(),
1713 &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
1714 );
1715
1716 buffer_2.update(cx, |buffer, cx| {
1717 buffer.edit_via_marked_text(
1718 indoc!(
1719 "
1720 eight
1721 «»eleven
1722 THIRTEEN
1723 FOURTEEN
1724 "
1725 ),
1726 None,
1727 cx,
1728 );
1729 });
1730
1731 assert_new_snapshot(
1732 &multibuffer,
1733 &mut snapshot,
1734 &mut subscription,
1735 cx,
1736 indoc!(
1737 "
1738 + ZERO
1739 one
1740 - two
1741 + TWO
1742 three
1743 - four
1744 - five
1745 six
1746
1747 - seven
1748 eight
1749 eleven
1750 - twelve
1751 + THIRTEEN
1752 + FOURTEEN
1753 "
1754 ),
1755 );
1756
1757 assert_line_indents(&snapshot);
1758}
1759
1760/// A naive implementation of a multi-buffer that does not maintain
1761/// any derived state, used for comparison in a randomized test.
1762#[derive(Default)]
1763struct ReferenceMultibuffer {
1764 excerpts: Vec<ReferenceExcerpt>,
1765 change_sets: HashMap<BufferId, Model<BufferChangeSet>>,
1766}
1767
1768struct ReferenceExcerpt {
1769 id: ExcerptId,
1770 buffer: Model<Buffer>,
1771 range: Range<text::Anchor>,
1772 expanded_diff_hunks: Vec<text::Anchor>,
1773}
1774
1775#[derive(Debug)]
1776struct ReferenceRegion {
1777 range: Range<usize>,
1778 buffer_start: Option<Point>,
1779 status: Option<DiffHunkStatus>,
1780}
1781
1782impl ReferenceMultibuffer {
1783 fn expand_excerpts(&mut self, excerpts: &HashSet<ExcerptId>, line_count: u32, cx: &AppContext) {
1784 if line_count == 0 {
1785 return;
1786 }
1787
1788 for id in excerpts {
1789 let excerpt = self.excerpts.iter_mut().find(|e| e.id == *id).unwrap();
1790 let snapshot = excerpt.buffer.read(cx).snapshot();
1791 let mut point_range = excerpt.range.to_point(&snapshot);
1792 point_range.start = Point::new(point_range.start.row.saturating_sub(line_count), 0);
1793 point_range.end =
1794 snapshot.clip_point(Point::new(point_range.end.row + line_count, 0), Bias::Left);
1795 point_range.end.column = snapshot.line_len(point_range.end.row);
1796 excerpt.range =
1797 snapshot.anchor_before(point_range.start)..snapshot.anchor_after(point_range.end);
1798 }
1799 }
1800
1801 fn remove_excerpt(&mut self, id: ExcerptId, cx: &AppContext) {
1802 let ix = self
1803 .excerpts
1804 .iter()
1805 .position(|excerpt| excerpt.id == id)
1806 .unwrap();
1807 let excerpt = self.excerpts.remove(ix);
1808 let buffer = excerpt.buffer.read(cx);
1809 log::info!(
1810 "Removing excerpt {}: {:?}",
1811 ix,
1812 buffer
1813 .text_for_range(excerpt.range.to_offset(buffer))
1814 .collect::<String>(),
1815 );
1816 }
1817
1818 fn insert_excerpt_after(
1819 &mut self,
1820 prev_id: ExcerptId,
1821 new_excerpt_id: ExcerptId,
1822 (buffer_handle, anchor_range): (Model<Buffer>, Range<text::Anchor>),
1823 ) {
1824 let excerpt_ix = if prev_id == ExcerptId::max() {
1825 self.excerpts.len()
1826 } else {
1827 self.excerpts
1828 .iter()
1829 .position(|excerpt| excerpt.id == prev_id)
1830 .unwrap()
1831 + 1
1832 };
1833 self.excerpts.insert(
1834 excerpt_ix,
1835 ReferenceExcerpt {
1836 id: new_excerpt_id,
1837 buffer: buffer_handle,
1838 range: anchor_range,
1839 expanded_diff_hunks: Vec::new(),
1840 },
1841 );
1842 }
1843
1844 fn expand_diff_hunks(
1845 &mut self,
1846 excerpt_id: ExcerptId,
1847 range: Range<text::Anchor>,
1848 cx: &AppContext,
1849 ) {
1850 let excerpt = self
1851 .excerpts
1852 .iter_mut()
1853 .find(|e| e.id == excerpt_id)
1854 .unwrap();
1855 let buffer = excerpt.buffer.read(cx).snapshot();
1856 let buffer_id = buffer.remote_id();
1857 let Some(change_set) = self.change_sets.get(&buffer_id) else {
1858 return;
1859 };
1860 let diff = change_set.read(cx).diff_to_buffer.clone();
1861 let excerpt_range = excerpt.range.to_offset(&buffer);
1862 if excerpt_range.is_empty() {
1863 return;
1864 }
1865 for hunk in diff.hunks_intersecting_range(range, &buffer) {
1866 let hunk_range = hunk.buffer_range.to_offset(&buffer);
1867 let hunk_precedes_excerpt = hunk
1868 .buffer_range
1869 .end
1870 .cmp(&excerpt.range.start, &buffer)
1871 .is_le();
1872 let hunk_follows_excerpt = hunk
1873 .buffer_range
1874 .start
1875 .cmp(&excerpt.range.end, &buffer)
1876 .is_ge();
1877 if hunk_precedes_excerpt || hunk_follows_excerpt {
1878 continue;
1879 }
1880
1881 if let Err(ix) = excerpt
1882 .expanded_diff_hunks
1883 .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
1884 {
1885 log::info!(
1886 "expanding diff hunk {:?}. excerpt: {:?}",
1887 hunk_range,
1888 excerpt_range
1889 );
1890 excerpt
1891 .expanded_diff_hunks
1892 .insert(ix, hunk.buffer_range.start);
1893 }
1894 }
1895 }
1896
1897 fn expected_content(&self, cx: &AppContext) -> (String, Vec<RowInfo>, HashSet<MultiBufferRow>) {
1898 let mut text = String::new();
1899 let mut regions = Vec::<ReferenceRegion>::new();
1900 let mut excerpt_boundary_rows = HashSet::default();
1901 for excerpt in &self.excerpts {
1902 excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
1903 let buffer = excerpt.buffer.read(cx);
1904 let buffer_range = excerpt.range.to_offset(buffer);
1905 let change_set = self.change_sets.get(&buffer.remote_id()).unwrap().read(cx);
1906 let diff = change_set.diff_to_buffer.clone();
1907 let base_buffer = change_set.base_text.as_ref().unwrap();
1908
1909 let mut offset = buffer_range.start;
1910 let mut hunks = diff
1911 .hunks_intersecting_range(excerpt.range.clone(), buffer)
1912 .peekable();
1913
1914 while let Some(hunk) = hunks.next() {
1915 if !hunk.buffer_range.start.is_valid(&buffer) {
1916 continue;
1917 }
1918
1919 // Ignore hunks that are outside the excerpt range.
1920 let mut hunk_range = hunk.buffer_range.to_offset(buffer);
1921 hunk_range.end = hunk_range.end.min(buffer_range.end);
1922 if hunk_range.start > buffer_range.end
1923 || hunk_range.end < buffer_range.start
1924 || buffer_range.is_empty()
1925 {
1926 continue;
1927 }
1928
1929 if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| {
1930 expanded_anchor.to_offset(&buffer).max(buffer_range.start)
1931 == hunk_range.start.max(buffer_range.start)
1932 }) {
1933 continue;
1934 }
1935
1936 if hunk_range.start >= offset {
1937 // Add the buffer text before the hunk
1938 let len = text.len();
1939 text.extend(buffer.text_for_range(offset..hunk_range.start));
1940 regions.push(ReferenceRegion {
1941 range: len..text.len(),
1942 buffer_start: Some(buffer.offset_to_point(offset)),
1943 status: None,
1944 });
1945
1946 // Add the deleted text for the hunk.
1947 if !hunk.diff_base_byte_range.is_empty() {
1948 let mut base_text = base_buffer
1949 .text_for_range(hunk.diff_base_byte_range.clone())
1950 .collect::<String>();
1951 if !base_text.ends_with('\n') {
1952 base_text.push('\n');
1953 }
1954 let len = text.len();
1955 text.push_str(&base_text);
1956 regions.push(ReferenceRegion {
1957 range: len..text.len(),
1958 buffer_start: Some(
1959 base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
1960 ),
1961 status: Some(DiffHunkStatus::Removed),
1962 });
1963 }
1964
1965 offset = hunk_range.start;
1966 }
1967
1968 // Add the inserted text for the hunk.
1969 if hunk_range.end > offset {
1970 let len = text.len();
1971 text.extend(buffer.text_for_range(offset..hunk_range.end));
1972 regions.push(ReferenceRegion {
1973 range: len..text.len(),
1974 buffer_start: Some(buffer.offset_to_point(offset)),
1975 status: Some(DiffHunkStatus::Added),
1976 });
1977 offset = hunk_range.end;
1978 }
1979 }
1980
1981 // Add the buffer text for the rest of the excerpt.
1982 let len = text.len();
1983 text.extend(buffer.text_for_range(offset..buffer_range.end));
1984 text.push('\n');
1985 regions.push(ReferenceRegion {
1986 range: len..text.len(),
1987 buffer_start: Some(buffer.offset_to_point(offset)),
1988 status: None,
1989 });
1990 }
1991
1992 // Remove final trailing newline.
1993 if self.excerpts.is_empty() {
1994 regions.push(ReferenceRegion {
1995 range: 0..1,
1996 buffer_start: Some(Point::new(0, 0)),
1997 status: None,
1998 });
1999 } else {
2000 text.pop();
2001 }
2002
2003 // Retrieve the row info using the region that contains
2004 // the start of each multi-buffer line.
2005 let mut ix = 0;
2006 let row_infos = text
2007 .split('\n')
2008 .map(|line| {
2009 let row_info = regions
2010 .iter()
2011 .find(|region| region.range.contains(&ix))
2012 .map_or(RowInfo::default(), |region| {
2013 let buffer_row = region.buffer_start.map(|start_point| {
2014 start_point.row
2015 + text[region.range.start..ix].matches('\n').count() as u32
2016 });
2017 RowInfo {
2018 diff_status: region.status,
2019 buffer_row,
2020 multibuffer_row: Some(MultiBufferRow(
2021 text[..ix].matches('\n').count() as u32
2022 )),
2023 }
2024 });
2025 ix += line.len() + 1;
2026 row_info
2027 })
2028 .collect();
2029
2030 (text, row_infos, excerpt_boundary_rows)
2031 }
2032
2033 fn diffs_updated(&mut self, cx: &AppContext) {
2034 for excerpt in &mut self.excerpts {
2035 let buffer = excerpt.buffer.read(cx).snapshot();
2036 let excerpt_range = excerpt.range.to_offset(&buffer);
2037 let buffer_id = buffer.remote_id();
2038 let diff = &self
2039 .change_sets
2040 .get(&buffer_id)
2041 .unwrap()
2042 .read(cx)
2043 .diff_to_buffer;
2044 let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer).peekable();
2045 excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
2046 if !hunk_anchor.is_valid(&buffer) {
2047 return false;
2048 }
2049 while let Some(hunk) = hunks.peek() {
2050 match hunk.buffer_range.start.cmp(&hunk_anchor, &buffer) {
2051 cmp::Ordering::Less => {
2052 hunks.next();
2053 }
2054 cmp::Ordering::Equal => {
2055 let hunk_range = hunk.buffer_range.to_offset(&buffer);
2056 return hunk_range.end >= excerpt_range.start
2057 && hunk_range.start <= excerpt_range.end;
2058 }
2059 cmp::Ordering::Greater => break,
2060 }
2061 }
2062 false
2063 });
2064 }
2065 }
2066
2067 fn add_change_set(&mut self, change_set: Model<BufferChangeSet>, cx: &mut AppContext) {
2068 let buffer_id = change_set.read(cx).buffer_id;
2069 self.change_sets.insert(buffer_id, change_set);
2070 }
2071}
2072
2073#[gpui::test(iterations = 100)]
2074fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) {
2075 let operations = env::var("OPERATIONS")
2076 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2077 .unwrap_or(10);
2078
2079 let mut buffers: Vec<Model<Buffer>> = Vec::new();
2080 let multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
2081 let mut reference = ReferenceMultibuffer::default();
2082 let mut anchors = Vec::new();
2083 let mut old_versions = Vec::new();
2084 let mut needs_diff_calculation = false;
2085
2086 for _ in 0..operations {
2087 match rng.gen_range(0..100) {
2088 0..=14 if !buffers.is_empty() => {
2089 let buffer = buffers.choose(&mut rng).unwrap();
2090 buffer.update(cx, |buf, cx| {
2091 let edit_count = rng.gen_range(1..5);
2092 buf.randomly_edit(&mut rng, edit_count, cx);
2093 needs_diff_calculation = true;
2094 });
2095 reference.diffs_updated(cx);
2096 }
2097 15..=19 if !reference.excerpts.is_empty() => {
2098 multibuffer.update(cx, |multibuffer, cx| {
2099 let ids = multibuffer.excerpt_ids();
2100 let mut excerpts = HashSet::default();
2101 for _ in 0..rng.gen_range(0..ids.len()) {
2102 excerpts.extend(ids.choose(&mut rng).copied());
2103 }
2104
2105 let line_count = rng.gen_range(0..5);
2106
2107 let excerpt_ixs = excerpts
2108 .iter()
2109 .map(|id| reference.excerpts.iter().position(|e| e.id == *id).unwrap())
2110 .collect::<Vec<_>>();
2111 log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
2112 multibuffer.expand_excerpts(
2113 excerpts.iter().cloned(),
2114 line_count,
2115 ExpandExcerptDirection::UpAndDown,
2116 cx,
2117 );
2118
2119 reference.expand_excerpts(&excerpts, line_count, cx);
2120 });
2121 }
2122 20..=29 if !reference.excerpts.is_empty() => {
2123 let mut ids_to_remove = vec![];
2124 for _ in 0..rng.gen_range(1..=3) {
2125 let Some(excerpt) = reference.excerpts.choose(&mut rng) else {
2126 break;
2127 };
2128 let id = excerpt.id;
2129 reference.remove_excerpt(id, cx);
2130 ids_to_remove.push(id);
2131 }
2132 let snapshot = multibuffer.read(cx).read(cx);
2133 ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
2134 drop(snapshot);
2135 multibuffer.update(cx, |multibuffer, cx| {
2136 multibuffer.remove_excerpts(ids_to_remove, cx)
2137 });
2138 }
2139 30..=39 if !reference.excerpts.is_empty() => {
2140 let multibuffer = multibuffer.read(cx).read(cx);
2141 let offset =
2142 multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
2143 let bias = if rng.gen() { Bias::Left } else { Bias::Right };
2144 log::info!("Creating anchor at {} with bias {:?}", offset, bias);
2145 anchors.push(multibuffer.anchor_at(offset, bias));
2146 anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
2147 }
2148 40..=44 if !anchors.is_empty() => {
2149 let multibuffer = multibuffer.read(cx).read(cx);
2150 let prev_len = anchors.len();
2151 anchors = multibuffer
2152 .refresh_anchors(&anchors)
2153 .into_iter()
2154 .map(|a| a.1)
2155 .collect();
2156
2157 // Ensure the newly-refreshed anchors point to a valid excerpt and don't
2158 // overshoot its boundaries.
2159 assert_eq!(anchors.len(), prev_len);
2160 for anchor in &anchors {
2161 if anchor.excerpt_id == ExcerptId::min()
2162 || anchor.excerpt_id == ExcerptId::max()
2163 {
2164 continue;
2165 }
2166
2167 let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
2168 assert_eq!(excerpt.id, anchor.excerpt_id);
2169 assert!(excerpt.contains(anchor));
2170 }
2171 }
2172 45..=55 if !reference.excerpts.is_empty() => {
2173 multibuffer.update(cx, |multibuffer, cx| {
2174 let snapshot = multibuffer.snapshot(cx);
2175 let excerpt_ix = rng.gen_range(0..reference.excerpts.len());
2176 let excerpt = &reference.excerpts[excerpt_ix];
2177 let start = excerpt.range.start;
2178 let end = excerpt.range.end;
2179 let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
2180 ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
2181
2182 log::info!("expanding diff hunks for excerpt {:?}", excerpt_ix);
2183 reference.expand_diff_hunks(excerpt.id, start..end, cx);
2184 multibuffer.expand_diff_hunks(vec![range], cx);
2185 });
2186 }
2187 56..=85 if needs_diff_calculation => {
2188 multibuffer.update(cx, |multibuffer, cx| {
2189 for buffer in multibuffer.all_buffers() {
2190 let snapshot = buffer.read(cx).snapshot();
2191 let _ = multibuffer
2192 .change_set_for(snapshot.remote_id())
2193 .unwrap()
2194 .update(cx, |change_set, cx| {
2195 log::info!(
2196 "recalculating diff for buffer {:?}",
2197 snapshot.remote_id(),
2198 );
2199 change_set.recalculate_diff_sync(
2200 change_set.base_text.clone().unwrap().text(),
2201 snapshot.text,
2202 false,
2203 cx,
2204 )
2205 });
2206 }
2207 reference.diffs_updated(cx);
2208 needs_diff_calculation = false;
2209 });
2210 }
2211 _ => {
2212 let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
2213 let base_text = util::RandomCharIter::new(&mut rng)
2214 .take(256)
2215 .collect::<String>();
2216
2217 let buffer = cx.new_model(|cx| Buffer::local(base_text.clone(), cx));
2218 let snapshot = buffer.read(cx).snapshot();
2219 let change_set = cx.new_model(|cx| {
2220 let mut change_set = BufferChangeSet::new(&buffer, cx);
2221 change_set.recalculate_diff_sync(base_text, snapshot.text, true, cx);
2222 change_set
2223 });
2224
2225 reference.add_change_set(change_set.clone(), cx);
2226 multibuffer.update(cx, |multibuffer, cx| {
2227 multibuffer.add_change_set(change_set, cx)
2228 });
2229 buffers.push(buffer);
2230 buffers.last().unwrap()
2231 } else {
2232 buffers.choose(&mut rng).unwrap()
2233 };
2234
2235 let buffer = buffer_handle.read(cx);
2236 let end_row = rng.gen_range(0..=buffer.max_point().row);
2237 let start_row = rng.gen_range(0..=end_row);
2238 let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
2239 let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
2240 let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
2241 let prev_excerpt_ix = rng.gen_range(0..=reference.excerpts.len());
2242 let prev_excerpt_id = reference
2243 .excerpts
2244 .get(prev_excerpt_ix)
2245 .map_or(ExcerptId::max(), |e| e.id);
2246 let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
2247
2248 log::info!(
2249 "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
2250 excerpt_ix,
2251 reference.excerpts.len(),
2252 buffer_handle.read(cx).remote_id(),
2253 buffer.text(),
2254 start_ix..end_ix,
2255 &buffer.text()[start_ix..end_ix]
2256 );
2257
2258 let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
2259 multibuffer
2260 .insert_excerpts_after(
2261 prev_excerpt_id,
2262 buffer_handle.clone(),
2263 [ExcerptRange {
2264 context: start_ix..end_ix,
2265 primary: None,
2266 }],
2267 cx,
2268 )
2269 .pop()
2270 .unwrap()
2271 });
2272
2273 reference.insert_excerpt_after(
2274 prev_excerpt_id,
2275 excerpt_id,
2276 (buffer_handle.clone(), anchor_range),
2277 );
2278 }
2279 }
2280
2281 if rng.gen_bool(0.3) {
2282 multibuffer.update(cx, |multibuffer, cx| {
2283 old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
2284 })
2285 }
2286
2287 let snapshot = multibuffer.read(cx).snapshot(cx);
2288 let actual_text = snapshot.text();
2289 let actual_boundary_rows = snapshot
2290 .excerpt_boundaries_in_range(0..)
2291 .filter_map(|b| if b.next.is_some() { Some(b.row) } else { None })
2292 .collect::<HashSet<_>>();
2293 let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2294 let actual_diff = format_diff(&actual_text, &actual_row_infos, &actual_boundary_rows);
2295
2296 let (expected_text, expected_row_infos, expected_boundary_rows) =
2297 reference.expected_content(cx);
2298 let expected_diff =
2299 format_diff(&expected_text, &expected_row_infos, &expected_boundary_rows);
2300
2301 log::info!("Multibuffer content:\n{}", actual_diff);
2302
2303 assert_eq!(
2304 actual_row_infos.len(),
2305 actual_text.split('\n').count(),
2306 "line count: {}",
2307 actual_text.split('\n').count()
2308 );
2309 pretty_assertions::assert_eq!(actual_diff, expected_diff);
2310 pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
2311 pretty_assertions::assert_eq!(actual_text, expected_text);
2312
2313 for _ in 0..5 {
2314 let start_row = rng.gen_range(0..=expected_row_infos.len());
2315 assert_eq!(
2316 snapshot
2317 .row_infos(MultiBufferRow(start_row as u32))
2318 .collect::<Vec<_>>(),
2319 &expected_row_infos[start_row..],
2320 "buffer_rows({})",
2321 start_row
2322 );
2323 }
2324
2325 assert_eq!(
2326 snapshot.widest_line_number(),
2327 expected_row_infos
2328 .into_iter()
2329 .filter_map(
2330 |info| if info.diff_status == Some(DiffHunkStatus::Removed) {
2331 None
2332 } else {
2333 info.buffer_row
2334 }
2335 )
2336 .max()
2337 .unwrap()
2338 + 1
2339 );
2340
2341 assert_consistent_line_numbers(&snapshot);
2342 assert_position_translation(&snapshot);
2343
2344 for (row, line) in expected_text.split('\n').enumerate() {
2345 assert_eq!(
2346 snapshot.line_len(MultiBufferRow(row as u32)),
2347 line.len() as u32,
2348 "line_len({}).",
2349 row
2350 );
2351 }
2352
2353 let text_rope = Rope::from(expected_text.as_str());
2354 for _ in 0..10 {
2355 let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2356 let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
2357
2358 let text_for_range = snapshot
2359 .text_for_range(start_ix..end_ix)
2360 .collect::<String>();
2361 assert_eq!(
2362 text_for_range,
2363 &expected_text[start_ix..end_ix],
2364 "incorrect text for range {:?}",
2365 start_ix..end_ix
2366 );
2367
2368 let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
2369 assert_eq!(
2370 snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
2371 expected_summary,
2372 "incorrect summary for range {:?}",
2373 start_ix..end_ix
2374 );
2375 }
2376
2377 // Anchor resolution
2378 let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
2379 assert_eq!(anchors.len(), summaries.len());
2380 for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
2381 assert!(resolved_offset <= snapshot.len());
2382 assert_eq!(
2383 snapshot.summary_for_anchor::<usize>(anchor),
2384 resolved_offset,
2385 "anchor: {:?}",
2386 anchor
2387 );
2388 }
2389
2390 for _ in 0..10 {
2391 let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2392 assert_eq!(
2393 snapshot.reversed_chars_at(end_ix).collect::<String>(),
2394 expected_text[..end_ix].chars().rev().collect::<String>(),
2395 );
2396 }
2397
2398 for _ in 0..10 {
2399 let end_ix = rng.gen_range(0..=text_rope.len());
2400 let start_ix = rng.gen_range(0..=end_ix);
2401 assert_eq!(
2402 snapshot
2403 .bytes_in_range(start_ix..end_ix)
2404 .flatten()
2405 .copied()
2406 .collect::<Vec<_>>(),
2407 expected_text.as_bytes()[start_ix..end_ix].to_vec(),
2408 "bytes_in_range({:?})",
2409 start_ix..end_ix,
2410 );
2411 }
2412 }
2413
2414 let snapshot = multibuffer.read(cx).snapshot(cx);
2415 for (old_snapshot, subscription) in old_versions {
2416 let edits = subscription.consume().into_inner();
2417
2418 log::info!(
2419 "applying subscription edits to old text: {:?}: {:?}",
2420 old_snapshot.text(),
2421 edits,
2422 );
2423
2424 let mut text = old_snapshot.text();
2425 for edit in edits {
2426 let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
2427 text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
2428 }
2429 assert_eq!(text.to_string(), snapshot.text());
2430 }
2431}
2432
2433#[gpui::test]
2434fn test_history(cx: &mut AppContext) {
2435 let test_settings = SettingsStore::test(cx);
2436 cx.set_global(test_settings);
2437 let group_interval: Duration = Duration::from_millis(1);
2438 let buffer_1 = cx.new_model(|cx| {
2439 let mut buf = Buffer::local("1234", cx);
2440 buf.set_group_interval(group_interval);
2441 buf
2442 });
2443 let buffer_2 = cx.new_model(|cx| {
2444 let mut buf = Buffer::local("5678", cx);
2445 buf.set_group_interval(group_interval);
2446 buf
2447 });
2448 let multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
2449 multibuffer.update(cx, |this, _| {
2450 this.history.group_interval = group_interval;
2451 });
2452 multibuffer.update(cx, |multibuffer, cx| {
2453 multibuffer.push_excerpts(
2454 buffer_1.clone(),
2455 [ExcerptRange {
2456 context: 0..buffer_1.read(cx).len(),
2457 primary: None,
2458 }],
2459 cx,
2460 );
2461 multibuffer.push_excerpts(
2462 buffer_2.clone(),
2463 [ExcerptRange {
2464 context: 0..buffer_2.read(cx).len(),
2465 primary: None,
2466 }],
2467 cx,
2468 );
2469 });
2470
2471 let mut now = Instant::now();
2472
2473 multibuffer.update(cx, |multibuffer, cx| {
2474 let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
2475 multibuffer.edit(
2476 [
2477 (Point::new(0, 0)..Point::new(0, 0), "A"),
2478 (Point::new(1, 0)..Point::new(1, 0), "A"),
2479 ],
2480 None,
2481 cx,
2482 );
2483 multibuffer.edit(
2484 [
2485 (Point::new(0, 1)..Point::new(0, 1), "B"),
2486 (Point::new(1, 1)..Point::new(1, 1), "B"),
2487 ],
2488 None,
2489 cx,
2490 );
2491 multibuffer.end_transaction_at(now, cx);
2492 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2493
2494 // Verify edited ranges for transaction 1
2495 assert_eq!(
2496 multibuffer.edited_ranges_for_transaction(transaction_1, cx),
2497 &[
2498 Point::new(0, 0)..Point::new(0, 2),
2499 Point::new(1, 0)..Point::new(1, 2)
2500 ]
2501 );
2502
2503 // Edit buffer 1 through the multibuffer
2504 now += 2 * group_interval;
2505 multibuffer.start_transaction_at(now, cx);
2506 multibuffer.edit([(2..2, "C")], None, cx);
2507 multibuffer.end_transaction_at(now, cx);
2508 assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
2509
2510 // Edit buffer 1 independently
2511 buffer_1.update(cx, |buffer_1, cx| {
2512 buffer_1.start_transaction_at(now);
2513 buffer_1.edit([(3..3, "D")], None, cx);
2514 buffer_1.end_transaction_at(now, cx);
2515
2516 now += 2 * group_interval;
2517 buffer_1.start_transaction_at(now);
2518 buffer_1.edit([(4..4, "E")], None, cx);
2519 buffer_1.end_transaction_at(now, cx);
2520 });
2521 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2522
2523 // An undo in the multibuffer undoes the multibuffer transaction
2524 // and also any individual buffer edits that have occurred since
2525 // that transaction.
2526 multibuffer.undo(cx);
2527 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2528
2529 multibuffer.undo(cx);
2530 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2531
2532 multibuffer.redo(cx);
2533 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2534
2535 multibuffer.redo(cx);
2536 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2537
2538 // Undo buffer 2 independently.
2539 buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
2540 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
2541
2542 // An undo in the multibuffer undoes the components of the
2543 // the last multibuffer transaction that are not already undone.
2544 multibuffer.undo(cx);
2545 assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
2546
2547 multibuffer.undo(cx);
2548 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2549
2550 multibuffer.redo(cx);
2551 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2552
2553 buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
2554 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2555
2556 // Redo stack gets cleared after an edit.
2557 now += 2 * group_interval;
2558 multibuffer.start_transaction_at(now, cx);
2559 multibuffer.edit([(0..0, "X")], None, cx);
2560 multibuffer.end_transaction_at(now, cx);
2561 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2562 multibuffer.redo(cx);
2563 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2564 multibuffer.undo(cx);
2565 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2566 multibuffer.undo(cx);
2567 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2568
2569 // Transactions can be grouped manually.
2570 multibuffer.redo(cx);
2571 multibuffer.redo(cx);
2572 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2573 multibuffer.group_until_transaction(transaction_1, cx);
2574 multibuffer.undo(cx);
2575 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2576 multibuffer.redo(cx);
2577 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2578 });
2579}
2580
2581#[gpui::test]
2582async fn test_enclosing_indent(cx: &mut TestAppContext) {
2583 async fn enclosing_indent(
2584 text: &str,
2585 buffer_row: u32,
2586 cx: &mut TestAppContext,
2587 ) -> Option<(Range<u32>, LineIndent)> {
2588 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2589 let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
2590 let (range, indent) = snapshot
2591 .enclosing_indent(MultiBufferRow(buffer_row))
2592 .await?;
2593 Some((range.start.0..range.end.0, indent))
2594 }
2595
2596 assert_eq!(
2597 enclosing_indent(
2598 indoc!(
2599 "
2600 fn b() {
2601 if c {
2602 let d = 2;
2603 }
2604 }
2605 "
2606 ),
2607 1,
2608 cx,
2609 )
2610 .await,
2611 Some((
2612 1..2,
2613 LineIndent {
2614 tabs: 0,
2615 spaces: 4,
2616 line_blank: false,
2617 }
2618 ))
2619 );
2620
2621 assert_eq!(
2622 enclosing_indent(
2623 indoc!(
2624 "
2625 fn b() {
2626 if c {
2627 let d = 2;
2628 }
2629 }
2630 "
2631 ),
2632 2,
2633 cx,
2634 )
2635 .await,
2636 Some((
2637 1..2,
2638 LineIndent {
2639 tabs: 0,
2640 spaces: 4,
2641 line_blank: false,
2642 }
2643 ))
2644 );
2645
2646 assert_eq!(
2647 enclosing_indent(
2648 indoc!(
2649 "
2650 fn b() {
2651 if c {
2652 let d = 2;
2653
2654 let e = 5;
2655 }
2656 }
2657 "
2658 ),
2659 3,
2660 cx,
2661 )
2662 .await,
2663 Some((
2664 1..4,
2665 LineIndent {
2666 tabs: 0,
2667 spaces: 4,
2668 line_blank: false,
2669 }
2670 ))
2671 );
2672}
2673
2674fn format_diff(
2675 text: &str,
2676 row_infos: &Vec<RowInfo>,
2677 boundary_rows: &HashSet<MultiBufferRow>,
2678) -> String {
2679 let has_diff = row_infos.iter().any(|info| info.diff_status.is_some());
2680 text.split('\n')
2681 .enumerate()
2682 .zip(row_infos)
2683 .map(|((ix, line), info)| {
2684 let marker = match info.diff_status {
2685 Some(DiffHunkStatus::Added) => "+ ",
2686 Some(DiffHunkStatus::Removed) => "- ",
2687 Some(DiffHunkStatus::Modified) => unreachable!(),
2688 None => {
2689 if has_diff && !line.is_empty() {
2690 " "
2691 } else {
2692 ""
2693 }
2694 }
2695 };
2696 let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
2697 if has_diff {
2698 " ----------\n"
2699 } else {
2700 "---------\n"
2701 }
2702 } else {
2703 ""
2704 };
2705 format!("{boundary_row}{marker}{line}")
2706 })
2707 .collect::<Vec<_>>()
2708 .join("\n")
2709}
2710
2711#[track_caller]
2712fn assert_new_snapshot(
2713 multibuffer: &Model<MultiBuffer>,
2714 snapshot: &mut MultiBufferSnapshot,
2715 subscription: &mut Subscription,
2716 cx: &mut TestAppContext,
2717 expected_diff: &str,
2718) {
2719 let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2720 let actual_text = new_snapshot.text();
2721 let line_infos = new_snapshot
2722 .row_infos(MultiBufferRow(0))
2723 .collect::<Vec<_>>();
2724 let actual_diff = format_diff(&actual_text, &line_infos, &Default::default());
2725 pretty_assertions::assert_eq!(actual_diff, expected_diff);
2726 check_edits(
2727 snapshot,
2728 &new_snapshot,
2729 &subscription.consume().into_inner(),
2730 );
2731 *snapshot = new_snapshot;
2732}
2733
2734#[track_caller]
2735fn check_edits(
2736 old_snapshot: &MultiBufferSnapshot,
2737 new_snapshot: &MultiBufferSnapshot,
2738 edits: &[Edit<usize>],
2739) {
2740 let mut text = old_snapshot.text();
2741 let new_text = new_snapshot.text();
2742 for edit in edits.iter().rev() {
2743 if !text.is_char_boundary(edit.old.start)
2744 || !text.is_char_boundary(edit.old.end)
2745 || !new_text.is_char_boundary(edit.new.start)
2746 || !new_text.is_char_boundary(edit.new.end)
2747 {
2748 panic!(
2749 "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
2750 edits, text, new_text
2751 );
2752 }
2753
2754 text.replace_range(
2755 edit.old.start..edit.old.end,
2756 &new_text[edit.new.start..edit.new.end],
2757 );
2758 }
2759
2760 pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
2761}
2762
2763#[track_caller]
2764fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
2765 let full_text = snapshot.text();
2766 for ix in 0..full_text.len() {
2767 let mut chunks = snapshot.chunks(0..snapshot.len(), false);
2768 chunks.seek(ix..snapshot.len());
2769 let tail = chunks.map(|chunk| chunk.text).collect::<String>();
2770 assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
2771 }
2772}
2773
2774#[track_caller]
2775fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
2776 let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2777 for start_row in 1..all_line_numbers.len() {
2778 let line_numbers = snapshot
2779 .row_infos(MultiBufferRow(start_row as u32))
2780 .collect::<Vec<_>>();
2781 assert_eq!(
2782 line_numbers,
2783 all_line_numbers[start_row..],
2784 "start_row: {start_row}"
2785 );
2786 }
2787}
2788
2789#[track_caller]
2790fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
2791 let text = Rope::from(snapshot.text());
2792
2793 let mut left_anchors = Vec::new();
2794 let mut right_anchors = Vec::new();
2795 let mut offsets = Vec::new();
2796 let mut points = Vec::new();
2797 for offset in 0..=text.len() + 1 {
2798 let clipped_left = snapshot.clip_offset(offset, Bias::Left);
2799 let clipped_right = snapshot.clip_offset(offset, Bias::Right);
2800 assert_eq!(
2801 clipped_left,
2802 text.clip_offset(offset, Bias::Left),
2803 "clip_offset({offset:?}, Left)"
2804 );
2805 assert_eq!(
2806 clipped_right,
2807 text.clip_offset(offset, Bias::Right),
2808 "clip_offset({offset:?}, Right)"
2809 );
2810 assert_eq!(
2811 snapshot.offset_to_point(clipped_left),
2812 text.offset_to_point(clipped_left),
2813 "offset_to_point({clipped_left})"
2814 );
2815 assert_eq!(
2816 snapshot.offset_to_point(clipped_right),
2817 text.offset_to_point(clipped_right),
2818 "offset_to_point({clipped_right})"
2819 );
2820 let anchor_after = snapshot.anchor_after(clipped_left);
2821 assert_eq!(
2822 anchor_after.to_offset(snapshot),
2823 clipped_left,
2824 "anchor_after({clipped_left}).to_offset {anchor_after:?}"
2825 );
2826 let anchor_before = snapshot.anchor_before(clipped_left);
2827 assert_eq!(
2828 anchor_before.to_offset(snapshot),
2829 clipped_left,
2830 "anchor_before({clipped_left}).to_offset"
2831 );
2832 left_anchors.push(anchor_before);
2833 right_anchors.push(anchor_after);
2834 offsets.push(clipped_left);
2835 points.push(text.offset_to_point(clipped_left));
2836 }
2837
2838 for row in 0..text.max_point().row {
2839 for column in 0..text.line_len(row) + 1 {
2840 let point = Point { row, column };
2841 let clipped_left = snapshot.clip_point(point, Bias::Left);
2842 let clipped_right = snapshot.clip_point(point, Bias::Right);
2843 assert_eq!(
2844 clipped_left,
2845 text.clip_point(point, Bias::Left),
2846 "clip_point({point:?}, Left)"
2847 );
2848 assert_eq!(
2849 clipped_right,
2850 text.clip_point(point, Bias::Right),
2851 "clip_point({point:?}, Right)"
2852 );
2853 assert_eq!(
2854 snapshot.point_to_offset(clipped_left),
2855 text.point_to_offset(clipped_left),
2856 "point_to_offset({clipped_left:?})"
2857 );
2858 assert_eq!(
2859 snapshot.point_to_offset(clipped_right),
2860 text.point_to_offset(clipped_right),
2861 "point_to_offset({clipped_right:?})"
2862 );
2863 }
2864 }
2865
2866 assert_eq!(
2867 snapshot.summaries_for_anchors::<usize, _>(&left_anchors),
2868 offsets,
2869 "left_anchors <-> offsets"
2870 );
2871 assert_eq!(
2872 snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
2873 points,
2874 "left_anchors <-> points"
2875 );
2876 assert_eq!(
2877 snapshot.summaries_for_anchors::<usize, _>(&right_anchors),
2878 offsets,
2879 "right_anchors <-> offsets"
2880 );
2881 assert_eq!(
2882 snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
2883 points,
2884 "right_anchors <-> points"
2885 );
2886
2887 for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
2888 for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
2889 if ix > 0 {
2890 if *offset == 252 {
2891 if offset > &offsets[ix - 1] {
2892 let prev_anchor = left_anchors[ix - 1];
2893 assert!(
2894 anchor.cmp(&prev_anchor, snapshot).is_gt(),
2895 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
2896 offsets[ix],
2897 offsets[ix - 1],
2898 );
2899 assert!(
2900 prev_anchor.cmp(&anchor, snapshot).is_lt(),
2901 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
2902 offsets[ix - 1],
2903 offsets[ix],
2904 );
2905 }
2906 }
2907 }
2908 }
2909 }
2910}
2911
2912fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
2913 let max_row = snapshot.max_point().row;
2914 let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
2915 let text = text::Buffer::new(0, buffer_id, snapshot.text());
2916 let mut line_indents = text
2917 .line_indents_in_row_range(0..max_row + 1)
2918 .collect::<Vec<_>>();
2919 for start_row in 0..snapshot.max_point().row {
2920 pretty_assertions::assert_eq!(
2921 snapshot
2922 .line_indents(MultiBufferRow(start_row), |_| true)
2923 .map(|(row, indent, _)| (row.0, indent))
2924 .collect::<Vec<_>>(),
2925 &line_indents[(start_row as usize)..],
2926 "line_indents({start_row})"
2927 );
2928 }
2929
2930 line_indents.reverse();
2931 pretty_assertions::assert_eq!(
2932 snapshot
2933 .reversed_line_indents(MultiBufferRow(max_row), |_| true)
2934 .map(|(row, indent, _)| (row.0, indent))
2935 .collect::<Vec<_>>(),
2936 &line_indents[..],
2937 "reversed_line_indents({max_row})"
2938 );
2939}