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