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_empty_diff_excerpt(cx: &mut TestAppContext) {
994 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
995 let buffer = cx.new(|cx| Buffer::local("", cx));
996 let base_text = "a\nb\nc";
997
998 let change_set = cx.new(|cx| {
999 let snapshot = buffer.read(cx).snapshot();
1000 let mut change_set = BufferChangeSet::new(&buffer, cx);
1001 let _ = change_set.set_base_text(base_text.into(), snapshot.text, cx);
1002 change_set
1003 });
1004 multibuffer.update(cx, |multibuffer, cx| {
1005 multibuffer.set_all_diff_hunks_expanded(cx);
1006 multibuffer.add_change_set(change_set.clone(), cx);
1007 multibuffer.push_excerpts(
1008 buffer.clone(),
1009 [ExcerptRange {
1010 context: 0..0,
1011 primary: None,
1012 }],
1013 cx,
1014 );
1015 });
1016 cx.run_until_parked();
1017
1018 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1019 assert_eq!(snapshot.text(), "a\nb\nc\n");
1020
1021 let hunk = snapshot
1022 .diff_hunks_in_range(Point::new(1, 1)..Point::new(1, 1))
1023 .next()
1024 .unwrap();
1025
1026 assert_eq!(hunk.diff_base_byte_range.start, 0);
1027
1028 let buf2 = cx.new(|cx| Buffer::local("X", cx));
1029 multibuffer.update(cx, |multibuffer, cx| {
1030 multibuffer.push_excerpts(
1031 buf2,
1032 [ExcerptRange {
1033 context: 0..1,
1034 primary: None,
1035 }],
1036 cx,
1037 );
1038 });
1039
1040 buffer.update(cx, |buffer, cx| {
1041 buffer.edit([(0..0, "a\nb\nc")], None, cx);
1042 change_set.update(cx, |change_set, cx| {
1043 let _ = change_set.recalculate_diff(buffer.snapshot().text, cx);
1044 });
1045 assert_eq!(buffer.text(), "a\nb\nc")
1046 });
1047 cx.run_until_parked();
1048
1049 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1050 assert_eq!(snapshot.text(), "a\nb\nc\nX");
1051
1052 buffer.update(cx, |buffer, cx| {
1053 buffer.undo(cx);
1054 change_set.update(cx, |change_set, cx| {
1055 let _ = change_set.recalculate_diff(buffer.snapshot().text, cx);
1056 });
1057 assert_eq!(buffer.text(), "")
1058 });
1059 cx.run_until_parked();
1060
1061 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1062 assert_eq!(snapshot.text(), "a\nb\nc\n\nX");
1063}
1064
1065#[gpui::test]
1066fn test_singleton_multibuffer_anchors(cx: &mut App) {
1067 let buffer = cx.new(|cx| Buffer::local("abcd", cx));
1068 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
1069 let old_snapshot = multibuffer.read(cx).snapshot(cx);
1070 buffer.update(cx, |buffer, cx| {
1071 buffer.edit([(0..0, "X")], None, cx);
1072 buffer.edit([(5..5, "Y")], None, cx);
1073 });
1074 let new_snapshot = multibuffer.read(cx).snapshot(cx);
1075
1076 assert_eq!(old_snapshot.text(), "abcd");
1077 assert_eq!(new_snapshot.text(), "XabcdY");
1078
1079 assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1080 assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1081 assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5);
1082 assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6);
1083}
1084
1085#[gpui::test]
1086fn test_multibuffer_anchors(cx: &mut App) {
1087 let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1088 let buffer_2 = cx.new(|cx| Buffer::local("efghi", cx));
1089 let multibuffer = cx.new(|cx| {
1090 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1091 multibuffer.push_excerpts(
1092 buffer_1.clone(),
1093 [ExcerptRange {
1094 context: 0..4,
1095 primary: None,
1096 }],
1097 cx,
1098 );
1099 multibuffer.push_excerpts(
1100 buffer_2.clone(),
1101 [ExcerptRange {
1102 context: 0..5,
1103 primary: None,
1104 }],
1105 cx,
1106 );
1107 multibuffer
1108 });
1109 let old_snapshot = multibuffer.read(cx).snapshot(cx);
1110
1111 assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0);
1112 assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0);
1113 assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1114 assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
1115 assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1116 assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
1117
1118 buffer_1.update(cx, |buffer, cx| {
1119 buffer.edit([(0..0, "W")], None, cx);
1120 buffer.edit([(5..5, "X")], None, cx);
1121 });
1122 buffer_2.update(cx, |buffer, cx| {
1123 buffer.edit([(0..0, "Y")], None, cx);
1124 buffer.edit([(6..6, "Z")], None, cx);
1125 });
1126 let new_snapshot = multibuffer.read(cx).snapshot(cx);
1127
1128 assert_eq!(old_snapshot.text(), "abcd\nefghi");
1129 assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
1130
1131 assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
1132 assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
1133 assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2);
1134 assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2);
1135 assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3);
1136 assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3);
1137 assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7);
1138 assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8);
1139 assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13);
1140 assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14);
1141}
1142
1143#[gpui::test]
1144fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
1145 let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1146 let buffer_2 = cx.new(|cx| Buffer::local("ABCDEFGHIJKLMNOP", cx));
1147 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1148
1149 // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
1150 // Add an excerpt from buffer 1 that spans this new insertion.
1151 buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx));
1152 let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| {
1153 multibuffer
1154 .push_excerpts(
1155 buffer_1.clone(),
1156 [ExcerptRange {
1157 context: 0..7,
1158 primary: None,
1159 }],
1160 cx,
1161 )
1162 .pop()
1163 .unwrap()
1164 });
1165
1166 let snapshot_1 = multibuffer.read(cx).snapshot(cx);
1167 assert_eq!(snapshot_1.text(), "abcd123");
1168
1169 // Replace the buffer 1 excerpt with new excerpts from buffer 2.
1170 let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
1171 multibuffer.remove_excerpts([excerpt_id_1], cx);
1172 let mut ids = multibuffer
1173 .push_excerpts(
1174 buffer_2.clone(),
1175 [
1176 ExcerptRange {
1177 context: 0..4,
1178 primary: None,
1179 },
1180 ExcerptRange {
1181 context: 6..10,
1182 primary: None,
1183 },
1184 ExcerptRange {
1185 context: 12..16,
1186 primary: None,
1187 },
1188 ],
1189 cx,
1190 )
1191 .into_iter();
1192 (ids.next().unwrap(), ids.next().unwrap())
1193 });
1194 let snapshot_2 = multibuffer.read(cx).snapshot(cx);
1195 assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP");
1196
1197 // The old excerpt id doesn't get reused.
1198 assert_ne!(excerpt_id_2, excerpt_id_1);
1199
1200 // Resolve some anchors from the previous snapshot in the new snapshot.
1201 // The current excerpts are from a different buffer, so we don't attempt to
1202 // resolve the old text anchor in the new buffer.
1203 assert_eq!(
1204 snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)),
1205 0
1206 );
1207 assert_eq!(
1208 snapshot_2.summaries_for_anchors::<usize, _>(&[
1209 snapshot_1.anchor_before(2),
1210 snapshot_1.anchor_after(3)
1211 ]),
1212 vec![0, 0]
1213 );
1214
1215 // Refresh anchors from the old snapshot. The return value indicates that both
1216 // anchors lost their original excerpt.
1217 let refresh =
1218 snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]);
1219 assert_eq!(
1220 refresh,
1221 &[
1222 (0, snapshot_2.anchor_before(0), false),
1223 (1, snapshot_2.anchor_after(0), false),
1224 ]
1225 );
1226
1227 // Replace the middle excerpt with a smaller excerpt in buffer 2,
1228 // that intersects the old excerpt.
1229 let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| {
1230 multibuffer.remove_excerpts([excerpt_id_3], cx);
1231 multibuffer
1232 .insert_excerpts_after(
1233 excerpt_id_2,
1234 buffer_2.clone(),
1235 [ExcerptRange {
1236 context: 5..8,
1237 primary: None,
1238 }],
1239 cx,
1240 )
1241 .pop()
1242 .unwrap()
1243 });
1244
1245 let snapshot_3 = multibuffer.read(cx).snapshot(cx);
1246 assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP");
1247 assert_ne!(excerpt_id_5, excerpt_id_3);
1248
1249 // Resolve some anchors from the previous snapshot in the new snapshot.
1250 // The third anchor can't be resolved, since its excerpt has been removed,
1251 // so it resolves to the same position as its predecessor.
1252 let anchors = [
1253 snapshot_2.anchor_before(0),
1254 snapshot_2.anchor_after(2),
1255 snapshot_2.anchor_after(6),
1256 snapshot_2.anchor_after(14),
1257 ];
1258 assert_eq!(
1259 snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
1260 &[0, 2, 9, 13]
1261 );
1262
1263 let new_anchors = snapshot_3.refresh_anchors(&anchors);
1264 assert_eq!(
1265 new_anchors.iter().map(|a| (a.0, a.2)).collect::<Vec<_>>(),
1266 &[(0, true), (1, true), (2, true), (3, true)]
1267 );
1268 assert_eq!(
1269 snapshot_3.summaries_for_anchors::<usize, _>(new_anchors.iter().map(|a| &a.1)),
1270 &[0, 2, 7, 13]
1271 );
1272}
1273
1274#[gpui::test]
1275fn test_basic_diff_hunks(cx: &mut TestAppContext) {
1276 let text = indoc!(
1277 "
1278 ZERO
1279 one
1280 TWO
1281 three
1282 six
1283 "
1284 );
1285 let base_text = indoc!(
1286 "
1287 one
1288 two
1289 three
1290 four
1291 five
1292 six
1293 "
1294 );
1295
1296 let buffer = cx.new(|cx| Buffer::local(text, cx));
1297 let change_set =
1298 cx.new(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
1299 cx.run_until_parked();
1300
1301 let multibuffer = cx.new(|cx| {
1302 let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1303 multibuffer.add_change_set(change_set.clone(), cx);
1304 multibuffer
1305 });
1306
1307 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1308 (multibuffer.snapshot(cx), multibuffer.subscribe())
1309 });
1310 assert_eq!(
1311 snapshot.text(),
1312 indoc!(
1313 "
1314 ZERO
1315 one
1316 TWO
1317 three
1318 six
1319 "
1320 ),
1321 );
1322
1323 multibuffer.update(cx, |multibuffer, cx| {
1324 multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1325 });
1326
1327 assert_new_snapshot(
1328 &multibuffer,
1329 &mut snapshot,
1330 &mut subscription,
1331 cx,
1332 indoc!(
1333 "
1334 + ZERO
1335 one
1336 - two
1337 + TWO
1338 three
1339 - four
1340 - five
1341 six
1342 "
1343 ),
1344 );
1345
1346 assert_eq!(
1347 snapshot
1348 .row_infos(MultiBufferRow(0))
1349 .map(|info| (info.buffer_row, info.diff_status))
1350 .collect::<Vec<_>>(),
1351 vec![
1352 (Some(0), Some(DiffHunkStatus::Added)),
1353 (Some(1), None),
1354 (Some(1), Some(DiffHunkStatus::Removed)),
1355 (Some(2), Some(DiffHunkStatus::Added)),
1356 (Some(3), None),
1357 (Some(3), Some(DiffHunkStatus::Removed)),
1358 (Some(4), Some(DiffHunkStatus::Removed)),
1359 (Some(4), None),
1360 (Some(5), None)
1361 ]
1362 );
1363
1364 assert_chunks_in_ranges(&snapshot);
1365 assert_consistent_line_numbers(&snapshot);
1366 assert_position_translation(&snapshot);
1367 assert_line_indents(&snapshot);
1368
1369 multibuffer.update(cx, |multibuffer, cx| {
1370 multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
1371 });
1372 assert_new_snapshot(
1373 &multibuffer,
1374 &mut snapshot,
1375 &mut subscription,
1376 cx,
1377 indoc!(
1378 "
1379 ZERO
1380 one
1381 TWO
1382 three
1383 six
1384 "
1385 ),
1386 );
1387
1388 assert_chunks_in_ranges(&snapshot);
1389 assert_consistent_line_numbers(&snapshot);
1390 assert_position_translation(&snapshot);
1391 assert_line_indents(&snapshot);
1392
1393 // Expand the first diff hunk
1394 multibuffer.update(cx, |multibuffer, cx| {
1395 let position = multibuffer.read(cx).anchor_before(Point::new(2, 2));
1396 multibuffer.expand_diff_hunks(vec![position..position], cx)
1397 });
1398 assert_new_snapshot(
1399 &multibuffer,
1400 &mut snapshot,
1401 &mut subscription,
1402 cx,
1403 indoc!(
1404 "
1405 ZERO
1406 one
1407 - two
1408 + TWO
1409 three
1410 six
1411 "
1412 ),
1413 );
1414
1415 // Expand the second diff hunk
1416 multibuffer.update(cx, |multibuffer, cx| {
1417 let start = multibuffer.read(cx).anchor_before(Point::new(4, 0));
1418 let end = multibuffer.read(cx).anchor_before(Point::new(5, 0));
1419 multibuffer.expand_diff_hunks(vec![start..end], cx)
1420 });
1421 assert_new_snapshot(
1422 &multibuffer,
1423 &mut snapshot,
1424 &mut subscription,
1425 cx,
1426 indoc!(
1427 "
1428 ZERO
1429 one
1430 - two
1431 + TWO
1432 three
1433 - four
1434 - five
1435 six
1436 "
1437 ),
1438 );
1439
1440 assert_chunks_in_ranges(&snapshot);
1441 assert_consistent_line_numbers(&snapshot);
1442 assert_position_translation(&snapshot);
1443 assert_line_indents(&snapshot);
1444
1445 // Edit the buffer before the first hunk
1446 buffer.update(cx, |buffer, cx| {
1447 buffer.edit_via_marked_text(
1448 indoc!(
1449 "
1450 ZERO
1451 one« hundred
1452 thousand»
1453 TWO
1454 three
1455 six
1456 "
1457 ),
1458 None,
1459 cx,
1460 );
1461 });
1462 assert_new_snapshot(
1463 &multibuffer,
1464 &mut snapshot,
1465 &mut subscription,
1466 cx,
1467 indoc!(
1468 "
1469 ZERO
1470 one hundred
1471 thousand
1472 - two
1473 + TWO
1474 three
1475 - four
1476 - five
1477 six
1478 "
1479 ),
1480 );
1481
1482 assert_chunks_in_ranges(&snapshot);
1483 assert_consistent_line_numbers(&snapshot);
1484 assert_position_translation(&snapshot);
1485 assert_line_indents(&snapshot);
1486
1487 // Recalculate the diff, changing the first diff hunk.
1488 let _ = change_set.update(cx, |change_set, cx| {
1489 change_set.recalculate_diff(buffer.read(cx).text_snapshot(), cx)
1490 });
1491 cx.run_until_parked();
1492 assert_new_snapshot(
1493 &multibuffer,
1494 &mut snapshot,
1495 &mut subscription,
1496 cx,
1497 indoc!(
1498 "
1499 ZERO
1500 one hundred
1501 thousand
1502 TWO
1503 three
1504 - four
1505 - five
1506 six
1507 "
1508 ),
1509 );
1510
1511 assert_eq!(
1512 snapshot
1513 .diff_hunks_in_range(0..snapshot.len())
1514 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1515 .collect::<Vec<_>>(),
1516 &[0..4, 5..7]
1517 );
1518}
1519
1520#[gpui::test]
1521fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
1522 let text = indoc!(
1523 "
1524 one
1525 TWO
1526 THREE
1527 four
1528 FIVE
1529 six
1530 "
1531 );
1532 let base_text = indoc!(
1533 "
1534 one
1535 four
1536 six
1537 "
1538 );
1539
1540 let buffer = cx.new(|cx| Buffer::local(text, cx));
1541 let change_set =
1542 cx.new(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
1543 cx.run_until_parked();
1544
1545 let multibuffer = cx.new(|cx| {
1546 let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1547 multibuffer.add_change_set(change_set.clone(), cx);
1548 multibuffer
1549 });
1550
1551 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1552 (multibuffer.snapshot(cx), multibuffer.subscribe())
1553 });
1554
1555 multibuffer.update(cx, |multibuffer, cx| {
1556 multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1557 });
1558
1559 assert_new_snapshot(
1560 &multibuffer,
1561 &mut snapshot,
1562 &mut subscription,
1563 cx,
1564 indoc!(
1565 "
1566 one
1567 + TWO
1568 + THREE
1569 four
1570 + FIVE
1571 six
1572 "
1573 ),
1574 );
1575
1576 // Regression test: expanding diff hunks that are already expanded should not change anything.
1577 multibuffer.update(cx, |multibuffer, cx| {
1578 multibuffer.expand_diff_hunks(
1579 vec![
1580 snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_before(Point::new(2, 0)),
1581 ],
1582 cx,
1583 );
1584 });
1585
1586 assert_new_snapshot(
1587 &multibuffer,
1588 &mut snapshot,
1589 &mut subscription,
1590 cx,
1591 indoc!(
1592 "
1593 one
1594 + TWO
1595 + THREE
1596 four
1597 + FIVE
1598 six
1599 "
1600 ),
1601 );
1602}
1603
1604#[gpui::test]
1605fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
1606 let buf1 = cx.new(|cx| {
1607 Buffer::local(
1608 indoc! {
1609 "zero
1610 one
1611 two
1612 three
1613 four
1614 five
1615 six
1616 seven
1617 ",
1618 },
1619 cx,
1620 )
1621 });
1622 let path1: Arc<Path> = Arc::from(PathBuf::from("path1"));
1623 let buf2 = cx.new(|cx| {
1624 Buffer::local(
1625 indoc! {
1626 "000
1627 111
1628 222
1629 333
1630 444
1631 555
1632 666
1633 777
1634 888
1635 999
1636 "
1637 },
1638 cx,
1639 )
1640 });
1641 let path2: Arc<Path> = Arc::from(PathBuf::from("path2"));
1642
1643 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1644 multibuffer.update(cx, |multibuffer, cx| {
1645 multibuffer.set_excerpts_for_path(
1646 path1.clone(),
1647 buf1.clone(),
1648 vec![Point::row_range(0..1)],
1649 2,
1650 cx,
1651 );
1652 });
1653
1654 assert_excerpts_match(
1655 &multibuffer,
1656 cx,
1657 indoc! {
1658 "-----
1659 zero
1660 one
1661 two
1662 three
1663 "
1664 },
1665 );
1666
1667 multibuffer.update(cx, |multibuffer, cx| {
1668 multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1669 });
1670
1671 assert_excerpts_match(&multibuffer, cx, "");
1672
1673 multibuffer.update(cx, |multibuffer, cx| {
1674 multibuffer.set_excerpts_for_path(
1675 path1.clone(),
1676 buf1.clone(),
1677 vec![Point::row_range(0..1), Point::row_range(7..8)],
1678 2,
1679 cx,
1680 );
1681 });
1682
1683 assert_excerpts_match(
1684 &multibuffer,
1685 cx,
1686 indoc! {"-----
1687 zero
1688 one
1689 two
1690 three
1691 -----
1692 five
1693 six
1694 seven
1695 "},
1696 );
1697
1698 multibuffer.update(cx, |multibuffer, cx| {
1699 multibuffer.set_excerpts_for_path(
1700 path1.clone(),
1701 buf1.clone(),
1702 vec![Point::row_range(0..1), Point::row_range(5..6)],
1703 2,
1704 cx,
1705 );
1706 });
1707
1708 assert_excerpts_match(
1709 &multibuffer,
1710 cx,
1711 indoc! {"-----
1712 zero
1713 one
1714 two
1715 three
1716 four
1717 five
1718 six
1719 seven
1720 "},
1721 );
1722
1723 multibuffer.update(cx, |multibuffer, cx| {
1724 multibuffer.set_excerpts_for_path(
1725 path2.clone(),
1726 buf2.clone(),
1727 vec![Point::row_range(2..3)],
1728 2,
1729 cx,
1730 );
1731 });
1732
1733 assert_excerpts_match(
1734 &multibuffer,
1735 cx,
1736 indoc! {"-----
1737 zero
1738 one
1739 two
1740 three
1741 four
1742 five
1743 six
1744 seven
1745 -----
1746 000
1747 111
1748 222
1749 333
1750 444
1751 555
1752 "},
1753 );
1754
1755 multibuffer.update(cx, |multibuffer, cx| {
1756 multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1757 });
1758
1759 multibuffer.update(cx, |multibuffer, cx| {
1760 multibuffer.set_excerpts_for_path(
1761 path1.clone(),
1762 buf1.clone(),
1763 vec![Point::row_range(3..4)],
1764 2,
1765 cx,
1766 );
1767 });
1768
1769 assert_excerpts_match(
1770 &multibuffer,
1771 cx,
1772 indoc! {"-----
1773 one
1774 two
1775 three
1776 four
1777 five
1778 six
1779 -----
1780 000
1781 111
1782 222
1783 333
1784 444
1785 555
1786 "},
1787 );
1788
1789 multibuffer.update(cx, |multibuffer, cx| {
1790 multibuffer.set_excerpts_for_path(
1791 path1.clone(),
1792 buf1.clone(),
1793 vec![Point::row_range(3..4)],
1794 2,
1795 cx,
1796 );
1797 });
1798}
1799
1800#[gpui::test]
1801fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
1802 let base_text_1 = indoc!(
1803 "
1804 one
1805 two
1806 three
1807 four
1808 five
1809 six
1810 "
1811 );
1812 let text_1 = indoc!(
1813 "
1814 ZERO
1815 one
1816 TWO
1817 three
1818 six
1819 "
1820 );
1821 let base_text_2 = indoc!(
1822 "
1823 seven
1824 eight
1825 nine
1826 ten
1827 eleven
1828 twelve
1829 "
1830 );
1831 let text_2 = indoc!(
1832 "
1833 eight
1834 nine
1835 eleven
1836 THIRTEEN
1837 FOURTEEN
1838 "
1839 );
1840
1841 let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
1842 let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
1843 let change_set_1 =
1844 cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_1.to_string(), &buffer_1, cx));
1845 let change_set_2 =
1846 cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_2.to_string(), &buffer_2, cx));
1847 cx.run_until_parked();
1848
1849 let multibuffer = cx.new(|cx| {
1850 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1851 multibuffer.push_excerpts(
1852 buffer_1.clone(),
1853 [ExcerptRange {
1854 context: text::Anchor::MIN..text::Anchor::MAX,
1855 primary: None,
1856 }],
1857 cx,
1858 );
1859 multibuffer.push_excerpts(
1860 buffer_2.clone(),
1861 [ExcerptRange {
1862 context: text::Anchor::MIN..text::Anchor::MAX,
1863 primary: None,
1864 }],
1865 cx,
1866 );
1867 multibuffer.add_change_set(change_set_1.clone(), cx);
1868 multibuffer.add_change_set(change_set_2.clone(), cx);
1869 multibuffer
1870 });
1871
1872 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1873 (multibuffer.snapshot(cx), multibuffer.subscribe())
1874 });
1875 assert_eq!(
1876 snapshot.text(),
1877 indoc!(
1878 "
1879 ZERO
1880 one
1881 TWO
1882 three
1883 six
1884
1885 eight
1886 nine
1887 eleven
1888 THIRTEEN
1889 FOURTEEN
1890 "
1891 ),
1892 );
1893
1894 multibuffer.update(cx, |multibuffer, cx| {
1895 multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
1896 });
1897
1898 assert_new_snapshot(
1899 &multibuffer,
1900 &mut snapshot,
1901 &mut subscription,
1902 cx,
1903 indoc!(
1904 "
1905 + ZERO
1906 one
1907 - two
1908 + TWO
1909 three
1910 - four
1911 - five
1912 six
1913
1914 - seven
1915 eight
1916 nine
1917 - ten
1918 eleven
1919 - twelve
1920 + THIRTEEN
1921 + FOURTEEN
1922 "
1923 ),
1924 );
1925
1926 let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
1927 let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
1928 let base_id_1 = change_set_1.read_with(cx, |change_set, _| {
1929 change_set.base_text.as_ref().unwrap().remote_id()
1930 });
1931 let base_id_2 = change_set_2.read_with(cx, |change_set, _| {
1932 change_set.base_text.as_ref().unwrap().remote_id()
1933 });
1934
1935 let buffer_lines = (0..=snapshot.max_row().0)
1936 .map(|row| {
1937 let (buffer, range) = snapshot.buffer_line_for_row(MultiBufferRow(row))?;
1938 Some((
1939 buffer.remote_id(),
1940 buffer.text_for_range(range).collect::<String>(),
1941 ))
1942 })
1943 .collect::<Vec<_>>();
1944 pretty_assertions::assert_eq!(
1945 buffer_lines,
1946 [
1947 Some((id_1, "ZERO".into())),
1948 Some((id_1, "one".into())),
1949 Some((base_id_1, "two".into())),
1950 Some((id_1, "TWO".into())),
1951 Some((id_1, " three".into())),
1952 Some((base_id_1, "four".into())),
1953 Some((base_id_1, "five".into())),
1954 Some((id_1, "six".into())),
1955 Some((id_1, "".into())),
1956 Some((base_id_2, "seven".into())),
1957 Some((id_2, " eight".into())),
1958 Some((id_2, "nine".into())),
1959 Some((base_id_2, "ten".into())),
1960 Some((id_2, "eleven".into())),
1961 Some((base_id_2, "twelve".into())),
1962 Some((id_2, "THIRTEEN".into())),
1963 Some((id_2, "FOURTEEN".into())),
1964 Some((id_2, "".into())),
1965 ]
1966 );
1967
1968 assert_position_translation(&snapshot);
1969 assert_line_indents(&snapshot);
1970
1971 assert_eq!(
1972 snapshot
1973 .diff_hunks_in_range(0..snapshot.len())
1974 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1975 .collect::<Vec<_>>(),
1976 &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
1977 );
1978
1979 buffer_2.update(cx, |buffer, cx| {
1980 buffer.edit_via_marked_text(
1981 indoc!(
1982 "
1983 eight
1984 «»eleven
1985 THIRTEEN
1986 FOURTEEN
1987 "
1988 ),
1989 None,
1990 cx,
1991 );
1992 });
1993
1994 assert_new_snapshot(
1995 &multibuffer,
1996 &mut snapshot,
1997 &mut subscription,
1998 cx,
1999 indoc!(
2000 "
2001 + ZERO
2002 one
2003 - two
2004 + TWO
2005 three
2006 - four
2007 - five
2008 six
2009
2010 - seven
2011 eight
2012 eleven
2013 - twelve
2014 + THIRTEEN
2015 + FOURTEEN
2016 "
2017 ),
2018 );
2019
2020 assert_line_indents(&snapshot);
2021}
2022
2023/// A naive implementation of a multi-buffer that does not maintain
2024/// any derived state, used for comparison in a randomized test.
2025#[derive(Default)]
2026struct ReferenceMultibuffer {
2027 excerpts: Vec<ReferenceExcerpt>,
2028 change_sets: HashMap<BufferId, Entity<BufferChangeSet>>,
2029}
2030
2031struct ReferenceExcerpt {
2032 id: ExcerptId,
2033 buffer: Entity<Buffer>,
2034 range: Range<text::Anchor>,
2035 expanded_diff_hunks: Vec<text::Anchor>,
2036}
2037
2038#[derive(Debug)]
2039struct ReferenceRegion {
2040 range: Range<usize>,
2041 buffer_start: Option<Point>,
2042 status: Option<DiffHunkStatus>,
2043}
2044
2045impl ReferenceMultibuffer {
2046 fn expand_excerpts(&mut self, excerpts: &HashSet<ExcerptId>, line_count: u32, cx: &App) {
2047 if line_count == 0 {
2048 return;
2049 }
2050
2051 for id in excerpts {
2052 let excerpt = self.excerpts.iter_mut().find(|e| e.id == *id).unwrap();
2053 let snapshot = excerpt.buffer.read(cx).snapshot();
2054 let mut point_range = excerpt.range.to_point(&snapshot);
2055 point_range.start = Point::new(point_range.start.row.saturating_sub(line_count), 0);
2056 point_range.end =
2057 snapshot.clip_point(Point::new(point_range.end.row + line_count, 0), Bias::Left);
2058 point_range.end.column = snapshot.line_len(point_range.end.row);
2059 excerpt.range =
2060 snapshot.anchor_before(point_range.start)..snapshot.anchor_after(point_range.end);
2061 }
2062 }
2063
2064 fn remove_excerpt(&mut self, id: ExcerptId, cx: &App) {
2065 let ix = self
2066 .excerpts
2067 .iter()
2068 .position(|excerpt| excerpt.id == id)
2069 .unwrap();
2070 let excerpt = self.excerpts.remove(ix);
2071 let buffer = excerpt.buffer.read(cx);
2072 log::info!(
2073 "Removing excerpt {}: {:?}",
2074 ix,
2075 buffer
2076 .text_for_range(excerpt.range.to_offset(buffer))
2077 .collect::<String>(),
2078 );
2079 }
2080
2081 fn insert_excerpt_after(
2082 &mut self,
2083 prev_id: ExcerptId,
2084 new_excerpt_id: ExcerptId,
2085 (buffer_handle, anchor_range): (Entity<Buffer>, Range<text::Anchor>),
2086 ) {
2087 let excerpt_ix = if prev_id == ExcerptId::max() {
2088 self.excerpts.len()
2089 } else {
2090 self.excerpts
2091 .iter()
2092 .position(|excerpt| excerpt.id == prev_id)
2093 .unwrap()
2094 + 1
2095 };
2096 self.excerpts.insert(
2097 excerpt_ix,
2098 ReferenceExcerpt {
2099 id: new_excerpt_id,
2100 buffer: buffer_handle,
2101 range: anchor_range,
2102 expanded_diff_hunks: Vec::new(),
2103 },
2104 );
2105 }
2106
2107 fn expand_diff_hunks(&mut self, excerpt_id: ExcerptId, range: Range<text::Anchor>, cx: &App) {
2108 let excerpt = self
2109 .excerpts
2110 .iter_mut()
2111 .find(|e| e.id == excerpt_id)
2112 .unwrap();
2113 let buffer = excerpt.buffer.read(cx).snapshot();
2114 let buffer_id = buffer.remote_id();
2115 let Some(change_set) = self.change_sets.get(&buffer_id) else {
2116 return;
2117 };
2118 let diff = change_set.read(cx).diff_to_buffer.clone();
2119 let excerpt_range = excerpt.range.to_offset(&buffer);
2120 if excerpt_range.is_empty() {
2121 return;
2122 }
2123 for hunk in diff.hunks_intersecting_range(range, &buffer) {
2124 let hunk_range = hunk.buffer_range.to_offset(&buffer);
2125 let hunk_precedes_excerpt = hunk
2126 .buffer_range
2127 .end
2128 .cmp(&excerpt.range.start, &buffer)
2129 .is_lt();
2130 let hunk_follows_excerpt = hunk
2131 .buffer_range
2132 .start
2133 .cmp(&excerpt.range.end, &buffer)
2134 .is_ge();
2135 if hunk_precedes_excerpt || hunk_follows_excerpt {
2136 continue;
2137 }
2138
2139 if let Err(ix) = excerpt
2140 .expanded_diff_hunks
2141 .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
2142 {
2143 log::info!(
2144 "expanding diff hunk {:?}. excerpt: {:?}",
2145 hunk_range,
2146 excerpt_range
2147 );
2148 excerpt
2149 .expanded_diff_hunks
2150 .insert(ix, hunk.buffer_range.start);
2151 }
2152 }
2153 }
2154
2155 fn expected_content(&self, cx: &App) -> (String, Vec<RowInfo>, HashSet<MultiBufferRow>) {
2156 let mut text = String::new();
2157 let mut regions = Vec::<ReferenceRegion>::new();
2158 let mut excerpt_boundary_rows = HashSet::default();
2159 for excerpt in &self.excerpts {
2160 excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
2161 let buffer = excerpt.buffer.read(cx);
2162 let buffer_range = excerpt.range.to_offset(buffer);
2163 let change_set = self.change_sets.get(&buffer.remote_id()).unwrap().read(cx);
2164 let diff = change_set.diff_to_buffer.clone();
2165 let base_buffer = change_set.base_text.as_ref().unwrap();
2166
2167 let mut offset = buffer_range.start;
2168 let mut hunks = diff
2169 .hunks_intersecting_range(excerpt.range.clone(), buffer)
2170 .peekable();
2171
2172 while let Some(hunk) = hunks.next() {
2173 if !hunk.buffer_range.start.is_valid(&buffer) {
2174 continue;
2175 }
2176
2177 // Ignore hunks that are outside the excerpt range.
2178 let mut hunk_range = hunk.buffer_range.to_offset(buffer);
2179 hunk_range.end = hunk_range.end.min(buffer_range.end);
2180 if hunk_range.start > buffer_range.end
2181 || hunk_range.end < buffer_range.start
2182 || buffer_range.is_empty()
2183 {
2184 continue;
2185 }
2186
2187 if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| {
2188 expanded_anchor.to_offset(&buffer).max(buffer_range.start)
2189 == hunk_range.start.max(buffer_range.start)
2190 }) {
2191 continue;
2192 }
2193
2194 if hunk_range.start >= offset {
2195 // Add the buffer text before the hunk
2196 let len = text.len();
2197 text.extend(buffer.text_for_range(offset..hunk_range.start));
2198 regions.push(ReferenceRegion {
2199 range: len..text.len(),
2200 buffer_start: Some(buffer.offset_to_point(offset)),
2201 status: None,
2202 });
2203
2204 // Add the deleted text for the hunk.
2205 if !hunk.diff_base_byte_range.is_empty() {
2206 let mut base_text = base_buffer
2207 .text_for_range(hunk.diff_base_byte_range.clone())
2208 .collect::<String>();
2209 if !base_text.ends_with('\n') {
2210 base_text.push('\n');
2211 }
2212 let len = text.len();
2213 text.push_str(&base_text);
2214 regions.push(ReferenceRegion {
2215 range: len..text.len(),
2216 buffer_start: Some(
2217 base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
2218 ),
2219 status: Some(DiffHunkStatus::Removed),
2220 });
2221 }
2222
2223 offset = hunk_range.start;
2224 }
2225
2226 // Add the inserted text for the hunk.
2227 if hunk_range.end > offset {
2228 let len = text.len();
2229 text.extend(buffer.text_for_range(offset..hunk_range.end));
2230 regions.push(ReferenceRegion {
2231 range: len..text.len(),
2232 buffer_start: Some(buffer.offset_to_point(offset)),
2233 status: Some(DiffHunkStatus::Added),
2234 });
2235 offset = hunk_range.end;
2236 }
2237 }
2238
2239 // Add the buffer text for the rest of the excerpt.
2240 let len = text.len();
2241 text.extend(buffer.text_for_range(offset..buffer_range.end));
2242 text.push('\n');
2243 regions.push(ReferenceRegion {
2244 range: len..text.len(),
2245 buffer_start: Some(buffer.offset_to_point(offset)),
2246 status: None,
2247 });
2248 }
2249
2250 // Remove final trailing newline.
2251 if self.excerpts.is_empty() {
2252 regions.push(ReferenceRegion {
2253 range: 0..1,
2254 buffer_start: Some(Point::new(0, 0)),
2255 status: None,
2256 });
2257 } else {
2258 text.pop();
2259 }
2260
2261 // Retrieve the row info using the region that contains
2262 // the start of each multi-buffer line.
2263 let mut ix = 0;
2264 let row_infos = text
2265 .split('\n')
2266 .map(|line| {
2267 let row_info = regions
2268 .iter()
2269 .find(|region| region.range.contains(&ix))
2270 .map_or(RowInfo::default(), |region| {
2271 let buffer_row = region.buffer_start.map(|start_point| {
2272 start_point.row
2273 + text[region.range.start..ix].matches('\n').count() as u32
2274 });
2275 RowInfo {
2276 diff_status: region.status,
2277 buffer_row,
2278 multibuffer_row: Some(MultiBufferRow(
2279 text[..ix].matches('\n').count() as u32
2280 )),
2281 }
2282 });
2283 ix += line.len() + 1;
2284 row_info
2285 })
2286 .collect();
2287
2288 (text, row_infos, excerpt_boundary_rows)
2289 }
2290
2291 fn diffs_updated(&mut self, cx: &App) {
2292 for excerpt in &mut self.excerpts {
2293 let buffer = excerpt.buffer.read(cx).snapshot();
2294 let excerpt_range = excerpt.range.to_offset(&buffer);
2295 let buffer_id = buffer.remote_id();
2296 let diff = &self
2297 .change_sets
2298 .get(&buffer_id)
2299 .unwrap()
2300 .read(cx)
2301 .diff_to_buffer;
2302 let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer).peekable();
2303 excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
2304 if !hunk_anchor.is_valid(&buffer) {
2305 return false;
2306 }
2307 while let Some(hunk) = hunks.peek() {
2308 match hunk.buffer_range.start.cmp(&hunk_anchor, &buffer) {
2309 cmp::Ordering::Less => {
2310 hunks.next();
2311 }
2312 cmp::Ordering::Equal => {
2313 let hunk_range = hunk.buffer_range.to_offset(&buffer);
2314 return hunk_range.end >= excerpt_range.start
2315 && hunk_range.start <= excerpt_range.end;
2316 }
2317 cmp::Ordering::Greater => break,
2318 }
2319 }
2320 false
2321 });
2322 }
2323 }
2324
2325 fn add_change_set(&mut self, change_set: Entity<BufferChangeSet>, cx: &mut App) {
2326 let buffer_id = change_set.read(cx).buffer_id;
2327 self.change_sets.insert(buffer_id, change_set);
2328 }
2329}
2330
2331#[gpui::test(iterations = 100)]
2332async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
2333 let operations = env::var("OPERATIONS")
2334 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2335 .unwrap_or(10);
2336
2337 let mut buffers: Vec<Entity<Buffer>> = Vec::new();
2338 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2339 let mut reference = ReferenceMultibuffer::default();
2340 let mut anchors = Vec::new();
2341 let mut old_versions = Vec::new();
2342 let mut needs_diff_calculation = false;
2343
2344 for _ in 0..operations {
2345 match rng.gen_range(0..100) {
2346 0..=14 if !buffers.is_empty() => {
2347 let buffer = buffers.choose(&mut rng).unwrap();
2348 buffer.update(cx, |buf, cx| {
2349 let edit_count = rng.gen_range(1..5);
2350 buf.randomly_edit(&mut rng, edit_count, cx);
2351 needs_diff_calculation = true;
2352 });
2353 cx.update(|cx| reference.diffs_updated(cx));
2354 }
2355 15..=19 if !reference.excerpts.is_empty() => {
2356 multibuffer.update(cx, |multibuffer, cx| {
2357 let ids = multibuffer.excerpt_ids();
2358 let mut excerpts = HashSet::default();
2359 for _ in 0..rng.gen_range(0..ids.len()) {
2360 excerpts.extend(ids.choose(&mut rng).copied());
2361 }
2362
2363 let line_count = rng.gen_range(0..5);
2364
2365 let excerpt_ixs = excerpts
2366 .iter()
2367 .map(|id| reference.excerpts.iter().position(|e| e.id == *id).unwrap())
2368 .collect::<Vec<_>>();
2369 log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
2370 multibuffer.expand_excerpts(
2371 excerpts.iter().cloned(),
2372 line_count,
2373 ExpandExcerptDirection::UpAndDown,
2374 cx,
2375 );
2376
2377 reference.expand_excerpts(&excerpts, line_count, cx);
2378 });
2379 }
2380 20..=29 if !reference.excerpts.is_empty() => {
2381 let mut ids_to_remove = vec![];
2382 for _ in 0..rng.gen_range(1..=3) {
2383 let Some(excerpt) = reference.excerpts.choose(&mut rng) else {
2384 break;
2385 };
2386 let id = excerpt.id;
2387 cx.update(|cx| reference.remove_excerpt(id, cx));
2388 ids_to_remove.push(id);
2389 }
2390 let snapshot =
2391 multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2392 ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
2393 drop(snapshot);
2394 multibuffer.update(cx, |multibuffer, cx| {
2395 multibuffer.remove_excerpts(ids_to_remove, cx)
2396 });
2397 }
2398 30..=39 if !reference.excerpts.is_empty() => {
2399 let multibuffer =
2400 multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2401 let offset =
2402 multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
2403 let bias = if rng.gen() { Bias::Left } else { Bias::Right };
2404 log::info!("Creating anchor at {} with bias {:?}", offset, bias);
2405 anchors.push(multibuffer.anchor_at(offset, bias));
2406 anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
2407 }
2408 40..=44 if !anchors.is_empty() => {
2409 let multibuffer =
2410 multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2411 let prev_len = anchors.len();
2412 anchors = multibuffer
2413 .refresh_anchors(&anchors)
2414 .into_iter()
2415 .map(|a| a.1)
2416 .collect();
2417
2418 // Ensure the newly-refreshed anchors point to a valid excerpt and don't
2419 // overshoot its boundaries.
2420 assert_eq!(anchors.len(), prev_len);
2421 for anchor in &anchors {
2422 if anchor.excerpt_id == ExcerptId::min()
2423 || anchor.excerpt_id == ExcerptId::max()
2424 {
2425 continue;
2426 }
2427
2428 let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
2429 assert_eq!(excerpt.id, anchor.excerpt_id);
2430 assert!(excerpt.contains(anchor));
2431 }
2432 }
2433 45..=55 if !reference.excerpts.is_empty() => {
2434 multibuffer.update(cx, |multibuffer, cx| {
2435 let snapshot = multibuffer.snapshot(cx);
2436 let excerpt_ix = rng.gen_range(0..reference.excerpts.len());
2437 let excerpt = &reference.excerpts[excerpt_ix];
2438 let start = excerpt.range.start;
2439 let end = excerpt.range.end;
2440 let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
2441 ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
2442
2443 log::info!("expanding diff hunks for excerpt {:?}", excerpt_ix);
2444 reference.expand_diff_hunks(excerpt.id, start..end, cx);
2445 multibuffer.expand_diff_hunks(vec![range], cx);
2446 });
2447 }
2448 56..=85 if needs_diff_calculation => {
2449 multibuffer.update(cx, |multibuffer, cx| {
2450 for buffer in multibuffer.all_buffers() {
2451 let snapshot = buffer.read(cx).snapshot();
2452 let _ = multibuffer
2453 .change_set_for(snapshot.remote_id())
2454 .unwrap()
2455 .update(cx, |change_set, cx| {
2456 log::info!(
2457 "recalculating diff for buffer {:?}",
2458 snapshot.remote_id(),
2459 );
2460 change_set.recalculate_diff(snapshot.text, cx)
2461 });
2462 }
2463 reference.diffs_updated(cx);
2464 needs_diff_calculation = false;
2465 });
2466 }
2467 _ => {
2468 let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
2469 let base_text = util::RandomCharIter::new(&mut rng)
2470 .take(256)
2471 .collect::<String>();
2472
2473 let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
2474 let change_set = cx.new(|cx| BufferChangeSet::new(&buffer, cx));
2475 change_set
2476 .update(cx, |change_set, cx| {
2477 let snapshot = buffer.read(cx).snapshot();
2478 change_set.set_base_text(base_text, snapshot.text, cx)
2479 })
2480 .await
2481 .unwrap();
2482
2483 multibuffer.update(cx, |multibuffer, cx| {
2484 reference.add_change_set(change_set.clone(), cx);
2485 multibuffer.add_change_set(change_set, cx)
2486 });
2487 buffers.push(buffer);
2488 buffers.last().unwrap()
2489 } else {
2490 buffers.choose(&mut rng).unwrap()
2491 };
2492
2493 let prev_excerpt_ix = rng.gen_range(0..=reference.excerpts.len());
2494 let prev_excerpt_id = reference
2495 .excerpts
2496 .get(prev_excerpt_ix)
2497 .map_or(ExcerptId::max(), |e| e.id);
2498 let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
2499
2500 let (range, anchor_range) = buffer_handle.read_with(cx, |buffer, _| {
2501 let end_row = rng.gen_range(0..=buffer.max_point().row);
2502 let start_row = rng.gen_range(0..=end_row);
2503 let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
2504 let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
2505 let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
2506
2507 log::info!(
2508 "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
2509 excerpt_ix,
2510 reference.excerpts.len(),
2511 buffer.remote_id(),
2512 buffer.text(),
2513 start_ix..end_ix,
2514 &buffer.text()[start_ix..end_ix]
2515 );
2516
2517 (start_ix..end_ix, anchor_range)
2518 });
2519
2520 let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
2521 multibuffer
2522 .insert_excerpts_after(
2523 prev_excerpt_id,
2524 buffer_handle.clone(),
2525 [ExcerptRange {
2526 context: range,
2527 primary: None,
2528 }],
2529 cx,
2530 )
2531 .pop()
2532 .unwrap()
2533 });
2534
2535 reference.insert_excerpt_after(
2536 prev_excerpt_id,
2537 excerpt_id,
2538 (buffer_handle.clone(), anchor_range),
2539 );
2540 }
2541 }
2542
2543 if rng.gen_bool(0.3) {
2544 multibuffer.update(cx, |multibuffer, cx| {
2545 old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
2546 })
2547 }
2548
2549 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2550 let actual_text = snapshot.text();
2551 let actual_boundary_rows = snapshot
2552 .excerpt_boundaries_in_range(0..)
2553 .filter_map(|b| if b.next.is_some() { Some(b.row) } else { None })
2554 .collect::<HashSet<_>>();
2555 let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2556 let actual_diff = format_diff(&actual_text, &actual_row_infos, &actual_boundary_rows);
2557
2558 let (expected_text, expected_row_infos, expected_boundary_rows) =
2559 cx.update(|cx| reference.expected_content(cx));
2560 let expected_diff =
2561 format_diff(&expected_text, &expected_row_infos, &expected_boundary_rows);
2562
2563 log::info!("Multibuffer content:\n{}", actual_diff);
2564
2565 assert_eq!(
2566 actual_row_infos.len(),
2567 actual_text.split('\n').count(),
2568 "line count: {}",
2569 actual_text.split('\n').count()
2570 );
2571 pretty_assertions::assert_eq!(actual_diff, expected_diff);
2572 pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
2573 pretty_assertions::assert_eq!(actual_text, expected_text);
2574
2575 for _ in 0..5 {
2576 let start_row = rng.gen_range(0..=expected_row_infos.len());
2577 assert_eq!(
2578 snapshot
2579 .row_infos(MultiBufferRow(start_row as u32))
2580 .collect::<Vec<_>>(),
2581 &expected_row_infos[start_row..],
2582 "buffer_rows({})",
2583 start_row
2584 );
2585 }
2586
2587 assert_eq!(
2588 snapshot.widest_line_number(),
2589 expected_row_infos
2590 .into_iter()
2591 .filter_map(
2592 |info| if info.diff_status == Some(DiffHunkStatus::Removed) {
2593 None
2594 } else {
2595 info.buffer_row
2596 }
2597 )
2598 .max()
2599 .unwrap()
2600 + 1
2601 );
2602
2603 assert_consistent_line_numbers(&snapshot);
2604 assert_position_translation(&snapshot);
2605
2606 for (row, line) in expected_text.split('\n').enumerate() {
2607 assert_eq!(
2608 snapshot.line_len(MultiBufferRow(row as u32)),
2609 line.len() as u32,
2610 "line_len({}).",
2611 row
2612 );
2613 }
2614
2615 let text_rope = Rope::from(expected_text.as_str());
2616 for _ in 0..10 {
2617 let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2618 let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
2619
2620 let text_for_range = snapshot
2621 .text_for_range(start_ix..end_ix)
2622 .collect::<String>();
2623 assert_eq!(
2624 text_for_range,
2625 &expected_text[start_ix..end_ix],
2626 "incorrect text for range {:?}",
2627 start_ix..end_ix
2628 );
2629
2630 let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
2631 assert_eq!(
2632 snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
2633 expected_summary,
2634 "incorrect summary for range {:?}",
2635 start_ix..end_ix
2636 );
2637 }
2638
2639 // Anchor resolution
2640 let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
2641 assert_eq!(anchors.len(), summaries.len());
2642 for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
2643 assert!(resolved_offset <= snapshot.len());
2644 assert_eq!(
2645 snapshot.summary_for_anchor::<usize>(anchor),
2646 resolved_offset,
2647 "anchor: {:?}",
2648 anchor
2649 );
2650 }
2651
2652 for _ in 0..10 {
2653 let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
2654 assert_eq!(
2655 snapshot.reversed_chars_at(end_ix).collect::<String>(),
2656 expected_text[..end_ix].chars().rev().collect::<String>(),
2657 );
2658 }
2659
2660 for _ in 0..10 {
2661 let end_ix = rng.gen_range(0..=text_rope.len());
2662 let start_ix = rng.gen_range(0..=end_ix);
2663 assert_eq!(
2664 snapshot
2665 .bytes_in_range(start_ix..end_ix)
2666 .flatten()
2667 .copied()
2668 .collect::<Vec<_>>(),
2669 expected_text.as_bytes()[start_ix..end_ix].to_vec(),
2670 "bytes_in_range({:?})",
2671 start_ix..end_ix,
2672 );
2673 }
2674 }
2675
2676 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2677 for (old_snapshot, subscription) in old_versions {
2678 let edits = subscription.consume().into_inner();
2679
2680 log::info!(
2681 "applying subscription edits to old text: {:?}: {:?}",
2682 old_snapshot.text(),
2683 edits,
2684 );
2685
2686 let mut text = old_snapshot.text();
2687 for edit in edits {
2688 let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
2689 text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
2690 }
2691 assert_eq!(text.to_string(), snapshot.text());
2692 }
2693}
2694
2695#[gpui::test]
2696fn test_history(cx: &mut App) {
2697 let test_settings = SettingsStore::test(cx);
2698 cx.set_global(test_settings);
2699 let group_interval: Duration = Duration::from_millis(1);
2700 let buffer_1 = cx.new(|cx| {
2701 let mut buf = Buffer::local("1234", cx);
2702 buf.set_group_interval(group_interval);
2703 buf
2704 });
2705 let buffer_2 = cx.new(|cx| {
2706 let mut buf = Buffer::local("5678", cx);
2707 buf.set_group_interval(group_interval);
2708 buf
2709 });
2710 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2711 multibuffer.update(cx, |this, _| {
2712 this.history.group_interval = group_interval;
2713 });
2714 multibuffer.update(cx, |multibuffer, cx| {
2715 multibuffer.push_excerpts(
2716 buffer_1.clone(),
2717 [ExcerptRange {
2718 context: 0..buffer_1.read(cx).len(),
2719 primary: None,
2720 }],
2721 cx,
2722 );
2723 multibuffer.push_excerpts(
2724 buffer_2.clone(),
2725 [ExcerptRange {
2726 context: 0..buffer_2.read(cx).len(),
2727 primary: None,
2728 }],
2729 cx,
2730 );
2731 });
2732
2733 let mut now = Instant::now();
2734
2735 multibuffer.update(cx, |multibuffer, cx| {
2736 let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
2737 multibuffer.edit(
2738 [
2739 (Point::new(0, 0)..Point::new(0, 0), "A"),
2740 (Point::new(1, 0)..Point::new(1, 0), "A"),
2741 ],
2742 None,
2743 cx,
2744 );
2745 multibuffer.edit(
2746 [
2747 (Point::new(0, 1)..Point::new(0, 1), "B"),
2748 (Point::new(1, 1)..Point::new(1, 1), "B"),
2749 ],
2750 None,
2751 cx,
2752 );
2753 multibuffer.end_transaction_at(now, cx);
2754 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2755
2756 // Verify edited ranges for transaction 1
2757 assert_eq!(
2758 multibuffer.edited_ranges_for_transaction(transaction_1, cx),
2759 &[
2760 Point::new(0, 0)..Point::new(0, 2),
2761 Point::new(1, 0)..Point::new(1, 2)
2762 ]
2763 );
2764
2765 // Edit buffer 1 through the multibuffer
2766 now += 2 * group_interval;
2767 multibuffer.start_transaction_at(now, cx);
2768 multibuffer.edit([(2..2, "C")], None, cx);
2769 multibuffer.end_transaction_at(now, cx);
2770 assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
2771
2772 // Edit buffer 1 independently
2773 buffer_1.update(cx, |buffer_1, cx| {
2774 buffer_1.start_transaction_at(now);
2775 buffer_1.edit([(3..3, "D")], None, cx);
2776 buffer_1.end_transaction_at(now, cx);
2777
2778 now += 2 * group_interval;
2779 buffer_1.start_transaction_at(now);
2780 buffer_1.edit([(4..4, "E")], None, cx);
2781 buffer_1.end_transaction_at(now, cx);
2782 });
2783 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2784
2785 // An undo in the multibuffer undoes the multibuffer transaction
2786 // and also any individual buffer edits that have occurred since
2787 // that transaction.
2788 multibuffer.undo(cx);
2789 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2790
2791 multibuffer.undo(cx);
2792 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2793
2794 multibuffer.redo(cx);
2795 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2796
2797 multibuffer.redo(cx);
2798 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
2799
2800 // Undo buffer 2 independently.
2801 buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
2802 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
2803
2804 // An undo in the multibuffer undoes the components of the
2805 // the last multibuffer transaction that are not already undone.
2806 multibuffer.undo(cx);
2807 assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
2808
2809 multibuffer.undo(cx);
2810 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2811
2812 multibuffer.redo(cx);
2813 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
2814
2815 buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
2816 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2817
2818 // Redo stack gets cleared after an edit.
2819 now += 2 * group_interval;
2820 multibuffer.start_transaction_at(now, cx);
2821 multibuffer.edit([(0..0, "X")], None, cx);
2822 multibuffer.end_transaction_at(now, cx);
2823 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2824 multibuffer.redo(cx);
2825 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2826 multibuffer.undo(cx);
2827 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
2828 multibuffer.undo(cx);
2829 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2830
2831 // Transactions can be grouped manually.
2832 multibuffer.redo(cx);
2833 multibuffer.redo(cx);
2834 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2835 multibuffer.group_until_transaction(transaction_1, cx);
2836 multibuffer.undo(cx);
2837 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
2838 multibuffer.redo(cx);
2839 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
2840 });
2841}
2842
2843#[gpui::test]
2844async fn test_enclosing_indent(cx: &mut TestAppContext) {
2845 async fn enclosing_indent(
2846 text: &str,
2847 buffer_row: u32,
2848 cx: &mut TestAppContext,
2849 ) -> Option<(Range<u32>, LineIndent)> {
2850 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2851 let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
2852 let (range, indent) = snapshot
2853 .enclosing_indent(MultiBufferRow(buffer_row))
2854 .await?;
2855 Some((range.start.0..range.end.0, indent))
2856 }
2857
2858 assert_eq!(
2859 enclosing_indent(
2860 indoc!(
2861 "
2862 fn b() {
2863 if c {
2864 let d = 2;
2865 }
2866 }
2867 "
2868 ),
2869 1,
2870 cx,
2871 )
2872 .await,
2873 Some((
2874 1..2,
2875 LineIndent {
2876 tabs: 0,
2877 spaces: 4,
2878 line_blank: false,
2879 }
2880 ))
2881 );
2882
2883 assert_eq!(
2884 enclosing_indent(
2885 indoc!(
2886 "
2887 fn b() {
2888 if c {
2889 let d = 2;
2890 }
2891 }
2892 "
2893 ),
2894 2,
2895 cx,
2896 )
2897 .await,
2898 Some((
2899 1..2,
2900 LineIndent {
2901 tabs: 0,
2902 spaces: 4,
2903 line_blank: false,
2904 }
2905 ))
2906 );
2907
2908 assert_eq!(
2909 enclosing_indent(
2910 indoc!(
2911 "
2912 fn b() {
2913 if c {
2914 let d = 2;
2915
2916 let e = 5;
2917 }
2918 }
2919 "
2920 ),
2921 3,
2922 cx,
2923 )
2924 .await,
2925 Some((
2926 1..4,
2927 LineIndent {
2928 tabs: 0,
2929 spaces: 4,
2930 line_blank: false,
2931 }
2932 ))
2933 );
2934}
2935
2936fn format_diff(
2937 text: &str,
2938 row_infos: &Vec<RowInfo>,
2939 boundary_rows: &HashSet<MultiBufferRow>,
2940) -> String {
2941 let has_diff = row_infos.iter().any(|info| info.diff_status.is_some());
2942 text.split('\n')
2943 .enumerate()
2944 .zip(row_infos)
2945 .map(|((ix, line), info)| {
2946 let marker = match info.diff_status {
2947 Some(DiffHunkStatus::Added) => "+ ",
2948 Some(DiffHunkStatus::Removed) => "- ",
2949 Some(DiffHunkStatus::Modified) => unreachable!(),
2950 None => {
2951 if has_diff && !line.is_empty() {
2952 " "
2953 } else {
2954 ""
2955 }
2956 }
2957 };
2958 let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
2959 if has_diff {
2960 " ----------\n"
2961 } else {
2962 "---------\n"
2963 }
2964 } else {
2965 ""
2966 };
2967 format!("{boundary_row}{marker}{line}")
2968 })
2969 .collect::<Vec<_>>()
2970 .join("\n")
2971}
2972
2973#[track_caller]
2974fn assert_excerpts_match(
2975 multibuffer: &Entity<MultiBuffer>,
2976 cx: &mut TestAppContext,
2977 expected: &str,
2978) {
2979 let mut output = String::new();
2980 multibuffer.read_with(cx, |multibuffer, cx| {
2981 for (_, buffer, range) in multibuffer.snapshot(cx).excerpts() {
2982 output.push_str("-----\n");
2983 output.extend(buffer.text_for_range(range.context));
2984 if !output.ends_with('\n') {
2985 output.push('\n');
2986 }
2987 }
2988 });
2989 assert_eq!(output, expected);
2990}
2991
2992#[track_caller]
2993fn assert_new_snapshot(
2994 multibuffer: &Entity<MultiBuffer>,
2995 snapshot: &mut MultiBufferSnapshot,
2996 subscription: &mut Subscription,
2997 cx: &mut TestAppContext,
2998 expected_diff: &str,
2999) {
3000 let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3001 let actual_text = new_snapshot.text();
3002 let line_infos = new_snapshot
3003 .row_infos(MultiBufferRow(0))
3004 .collect::<Vec<_>>();
3005 let actual_diff = format_diff(&actual_text, &line_infos, &Default::default());
3006 pretty_assertions::assert_eq!(actual_diff, expected_diff);
3007 check_edits(
3008 snapshot,
3009 &new_snapshot,
3010 &subscription.consume().into_inner(),
3011 );
3012 *snapshot = new_snapshot;
3013}
3014
3015#[track_caller]
3016fn check_edits(
3017 old_snapshot: &MultiBufferSnapshot,
3018 new_snapshot: &MultiBufferSnapshot,
3019 edits: &[Edit<usize>],
3020) {
3021 let mut text = old_snapshot.text();
3022 let new_text = new_snapshot.text();
3023 for edit in edits.iter().rev() {
3024 if !text.is_char_boundary(edit.old.start)
3025 || !text.is_char_boundary(edit.old.end)
3026 || !new_text.is_char_boundary(edit.new.start)
3027 || !new_text.is_char_boundary(edit.new.end)
3028 {
3029 panic!(
3030 "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
3031 edits, text, new_text
3032 );
3033 }
3034
3035 text.replace_range(
3036 edit.old.start..edit.old.end,
3037 &new_text[edit.new.start..edit.new.end],
3038 );
3039 }
3040
3041 pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
3042}
3043
3044#[track_caller]
3045fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
3046 let full_text = snapshot.text();
3047 for ix in 0..full_text.len() {
3048 let mut chunks = snapshot.chunks(0..snapshot.len(), false);
3049 chunks.seek(ix..snapshot.len());
3050 let tail = chunks.map(|chunk| chunk.text).collect::<String>();
3051 assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
3052 }
3053}
3054
3055#[track_caller]
3056fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
3057 let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3058 for start_row in 1..all_line_numbers.len() {
3059 let line_numbers = snapshot
3060 .row_infos(MultiBufferRow(start_row as u32))
3061 .collect::<Vec<_>>();
3062 assert_eq!(
3063 line_numbers,
3064 all_line_numbers[start_row..],
3065 "start_row: {start_row}"
3066 );
3067 }
3068}
3069
3070#[track_caller]
3071fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
3072 let text = Rope::from(snapshot.text());
3073
3074 let mut left_anchors = Vec::new();
3075 let mut right_anchors = Vec::new();
3076 let mut offsets = Vec::new();
3077 let mut points = Vec::new();
3078 for offset in 0..=text.len() + 1 {
3079 let clipped_left = snapshot.clip_offset(offset, Bias::Left);
3080 let clipped_right = snapshot.clip_offset(offset, Bias::Right);
3081 assert_eq!(
3082 clipped_left,
3083 text.clip_offset(offset, Bias::Left),
3084 "clip_offset({offset:?}, Left)"
3085 );
3086 assert_eq!(
3087 clipped_right,
3088 text.clip_offset(offset, Bias::Right),
3089 "clip_offset({offset:?}, Right)"
3090 );
3091 assert_eq!(
3092 snapshot.offset_to_point(clipped_left),
3093 text.offset_to_point(clipped_left),
3094 "offset_to_point({clipped_left})"
3095 );
3096 assert_eq!(
3097 snapshot.offset_to_point(clipped_right),
3098 text.offset_to_point(clipped_right),
3099 "offset_to_point({clipped_right})"
3100 );
3101 let anchor_after = snapshot.anchor_after(clipped_left);
3102 assert_eq!(
3103 anchor_after.to_offset(snapshot),
3104 clipped_left,
3105 "anchor_after({clipped_left}).to_offset {anchor_after:?}"
3106 );
3107 let anchor_before = snapshot.anchor_before(clipped_left);
3108 assert_eq!(
3109 anchor_before.to_offset(snapshot),
3110 clipped_left,
3111 "anchor_before({clipped_left}).to_offset"
3112 );
3113 left_anchors.push(anchor_before);
3114 right_anchors.push(anchor_after);
3115 offsets.push(clipped_left);
3116 points.push(text.offset_to_point(clipped_left));
3117 }
3118
3119 for row in 0..text.max_point().row {
3120 for column in 0..text.line_len(row) + 1 {
3121 let point = Point { row, column };
3122 let clipped_left = snapshot.clip_point(point, Bias::Left);
3123 let clipped_right = snapshot.clip_point(point, Bias::Right);
3124 assert_eq!(
3125 clipped_left,
3126 text.clip_point(point, Bias::Left),
3127 "clip_point({point:?}, Left)"
3128 );
3129 assert_eq!(
3130 clipped_right,
3131 text.clip_point(point, Bias::Right),
3132 "clip_point({point:?}, Right)"
3133 );
3134 assert_eq!(
3135 snapshot.point_to_offset(clipped_left),
3136 text.point_to_offset(clipped_left),
3137 "point_to_offset({clipped_left:?})"
3138 );
3139 assert_eq!(
3140 snapshot.point_to_offset(clipped_right),
3141 text.point_to_offset(clipped_right),
3142 "point_to_offset({clipped_right:?})"
3143 );
3144 }
3145 }
3146
3147 assert_eq!(
3148 snapshot.summaries_for_anchors::<usize, _>(&left_anchors),
3149 offsets,
3150 "left_anchors <-> offsets"
3151 );
3152 assert_eq!(
3153 snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
3154 points,
3155 "left_anchors <-> points"
3156 );
3157 assert_eq!(
3158 snapshot.summaries_for_anchors::<usize, _>(&right_anchors),
3159 offsets,
3160 "right_anchors <-> offsets"
3161 );
3162 assert_eq!(
3163 snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
3164 points,
3165 "right_anchors <-> points"
3166 );
3167
3168 for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
3169 for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
3170 if ix > 0 {
3171 if *offset == 252 {
3172 if offset > &offsets[ix - 1] {
3173 let prev_anchor = left_anchors[ix - 1];
3174 assert!(
3175 anchor.cmp(&prev_anchor, snapshot).is_gt(),
3176 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
3177 offsets[ix],
3178 offsets[ix - 1],
3179 );
3180 assert!(
3181 prev_anchor.cmp(&anchor, snapshot).is_lt(),
3182 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
3183 offsets[ix - 1],
3184 offsets[ix],
3185 );
3186 }
3187 }
3188 }
3189 }
3190 }
3191}
3192
3193fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
3194 let max_row = snapshot.max_point().row;
3195 let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
3196 let text = text::Buffer::new(0, buffer_id, snapshot.text());
3197 let mut line_indents = text
3198 .line_indents_in_row_range(0..max_row + 1)
3199 .collect::<Vec<_>>();
3200 for start_row in 0..snapshot.max_point().row {
3201 pretty_assertions::assert_eq!(
3202 snapshot
3203 .line_indents(MultiBufferRow(start_row), |_| true)
3204 .map(|(row, indent, _)| (row.0, indent))
3205 .collect::<Vec<_>>(),
3206 &line_indents[(start_row as usize)..],
3207 "line_indents({start_row})"
3208 );
3209 }
3210
3211 line_indents.reverse();
3212 pretty_assertions::assert_eq!(
3213 snapshot
3214 .reversed_line_indents(MultiBufferRow(max_row), |_| true)
3215 .map(|(row, indent, _)| (row.0, indent))
3216 .collect::<Vec<_>>(),
3217 &line_indents[..],
3218 "reversed_line_indents({max_row})"
3219 );
3220}