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