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