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