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