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