1use super::*;
2use git::diff::DiffHunkStatus;
3use gpui::{App, TestAppContext};
4use indoc::indoc;
5use language::{Buffer, Rope};
6use parking_lot::RwLock;
7use rand::prelude::*;
8use settings::SettingsStore;
9use std::env;
10use util::test::sample_text;
11
12#[ctor::ctor]
13fn init_logger() {
14 if std::env::var("RUST_LOG").is_ok() {
15 env_logger::init();
16 }
17}
18
19#[gpui::test]
20fn test_empty_singleton(cx: &mut App) {
21 let buffer = cx.new(|cx| Buffer::local("", cx));
22 let buffer_id = buffer.read(cx).remote_id();
23 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
24 let snapshot = multibuffer.read(cx).snapshot(cx);
25 assert_eq!(snapshot.text(), "");
26 assert_eq!(
27 snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
28 [RowInfo {
29 buffer_id: Some(buffer_id),
30 buffer_row: Some(0),
31 multibuffer_row: Some(MultiBufferRow(0)),
32 diff_status: None
33 }]
34 );
35}
36
37#[gpui::test]
38fn test_singleton(cx: &mut App) {
39 let buffer = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
40 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
41
42 let snapshot = multibuffer.read(cx).snapshot(cx);
43 assert_eq!(snapshot.text(), buffer.read(cx).text());
44
45 assert_eq!(
46 snapshot
47 .row_infos(MultiBufferRow(0))
48 .map(|info| info.buffer_row)
49 .collect::<Vec<_>>(),
50 (0..buffer.read(cx).row_count())
51 .map(Some)
52 .collect::<Vec<_>>()
53 );
54 assert_consistent_line_numbers(&snapshot);
55
56 buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx));
57 let snapshot = multibuffer.read(cx).snapshot(cx);
58
59 assert_eq!(snapshot.text(), buffer.read(cx).text());
60 assert_eq!(
61 snapshot
62 .row_infos(MultiBufferRow(0))
63 .map(|info| info.buffer_row)
64 .collect::<Vec<_>>(),
65 (0..buffer.read(cx).row_count())
66 .map(Some)
67 .collect::<Vec<_>>()
68 );
69 assert_consistent_line_numbers(&snapshot);
70}
71
72#[gpui::test]
73fn test_remote(cx: &mut App) {
74 let host_buffer = cx.new(|cx| Buffer::local("a", cx));
75 let guest_buffer = cx.new(|cx| {
76 let state = host_buffer.read(cx).to_proto(cx);
77 let ops = cx
78 .background_executor()
79 .block(host_buffer.read(cx).serialize_ops(None, cx));
80 let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
81 buffer.apply_ops(
82 ops.into_iter()
83 .map(|op| language::proto::deserialize_operation(op).unwrap()),
84 cx,
85 );
86 buffer
87 });
88 let multibuffer = cx.new(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx));
89 let snapshot = multibuffer.read(cx).snapshot(cx);
90 assert_eq!(snapshot.text(), "a");
91
92 guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx));
93 let snapshot = multibuffer.read(cx).snapshot(cx);
94 assert_eq!(snapshot.text(), "ab");
95
96 guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx));
97 let snapshot = multibuffer.read(cx).snapshot(cx);
98 assert_eq!(snapshot.text(), "abc");
99}
100
101#[gpui::test]
102fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
103 let buffer_1 = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
104 let buffer_2 = cx.new(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
105 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
106
107 let events = Arc::new(RwLock::new(Vec::<Event>::new()));
108 multibuffer.update(cx, |_, cx| {
109 let events = events.clone();
110 cx.subscribe(&multibuffer, move |_, _, event, _| {
111 if let Event::Edited { .. } = event {
112 events.write().push(event.clone())
113 }
114 })
115 .detach();
116 });
117
118 let subscription = multibuffer.update(cx, |multibuffer, cx| {
119 let subscription = multibuffer.subscribe();
120 multibuffer.push_excerpts(
121 buffer_1.clone(),
122 [ExcerptRange {
123 context: Point::new(1, 2)..Point::new(2, 5),
124 primary: None,
125 }],
126 cx,
127 );
128 assert_eq!(
129 subscription.consume().into_inner(),
130 [Edit {
131 old: 0..0,
132 new: 0..10
133 }]
134 );
135
136 multibuffer.push_excerpts(
137 buffer_1.clone(),
138 [ExcerptRange {
139 context: Point::new(3, 3)..Point::new(4, 4),
140 primary: None,
141 }],
142 cx,
143 );
144 multibuffer.push_excerpts(
145 buffer_2.clone(),
146 [ExcerptRange {
147 context: Point::new(3, 1)..Point::new(3, 3),
148 primary: None,
149 }],
150 cx,
151 );
152 assert_eq!(
153 subscription.consume().into_inner(),
154 [Edit {
155 old: 10..10,
156 new: 10..22
157 }]
158 );
159
160 subscription
161 });
162
163 // Adding excerpts emits an edited event.
164 assert_eq!(
165 events.read().as_slice(),
166 &[
167 Event::Edited {
168 singleton_buffer_edited: false,
169 edited_buffer: None,
170 },
171 Event::Edited {
172 singleton_buffer_edited: false,
173 edited_buffer: None,
174 },
175 Event::Edited {
176 singleton_buffer_edited: false,
177 edited_buffer: None,
178 }
179 ]
180 );
181
182 let snapshot = multibuffer.read(cx).snapshot(cx);
183 assert_eq!(
184 snapshot.text(),
185 indoc!(
186 "
187 bbbb
188 ccccc
189 ddd
190 eeee
191 jj"
192 ),
193 );
194 assert_eq!(
195 snapshot
196 .row_infos(MultiBufferRow(0))
197 .map(|info| info.buffer_row)
198 .collect::<Vec<_>>(),
199 [Some(1), Some(2), Some(3), Some(4), Some(3)]
200 );
201 assert_eq!(
202 snapshot
203 .row_infos(MultiBufferRow(2))
204 .map(|info| info.buffer_row)
205 .collect::<Vec<_>>(),
206 [Some(3), Some(4), Some(3)]
207 );
208 assert_eq!(
209 snapshot
210 .row_infos(MultiBufferRow(4))
211 .map(|info| info.buffer_row)
212 .collect::<Vec<_>>(),
213 [Some(3)]
214 );
215 assert_eq!(
216 snapshot
217 .row_infos(MultiBufferRow(5))
218 .map(|info| info.buffer_row)
219 .collect::<Vec<_>>(),
220 []
221 );
222
223 assert_eq!(
224 boundaries_in_range(Point::new(0, 0)..Point::new(4, 2), &snapshot),
225 &[
226 (MultiBufferRow(0), "bbbb\nccccc".to_string(), true),
227 (MultiBufferRow(2), "ddd\neeee".to_string(), false),
228 (MultiBufferRow(4), "jj".to_string(), true),
229 ]
230 );
231 assert_eq!(
232 boundaries_in_range(Point::new(0, 0)..Point::new(2, 0), &snapshot),
233 &[(MultiBufferRow(0), "bbbb\nccccc".to_string(), true)]
234 );
235 assert_eq!(
236 boundaries_in_range(Point::new(1, 0)..Point::new(1, 5), &snapshot),
237 &[]
238 );
239 assert_eq!(
240 boundaries_in_range(Point::new(1, 0)..Point::new(2, 0), &snapshot),
241 &[]
242 );
243 assert_eq!(
244 boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
245 &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
246 );
247 assert_eq!(
248 boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
249 &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
250 );
251 assert_eq!(
252 boundaries_in_range(Point::new(2, 0)..Point::new(3, 0), &snapshot),
253 &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
254 );
255 assert_eq!(
256 boundaries_in_range(Point::new(4, 0)..Point::new(4, 2), &snapshot),
257 &[(MultiBufferRow(4), "jj".to_string(), true)]
258 );
259 assert_eq!(
260 boundaries_in_range(Point::new(4, 2)..Point::new(4, 2), &snapshot),
261 &[]
262 );
263
264 buffer_1.update(cx, |buffer, cx| {
265 let text = "\n";
266 buffer.edit(
267 [
268 (Point::new(0, 0)..Point::new(0, 0), text),
269 (Point::new(2, 1)..Point::new(2, 3), text),
270 ],
271 None,
272 cx,
273 );
274 });
275
276 let snapshot = multibuffer.read(cx).snapshot(cx);
277 assert_eq!(
278 snapshot.text(),
279 concat!(
280 "bbbb\n", // Preserve newlines
281 "c\n", //
282 "cc\n", //
283 "ddd\n", //
284 "eeee\n", //
285 "jj" //
286 )
287 );
288
289 assert_eq!(
290 subscription.consume().into_inner(),
291 [Edit {
292 old: 6..8,
293 new: 6..7
294 }]
295 );
296
297 let snapshot = multibuffer.read(cx).snapshot(cx);
298 assert_eq!(
299 snapshot.clip_point(Point::new(0, 5), Bias::Left),
300 Point::new(0, 4)
301 );
302 assert_eq!(
303 snapshot.clip_point(Point::new(0, 5), Bias::Right),
304 Point::new(0, 4)
305 );
306 assert_eq!(
307 snapshot.clip_point(Point::new(5, 1), Bias::Right),
308 Point::new(5, 1)
309 );
310 assert_eq!(
311 snapshot.clip_point(Point::new(5, 2), Bias::Right),
312 Point::new(5, 2)
313 );
314 assert_eq!(
315 snapshot.clip_point(Point::new(5, 3), Bias::Right),
316 Point::new(5, 2)
317 );
318
319 let snapshot = multibuffer.update(cx, |multibuffer, cx| {
320 let (buffer_2_excerpt_id, _) =
321 multibuffer.excerpts_for_buffer(buffer_2.read(cx).remote_id(), cx)[0].clone();
322 multibuffer.remove_excerpts([buffer_2_excerpt_id], cx);
323 multibuffer.snapshot(cx)
324 });
325
326 assert_eq!(
327 snapshot.text(),
328 concat!(
329 "bbbb\n", // Preserve newlines
330 "c\n", //
331 "cc\n", //
332 "ddd\n", //
333 "eeee", //
334 )
335 );
336
337 fn boundaries_in_range(
338 range: Range<Point>,
339 snapshot: &MultiBufferSnapshot,
340 ) -> Vec<(MultiBufferRow, String, bool)> {
341 snapshot
342 .excerpt_boundaries_in_range(range)
343 .filter_map(|boundary| {
344 let starts_new_buffer = boundary.starts_new_buffer();
345 boundary.next.map(|next| {
346 (
347 boundary.row,
348 next.buffer
349 .text_for_range(next.range.context)
350 .collect::<String>(),
351 starts_new_buffer,
352 )
353 })
354 })
355 .collect::<Vec<_>>()
356 }
357}
358
359#[gpui::test]
360fn test_diff_boundary_anchors(cx: &mut TestAppContext) {
361 let base_text = "one\ntwo\nthree\n";
362 let text = "one\nthree\n";
363 let buffer = cx.new(|cx| Buffer::local(text, cx));
364 let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx));
365 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
366 multibuffer.update(cx, |multibuffer, cx| {
367 multibuffer.add_change_set(change_set, cx)
368 });
369
370 let (before, after) = multibuffer.update(cx, |multibuffer, cx| {
371 let before = multibuffer.snapshot(cx).anchor_before(Point::new(1, 0));
372 let after = multibuffer.snapshot(cx).anchor_after(Point::new(1, 0));
373 multibuffer.set_all_diff_hunks_expanded(cx);
374 (before, after)
375 });
376 cx.run_until_parked();
377
378 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
379 let actual_text = snapshot.text();
380 let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
381 let actual_diff = format_diff(&actual_text, &actual_row_infos, &Default::default(), None);
382 pretty_assertions::assert_eq!(
383 actual_diff,
384 indoc! {
385 " one
386 - two
387 three
388 "
389 },
390 );
391
392 multibuffer.update(cx, |multibuffer, cx| {
393 let snapshot = multibuffer.snapshot(cx);
394 assert_eq!(before.to_point(&snapshot), Point::new(1, 0));
395 assert_eq!(after.to_point(&snapshot), Point::new(2, 0));
396 assert_eq!(
397 vec![Point::new(1, 0), Point::new(2, 0),],
398 snapshot.summaries_for_anchors::<Point, _>(&[before, after]),
399 )
400 })
401}
402
403#[gpui::test]
404fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
405 let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n";
406 let text = "one\nfour\nseven\n";
407 let buffer = cx.new(|cx| Buffer::local(text, cx));
408 let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx));
409 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
410 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
411 (multibuffer.snapshot(cx), multibuffer.subscribe())
412 });
413
414 multibuffer.update(cx, |multibuffer, cx| {
415 multibuffer.add_change_set(change_set, cx);
416 multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
417 });
418
419 assert_new_snapshot(
420 &multibuffer,
421 &mut snapshot,
422 &mut subscription,
423 cx,
424 indoc! {
425 " one
426 - two
427 - three
428 four
429 - five
430 - six
431 seven
432 - eight
433 "
434 },
435 );
436
437 assert_eq!(
438 snapshot
439 .diff_hunks_in_range(Point::new(1, 0)..Point::MAX)
440 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
441 .collect::<Vec<_>>(),
442 vec![1..3, 4..6, 7..8]
443 );
444
445 assert_eq!(
446 snapshot
447 .diff_hunk_before(Point::new(1, 1))
448 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
449 None,
450 );
451 assert_eq!(
452 snapshot
453 .diff_hunk_before(Point::new(7, 0))
454 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
455 Some(4..6)
456 );
457 assert_eq!(
458 snapshot
459 .diff_hunk_before(Point::new(4, 0))
460 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
461 Some(1..3)
462 );
463
464 multibuffer.update(cx, |multibuffer, cx| {
465 multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
466 });
467
468 assert_new_snapshot(
469 &multibuffer,
470 &mut snapshot,
471 &mut subscription,
472 cx,
473 indoc! {
474 "
475 one
476 four
477 seven
478 "
479 },
480 );
481
482 assert_eq!(
483 snapshot
484 .diff_hunk_before(Point::new(2, 0))
485 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
486 Some(1..1),
487 );
488 assert_eq!(
489 snapshot
490 .diff_hunk_before(Point::new(4, 0))
491 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
492 Some(2..2)
493 );
494}
495
496#[gpui::test]
497fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) {
498 let base_text = "one\ntwo\nfour\nfive\nsix\nseven\n";
499 let text = "one\ntwo\nTHREE\nfour\nfive\nseven\n";
500 let buffer = cx.new(|cx| Buffer::local(text, cx));
501 let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(&base_text, &buffer, cx));
502 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
503
504 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
505 multibuffer.add_change_set(change_set.clone(), cx);
506 (multibuffer.snapshot(cx), multibuffer.subscribe())
507 });
508
509 cx.executor().run_until_parked();
510 multibuffer.update(cx, |multibuffer, cx| {
511 multibuffer.set_all_diff_hunks_expanded(cx);
512 });
513
514 assert_new_snapshot(
515 &multibuffer,
516 &mut snapshot,
517 &mut subscription,
518 cx,
519 indoc! {
520 "
521 one
522 two
523 + THREE
524 four
525 five
526 - six
527 seven
528 "
529 },
530 );
531
532 // Insert a newline within an insertion hunk
533 multibuffer.update(cx, |multibuffer, cx| {
534 multibuffer.edit([(Point::new(2, 0)..Point::new(2, 0), "__\n__")], None, cx);
535 });
536 assert_new_snapshot(
537 &multibuffer,
538 &mut snapshot,
539 &mut subscription,
540 cx,
541 indoc! {
542 "
543 one
544 two
545 + __
546 + __THREE
547 four
548 five
549 - six
550 seven
551 "
552 },
553 );
554
555 // Delete the newline before a deleted hunk.
556 multibuffer.update(cx, |multibuffer, cx| {
557 multibuffer.edit([(Point::new(5, 4)..Point::new(6, 0), "")], None, cx);
558 });
559 assert_new_snapshot(
560 &multibuffer,
561 &mut snapshot,
562 &mut subscription,
563 cx,
564 indoc! {
565 "
566 one
567 two
568 + __
569 + __THREE
570 four
571 fiveseven
572 "
573 },
574 );
575
576 multibuffer.update(cx, |multibuffer, cx| multibuffer.undo(cx));
577 assert_new_snapshot(
578 &multibuffer,
579 &mut snapshot,
580 &mut subscription,
581 cx,
582 indoc! {
583 "
584 one
585 two
586 + __
587 + __THREE
588 four
589 five
590 - six
591 seven
592 "
593 },
594 );
595
596 // Cannot (yet) insert at the beginning of a deleted hunk.
597 // (because it would put the newline in the wrong place)
598 multibuffer.update(cx, |multibuffer, cx| {
599 multibuffer.edit([(Point::new(6, 0)..Point::new(6, 0), "\n")], None, cx);
600 });
601 assert_new_snapshot(
602 &multibuffer,
603 &mut snapshot,
604 &mut subscription,
605 cx,
606 indoc! {
607 "
608 one
609 two
610 + __
611 + __THREE
612 four
613 five
614 - six
615 seven
616 "
617 },
618 );
619
620 // Replace a range that ends in a deleted hunk.
621 multibuffer.update(cx, |multibuffer, cx| {
622 multibuffer.edit([(Point::new(5, 2)..Point::new(6, 2), "fty-")], None, cx);
623 });
624 assert_new_snapshot(
625 &multibuffer,
626 &mut snapshot,
627 &mut subscription,
628 cx,
629 indoc! {
630 "
631 one
632 two
633 + __
634 + __THREE
635 four
636 fifty-seven
637 "
638 },
639 );
640}
641
642#[gpui::test]
643fn test_excerpt_events(cx: &mut App) {
644 let buffer_1 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'a'), cx));
645 let buffer_2 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'm'), cx));
646
647 let leader_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
648 let follower_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
649 let follower_edit_event_count = Arc::new(RwLock::new(0));
650
651 follower_multibuffer.update(cx, |_, cx| {
652 let follower_edit_event_count = follower_edit_event_count.clone();
653 cx.subscribe(
654 &leader_multibuffer,
655 move |follower, _, event, cx| match event.clone() {
656 Event::ExcerptsAdded {
657 buffer,
658 predecessor,
659 excerpts,
660 } => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx),
661 Event::ExcerptsRemoved { ids } => follower.remove_excerpts(ids, cx),
662 Event::Edited { .. } => {
663 *follower_edit_event_count.write() += 1;
664 }
665 _ => {}
666 },
667 )
668 .detach();
669 });
670
671 leader_multibuffer.update(cx, |leader, cx| {
672 leader.push_excerpts(
673 buffer_1.clone(),
674 [
675 ExcerptRange {
676 context: 0..8,
677 primary: None,
678 },
679 ExcerptRange {
680 context: 12..16,
681 primary: None,
682 },
683 ],
684 cx,
685 );
686 leader.insert_excerpts_after(
687 leader.excerpt_ids()[0],
688 buffer_2.clone(),
689 [
690 ExcerptRange {
691 context: 0..5,
692 primary: None,
693 },
694 ExcerptRange {
695 context: 10..15,
696 primary: None,
697 },
698 ],
699 cx,
700 )
701 });
702 assert_eq!(
703 leader_multibuffer.read(cx).snapshot(cx).text(),
704 follower_multibuffer.read(cx).snapshot(cx).text(),
705 );
706 assert_eq!(*follower_edit_event_count.read(), 2);
707
708 leader_multibuffer.update(cx, |leader, cx| {
709 let excerpt_ids = leader.excerpt_ids();
710 leader.remove_excerpts([excerpt_ids[1], excerpt_ids[3]], cx);
711 });
712 assert_eq!(
713 leader_multibuffer.read(cx).snapshot(cx).text(),
714 follower_multibuffer.read(cx).snapshot(cx).text(),
715 );
716 assert_eq!(*follower_edit_event_count.read(), 3);
717
718 // Removing an empty set of excerpts is a noop.
719 leader_multibuffer.update(cx, |leader, cx| {
720 leader.remove_excerpts([], cx);
721 });
722 assert_eq!(
723 leader_multibuffer.read(cx).snapshot(cx).text(),
724 follower_multibuffer.read(cx).snapshot(cx).text(),
725 );
726 assert_eq!(*follower_edit_event_count.read(), 3);
727
728 // Adding an empty set of excerpts is a noop.
729 leader_multibuffer.update(cx, |leader, cx| {
730 leader.push_excerpts::<usize>(buffer_2.clone(), [], cx);
731 });
732 assert_eq!(
733 leader_multibuffer.read(cx).snapshot(cx).text(),
734 follower_multibuffer.read(cx).snapshot(cx).text(),
735 );
736 assert_eq!(*follower_edit_event_count.read(), 3);
737
738 leader_multibuffer.update(cx, |leader, cx| {
739 leader.clear(cx);
740 });
741 assert_eq!(
742 leader_multibuffer.read(cx).snapshot(cx).text(),
743 follower_multibuffer.read(cx).snapshot(cx).text(),
744 );
745 assert_eq!(*follower_edit_event_count.read(), 4);
746}
747
748#[gpui::test]
749fn test_expand_excerpts(cx: &mut App) {
750 let buffer = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
751 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
752
753 multibuffer.update(cx, |multibuffer, cx| {
754 multibuffer.push_excerpts_with_context_lines(
755 buffer.clone(),
756 vec![
757 // Note that in this test, this first excerpt
758 // does not contain a new line
759 Point::new(3, 2)..Point::new(3, 3),
760 Point::new(7, 1)..Point::new(7, 3),
761 Point::new(15, 0)..Point::new(15, 0),
762 ],
763 1,
764 cx,
765 )
766 });
767
768 let snapshot = multibuffer.read(cx).snapshot(cx);
769
770 assert_eq!(
771 snapshot.text(),
772 concat!(
773 "ccc\n", //
774 "ddd\n", //
775 "eee", //
776 "\n", // End of excerpt
777 "ggg\n", //
778 "hhh\n", //
779 "iii", //
780 "\n", // End of excerpt
781 "ooo\n", //
782 "ppp\n", //
783 "qqq", // End of excerpt
784 )
785 );
786 drop(snapshot);
787
788 multibuffer.update(cx, |multibuffer, cx| {
789 multibuffer.expand_excerpts(
790 multibuffer.excerpt_ids(),
791 1,
792 ExpandExcerptDirection::UpAndDown,
793 cx,
794 )
795 });
796
797 let snapshot = multibuffer.read(cx).snapshot(cx);
798
799 // Expanding context lines causes the line containing 'fff' to appear in two different excerpts.
800 // We don't attempt to merge them, because removing the excerpt could create inconsistency with other layers
801 // that are tracking excerpt ids.
802 assert_eq!(
803 snapshot.text(),
804 concat!(
805 "bbb\n", //
806 "ccc\n", //
807 "ddd\n", //
808 "eee\n", //
809 "fff\n", // End of excerpt
810 "fff\n", //
811 "ggg\n", //
812 "hhh\n", //
813 "iii\n", //
814 "jjj\n", // End of excerpt
815 "nnn\n", //
816 "ooo\n", //
817 "ppp\n", //
818 "qqq\n", //
819 "rrr", // End of excerpt
820 )
821 );
822}
823
824#[gpui::test]
825fn test_push_excerpts_with_context_lines(cx: &mut App) {
826 let buffer = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
827 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
828 let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
829 multibuffer.push_excerpts_with_context_lines(
830 buffer.clone(),
831 vec![
832 // Note that in this test, this first excerpt
833 // does contain a new line
834 Point::new(3, 2)..Point::new(4, 2),
835 Point::new(7, 1)..Point::new(7, 3),
836 Point::new(15, 0)..Point::new(15, 0),
837 ],
838 2,
839 cx,
840 )
841 });
842
843 let snapshot = multibuffer.read(cx).snapshot(cx);
844 assert_eq!(
845 snapshot.text(),
846 concat!(
847 "bbb\n", // Preserve newlines
848 "ccc\n", //
849 "ddd\n", //
850 "eee\n", //
851 "fff\n", //
852 "ggg\n", //
853 "hhh\n", //
854 "iii\n", //
855 "jjj\n", //
856 "nnn\n", //
857 "ooo\n", //
858 "ppp\n", //
859 "qqq\n", //
860 "rrr", //
861 )
862 );
863
864 assert_eq!(
865 anchor_ranges
866 .iter()
867 .map(|range| range.to_point(&snapshot))
868 .collect::<Vec<_>>(),
869 vec![
870 Point::new(2, 2)..Point::new(3, 2),
871 Point::new(6, 1)..Point::new(6, 3),
872 Point::new(11, 0)..Point::new(11, 0)
873 ]
874 );
875}
876
877#[gpui::test(iterations = 100)]
878async fn test_push_multiple_excerpts_with_context_lines(cx: &mut TestAppContext) {
879 let buffer_1 = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
880 let buffer_2 = cx.new(|cx| Buffer::local(sample_text(15, 4, 'a'), cx));
881 let snapshot_1 = buffer_1.update(cx, |buffer, _| buffer.snapshot());
882 let snapshot_2 = buffer_2.update(cx, |buffer, _| buffer.snapshot());
883 let ranges_1 = vec![
884 snapshot_1.anchor_before(Point::new(3, 2))..snapshot_1.anchor_before(Point::new(4, 2)),
885 snapshot_1.anchor_before(Point::new(7, 1))..snapshot_1.anchor_before(Point::new(7, 3)),
886 snapshot_1.anchor_before(Point::new(15, 0))..snapshot_1.anchor_before(Point::new(15, 0)),
887 ];
888 let ranges_2 = vec![
889 snapshot_2.anchor_before(Point::new(2, 1))..snapshot_2.anchor_before(Point::new(3, 1)),
890 snapshot_2.anchor_before(Point::new(10, 0))..snapshot_2.anchor_before(Point::new(10, 2)),
891 ];
892
893 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
894 let anchor_ranges = multibuffer
895 .update(cx, |multibuffer, cx| {
896 multibuffer.push_multiple_excerpts_with_context_lines(
897 vec![(buffer_1.clone(), ranges_1), (buffer_2.clone(), ranges_2)],
898 2,
899 cx,
900 )
901 })
902 .await;
903
904 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
905 assert_eq!(
906 snapshot.text(),
907 concat!(
908 "bbb\n", // buffer_1
909 "ccc\n", //
910 "ddd\n", // <-- excerpt 1
911 "eee\n", // <-- excerpt 1
912 "fff\n", //
913 "ggg\n", //
914 "hhh\n", // <-- excerpt 2
915 "iii\n", //
916 "jjj\n", //
917 //
918 "nnn\n", //
919 "ooo\n", //
920 "ppp\n", // <-- excerpt 3
921 "qqq\n", //
922 "rrr\n", //
923 //
924 "aaaa\n", // buffer 2
925 "bbbb\n", //
926 "cccc\n", // <-- excerpt 4
927 "dddd\n", // <-- excerpt 4
928 "eeee\n", //
929 "ffff\n", //
930 //
931 "iiii\n", //
932 "jjjj\n", //
933 "kkkk\n", // <-- excerpt 5
934 "llll\n", //
935 "mmmm", //
936 )
937 );
938
939 assert_eq!(
940 anchor_ranges
941 .iter()
942 .map(|range| range.to_point(&snapshot))
943 .collect::<Vec<_>>(),
944 vec![
945 Point::new(2, 2)..Point::new(3, 2),
946 Point::new(6, 1)..Point::new(6, 3),
947 Point::new(11, 0)..Point::new(11, 0),
948 Point::new(16, 1)..Point::new(17, 1),
949 Point::new(22, 0)..Point::new(22, 2)
950 ]
951 );
952}
953
954#[gpui::test]
955fn test_empty_multibuffer(cx: &mut App) {
956 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
957
958 let snapshot = multibuffer.read(cx).snapshot(cx);
959 assert_eq!(snapshot.text(), "");
960 assert_eq!(
961 snapshot
962 .row_infos(MultiBufferRow(0))
963 .map(|info| info.buffer_row)
964 .collect::<Vec<_>>(),
965 &[Some(0)]
966 );
967 assert_eq!(
968 snapshot
969 .row_infos(MultiBufferRow(1))
970 .map(|info| info.buffer_row)
971 .collect::<Vec<_>>(),
972 &[]
973 );
974}
975
976#[gpui::test]
977fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
978 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
979 let buffer = cx.new(|cx| Buffer::local("", cx));
980 let base_text = "a\nb\nc";
981
982 let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx));
983 multibuffer.update(cx, |multibuffer, cx| {
984 multibuffer.set_all_diff_hunks_expanded(cx);
985 multibuffer.add_change_set(change_set.clone(), cx);
986 multibuffer.push_excerpts(
987 buffer.clone(),
988 [ExcerptRange {
989 context: 0..0,
990 primary: None,
991 }],
992 cx,
993 );
994 });
995 cx.run_until_parked();
996
997 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
998 assert_eq!(snapshot.text(), "a\nb\nc\n");
999
1000 let hunk = snapshot
1001 .diff_hunks_in_range(Point::new(1, 1)..Point::new(1, 1))
1002 .next()
1003 .unwrap();
1004
1005 assert_eq!(hunk.diff_base_byte_range.start, 0);
1006
1007 let buf2 = cx.new(|cx| Buffer::local("X", cx));
1008 multibuffer.update(cx, |multibuffer, cx| {
1009 multibuffer.push_excerpts(
1010 buf2,
1011 [ExcerptRange {
1012 context: 0..1,
1013 primary: None,
1014 }],
1015 cx,
1016 );
1017 });
1018
1019 buffer.update(cx, |buffer, cx| {
1020 buffer.edit([(0..0, "a\nb\nc")], None, cx);
1021 change_set.update(cx, |change_set, cx| {
1022 change_set.recalculate_diff_sync(buffer.snapshot().text, cx);
1023 });
1024 assert_eq!(buffer.text(), "a\nb\nc")
1025 });
1026 cx.run_until_parked();
1027
1028 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1029 assert_eq!(snapshot.text(), "a\nb\nc\nX");
1030
1031 buffer.update(cx, |buffer, cx| {
1032 buffer.undo(cx);
1033 change_set.update(cx, |change_set, cx| {
1034 change_set.recalculate_diff_sync(buffer.snapshot().text, cx);
1035 });
1036 assert_eq!(buffer.text(), "")
1037 });
1038 cx.run_until_parked();
1039
1040 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1041 assert_eq!(snapshot.text(), "a\nb\nc\n\nX");
1042}
1043
1044#[gpui::test]
1045fn test_singleton_multibuffer_anchors(cx: &mut App) {
1046 let buffer = cx.new(|cx| Buffer::local("abcd", cx));
1047 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
1048 let old_snapshot = multibuffer.read(cx).snapshot(cx);
1049 buffer.update(cx, |buffer, cx| {
1050 buffer.edit([(0..0, "X")], None, cx);
1051 buffer.edit([(5..5, "Y")], None, cx);
1052 });
1053 let new_snapshot = multibuffer.read(cx).snapshot(cx);
1054
1055 assert_eq!(old_snapshot.text(), "abcd");
1056 assert_eq!(new_snapshot.text(), "XabcdY");
1057
1058 assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1059 assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1060 assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5);
1061 assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6);
1062}
1063
1064#[gpui::test]
1065fn test_multibuffer_anchors(cx: &mut App) {
1066 let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1067 let buffer_2 = cx.new(|cx| Buffer::local("efghi", cx));
1068 let multibuffer = cx.new(|cx| {
1069 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1070 multibuffer.push_excerpts(
1071 buffer_1.clone(),
1072 [ExcerptRange {
1073 context: 0..4,
1074 primary: None,
1075 }],
1076 cx,
1077 );
1078 multibuffer.push_excerpts(
1079 buffer_2.clone(),
1080 [ExcerptRange {
1081 context: 0..5,
1082 primary: None,
1083 }],
1084 cx,
1085 );
1086 multibuffer
1087 });
1088 let old_snapshot = multibuffer.read(cx).snapshot(cx);
1089
1090 assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0);
1091 assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0);
1092 assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1093 assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1094 assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1095 assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1096
1097 buffer_1.update(cx, |buffer, cx| {
1098 buffer.edit([(0..0, "W")], None, cx);
1099 buffer.edit([(5..5, "X")], None, cx);
1100 });
1101 buffer_2.update(cx, |buffer, cx| {
1102 buffer.edit([(0..0, "Y")], None, cx);
1103 buffer.edit([(6..6, "Z")], None, cx);
1104 });
1105 let new_snapshot = multibuffer.read(cx).snapshot(cx);
1106
1107 assert_eq!(old_snapshot.text(), "abcd\nefghi");
1108 assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
1109
1110 assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1111 assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1112 assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2);
1113 assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2);
1114 assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3);
1115 assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3);
1116 assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7);
1117 assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8);
1118 assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13);
1119 assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14);
1120}
1121
1122#[gpui::test]
1123fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
1124 let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1125 let buffer_2 = cx.new(|cx| Buffer::local("ABCDEFGHIJKLMNOP", cx));
1126 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1127
1128 // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
1129 // Add an excerpt from buffer 1 that spans this new insertion.
1130 buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx));
1131 let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| {
1132 multibuffer
1133 .push_excerpts(
1134 buffer_1.clone(),
1135 [ExcerptRange {
1136 context: 0..7,
1137 primary: None,
1138 }],
1139 cx,
1140 )
1141 .pop()
1142 .unwrap()
1143 });
1144
1145 let snapshot_1 = multibuffer.read(cx).snapshot(cx);
1146 assert_eq!(snapshot_1.text(), "abcd123");
1147
1148 // Replace the buffer 1 excerpt with new excerpts from buffer 2.
1149 let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
1150 multibuffer.remove_excerpts([excerpt_id_1], cx);
1151 let mut ids = multibuffer
1152 .push_excerpts(
1153 buffer_2.clone(),
1154 [
1155 ExcerptRange {
1156 context: 0..4,
1157 primary: None,
1158 },
1159 ExcerptRange {
1160 context: 6..10,
1161 primary: None,
1162 },
1163 ExcerptRange {
1164 context: 12..16,
1165 primary: None,
1166 },
1167 ],
1168 cx,
1169 )
1170 .into_iter();
1171 (ids.next().unwrap(), ids.next().unwrap())
1172 });
1173 let snapshot_2 = multibuffer.read(cx).snapshot(cx);
1174 assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP");
1175
1176 // The old excerpt id doesn't get reused.
1177 assert_ne!(excerpt_id_2, excerpt_id_1);
1178
1179 // Resolve some anchors from the previous snapshot in the new snapshot.
1180 // The current excerpts are from a different buffer, so we don't attempt to
1181 // resolve the old text anchor in the new buffer.
1182 assert_eq!(
1183 snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)),
1184 0
1185 );
1186 assert_eq!(
1187 snapshot_2.summaries_for_anchors::<usize, _>(&[
1188 snapshot_1.anchor_before(2),
1189 snapshot_1.anchor_after(3)
1190 ]),
1191 vec![0, 0]
1192 );
1193
1194 // Refresh anchors from the old snapshot. The return value indicates that both
1195 // anchors lost their original excerpt.
1196 let refresh =
1197 snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]);
1198 assert_eq!(
1199 refresh,
1200 &[
1201 (0, snapshot_2.anchor_before(0), false),
1202 (1, snapshot_2.anchor_after(0), false),
1203 ]
1204 );
1205
1206 // Replace the middle excerpt with a smaller excerpt in buffer 2,
1207 // that intersects the old excerpt.
1208 let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| {
1209 multibuffer.remove_excerpts([excerpt_id_3], cx);
1210 multibuffer
1211 .insert_excerpts_after(
1212 excerpt_id_2,
1213 buffer_2.clone(),
1214 [ExcerptRange {
1215 context: 5..8,
1216 primary: None,
1217 }],
1218 cx,
1219 )
1220 .pop()
1221 .unwrap()
1222 });
1223
1224 let snapshot_3 = multibuffer.read(cx).snapshot(cx);
1225 assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP");
1226 assert_ne!(excerpt_id_5, excerpt_id_3);
1227
1228 // Resolve some anchors from the previous snapshot in the new snapshot.
1229 // The third anchor can't be resolved, since its excerpt has been removed,
1230 // so it resolves to the same position as its predecessor.
1231 let anchors = [
1232 snapshot_2.anchor_before(0),
1233 snapshot_2.anchor_after(2),
1234 snapshot_2.anchor_after(6),
1235 snapshot_2.anchor_after(14),
1236 ];
1237 assert_eq!(
1238 snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
1239 &[0, 2, 9, 13]
1240 );
1241
1242 let new_anchors = snapshot_3.refresh_anchors(&anchors);
1243 assert_eq!(
1244 new_anchors.iter().map(|a| (a.0, a.2)).collect::<Vec<_>>(),
1245 &[(0, true), (1, true), (2, true), (3, true)]
1246 );
1247 assert_eq!(
1248 snapshot_3.summaries_for_anchors::<usize, _>(new_anchors.iter().map(|a| &a.1)),
1249 &[0, 2, 7, 13]
1250 );
1251}
1252
1253#[gpui::test]
1254fn test_basic_diff_hunks(cx: &mut TestAppContext) {
1255 let text = indoc!(
1256 "
1257 ZERO
1258 one
1259 TWO
1260 three
1261 six
1262 "
1263 );
1264 let base_text = indoc!(
1265 "
1266 one
1267 two
1268 three
1269 four
1270 five
1271 six
1272 "
1273 );
1274
1275 let buffer = cx.new(|cx| Buffer::local(text, cx));
1276 let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx));
1277 cx.run_until_parked();
1278
1279 let multibuffer = cx.new(|cx| {
1280 let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1281 multibuffer.add_change_set(change_set.clone(), cx);
1282 multibuffer
1283 });
1284
1285 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1286 (multibuffer.snapshot(cx), multibuffer.subscribe())
1287 });
1288 assert_eq!(
1289 snapshot.text(),
1290 indoc!(
1291 "
1292 ZERO
1293 one
1294 TWO
1295 three
1296 six
1297 "
1298 ),
1299 );
1300
1301 multibuffer.update(cx, |multibuffer, cx| {
1302 multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1303 });
1304
1305 assert_new_snapshot(
1306 &multibuffer,
1307 &mut snapshot,
1308 &mut subscription,
1309 cx,
1310 indoc!(
1311 "
1312 + ZERO
1313 one
1314 - two
1315 + TWO
1316 three
1317 - four
1318 - five
1319 six
1320 "
1321 ),
1322 );
1323
1324 assert_eq!(
1325 snapshot
1326 .row_infos(MultiBufferRow(0))
1327 .map(|info| (info.buffer_row, info.diff_status))
1328 .collect::<Vec<_>>(),
1329 vec![
1330 (Some(0), Some(DiffHunkStatus::Added)),
1331 (Some(1), None),
1332 (Some(1), Some(DiffHunkStatus::Removed)),
1333 (Some(2), Some(DiffHunkStatus::Added)),
1334 (Some(3), None),
1335 (Some(3), Some(DiffHunkStatus::Removed)),
1336 (Some(4), Some(DiffHunkStatus::Removed)),
1337 (Some(4), None),
1338 (Some(5), None)
1339 ]
1340 );
1341
1342 assert_chunks_in_ranges(&snapshot);
1343 assert_consistent_line_numbers(&snapshot);
1344 assert_position_translation(&snapshot);
1345 assert_line_indents(&snapshot);
1346
1347 multibuffer.update(cx, |multibuffer, cx| {
1348 multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
1349 });
1350 assert_new_snapshot(
1351 &multibuffer,
1352 &mut snapshot,
1353 &mut subscription,
1354 cx,
1355 indoc!(
1356 "
1357 ZERO
1358 one
1359 TWO
1360 three
1361 six
1362 "
1363 ),
1364 );
1365
1366 assert_chunks_in_ranges(&snapshot);
1367 assert_consistent_line_numbers(&snapshot);
1368 assert_position_translation(&snapshot);
1369 assert_line_indents(&snapshot);
1370
1371 // Expand the first diff hunk
1372 multibuffer.update(cx, |multibuffer, cx| {
1373 let position = multibuffer.read(cx).anchor_before(Point::new(2, 2));
1374 multibuffer.expand_diff_hunks(vec![position..position], cx)
1375 });
1376 assert_new_snapshot(
1377 &multibuffer,
1378 &mut snapshot,
1379 &mut subscription,
1380 cx,
1381 indoc!(
1382 "
1383 ZERO
1384 one
1385 - two
1386 + TWO
1387 three
1388 six
1389 "
1390 ),
1391 );
1392
1393 // Expand the second diff hunk
1394 multibuffer.update(cx, |multibuffer, cx| {
1395 let start = multibuffer.read(cx).anchor_before(Point::new(4, 0));
1396 let end = multibuffer.read(cx).anchor_before(Point::new(5, 0));
1397 multibuffer.expand_diff_hunks(vec![start..end], cx)
1398 });
1399 assert_new_snapshot(
1400 &multibuffer,
1401 &mut snapshot,
1402 &mut subscription,
1403 cx,
1404 indoc!(
1405 "
1406 ZERO
1407 one
1408 - two
1409 + TWO
1410 three
1411 - four
1412 - five
1413 six
1414 "
1415 ),
1416 );
1417
1418 assert_chunks_in_ranges(&snapshot);
1419 assert_consistent_line_numbers(&snapshot);
1420 assert_position_translation(&snapshot);
1421 assert_line_indents(&snapshot);
1422
1423 // Edit the buffer before the first hunk
1424 buffer.update(cx, |buffer, cx| {
1425 buffer.edit_via_marked_text(
1426 indoc!(
1427 "
1428 ZERO
1429 one« hundred
1430 thousand»
1431 TWO
1432 three
1433 six
1434 "
1435 ),
1436 None,
1437 cx,
1438 );
1439 });
1440 assert_new_snapshot(
1441 &multibuffer,
1442 &mut snapshot,
1443 &mut subscription,
1444 cx,
1445 indoc!(
1446 "
1447 ZERO
1448 one hundred
1449 thousand
1450 - two
1451 + TWO
1452 three
1453 - four
1454 - five
1455 six
1456 "
1457 ),
1458 );
1459
1460 assert_chunks_in_ranges(&snapshot);
1461 assert_consistent_line_numbers(&snapshot);
1462 assert_position_translation(&snapshot);
1463 assert_line_indents(&snapshot);
1464
1465 // Recalculate the diff, changing the first diff hunk.
1466 change_set.update(cx, |change_set, cx| {
1467 change_set.recalculate_diff_sync(buffer.read(cx).text_snapshot(), cx);
1468 });
1469 cx.run_until_parked();
1470 assert_new_snapshot(
1471 &multibuffer,
1472 &mut snapshot,
1473 &mut subscription,
1474 cx,
1475 indoc!(
1476 "
1477 ZERO
1478 one hundred
1479 thousand
1480 TWO
1481 three
1482 - four
1483 - five
1484 six
1485 "
1486 ),
1487 );
1488
1489 assert_eq!(
1490 snapshot
1491 .diff_hunks_in_range(0..snapshot.len())
1492 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1493 .collect::<Vec<_>>(),
1494 &[0..4, 5..7]
1495 );
1496}
1497
1498#[gpui::test]
1499fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
1500 let text = indoc!(
1501 "
1502 one
1503 TWO
1504 THREE
1505 four
1506 FIVE
1507 six
1508 "
1509 );
1510 let base_text = indoc!(
1511 "
1512 one
1513 four
1514 six
1515 "
1516 );
1517
1518 let buffer = cx.new(|cx| Buffer::local(text, cx));
1519 let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx));
1520 cx.run_until_parked();
1521
1522 let multibuffer = cx.new(|cx| {
1523 let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1524 multibuffer.add_change_set(change_set.clone(), cx);
1525 multibuffer
1526 });
1527
1528 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1529 (multibuffer.snapshot(cx), multibuffer.subscribe())
1530 });
1531
1532 multibuffer.update(cx, |multibuffer, cx| {
1533 multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1534 });
1535
1536 assert_new_snapshot(
1537 &multibuffer,
1538 &mut snapshot,
1539 &mut subscription,
1540 cx,
1541 indoc!(
1542 "
1543 one
1544 + TWO
1545 + THREE
1546 four
1547 + FIVE
1548 six
1549 "
1550 ),
1551 );
1552
1553 // Regression test: expanding diff hunks that are already expanded should not change anything.
1554 multibuffer.update(cx, |multibuffer, cx| {
1555 multibuffer.expand_diff_hunks(
1556 vec![
1557 snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_before(Point::new(2, 0)),
1558 ],
1559 cx,
1560 );
1561 });
1562
1563 assert_new_snapshot(
1564 &multibuffer,
1565 &mut snapshot,
1566 &mut subscription,
1567 cx,
1568 indoc!(
1569 "
1570 one
1571 + TWO
1572 + THREE
1573 four
1574 + FIVE
1575 six
1576 "
1577 ),
1578 );
1579}
1580
1581#[gpui::test]
1582fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
1583 let buf1 = cx.new(|cx| {
1584 Buffer::local(
1585 indoc! {
1586 "zero
1587 one
1588 two
1589 three
1590 four
1591 five
1592 six
1593 seven
1594 ",
1595 },
1596 cx,
1597 )
1598 });
1599 let path1: PathKey = PathKey::namespaced("0", Path::new("/"));
1600 let buf2 = cx.new(|cx| {
1601 Buffer::local(
1602 indoc! {
1603 "000
1604 111
1605 222
1606 333
1607 444
1608 555
1609 666
1610 777
1611 888
1612 999
1613 "
1614 },
1615 cx,
1616 )
1617 });
1618 let path2 = PathKey::namespaced("x", Path::new("/"));
1619
1620 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1621 multibuffer.update(cx, |multibuffer, cx| {
1622 multibuffer.set_excerpts_for_path(
1623 path1.clone(),
1624 buf1.clone(),
1625 vec![Point::row_range(0..1)],
1626 2,
1627 cx,
1628 );
1629 });
1630
1631 assert_excerpts_match(
1632 &multibuffer,
1633 cx,
1634 indoc! {
1635 "-----
1636 zero
1637 one
1638 two
1639 three
1640 "
1641 },
1642 );
1643
1644 multibuffer.update(cx, |multibuffer, cx| {
1645 multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1646 });
1647
1648 assert_excerpts_match(&multibuffer, cx, "");
1649
1650 multibuffer.update(cx, |multibuffer, cx| {
1651 multibuffer.set_excerpts_for_path(
1652 path1.clone(),
1653 buf1.clone(),
1654 vec![Point::row_range(0..1), Point::row_range(7..8)],
1655 2,
1656 cx,
1657 );
1658 });
1659
1660 assert_excerpts_match(
1661 &multibuffer,
1662 cx,
1663 indoc! {"-----
1664 zero
1665 one
1666 two
1667 three
1668 -----
1669 five
1670 six
1671 seven
1672 "},
1673 );
1674
1675 multibuffer.update(cx, |multibuffer, cx| {
1676 multibuffer.set_excerpts_for_path(
1677 path1.clone(),
1678 buf1.clone(),
1679 vec![Point::row_range(0..1), Point::row_range(5..6)],
1680 2,
1681 cx,
1682 );
1683 });
1684
1685 assert_excerpts_match(
1686 &multibuffer,
1687 cx,
1688 indoc! {"-----
1689 zero
1690 one
1691 two
1692 three
1693 four
1694 five
1695 six
1696 seven
1697 "},
1698 );
1699
1700 multibuffer.update(cx, |multibuffer, cx| {
1701 multibuffer.set_excerpts_for_path(
1702 path2.clone(),
1703 buf2.clone(),
1704 vec![Point::row_range(2..3)],
1705 2,
1706 cx,
1707 );
1708 });
1709
1710 assert_excerpts_match(
1711 &multibuffer,
1712 cx,
1713 indoc! {"-----
1714 zero
1715 one
1716 two
1717 three
1718 four
1719 five
1720 six
1721 seven
1722 -----
1723 000
1724 111
1725 222
1726 333
1727 444
1728 555
1729 "},
1730 );
1731
1732 multibuffer.update(cx, |multibuffer, cx| {
1733 multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1734 });
1735
1736 multibuffer.update(cx, |multibuffer, cx| {
1737 multibuffer.set_excerpts_for_path(
1738 path1.clone(),
1739 buf1.clone(),
1740 vec![Point::row_range(3..4)],
1741 2,
1742 cx,
1743 );
1744 });
1745
1746 assert_excerpts_match(
1747 &multibuffer,
1748 cx,
1749 indoc! {"-----
1750 one
1751 two
1752 three
1753 four
1754 five
1755 six
1756 -----
1757 000
1758 111
1759 222
1760 333
1761 444
1762 555
1763 "},
1764 );
1765
1766 multibuffer.update(cx, |multibuffer, cx| {
1767 multibuffer.set_excerpts_for_path(
1768 path1.clone(),
1769 buf1.clone(),
1770 vec![Point::row_range(3..4)],
1771 2,
1772 cx,
1773 );
1774 });
1775}
1776
1777#[gpui::test]
1778fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
1779 let base_text_1 = indoc!(
1780 "
1781 one
1782 two
1783 three
1784 four
1785 five
1786 six
1787 "
1788 );
1789 let text_1 = indoc!(
1790 "
1791 ZERO
1792 one
1793 TWO
1794 three
1795 six
1796 "
1797 );
1798 let base_text_2 = indoc!(
1799 "
1800 seven
1801 eight
1802 nine
1803 ten
1804 eleven
1805 twelve
1806 "
1807 );
1808 let text_2 = indoc!(
1809 "
1810 eight
1811 nine
1812 eleven
1813 THIRTEEN
1814 FOURTEEN
1815 "
1816 );
1817
1818 let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
1819 let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
1820 let change_set_1 = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_1, &buffer_1, cx));
1821 let change_set_2 = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_2, &buffer_2, cx));
1822 cx.run_until_parked();
1823
1824 let multibuffer = cx.new(|cx| {
1825 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1826 multibuffer.push_excerpts(
1827 buffer_1.clone(),
1828 [ExcerptRange {
1829 context: text::Anchor::MIN..text::Anchor::MAX,
1830 primary: None,
1831 }],
1832 cx,
1833 );
1834 multibuffer.push_excerpts(
1835 buffer_2.clone(),
1836 [ExcerptRange {
1837 context: text::Anchor::MIN..text::Anchor::MAX,
1838 primary: None,
1839 }],
1840 cx,
1841 );
1842 multibuffer.add_change_set(change_set_1.clone(), cx);
1843 multibuffer.add_change_set(change_set_2.clone(), cx);
1844 multibuffer
1845 });
1846
1847 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1848 (multibuffer.snapshot(cx), multibuffer.subscribe())
1849 });
1850 assert_eq!(
1851 snapshot.text(),
1852 indoc!(
1853 "
1854 ZERO
1855 one
1856 TWO
1857 three
1858 six
1859
1860 eight
1861 nine
1862 eleven
1863 THIRTEEN
1864 FOURTEEN
1865 "
1866 ),
1867 );
1868
1869 multibuffer.update(cx, |multibuffer, cx| {
1870 multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1871 });
1872
1873 assert_new_snapshot(
1874 &multibuffer,
1875 &mut snapshot,
1876 &mut subscription,
1877 cx,
1878 indoc!(
1879 "
1880 + ZERO
1881 one
1882 - two
1883 + TWO
1884 three
1885 - four
1886 - five
1887 six
1888
1889 - seven
1890 eight
1891 nine
1892 - ten
1893 eleven
1894 - twelve
1895 + THIRTEEN
1896 + FOURTEEN
1897 "
1898 ),
1899 );
1900
1901 let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
1902 let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
1903 let base_id_1 = change_set_1.read_with(cx, |change_set, _| {
1904 change_set.base_text.as_ref().unwrap().remote_id()
1905 });
1906 let base_id_2 = change_set_2.read_with(cx, |change_set, _| {
1907 change_set.base_text.as_ref().unwrap().remote_id()
1908 });
1909
1910 let buffer_lines = (0..=snapshot.max_row().0)
1911 .map(|row| {
1912 let (buffer, range) = snapshot.buffer_line_for_row(MultiBufferRow(row))?;
1913 Some((
1914 buffer.remote_id(),
1915 buffer.text_for_range(range).collect::<String>(),
1916 ))
1917 })
1918 .collect::<Vec<_>>();
1919 pretty_assertions::assert_eq!(
1920 buffer_lines,
1921 [
1922 Some((id_1, "ZERO".into())),
1923 Some((id_1, "one".into())),
1924 Some((base_id_1, "two".into())),
1925 Some((id_1, "TWO".into())),
1926 Some((id_1, " three".into())),
1927 Some((base_id_1, "four".into())),
1928 Some((base_id_1, "five".into())),
1929 Some((id_1, "six".into())),
1930 Some((id_1, "".into())),
1931 Some((base_id_2, "seven".into())),
1932 Some((id_2, " eight".into())),
1933 Some((id_2, "nine".into())),
1934 Some((base_id_2, "ten".into())),
1935 Some((id_2, "eleven".into())),
1936 Some((base_id_2, "twelve".into())),
1937 Some((id_2, "THIRTEEN".into())),
1938 Some((id_2, "FOURTEEN".into())),
1939 Some((id_2, "".into())),
1940 ]
1941 );
1942
1943 assert_position_translation(&snapshot);
1944 assert_line_indents(&snapshot);
1945
1946 assert_eq!(
1947 snapshot
1948 .diff_hunks_in_range(0..snapshot.len())
1949 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1950 .collect::<Vec<_>>(),
1951 &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
1952 );
1953
1954 buffer_2.update(cx, |buffer, cx| {
1955 buffer.edit_via_marked_text(
1956 indoc!(
1957 "
1958 eight
1959 «»eleven
1960 THIRTEEN
1961 FOURTEEN
1962 "
1963 ),
1964 None,
1965 cx,
1966 );
1967 });
1968
1969 assert_new_snapshot(
1970 &multibuffer,
1971 &mut snapshot,
1972 &mut subscription,
1973 cx,
1974 indoc!(
1975 "
1976 + ZERO
1977 one
1978 - two
1979 + TWO
1980 three
1981 - four
1982 - five
1983 six
1984
1985 - seven
1986 eight
1987 eleven
1988 - twelve
1989 + THIRTEEN
1990 + FOURTEEN
1991 "
1992 ),
1993 );
1994
1995 assert_line_indents(&snapshot);
1996}
1997
1998/// A naive implementation of a multi-buffer that does not maintain
1999/// any derived state, used for comparison in a randomized test.
2000#[derive(Default)]
2001struct ReferenceMultibuffer {
2002 excerpts: Vec<ReferenceExcerpt>,
2003 change_sets: HashMap<BufferId, Entity<BufferChangeSet>>,
2004}
2005
2006#[derive(Debug)]
2007struct ReferenceExcerpt {
2008 id: ExcerptId,
2009 buffer: Entity<Buffer>,
2010 range: Range<text::Anchor>,
2011 expanded_diff_hunks: Vec<text::Anchor>,
2012}
2013
2014#[derive(Debug)]
2015struct ReferenceRegion {
2016 buffer_id: Option<BufferId>,
2017 range: Range<usize>,
2018 buffer_start: Option<Point>,
2019 status: Option<DiffHunkStatus>,
2020}
2021
2022impl ReferenceMultibuffer {
2023 fn expand_excerpts(&mut self, excerpts: &HashSet<ExcerptId>, line_count: u32, cx: &App) {
2024 if line_count == 0 {
2025 return;
2026 }
2027
2028 for id in excerpts {
2029 let excerpt = self.excerpts.iter_mut().find(|e| e.id == *id).unwrap();
2030 let snapshot = excerpt.buffer.read(cx).snapshot();
2031 let mut point_range = excerpt.range.to_point(&snapshot);
2032 point_range.start = Point::new(point_range.start.row.saturating_sub(line_count), 0);
2033 point_range.end =
2034 snapshot.clip_point(Point::new(point_range.end.row + line_count, 0), Bias::Left);
2035 point_range.end.column = snapshot.line_len(point_range.end.row);
2036 excerpt.range =
2037 snapshot.anchor_before(point_range.start)..snapshot.anchor_after(point_range.end);
2038 }
2039 }
2040
2041 fn remove_excerpt(&mut self, id: ExcerptId, cx: &App) {
2042 let ix = self
2043 .excerpts
2044 .iter()
2045 .position(|excerpt| excerpt.id == id)
2046 .unwrap();
2047 let excerpt = self.excerpts.remove(ix);
2048 let buffer = excerpt.buffer.read(cx);
2049 log::info!(
2050 "Removing excerpt {}: {:?}",
2051 ix,
2052 buffer
2053 .text_for_range(excerpt.range.to_offset(buffer))
2054 .collect::<String>(),
2055 );
2056 }
2057
2058 fn insert_excerpt_after(
2059 &mut self,
2060 prev_id: ExcerptId,
2061 new_excerpt_id: ExcerptId,
2062 (buffer_handle, anchor_range): (Entity<Buffer>, Range<text::Anchor>),
2063 ) {
2064 let excerpt_ix = if prev_id == ExcerptId::max() {
2065 self.excerpts.len()
2066 } else {
2067 self.excerpts
2068 .iter()
2069 .position(|excerpt| excerpt.id == prev_id)
2070 .unwrap()
2071 + 1
2072 };
2073 self.excerpts.insert(
2074 excerpt_ix,
2075 ReferenceExcerpt {
2076 id: new_excerpt_id,
2077 buffer: buffer_handle,
2078 range: anchor_range,
2079 expanded_diff_hunks: Vec::new(),
2080 },
2081 );
2082 }
2083
2084 fn expand_diff_hunks(&mut self, excerpt_id: ExcerptId, range: Range<text::Anchor>, cx: &App) {
2085 let excerpt = self
2086 .excerpts
2087 .iter_mut()
2088 .find(|e| e.id == excerpt_id)
2089 .unwrap();
2090 let buffer = excerpt.buffer.read(cx).snapshot();
2091 let buffer_id = buffer.remote_id();
2092 let Some(change_set) = self.change_sets.get(&buffer_id) else {
2093 return;
2094 };
2095 let diff = change_set.read(cx).diff_to_buffer.clone();
2096 let excerpt_range = excerpt.range.to_offset(&buffer);
2097 for hunk in diff.hunks_intersecting_range(range, &buffer) {
2098 let hunk_range = hunk.buffer_range.to_offset(&buffer);
2099 if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end {
2100 continue;
2101 }
2102 if let Err(ix) = excerpt
2103 .expanded_diff_hunks
2104 .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
2105 {
2106 log::info!(
2107 "expanding diff hunk {:?}. excerpt:{:?}, excerpt range:{:?}",
2108 hunk_range,
2109 excerpt_id,
2110 excerpt_range
2111 );
2112 excerpt
2113 .expanded_diff_hunks
2114 .insert(ix, hunk.buffer_range.start);
2115 } else {
2116 log::trace!("hunk {hunk_range:?} already expanded in excerpt {excerpt_id:?}");
2117 }
2118 }
2119 }
2120
2121 fn expected_content(&self, cx: &App) -> (String, Vec<RowInfo>, HashSet<MultiBufferRow>) {
2122 let mut text = String::new();
2123 let mut regions = Vec::<ReferenceRegion>::new();
2124 let mut excerpt_boundary_rows = HashSet::default();
2125 for excerpt in &self.excerpts {
2126 excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
2127 let buffer = excerpt.buffer.read(cx);
2128 let buffer_range = excerpt.range.to_offset(buffer);
2129 let change_set = self.change_sets.get(&buffer.remote_id()).unwrap().read(cx);
2130 let diff = change_set.diff_to_buffer.clone();
2131 let base_buffer = change_set.base_text.as_ref().unwrap();
2132
2133 let mut offset = buffer_range.start;
2134 let mut hunks = diff
2135 .hunks_intersecting_range(excerpt.range.clone(), buffer)
2136 .peekable();
2137
2138 while let Some(hunk) = hunks.next() {
2139 // Ignore hunks that are outside the excerpt range.
2140 let mut hunk_range = hunk.buffer_range.to_offset(buffer);
2141
2142 hunk_range.end = hunk_range.end.min(buffer_range.end);
2143 if hunk_range.start > buffer_range.end || hunk_range.start < buffer_range.start {
2144 log::trace!("skipping hunk outside excerpt range");
2145 continue;
2146 }
2147
2148 if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| {
2149 expanded_anchor.to_offset(&buffer).max(buffer_range.start)
2150 == hunk_range.start.max(buffer_range.start)
2151 }) {
2152 log::trace!("skipping a hunk that's not marked as expanded");
2153 continue;
2154 }
2155
2156 if !hunk.buffer_range.start.is_valid(&buffer) {
2157 log::trace!("skipping hunk with deleted start: {:?}", hunk.row_range);
2158 continue;
2159 }
2160
2161 if hunk_range.start >= offset {
2162 // Add the buffer text before the hunk
2163 let len = text.len();
2164 text.extend(buffer.text_for_range(offset..hunk_range.start));
2165 regions.push(ReferenceRegion {
2166 buffer_id: Some(buffer.remote_id()),
2167 range: len..text.len(),
2168 buffer_start: Some(buffer.offset_to_point(offset)),
2169 status: None,
2170 });
2171
2172 // Add the deleted text for the hunk.
2173 if !hunk.diff_base_byte_range.is_empty() {
2174 let mut base_text = base_buffer
2175 .text_for_range(hunk.diff_base_byte_range.clone())
2176 .collect::<String>();
2177 if !base_text.ends_with('\n') {
2178 base_text.push('\n');
2179 }
2180 let len = text.len();
2181 text.push_str(&base_text);
2182 regions.push(ReferenceRegion {
2183 buffer_id: Some(base_buffer.remote_id()),
2184 range: len..text.len(),
2185 buffer_start: Some(
2186 base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
2187 ),
2188 status: Some(DiffHunkStatus::Removed),
2189 });
2190 }
2191
2192 offset = hunk_range.start;
2193 }
2194
2195 // Add the inserted text for the hunk.
2196 if hunk_range.end > offset {
2197 let len = text.len();
2198 text.extend(buffer.text_for_range(offset..hunk_range.end));
2199 regions.push(ReferenceRegion {
2200 buffer_id: Some(buffer.remote_id()),
2201 range: len..text.len(),
2202 buffer_start: Some(buffer.offset_to_point(offset)),
2203 status: Some(DiffHunkStatus::Added),
2204 });
2205 offset = hunk_range.end;
2206 }
2207 }
2208
2209 // Add the buffer text for the rest of the excerpt.
2210 let len = text.len();
2211 text.extend(buffer.text_for_range(offset..buffer_range.end));
2212 text.push('\n');
2213 regions.push(ReferenceRegion {
2214 buffer_id: Some(buffer.remote_id()),
2215 range: len..text.len(),
2216 buffer_start: Some(buffer.offset_to_point(offset)),
2217 status: None,
2218 });
2219 }
2220
2221 // Remove final trailing newline.
2222 if self.excerpts.is_empty() {
2223 regions.push(ReferenceRegion {
2224 buffer_id: None,
2225 range: 0..1,
2226 buffer_start: Some(Point::new(0, 0)),
2227 status: None,
2228 });
2229 } else {
2230 text.pop();
2231 }
2232
2233 // Retrieve the row info using the region that contains
2234 // the start of each multi-buffer line.
2235 let mut ix = 0;
2236 let row_infos = text
2237 .split('\n')
2238 .map(|line| {
2239 let row_info = regions
2240 .iter()
2241 .find(|region| region.range.contains(&ix))
2242 .map_or(RowInfo::default(), |region| {
2243 let buffer_row = region.buffer_start.map(|start_point| {
2244 start_point.row
2245 + text[region.range.start..ix].matches('\n').count() as u32
2246 });
2247 RowInfo {
2248 buffer_id: region.buffer_id,
2249 diff_status: region.status,
2250 buffer_row,
2251 multibuffer_row: Some(MultiBufferRow(
2252 text[..ix].matches('\n').count() as u32
2253 )),
2254 }
2255 });
2256 ix += line.len() + 1;
2257 row_info
2258 })
2259 .collect();
2260
2261 (text, row_infos, excerpt_boundary_rows)
2262 }
2263
2264 fn diffs_updated(&mut self, cx: &App) {
2265 for excerpt in &mut self.excerpts {
2266 let buffer = excerpt.buffer.read(cx).snapshot();
2267 let excerpt_range = excerpt.range.to_offset(&buffer);
2268 let buffer_id = buffer.remote_id();
2269 let diff = &self
2270 .change_sets
2271 .get(&buffer_id)
2272 .unwrap()
2273 .read(cx)
2274 .diff_to_buffer;
2275 let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer).peekable();
2276 excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
2277 if !hunk_anchor.is_valid(&buffer) {
2278 return false;
2279 }
2280 while let Some(hunk) = hunks.peek() {
2281 match hunk.buffer_range.start.cmp(&hunk_anchor, &buffer) {
2282 cmp::Ordering::Less => {
2283 hunks.next();
2284 }
2285 cmp::Ordering::Equal => {
2286 let hunk_range = hunk.buffer_range.to_offset(&buffer);
2287 return hunk_range.end >= excerpt_range.start
2288 && hunk_range.start <= excerpt_range.end;
2289 }
2290 cmp::Ordering::Greater => break,
2291 }
2292 }
2293 false
2294 });
2295 }
2296 }
2297
2298 fn add_change_set(&mut self, change_set: Entity<BufferChangeSet>, cx: &mut App) {
2299 let buffer_id = change_set.read(cx).buffer_id;
2300 self.change_sets.insert(buffer_id, change_set);
2301 }
2302}
2303
2304#[gpui::test(iterations = 100)]
2305async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
2306 let operations = env::var("OPERATIONS")
2307 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2308 .unwrap_or(10);
2309
2310 let mut buffers: Vec<Entity<Buffer>> = Vec::new();
2311 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2312 let mut reference = ReferenceMultibuffer::default();
2313 let mut anchors = Vec::new();
2314 let mut old_versions = Vec::new();
2315 let mut needs_diff_calculation = false;
2316
2317 for _ in 0..operations {
2318 match rng.gen_range(0..100) {
2319 0..=14 if !buffers.is_empty() => {
2320 let buffer = buffers.choose(&mut rng).unwrap();
2321 buffer.update(cx, |buf, cx| {
2322 let edit_count = rng.gen_range(1..5);
2323 buf.randomly_edit(&mut rng, edit_count, cx);
2324 log::info!("buffer text:\n{}", buf.text());
2325 needs_diff_calculation = true;
2326 });
2327 cx.update(|cx| reference.diffs_updated(cx));
2328 }
2329 15..=19 if !reference.excerpts.is_empty() => {
2330 multibuffer.update(cx, |multibuffer, cx| {
2331 let ids = multibuffer.excerpt_ids();
2332 let mut excerpts = HashSet::default();
2333 for _ in 0..rng.gen_range(0..ids.len()) {
2334 excerpts.extend(ids.choose(&mut rng).copied());
2335 }
2336
2337 let line_count = rng.gen_range(0..5);
2338
2339 let excerpt_ixs = excerpts
2340 .iter()
2341 .map(|id| reference.excerpts.iter().position(|e| e.id == *id).unwrap())
2342 .collect::<Vec<_>>();
2343 log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
2344 multibuffer.expand_excerpts(
2345 excerpts.iter().cloned(),
2346 line_count,
2347 ExpandExcerptDirection::UpAndDown,
2348 cx,
2349 );
2350
2351 reference.expand_excerpts(&excerpts, line_count, cx);
2352 });
2353 }
2354 20..=29 if !reference.excerpts.is_empty() => {
2355 let mut ids_to_remove = vec![];
2356 for _ in 0..rng.gen_range(1..=3) {
2357 let Some(excerpt) = reference.excerpts.choose(&mut rng) else {
2358 break;
2359 };
2360 let id = excerpt.id;
2361 cx.update(|cx| reference.remove_excerpt(id, cx));
2362 ids_to_remove.push(id);
2363 }
2364 let snapshot =
2365 multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2366 ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
2367 drop(snapshot);
2368 multibuffer.update(cx, |multibuffer, cx| {
2369 multibuffer.remove_excerpts(ids_to_remove, cx)
2370 });
2371 }
2372 30..=39 if !reference.excerpts.is_empty() => {
2373 let multibuffer =
2374 multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2375 let offset =
2376 multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
2377 let bias = if rng.gen() { Bias::Left } else { Bias::Right };
2378 log::info!("Creating anchor at {} with bias {:?}", offset, bias);
2379 anchors.push(multibuffer.anchor_at(offset, bias));
2380 anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
2381 }
2382 40..=44 if !anchors.is_empty() => {
2383 let multibuffer =
2384 multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2385 let prev_len = anchors.len();
2386 anchors = multibuffer
2387 .refresh_anchors(&anchors)
2388 .into_iter()
2389 .map(|a| a.1)
2390 .collect();
2391
2392 // Ensure the newly-refreshed anchors point to a valid excerpt and don't
2393 // overshoot its boundaries.
2394 assert_eq!(anchors.len(), prev_len);
2395 for anchor in &anchors {
2396 if anchor.excerpt_id == ExcerptId::min()
2397 || anchor.excerpt_id == ExcerptId::max()
2398 {
2399 continue;
2400 }
2401
2402 let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
2403 assert_eq!(excerpt.id, anchor.excerpt_id);
2404 assert!(excerpt.contains(anchor));
2405 }
2406 }
2407 45..=55 if !reference.excerpts.is_empty() => {
2408 multibuffer.update(cx, |multibuffer, cx| {
2409 let snapshot = multibuffer.snapshot(cx);
2410 let excerpt_ix = rng.gen_range(0..reference.excerpts.len());
2411 let excerpt = &reference.excerpts[excerpt_ix];
2412 let start = excerpt.range.start;
2413 let end = excerpt.range.end;
2414 let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
2415 ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
2416
2417 log::info!(
2418 "expanding diff hunks in range {:?} (excerpt id {:?}) index {excerpt_ix:?})",
2419 range.to_offset(&snapshot),
2420 excerpt.id
2421 );
2422 reference.expand_diff_hunks(excerpt.id, start..end, cx);
2423 multibuffer.expand_diff_hunks(vec![range], cx);
2424 });
2425 }
2426 56..=85 if needs_diff_calculation => {
2427 multibuffer.update(cx, |multibuffer, cx| {
2428 for buffer in multibuffer.all_buffers() {
2429 let snapshot = buffer.read(cx).snapshot();
2430 let _ = multibuffer
2431 .change_set_for(snapshot.remote_id())
2432 .unwrap()
2433 .update(cx, |change_set, cx| {
2434 log::info!(
2435 "recalculating diff for buffer {:?}",
2436 snapshot.remote_id(),
2437 );
2438 change_set.recalculate_diff_sync(snapshot.text, cx);
2439 });
2440 }
2441 reference.diffs_updated(cx);
2442 needs_diff_calculation = false;
2443 });
2444 }
2445 _ => {
2446 let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
2447 let base_text = util::RandomCharIter::new(&mut rng)
2448 .take(256)
2449 .collect::<String>();
2450
2451 let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
2452 let change_set =
2453 cx.new(|cx| BufferChangeSet::new_with_base_text(&base_text, &buffer, cx));
2454
2455 multibuffer.update(cx, |multibuffer, cx| {
2456 reference.add_change_set(change_set.clone(), cx);
2457 multibuffer.add_change_set(change_set, cx)
2458 });
2459 buffers.push(buffer);
2460 buffers.last().unwrap()
2461 } else {
2462 buffers.choose(&mut rng).unwrap()
2463 };
2464
2465 let prev_excerpt_ix = rng.gen_range(0..=reference.excerpts.len());
2466 let prev_excerpt_id = reference
2467 .excerpts
2468 .get(prev_excerpt_ix)
2469 .map_or(ExcerptId::max(), |e| e.id);
2470 let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
2471
2472 let (range, anchor_range) = buffer_handle.read_with(cx, |buffer, _| {
2473 let end_row = rng.gen_range(0..=buffer.max_point().row);
2474 let start_row = rng.gen_range(0..=end_row);
2475 let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
2476 let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
2477 let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
2478
2479 log::info!(
2480 "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
2481 excerpt_ix,
2482 reference.excerpts.len(),
2483 buffer.remote_id(),
2484 buffer.text(),
2485 start_ix..end_ix,
2486 &buffer.text()[start_ix..end_ix]
2487 );
2488
2489 (start_ix..end_ix, anchor_range)
2490 });
2491
2492 let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
2493 multibuffer
2494 .insert_excerpts_after(
2495 prev_excerpt_id,
2496 buffer_handle.clone(),
2497 [ExcerptRange {
2498 context: range,
2499 primary: None,
2500 }],
2501 cx,
2502 )
2503 .pop()
2504 .unwrap()
2505 });
2506
2507 reference.insert_excerpt_after(
2508 prev_excerpt_id,
2509 excerpt_id,
2510 (buffer_handle.clone(), anchor_range),
2511 );
2512 }
2513 }
2514
2515 if rng.gen_bool(0.3) {
2516 multibuffer.update(cx, |multibuffer, cx| {
2517 old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
2518 })
2519 }
2520
2521 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2522 let actual_text = snapshot.text();
2523 let actual_boundary_rows = snapshot
2524 .excerpt_boundaries_in_range(0..)
2525 .filter_map(|b| if b.next.is_some() { Some(b.row) } else { None })
2526 .collect::<HashSet<_>>();
2527 let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2528
2529 let (expected_text, expected_row_infos, expected_boundary_rows) =
2530 cx.update(|cx| reference.expected_content(cx));
2531
2532 let has_diff = actual_row_infos
2533 .iter()
2534 .any(|info| info.diff_status.is_some())
2535 || expected_row_infos
2536 .iter()
2537 .any(|info| info.diff_status.is_some());
2538 let actual_diff = format_diff(
2539 &actual_text,
2540 &actual_row_infos,
2541 &actual_boundary_rows,
2542 Some(has_diff),
2543 );
2544 let expected_diff = format_diff(
2545 &expected_text,
2546 &expected_row_infos,
2547 &expected_boundary_rows,
2548 Some(has_diff),
2549 );
2550
2551 log::info!("Multibuffer content:\n{}", actual_diff);
2552
2553 assert_eq!(
2554 actual_row_infos.len(),
2555 actual_text.split('\n').count(),
2556 "line count: {}",
2557 actual_text.split('\n').count()
2558 );
2559 pretty_assertions::assert_eq!(actual_diff, expected_diff);
2560 pretty_assertions::assert_eq!(actual_text, expected_text);
2561 pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
2562
2563 for _ in 0..5 {
2564 let start_row = rng.gen_range(0..=expected_row_infos.len());
2565 assert_eq!(
2566 snapshot
2567 .row_infos(MultiBufferRow(start_row as u32))
2568 .collect::<Vec<_>>(),
2569 &expected_row_infos[start_row..],
2570 "buffer_rows({})",
2571 start_row
2572 );
2573 }
2574
2575 assert_eq!(
2576 snapshot.widest_line_number(),
2577 expected_row_infos
2578 .into_iter()
2579 .filter_map(
2580 |info| if info.diff_status == Some(DiffHunkStatus::Removed) {
2581 None
2582 } else {
2583 info.buffer_row
2584 }
2585 )
2586 .max()
2587 .unwrap()
2588 + 1
2589 );
2590
2591 assert_consistent_line_numbers(&snapshot);
2592 assert_position_translation(&snapshot);
2593
2594 for (row, line) in expected_text.split('\n').enumerate() {
2595 assert_eq!(
2596 snapshot.line_len(MultiBufferRow(row as u32)),
2597 line.len() as u32,
2598 "line_len({}).",
2599 row
2600 );
2601 }
2602
2603 let text_rope = Rope::from(expected_text.as_str());
2604 for _ in 0..10 {
2605 let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2606 let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
2607
2608 let text_for_range = snapshot
2609 .text_for_range(start_ix..end_ix)
2610 .collect::<String>();
2611 assert_eq!(
2612 text_for_range,
2613 &expected_text[start_ix..end_ix],
2614 "incorrect text for range {:?}",
2615 start_ix..end_ix
2616 );
2617
2618 let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
2619 assert_eq!(
2620 snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
2621 expected_summary,
2622 "incorrect summary for range {:?}",
2623 start_ix..end_ix
2624 );
2625 }
2626
2627 // Anchor resolution
2628 let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
2629 assert_eq!(anchors.len(), summaries.len());
2630 for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
2631 assert!(resolved_offset <= snapshot.len());
2632 assert_eq!(
2633 snapshot.summary_for_anchor::<usize>(anchor),
2634 resolved_offset,
2635 "anchor: {:?}",
2636 anchor
2637 );
2638 }
2639
2640 for _ in 0..10 {
2641 let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2642 assert_eq!(
2643 snapshot.reversed_chars_at(end_ix).collect::<String>(),
2644 expected_text[..end_ix].chars().rev().collect::<String>(),
2645 );
2646 }
2647
2648 for _ in 0..10 {
2649 let end_ix = rng.gen_range(0..=text_rope.len());
2650 let start_ix = rng.gen_range(0..=end_ix);
2651 assert_eq!(
2652 snapshot
2653 .bytes_in_range(start_ix..end_ix)
2654 .flatten()
2655 .copied()
2656 .collect::<Vec<_>>(),
2657 expected_text.as_bytes()[start_ix..end_ix].to_vec(),
2658 "bytes_in_range({:?})",
2659 start_ix..end_ix,
2660 );
2661 }
2662 }
2663
2664 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2665 for (old_snapshot, subscription) in old_versions {
2666 let edits = subscription.consume().into_inner();
2667
2668 log::info!(
2669 "applying subscription edits to old text: {:?}: {:?}",
2670 old_snapshot.text(),
2671 edits,
2672 );
2673
2674 let mut text = old_snapshot.text();
2675 for edit in edits {
2676 let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
2677 text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
2678 }
2679 assert_eq!(text.to_string(), snapshot.text());
2680 }
2681}
2682
2683#[gpui::test]
2684fn test_history(cx: &mut App) {
2685 let test_settings = SettingsStore::test(cx);
2686 cx.set_global(test_settings);
2687 let group_interval: Duration = Duration::from_millis(1);
2688 let buffer_1 = cx.new(|cx| {
2689 let mut buf = Buffer::local("1234", cx);
2690 buf.set_group_interval(group_interval);
2691 buf
2692 });
2693 let buffer_2 = cx.new(|cx| {
2694 let mut buf = Buffer::local("5678", cx);
2695 buf.set_group_interval(group_interval);
2696 buf
2697 });
2698 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2699 multibuffer.update(cx, |this, _| {
2700 this.history.group_interval = group_interval;
2701 });
2702 multibuffer.update(cx, |multibuffer, cx| {
2703 multibuffer.push_excerpts(
2704 buffer_1.clone(),
2705 [ExcerptRange {
2706 context: 0..buffer_1.read(cx).len(),
2707 primary: None,
2708 }],
2709 cx,
2710 );
2711 multibuffer.push_excerpts(
2712 buffer_2.clone(),
2713 [ExcerptRange {
2714 context: 0..buffer_2.read(cx).len(),
2715 primary: None,
2716 }],
2717 cx,
2718 );
2719 });
2720
2721 let mut now = Instant::now();
2722
2723 multibuffer.update(cx, |multibuffer, cx| {
2724 let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
2725 multibuffer.edit(
2726 [
2727 (Point::new(0, 0)..Point::new(0, 0), "A"),
2728 (Point::new(1, 0)..Point::new(1, 0), "A"),
2729 ],
2730 None,
2731 cx,
2732 );
2733 multibuffer.edit(
2734 [
2735 (Point::new(0, 1)..Point::new(0, 1), "B"),
2736 (Point::new(1, 1)..Point::new(1, 1), "B"),
2737 ],
2738 None,
2739 cx,
2740 );
2741 multibuffer.end_transaction_at(now, cx);
2742 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2743
2744 // Verify edited ranges for transaction 1
2745 assert_eq!(
2746 multibuffer.edited_ranges_for_transaction(transaction_1, cx),
2747 &[
2748 Point::new(0, 0)..Point::new(0, 2),
2749 Point::new(1, 0)..Point::new(1, 2)
2750 ]
2751 );
2752
2753 // Edit buffer 1 through the multibuffer
2754 now += 2 * group_interval;
2755 multibuffer.start_transaction_at(now, cx);
2756 multibuffer.edit([(2..2, "C")], None, cx);
2757 multibuffer.end_transaction_at(now, cx);
2758 assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
2759
2760 // Edit buffer 1 independently
2761 buffer_1.update(cx, |buffer_1, cx| {
2762 buffer_1.start_transaction_at(now);
2763 buffer_1.edit([(3..3, "D")], None, cx);
2764 buffer_1.end_transaction_at(now, cx);
2765
2766 now += 2 * group_interval;
2767 buffer_1.start_transaction_at(now);
2768 buffer_1.edit([(4..4, "E")], None, cx);
2769 buffer_1.end_transaction_at(now, cx);
2770 });
2771 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2772
2773 // An undo in the multibuffer undoes the multibuffer transaction
2774 // and also any individual buffer edits that have occurred since
2775 // that transaction.
2776 multibuffer.undo(cx);
2777 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2778
2779 multibuffer.undo(cx);
2780 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2781
2782 multibuffer.redo(cx);
2783 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2784
2785 multibuffer.redo(cx);
2786 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2787
2788 // Undo buffer 2 independently.
2789 buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
2790 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
2791
2792 // An undo in the multibuffer undoes the components of the
2793 // the last multibuffer transaction that are not already undone.
2794 multibuffer.undo(cx);
2795 assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
2796
2797 multibuffer.undo(cx);
2798 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2799
2800 multibuffer.redo(cx);
2801 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2802
2803 buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
2804 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2805
2806 // Redo stack gets cleared after an edit.
2807 now += 2 * group_interval;
2808 multibuffer.start_transaction_at(now, cx);
2809 multibuffer.edit([(0..0, "X")], None, cx);
2810 multibuffer.end_transaction_at(now, cx);
2811 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2812 multibuffer.redo(cx);
2813 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2814 multibuffer.undo(cx);
2815 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2816 multibuffer.undo(cx);
2817 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2818
2819 // Transactions can be grouped manually.
2820 multibuffer.redo(cx);
2821 multibuffer.redo(cx);
2822 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2823 multibuffer.group_until_transaction(transaction_1, cx);
2824 multibuffer.undo(cx);
2825 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2826 multibuffer.redo(cx);
2827 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2828 });
2829}
2830
2831#[gpui::test]
2832async fn test_enclosing_indent(cx: &mut TestAppContext) {
2833 async fn enclosing_indent(
2834 text: &str,
2835 buffer_row: u32,
2836 cx: &mut TestAppContext,
2837 ) -> Option<(Range<u32>, LineIndent)> {
2838 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2839 let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
2840 let (range, indent) = snapshot
2841 .enclosing_indent(MultiBufferRow(buffer_row))
2842 .await?;
2843 Some((range.start.0..range.end.0, indent))
2844 }
2845
2846 assert_eq!(
2847 enclosing_indent(
2848 indoc!(
2849 "
2850 fn b() {
2851 if c {
2852 let d = 2;
2853 }
2854 }
2855 "
2856 ),
2857 1,
2858 cx,
2859 )
2860 .await,
2861 Some((
2862 1..2,
2863 LineIndent {
2864 tabs: 0,
2865 spaces: 4,
2866 line_blank: false,
2867 }
2868 ))
2869 );
2870
2871 assert_eq!(
2872 enclosing_indent(
2873 indoc!(
2874 "
2875 fn b() {
2876 if c {
2877 let d = 2;
2878 }
2879 }
2880 "
2881 ),
2882 2,
2883 cx,
2884 )
2885 .await,
2886 Some((
2887 1..2,
2888 LineIndent {
2889 tabs: 0,
2890 spaces: 4,
2891 line_blank: false,
2892 }
2893 ))
2894 );
2895
2896 assert_eq!(
2897 enclosing_indent(
2898 indoc!(
2899 "
2900 fn b() {
2901 if c {
2902 let d = 2;
2903
2904 let e = 5;
2905 }
2906 }
2907 "
2908 ),
2909 3,
2910 cx,
2911 )
2912 .await,
2913 Some((
2914 1..4,
2915 LineIndent {
2916 tabs: 0,
2917 spaces: 4,
2918 line_blank: false,
2919 }
2920 ))
2921 );
2922}
2923
2924fn format_diff(
2925 text: &str,
2926 row_infos: &Vec<RowInfo>,
2927 boundary_rows: &HashSet<MultiBufferRow>,
2928 has_diff: Option<bool>,
2929) -> String {
2930 let has_diff =
2931 has_diff.unwrap_or_else(|| row_infos.iter().any(|info| info.diff_status.is_some()));
2932 text.split('\n')
2933 .enumerate()
2934 .zip(row_infos)
2935 .map(|((ix, line), info)| {
2936 let marker = match info.diff_status {
2937 Some(DiffHunkStatus::Added) => "+ ",
2938 Some(DiffHunkStatus::Removed) => "- ",
2939 Some(DiffHunkStatus::Modified) => unreachable!(),
2940 None => {
2941 if has_diff && !line.is_empty() {
2942 " "
2943 } else {
2944 ""
2945 }
2946 }
2947 };
2948 let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
2949 if has_diff {
2950 " ----------\n"
2951 } else {
2952 "---------\n"
2953 }
2954 } else {
2955 ""
2956 };
2957 format!("{boundary_row}{marker}{line}")
2958 })
2959 .collect::<Vec<_>>()
2960 .join("\n")
2961}
2962
2963#[track_caller]
2964fn assert_excerpts_match(
2965 multibuffer: &Entity<MultiBuffer>,
2966 cx: &mut TestAppContext,
2967 expected: &str,
2968) {
2969 let mut output = String::new();
2970 multibuffer.read_with(cx, |multibuffer, cx| {
2971 for (_, buffer, range) in multibuffer.snapshot(cx).excerpts() {
2972 output.push_str("-----\n");
2973 output.extend(buffer.text_for_range(range.context));
2974 if !output.ends_with('\n') {
2975 output.push('\n');
2976 }
2977 }
2978 });
2979 assert_eq!(output, expected);
2980}
2981
2982#[track_caller]
2983fn assert_new_snapshot(
2984 multibuffer: &Entity<MultiBuffer>,
2985 snapshot: &mut MultiBufferSnapshot,
2986 subscription: &mut Subscription,
2987 cx: &mut TestAppContext,
2988 expected_diff: &str,
2989) {
2990 let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2991 let actual_text = new_snapshot.text();
2992 let line_infos = new_snapshot
2993 .row_infos(MultiBufferRow(0))
2994 .collect::<Vec<_>>();
2995 let actual_diff = format_diff(&actual_text, &line_infos, &Default::default(), None);
2996 pretty_assertions::assert_eq!(actual_diff, expected_diff);
2997 check_edits(
2998 snapshot,
2999 &new_snapshot,
3000 &subscription.consume().into_inner(),
3001 );
3002 *snapshot = new_snapshot;
3003}
3004
3005#[track_caller]
3006fn check_edits(
3007 old_snapshot: &MultiBufferSnapshot,
3008 new_snapshot: &MultiBufferSnapshot,
3009 edits: &[Edit<usize>],
3010) {
3011 let mut text = old_snapshot.text();
3012 let new_text = new_snapshot.text();
3013 for edit in edits.iter().rev() {
3014 if !text.is_char_boundary(edit.old.start)
3015 || !text.is_char_boundary(edit.old.end)
3016 || !new_text.is_char_boundary(edit.new.start)
3017 || !new_text.is_char_boundary(edit.new.end)
3018 {
3019 panic!(
3020 "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
3021 edits, text, new_text
3022 );
3023 }
3024
3025 text.replace_range(
3026 edit.old.start..edit.old.end,
3027 &new_text[edit.new.start..edit.new.end],
3028 );
3029 }
3030
3031 pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
3032}
3033
3034#[track_caller]
3035fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
3036 let full_text = snapshot.text();
3037 for ix in 0..full_text.len() {
3038 let mut chunks = snapshot.chunks(0..snapshot.len(), false);
3039 chunks.seek(ix..snapshot.len());
3040 let tail = chunks.map(|chunk| chunk.text).collect::<String>();
3041 assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
3042 }
3043}
3044
3045#[track_caller]
3046fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
3047 let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3048 for start_row in 1..all_line_numbers.len() {
3049 let line_numbers = snapshot
3050 .row_infos(MultiBufferRow(start_row as u32))
3051 .collect::<Vec<_>>();
3052 assert_eq!(
3053 line_numbers,
3054 all_line_numbers[start_row..],
3055 "start_row: {start_row}"
3056 );
3057 }
3058}
3059
3060#[track_caller]
3061fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
3062 let text = Rope::from(snapshot.text());
3063
3064 let mut left_anchors = Vec::new();
3065 let mut right_anchors = Vec::new();
3066 let mut offsets = Vec::new();
3067 let mut points = Vec::new();
3068 for offset in 0..=text.len() + 1 {
3069 let clipped_left = snapshot.clip_offset(offset, Bias::Left);
3070 let clipped_right = snapshot.clip_offset(offset, Bias::Right);
3071 assert_eq!(
3072 clipped_left,
3073 text.clip_offset(offset, Bias::Left),
3074 "clip_offset({offset:?}, Left)"
3075 );
3076 assert_eq!(
3077 clipped_right,
3078 text.clip_offset(offset, Bias::Right),
3079 "clip_offset({offset:?}, Right)"
3080 );
3081 assert_eq!(
3082 snapshot.offset_to_point(clipped_left),
3083 text.offset_to_point(clipped_left),
3084 "offset_to_point({clipped_left})"
3085 );
3086 assert_eq!(
3087 snapshot.offset_to_point(clipped_right),
3088 text.offset_to_point(clipped_right),
3089 "offset_to_point({clipped_right})"
3090 );
3091 let anchor_after = snapshot.anchor_after(clipped_left);
3092 assert_eq!(
3093 anchor_after.to_offset(snapshot),
3094 clipped_left,
3095 "anchor_after({clipped_left}).to_offset {anchor_after:?}"
3096 );
3097 let anchor_before = snapshot.anchor_before(clipped_left);
3098 assert_eq!(
3099 anchor_before.to_offset(snapshot),
3100 clipped_left,
3101 "anchor_before({clipped_left}).to_offset"
3102 );
3103 left_anchors.push(anchor_before);
3104 right_anchors.push(anchor_after);
3105 offsets.push(clipped_left);
3106 points.push(text.offset_to_point(clipped_left));
3107 }
3108
3109 for row in 0..text.max_point().row {
3110 for column in 0..text.line_len(row) + 1 {
3111 let point = Point { row, column };
3112 let clipped_left = snapshot.clip_point(point, Bias::Left);
3113 let clipped_right = snapshot.clip_point(point, Bias::Right);
3114 assert_eq!(
3115 clipped_left,
3116 text.clip_point(point, Bias::Left),
3117 "clip_point({point:?}, Left)"
3118 );
3119 assert_eq!(
3120 clipped_right,
3121 text.clip_point(point, Bias::Right),
3122 "clip_point({point:?}, Right)"
3123 );
3124 assert_eq!(
3125 snapshot.point_to_offset(clipped_left),
3126 text.point_to_offset(clipped_left),
3127 "point_to_offset({clipped_left:?})"
3128 );
3129 assert_eq!(
3130 snapshot.point_to_offset(clipped_right),
3131 text.point_to_offset(clipped_right),
3132 "point_to_offset({clipped_right:?})"
3133 );
3134 }
3135 }
3136
3137 assert_eq!(
3138 snapshot.summaries_for_anchors::<usize, _>(&left_anchors),
3139 offsets,
3140 "left_anchors <-> offsets"
3141 );
3142 assert_eq!(
3143 snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
3144 points,
3145 "left_anchors <-> points"
3146 );
3147 assert_eq!(
3148 snapshot.summaries_for_anchors::<usize, _>(&right_anchors),
3149 offsets,
3150 "right_anchors <-> offsets"
3151 );
3152 assert_eq!(
3153 snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
3154 points,
3155 "right_anchors <-> points"
3156 );
3157
3158 for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
3159 for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
3160 if ix > 0 {
3161 if *offset == 252 {
3162 if offset > &offsets[ix - 1] {
3163 let prev_anchor = left_anchors[ix - 1];
3164 assert!(
3165 anchor.cmp(&prev_anchor, snapshot).is_gt(),
3166 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
3167 offsets[ix],
3168 offsets[ix - 1],
3169 );
3170 assert!(
3171 prev_anchor.cmp(&anchor, snapshot).is_lt(),
3172 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
3173 offsets[ix - 1],
3174 offsets[ix],
3175 );
3176 }
3177 }
3178 }
3179 }
3180 }
3181}
3182
3183fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
3184 let max_row = snapshot.max_point().row;
3185 let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
3186 let text = text::Buffer::new(0, buffer_id, snapshot.text());
3187 let mut line_indents = text
3188 .line_indents_in_row_range(0..max_row + 1)
3189 .collect::<Vec<_>>();
3190 for start_row in 0..snapshot.max_point().row {
3191 pretty_assertions::assert_eq!(
3192 snapshot
3193 .line_indents(MultiBufferRow(start_row), |_| true)
3194 .map(|(row, indent, _)| (row.0, indent))
3195 .collect::<Vec<_>>(),
3196 &line_indents[(start_row as usize)..],
3197 "line_indents({start_row})"
3198 );
3199 }
3200
3201 line_indents.reverse();
3202 pretty_assertions::assert_eq!(
3203 snapshot
3204 .reversed_line_indents(MultiBufferRow(max_row), |_| true)
3205 .map(|(row, indent, _)| (row.0, indent))
3206 .collect::<Vec<_>>(),
3207 &line_indents[..],
3208 "reversed_line_indents({max_row})"
3209 );
3210}