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