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