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