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