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