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