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