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