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