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