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