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