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