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 std::time::{Duration, Instant};
11use util::RandomCharIter;
12use util::rel_path::rel_path;
13use util::test::sample_text;
14
15#[ctor::ctor]
16fn init_logger() {
17 zlog::init_test();
18}
19
20#[gpui::test]
21fn test_empty_singleton(cx: &mut App) {
22 let buffer = cx.new(|cx| Buffer::local("", cx));
23 let buffer_id = buffer.read(cx).remote_id();
24 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
25 let snapshot = multibuffer.read(cx).snapshot(cx);
26 assert_eq!(snapshot.text(), "");
27 assert_eq!(
28 snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
29 [RowInfo {
30 buffer_id: Some(buffer_id),
31 buffer_row: Some(0),
32 multibuffer_row: Some(MultiBufferRow(0)),
33 diff_status: None,
34 expand_info: None,
35 wrapped_buffer_row: None,
36 }]
37 );
38}
39
40#[gpui::test]
41fn test_singleton(cx: &mut App) {
42 let buffer = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
43 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
44
45 let snapshot = multibuffer.read(cx).snapshot(cx);
46 assert_eq!(snapshot.text(), buffer.read(cx).text());
47
48 assert_eq!(
49 snapshot
50 .row_infos(MultiBufferRow(0))
51 .map(|info| info.buffer_row)
52 .collect::<Vec<_>>(),
53 (0..buffer.read(cx).row_count())
54 .map(Some)
55 .collect::<Vec<_>>()
56 );
57 assert_consistent_line_numbers(&snapshot);
58
59 buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx));
60 let snapshot = multibuffer.read(cx).snapshot(cx);
61
62 assert_eq!(snapshot.text(), buffer.read(cx).text());
63 assert_eq!(
64 snapshot
65 .row_infos(MultiBufferRow(0))
66 .map(|info| info.buffer_row)
67 .collect::<Vec<_>>(),
68 (0..buffer.read(cx).row_count())
69 .map(Some)
70 .collect::<Vec<_>>()
71 );
72 assert_consistent_line_numbers(&snapshot);
73}
74
75#[gpui::test]
76fn test_buffer_point_to_anchor_at_end_of_singleton_buffer(cx: &mut App) {
77 let buffer = cx.new(|cx| Buffer::local("abc", cx));
78 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
79
80 let anchor = multibuffer
81 .read(cx)
82 .buffer_point_to_anchor(&buffer, Point::new(0, 3), cx)
83 .unwrap();
84 let (anchor, _) = multibuffer
85 .read(cx)
86 .snapshot(cx)
87 .anchor_to_buffer_anchor(anchor)
88 .unwrap();
89
90 assert_eq!(
91 anchor,
92 buffer.read(cx).snapshot().anchor_after(Point::new(0, 3)),
93 );
94}
95
96#[gpui::test]
97fn test_remote(cx: &mut App) {
98 let host_buffer = cx.new(|cx| Buffer::local("a", cx));
99 let guest_buffer = cx.new(|cx| {
100 let state = host_buffer.read(cx).to_proto(cx);
101 let ops = cx
102 .foreground_executor()
103 .block_on(host_buffer.read(cx).serialize_ops(None, cx));
104 let mut buffer =
105 Buffer::from_proto(ReplicaId::REMOTE_SERVER, Capability::ReadWrite, state, None)
106 .unwrap();
107 buffer.apply_ops(
108 ops.into_iter()
109 .map(|op| language::proto::deserialize_operation(op).unwrap()),
110 cx,
111 );
112 buffer
113 });
114 let multibuffer = cx.new(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx));
115 let snapshot = multibuffer.read(cx).snapshot(cx);
116 assert_eq!(snapshot.text(), "a");
117
118 guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx));
119 let snapshot = multibuffer.read(cx).snapshot(cx);
120 assert_eq!(snapshot.text(), "ab");
121
122 guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx));
123 let snapshot = multibuffer.read(cx).snapshot(cx);
124 assert_eq!(snapshot.text(), "abc");
125}
126
127#[gpui::test]
128fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
129 let buffer_1 = cx.new(|cx| Buffer::local(sample_text(7, 6, 'a'), cx));
130 let buffer_2 = cx.new(|cx| Buffer::local(sample_text(7, 6, 'g'), cx));
131 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
132
133 let events = Arc::new(RwLock::new(Vec::<Event>::new()));
134 multibuffer.update(cx, |_, cx| {
135 let events = events.clone();
136 cx.subscribe(&multibuffer, move |_, _, event, _| {
137 if let Event::Edited { .. } = event {
138 events.write().push(event.clone())
139 }
140 })
141 .detach();
142 });
143
144 let subscription = multibuffer.update(cx, |multibuffer, cx| {
145 let subscription = multibuffer.subscribe();
146 multibuffer.set_excerpt_ranges_for_path(
147 PathKey::sorted(0),
148 buffer_1.clone(),
149 &buffer_1.read(cx).snapshot(),
150 vec![ExcerptRange::new(Point::new(1, 2)..Point::new(2, 5))],
151 cx,
152 );
153 assert_eq!(
154 subscription.consume().into_inner(),
155 [Edit {
156 old: MultiBufferOffset(0)..MultiBufferOffset(0),
157 new: MultiBufferOffset(0)..MultiBufferOffset(10)
158 }]
159 );
160
161 multibuffer.set_excerpt_ranges_for_path(
162 PathKey::sorted(0),
163 buffer_1.clone(),
164 &buffer_1.read(cx).snapshot(),
165 vec![
166 ExcerptRange::new(Point::new(1, 2)..Point::new(2, 5)),
167 ExcerptRange::new(Point::new(5, 3)..Point::new(6, 4)),
168 ],
169 cx,
170 );
171 multibuffer.set_excerpt_ranges_for_path(
172 PathKey::sorted(1),
173 buffer_2.clone(),
174 &buffer_2.read(cx).snapshot(),
175 vec![ExcerptRange::new(Point::new(3, 1)..Point::new(3, 3))],
176 cx,
177 );
178 assert_eq!(
179 subscription.consume().into_inner(),
180 [Edit {
181 old: MultiBufferOffset(10)..MultiBufferOffset(10),
182 new: MultiBufferOffset(10)..MultiBufferOffset(22)
183 }]
184 );
185
186 subscription
187 });
188
189 // Adding excerpts emits an edited event.
190 assert_eq!(
191 events.read().as_slice(),
192 &[
193 Event::Edited {
194 edited_buffer: None,
195 is_local: true,
196 },
197 Event::Edited {
198 edited_buffer: None,
199 is_local: true,
200 },
201 Event::Edited {
202 edited_buffer: None,
203 is_local: true,
204 }
205 ]
206 );
207
208 let snapshot = multibuffer.read(cx).snapshot(cx);
209 assert_eq!(
210 snapshot.text(),
211 indoc!(
212 "
213 bbbb
214 ccccc
215 fff
216 gggg
217 jj"
218 ),
219 );
220 assert_eq!(
221 snapshot
222 .row_infos(MultiBufferRow(0))
223 .map(|info| info.buffer_row)
224 .collect::<Vec<_>>(),
225 [Some(1), Some(2), Some(5), Some(6), Some(3)]
226 );
227 assert_eq!(
228 snapshot
229 .row_infos(MultiBufferRow(2))
230 .map(|info| info.buffer_row)
231 .collect::<Vec<_>>(),
232 [Some(5), Some(6), Some(3)]
233 );
234 assert_eq!(
235 snapshot
236 .row_infos(MultiBufferRow(4))
237 .map(|info| info.buffer_row)
238 .collect::<Vec<_>>(),
239 [Some(3)]
240 );
241 assert!(
242 snapshot
243 .row_infos(MultiBufferRow(5))
244 .map(|info| info.buffer_row)
245 .collect::<Vec<_>>()
246 .is_empty()
247 );
248
249 assert_eq!(
250 boundaries_in_range(Point::new(0, 0)..Point::new(4, 2), &snapshot),
251 &[
252 (MultiBufferRow(0), "bbbb\nccccc".to_string(), true),
253 (MultiBufferRow(2), "fff\ngggg".to_string(), false),
254 (MultiBufferRow(4), "jj".to_string(), true),
255 ]
256 );
257 assert_eq!(
258 boundaries_in_range(Point::new(0, 0)..Point::new(2, 0), &snapshot),
259 &[(MultiBufferRow(0), "bbbb\nccccc".to_string(), true)]
260 );
261 assert_eq!(
262 boundaries_in_range(Point::new(1, 0)..Point::new(1, 5), &snapshot),
263 &[]
264 );
265 assert_eq!(
266 boundaries_in_range(Point::new(1, 0)..Point::new(2, 0), &snapshot),
267 &[]
268 );
269 assert_eq!(
270 boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
271 &[(MultiBufferRow(2), "fff\ngggg".to_string(), false)]
272 );
273 assert_eq!(
274 boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
275 &[(MultiBufferRow(2), "fff\ngggg".to_string(), false)]
276 );
277 assert_eq!(
278 boundaries_in_range(Point::new(2, 0)..Point::new(3, 0), &snapshot),
279 &[(MultiBufferRow(2), "fff\ngggg".to_string(), false)]
280 );
281 assert_eq!(
282 boundaries_in_range(Point::new(4, 0)..Point::new(4, 2), &snapshot),
283 &[(MultiBufferRow(4), "jj".to_string(), true)]
284 );
285 assert_eq!(
286 boundaries_in_range(Point::new(4, 2)..Point::new(4, 2), &snapshot),
287 &[]
288 );
289
290 buffer_1.update(cx, |buffer, cx| {
291 let text = "\n";
292 buffer.edit(
293 [
294 (Point::new(0, 0)..Point::new(0, 0), text),
295 (Point::new(2, 1)..Point::new(2, 3), text),
296 ],
297 None,
298 cx,
299 );
300 });
301
302 let snapshot = multibuffer.read(cx).snapshot(cx);
303 assert_eq!(
304 snapshot.text(),
305 concat!(
306 "bbbb\n", // Preserve newlines
307 "c\n", //
308 "cc\n", //
309 "fff\n", //
310 "gggg\n", //
311 "jj" //
312 )
313 );
314
315 assert_eq!(
316 subscription.consume().into_inner(),
317 [Edit {
318 old: MultiBufferOffset(6)..MultiBufferOffset(8),
319 new: MultiBufferOffset(6)..MultiBufferOffset(7)
320 }]
321 );
322
323 let snapshot = multibuffer.read(cx).snapshot(cx);
324 assert_eq!(
325 snapshot.clip_point(Point::new(0, 5), Bias::Left),
326 Point::new(0, 4)
327 );
328 assert_eq!(
329 snapshot.clip_point(Point::new(0, 5), Bias::Right),
330 Point::new(0, 4)
331 );
332 assert_eq!(
333 snapshot.clip_point(Point::new(5, 1), Bias::Right),
334 Point::new(5, 1)
335 );
336 assert_eq!(
337 snapshot.clip_point(Point::new(5, 2), Bias::Right),
338 Point::new(5, 2)
339 );
340 assert_eq!(
341 snapshot.clip_point(Point::new(5, 3), Bias::Right),
342 Point::new(5, 2)
343 );
344
345 let snapshot = multibuffer.update(cx, |multibuffer, cx| {
346 multibuffer.remove_excerpts(PathKey::sorted(1), cx);
347 multibuffer.snapshot(cx)
348 });
349
350 assert_eq!(
351 snapshot.text(),
352 concat!(
353 "bbbb\n", // Preserve newlines
354 "c\n", //
355 "cc\n", //
356 "fff\n", //
357 "gggg", //
358 )
359 );
360
361 fn boundaries_in_range(
362 range: Range<Point>,
363 snapshot: &MultiBufferSnapshot,
364 ) -> Vec<(MultiBufferRow, String, bool)> {
365 snapshot
366 .excerpt_boundaries_in_range(range)
367 .map(|boundary| {
368 let starts_new_buffer = boundary.starts_new_buffer();
369 (
370 boundary.row,
371 boundary
372 .next
373 .buffer(snapshot)
374 .text_for_range(boundary.next.range.context)
375 .collect::<String>(),
376 starts_new_buffer,
377 )
378 })
379 .collect::<Vec<_>>()
380 }
381}
382
383#[gpui::test]
384async fn test_diff_boundary_anchors(cx: &mut TestAppContext) {
385 let base_text = "one\ntwo\nthree\n";
386 let text = "one\nthree\n";
387 let buffer = cx.new(|cx| Buffer::local(text, cx));
388 let diff = cx
389 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
390 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
391 multibuffer.update(cx, |multibuffer, cx| multibuffer.add_diff(diff, cx));
392
393 let (before, after) = multibuffer.update(cx, |multibuffer, cx| {
394 let before = multibuffer.snapshot(cx).anchor_before(Point::new(1, 0));
395 let after = multibuffer.snapshot(cx).anchor_after(Point::new(1, 0));
396 multibuffer.set_all_diff_hunks_expanded(cx);
397 (before, after)
398 });
399 cx.run_until_parked();
400
401 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
402 let actual_text = snapshot.text();
403 let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
404 let actual_diff = format_diff(&actual_text, &actual_row_infos, &Default::default(), None);
405 pretty_assertions::assert_eq!(
406 actual_diff,
407 indoc! {
408 " one
409 - two
410 three
411 "
412 },
413 );
414
415 multibuffer.update(cx, |multibuffer, cx| {
416 let snapshot = multibuffer.snapshot(cx);
417 assert_eq!(before.to_point(&snapshot), Point::new(1, 0));
418 assert_eq!(after.to_point(&snapshot), Point::new(2, 0));
419 assert_eq!(
420 vec![Point::new(1, 0), Point::new(2, 0),],
421 snapshot.summaries_for_anchors::<Point, _>(&[before, after]),
422 )
423 })
424}
425
426#[gpui::test]
427async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
428 let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n";
429 let text = "one\nfour\nseven\n";
430 let buffer = cx.new(|cx| Buffer::local(text, cx));
431 let diff = cx
432 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
433 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
434 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
435 (multibuffer.snapshot(cx), multibuffer.subscribe())
436 });
437
438 multibuffer.update(cx, |multibuffer, cx| {
439 multibuffer.add_diff(diff, cx);
440 multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
441 });
442
443 assert_new_snapshot(
444 &multibuffer,
445 &mut snapshot,
446 &mut subscription,
447 cx,
448 indoc! {
449 " one
450 - two
451 - three
452 four
453 - five
454 - six
455 seven
456 - eight
457 "
458 },
459 );
460
461 assert_eq!(
462 snapshot
463 .diff_hunks_in_range(Point::new(1, 0)..Point::MAX)
464 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
465 .collect::<Vec<_>>(),
466 vec![1..3, 4..6, 7..8]
467 );
468
469 assert_eq!(snapshot.diff_hunk_before(Point::new(1, 1)), None,);
470 assert_eq!(
471 snapshot.diff_hunk_before(Point::new(7, 0)),
472 Some(MultiBufferRow(4))
473 );
474 assert_eq!(
475 snapshot.diff_hunk_before(Point::new(4, 0)),
476 Some(MultiBufferRow(1))
477 );
478
479 multibuffer.update(cx, |multibuffer, cx| {
480 multibuffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
481 });
482
483 assert_new_snapshot(
484 &multibuffer,
485 &mut snapshot,
486 &mut subscription,
487 cx,
488 indoc! {
489 "
490 one
491 four
492 seven
493 "
494 },
495 );
496
497 assert_eq!(
498 snapshot.diff_hunk_before(Point::new(2, 0)),
499 Some(MultiBufferRow(1)),
500 );
501 assert_eq!(
502 snapshot.diff_hunk_before(Point::new(4, 0)),
503 Some(MultiBufferRow(2))
504 );
505}
506
507#[gpui::test]
508async fn test_diff_hunks_in_range_query_starting_at_added_row(cx: &mut TestAppContext) {
509 let base_text = "one\ntwo\nthree\n";
510 let text = "one\nTWO\nthree\n";
511 let buffer = cx.new(|cx| Buffer::local(text, cx));
512 let diff = cx
513 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
514 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
515 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
516 (multibuffer.snapshot(cx), multibuffer.subscribe())
517 });
518
519 multibuffer.update(cx, |multibuffer, cx| {
520 multibuffer.add_diff(diff, cx);
521 multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
522 });
523
524 assert_new_snapshot(
525 &multibuffer,
526 &mut snapshot,
527 &mut subscription,
528 cx,
529 indoc! {
530 " one
531 - two
532 + TWO
533 three
534 "
535 },
536 );
537
538 assert_eq!(
539 snapshot
540 .diff_hunks_in_range(Point::new(2, 0)..Point::MAX)
541 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
542 .collect::<Vec<_>>(),
543 vec![1..3],
544 "querying starting at the added row should still return the full hunk including deleted lines"
545 );
546}
547
548#[gpui::test]
549async fn test_inverted_diff_hunks_in_range(cx: &mut TestAppContext) {
550 let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n";
551 let text = "ZERO\none\nTHREE\nfour\nseven\nEIGHT\nNINE\n";
552 let buffer = cx.new(|cx| Buffer::local(text, cx));
553 let diff = cx
554 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
555 let base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
556 let multibuffer = cx.new(|cx| MultiBuffer::singleton(base_text_buffer.clone(), cx));
557 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
558 (multibuffer.snapshot(cx), multibuffer.subscribe())
559 });
560
561 multibuffer.update(cx, |multibuffer, cx| {
562 multibuffer.add_inverted_diff(diff, buffer.clone(), cx);
563 });
564
565 assert_new_snapshot(
566 &multibuffer,
567 &mut snapshot,
568 &mut subscription,
569 cx,
570 indoc! {
571 " one
572 - two
573 - three
574 four
575 - five
576 - six
577 seven
578 - eight
579 "
580 },
581 );
582
583 assert_eq!(
584 snapshot
585 .diff_hunks_in_range(Point::new(0, 0)..Point::MAX)
586 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
587 .collect::<Vec<_>>(),
588 vec![0..0, 1..3, 4..6, 7..8]
589 );
590
591 assert_eq!(
592 snapshot.diff_hunk_before(Point::new(1, 1)),
593 Some(MultiBufferRow(0))
594 );
595 assert_eq!(
596 snapshot.diff_hunk_before(Point::new(7, 0)),
597 Some(MultiBufferRow(4))
598 );
599 assert_eq!(
600 snapshot.diff_hunk_before(Point::new(4, 0)),
601 Some(MultiBufferRow(1))
602 );
603}
604
605#[gpui::test]
606async fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) {
607 let base_text = "one\ntwo\nfour\nfive\nsix\nseven\n";
608 let text = "one\ntwo\nTHREE\nfour\nfive\nseven\n";
609 let buffer = cx.new(|cx| Buffer::local(text, cx));
610 let diff = cx
611 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
612 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
613
614 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
615 multibuffer.add_diff(diff.clone(), cx);
616 (multibuffer.snapshot(cx), multibuffer.subscribe())
617 });
618
619 cx.executor().run_until_parked();
620 multibuffer.update(cx, |multibuffer, cx| {
621 multibuffer.set_all_diff_hunks_expanded(cx);
622 });
623
624 assert_new_snapshot(
625 &multibuffer,
626 &mut snapshot,
627 &mut subscription,
628 cx,
629 indoc! {
630 "
631 one
632 two
633 + THREE
634 four
635 five
636 - six
637 seven
638 "
639 },
640 );
641
642 // Insert a newline within an insertion hunk
643 multibuffer.update(cx, |multibuffer, cx| {
644 multibuffer.edit([(Point::new(2, 0)..Point::new(2, 0), "__\n__")], None, cx);
645 });
646 assert_new_snapshot(
647 &multibuffer,
648 &mut snapshot,
649 &mut subscription,
650 cx,
651 indoc! {
652 "
653 one
654 two
655 + __
656 + __THREE
657 four
658 five
659 - six
660 seven
661 "
662 },
663 );
664
665 // Delete the newline before a deleted hunk.
666 multibuffer.update(cx, |multibuffer, cx| {
667 multibuffer.edit([(Point::new(5, 4)..Point::new(6, 0), "")], None, cx);
668 });
669 assert_new_snapshot(
670 &multibuffer,
671 &mut snapshot,
672 &mut subscription,
673 cx,
674 indoc! {
675 "
676 one
677 two
678 + __
679 + __THREE
680 four
681 fiveseven
682 "
683 },
684 );
685
686 multibuffer.update(cx, |multibuffer, cx| multibuffer.undo(cx));
687 assert_new_snapshot(
688 &multibuffer,
689 &mut snapshot,
690 &mut subscription,
691 cx,
692 indoc! {
693 "
694 one
695 two
696 + __
697 + __THREE
698 four
699 five
700 - six
701 seven
702 "
703 },
704 );
705
706 // Cannot (yet) insert at the beginning of a deleted hunk.
707 // (because it would put the newline in the wrong place)
708 multibuffer.update(cx, |multibuffer, cx| {
709 multibuffer.edit([(Point::new(6, 0)..Point::new(6, 0), "\n")], None, cx);
710 });
711 assert_new_snapshot(
712 &multibuffer,
713 &mut snapshot,
714 &mut subscription,
715 cx,
716 indoc! {
717 "
718 one
719 two
720 + __
721 + __THREE
722 four
723 five
724 - six
725 seven
726 "
727 },
728 );
729
730 // Replace a range that ends in a deleted hunk.
731 multibuffer.update(cx, |multibuffer, cx| {
732 multibuffer.edit([(Point::new(5, 2)..Point::new(6, 2), "fty-")], None, cx);
733 });
734 assert_new_snapshot(
735 &multibuffer,
736 &mut snapshot,
737 &mut subscription,
738 cx,
739 indoc! {
740 "
741 one
742 two
743 + __
744 + __THREE
745 four
746 fifty-seven
747 "
748 },
749 );
750}
751
752#[gpui::test]
753fn test_excerpt_events(cx: &mut App) {
754 let buffer_1 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'a'), cx));
755 let buffer_2 = cx.new(|cx| Buffer::local(sample_text(10, 3, 'm'), cx));
756
757 let leader_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
758 let follower_multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
759 let follower_edit_event_count = Arc::new(RwLock::new(0));
760
761 follower_multibuffer.update(cx, |_, cx| {
762 let follower_edit_event_count = follower_edit_event_count.clone();
763 cx.subscribe(
764 &leader_multibuffer,
765 move |follower, _, event, cx| match event.clone() {
766 Event::BufferRangesUpdated {
767 buffer,
768 path_key,
769 ranges,
770 } => {
771 let buffer_snapshot = buffer.read(cx).snapshot();
772 follower.set_merged_excerpt_ranges_for_path(
773 path_key,
774 buffer,
775 &buffer_snapshot,
776 ranges,
777 cx,
778 );
779 }
780 Event::BuffersRemoved {
781 removed_buffer_ids, ..
782 } => {
783 for id in removed_buffer_ids {
784 follower.remove_excerpts_for_buffer(id, cx);
785 }
786 }
787 Event::Edited { .. } => {
788 *follower_edit_event_count.write() += 1;
789 }
790 _ => {}
791 },
792 )
793 .detach();
794 });
795
796 let buffer_1_snapshot = buffer_1.read(cx).snapshot();
797 let buffer_2_snapshot = buffer_2.read(cx).snapshot();
798 leader_multibuffer.update(cx, |leader, cx| {
799 leader.set_excerpt_ranges_for_path(
800 PathKey::sorted(0),
801 buffer_1.clone(),
802 &buffer_1_snapshot,
803 vec![
804 ExcerptRange::new((0..8).to_point(&buffer_1_snapshot)),
805 ExcerptRange::new((22..26).to_point(&buffer_1_snapshot)),
806 ],
807 cx,
808 );
809 leader.set_excerpt_ranges_for_path(
810 PathKey::sorted(1),
811 buffer_2.clone(),
812 &buffer_2_snapshot,
813 vec![
814 ExcerptRange::new((0..5).to_point(&buffer_2_snapshot)),
815 ExcerptRange::new((20..25).to_point(&buffer_2_snapshot)),
816 ],
817 cx,
818 );
819 });
820 assert_eq!(
821 leader_multibuffer.read(cx).snapshot(cx).text(),
822 follower_multibuffer.read(cx).snapshot(cx).text(),
823 );
824 assert_eq!(*follower_edit_event_count.read(), 2);
825
826 leader_multibuffer.update(cx, |leader, cx| {
827 leader.set_excerpt_ranges_for_path(
828 PathKey::sorted(0),
829 buffer_1.clone(),
830 &buffer_1_snapshot,
831 vec![ExcerptRange::new((0..8).to_point(&buffer_1_snapshot))],
832 cx,
833 );
834 leader.set_excerpt_ranges_for_path(
835 PathKey::sorted(1),
836 buffer_2,
837 &buffer_2_snapshot,
838 vec![ExcerptRange::new((0..5).to_point(&buffer_2_snapshot))],
839 cx,
840 );
841 });
842 assert_eq!(
843 leader_multibuffer.read(cx).snapshot(cx).text(),
844 follower_multibuffer.read(cx).snapshot(cx).text(),
845 );
846 assert_eq!(*follower_edit_event_count.read(), 4);
847
848 leader_multibuffer.update(cx, |leader, cx| {
849 leader.clear(cx);
850 });
851 assert_eq!(
852 leader_multibuffer.read(cx).snapshot(cx).text(),
853 follower_multibuffer.read(cx).snapshot(cx).text(),
854 );
855 assert_eq!(*follower_edit_event_count.read(), 5);
856}
857
858#[gpui::test]
859fn test_expand_excerpts(cx: &mut App) {
860 let buffer = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
861 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
862
863 multibuffer.update(cx, |multibuffer, cx| {
864 multibuffer.set_excerpts_for_path(
865 PathKey::for_buffer(&buffer, cx),
866 buffer,
867 vec![
868 // Note that in this test, this first excerpt
869 // does not contain a new line
870 Point::new(3, 2)..Point::new(3, 3),
871 Point::new(7, 1)..Point::new(7, 3),
872 Point::new(15, 0)..Point::new(15, 0),
873 ],
874 1,
875 cx,
876 )
877 });
878
879 let snapshot = multibuffer.read(cx).snapshot(cx);
880
881 assert_eq!(
882 snapshot.text(),
883 concat!(
884 "ccc\n", //
885 "ddd\n", //
886 "eee", //
887 "\n", // End of excerpt
888 "ggg\n", //
889 "hhh\n", //
890 "iii", //
891 "\n", // End of excerpt
892 "ooo\n", //
893 "ppp\n", //
894 "qqq", // End of excerpt
895 )
896 );
897 drop(snapshot);
898
899 multibuffer.update(cx, |multibuffer, cx| {
900 let multibuffer_snapshot = multibuffer.snapshot(cx);
901 let line_zero = multibuffer_snapshot.anchor_before(Point::new(0, 0));
902 multibuffer.expand_excerpts(
903 multibuffer.snapshot(cx).excerpts().map(|excerpt| {
904 multibuffer_snapshot
905 .anchor_in_excerpt(excerpt.context.start)
906 .unwrap()
907 }),
908 1,
909 ExpandExcerptDirection::UpAndDown,
910 cx,
911 );
912 let snapshot = multibuffer.snapshot(cx);
913 let line_two = snapshot.anchor_before(Point::new(2, 0));
914 assert_eq!(line_two.cmp(&line_zero, &snapshot), cmp::Ordering::Greater);
915 });
916
917 let snapshot = multibuffer.read(cx).snapshot(cx);
918
919 assert_eq!(
920 snapshot.text(),
921 concat!(
922 "bbb\n", //
923 "ccc\n", //
924 "ddd\n", //
925 "eee\n", //
926 "fff\n", //
927 "ggg\n", //
928 "hhh\n", //
929 "iii\n", //
930 "jjj\n", // End of excerpt
931 "nnn\n", //
932 "ooo\n", //
933 "ppp\n", //
934 "qqq\n", //
935 "rrr", // End of excerpt
936 )
937 );
938}
939
940#[gpui::test(iterations = 100)]
941async fn test_set_anchored_excerpts_for_path(cx: &mut TestAppContext) {
942 let buffer_1 = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
943 let buffer_2 = cx.new(|cx| Buffer::local(sample_text(15, 4, 'a'), cx));
944 let snapshot_1 = buffer_1.update(cx, |buffer, _| buffer.snapshot());
945 let snapshot_2 = buffer_2.update(cx, |buffer, _| buffer.snapshot());
946 let ranges_1 = vec![
947 snapshot_1.anchor_before(Point::new(3, 2))..snapshot_1.anchor_before(Point::new(4, 2)),
948 snapshot_1.anchor_before(Point::new(7, 1))..snapshot_1.anchor_before(Point::new(7, 3)),
949 snapshot_1.anchor_before(Point::new(15, 0))..snapshot_1.anchor_before(Point::new(15, 0)),
950 ];
951 let ranges_2 = vec![
952 snapshot_2.anchor_before(Point::new(2, 1))..snapshot_2.anchor_before(Point::new(3, 1)),
953 snapshot_2.anchor_before(Point::new(10, 0))..snapshot_2.anchor_before(Point::new(10, 2)),
954 ];
955
956 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
957 let anchor_ranges_1 = multibuffer
958 .update(cx, |multibuffer, cx| {
959 multibuffer.set_anchored_excerpts_for_path(
960 PathKey::for_buffer(&buffer_1, cx),
961 buffer_1.clone(),
962 ranges_1,
963 2,
964 cx,
965 )
966 })
967 .await;
968 let snapshot_1 = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
969 assert_eq!(
970 anchor_ranges_1
971 .iter()
972 .map(|range| range.to_point(&snapshot_1))
973 .collect::<Vec<_>>(),
974 vec![
975 Point::new(2, 2)..Point::new(3, 2),
976 Point::new(6, 1)..Point::new(6, 3),
977 Point::new(11, 0)..Point::new(11, 0),
978 ]
979 );
980 let anchor_ranges_2 = multibuffer
981 .update(cx, |multibuffer, cx| {
982 multibuffer.set_anchored_excerpts_for_path(
983 PathKey::for_buffer(&buffer_2, cx),
984 buffer_2.clone(),
985 ranges_2,
986 2,
987 cx,
988 )
989 })
990 .await;
991 let snapshot_2 = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
992 assert_eq!(
993 anchor_ranges_2
994 .iter()
995 .map(|range| range.to_point(&snapshot_2))
996 .collect::<Vec<_>>(),
997 vec![
998 Point::new(16, 1)..Point::new(17, 1),
999 Point::new(22, 0)..Point::new(22, 2)
1000 ]
1001 );
1002
1003 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1004 assert_eq!(
1005 snapshot.text(),
1006 concat!(
1007 "bbb\n", // buffer_1
1008 "ccc\n", //
1009 "ddd\n", // <-- excerpt 1
1010 "eee\n", // <-- excerpt 1
1011 "fff\n", //
1012 "ggg\n", //
1013 "hhh\n", // <-- excerpt 2
1014 "iii\n", //
1015 "jjj\n", //
1016 //
1017 "nnn\n", //
1018 "ooo\n", //
1019 "ppp\n", // <-- excerpt 3
1020 "qqq\n", //
1021 "rrr\n", //
1022 //
1023 "aaaa\n", // buffer 2
1024 "bbbb\n", //
1025 "cccc\n", // <-- excerpt 4
1026 "dddd\n", // <-- excerpt 4
1027 "eeee\n", //
1028 "ffff\n", //
1029 //
1030 "iiii\n", //
1031 "jjjj\n", //
1032 "kkkk\n", // <-- excerpt 5
1033 "llll\n", //
1034 "mmmm", //
1035 )
1036 );
1037}
1038
1039#[gpui::test]
1040fn test_empty_multibuffer(cx: &mut App) {
1041 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1042
1043 let snapshot = multibuffer.read(cx).snapshot(cx);
1044 assert_eq!(snapshot.text(), "");
1045 assert_eq!(
1046 snapshot
1047 .row_infos(MultiBufferRow(0))
1048 .map(|info| info.buffer_row)
1049 .collect::<Vec<_>>(),
1050 &[Some(0)]
1051 );
1052 assert!(
1053 snapshot
1054 .row_infos(MultiBufferRow(1))
1055 .map(|info| info.buffer_row)
1056 .collect::<Vec<_>>()
1057 .is_empty(),
1058 );
1059}
1060
1061#[gpui::test]
1062async fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
1063 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1064 let buffer = cx.new(|cx| Buffer::local("", cx));
1065 let base_text = "a\nb\nc";
1066
1067 let diff = cx
1068 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
1069 multibuffer.update(cx, |multibuffer, cx| {
1070 multibuffer.set_excerpt_ranges_for_path(
1071 PathKey::sorted(0),
1072 buffer.clone(),
1073 &buffer.read(cx).snapshot(),
1074 vec![ExcerptRange::new(Point::zero()..Point::zero())],
1075 cx,
1076 );
1077 multibuffer.set_all_diff_hunks_expanded(cx);
1078 multibuffer.add_diff(diff.clone(), cx);
1079 });
1080 cx.run_until_parked();
1081
1082 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1083 assert_eq!(snapshot.text(), "a\nb\nc\n");
1084
1085 let hunk = snapshot
1086 .diff_hunks_in_range(Point::new(1, 1)..Point::new(1, 1))
1087 .next()
1088 .unwrap();
1089
1090 assert_eq!(hunk.diff_base_byte_range.start, BufferOffset(0));
1091
1092 let buf2 = cx.new(|cx| Buffer::local("X", cx));
1093 multibuffer.update(cx, |multibuffer, cx| {
1094 multibuffer.set_excerpts_for_path(
1095 PathKey::sorted(1),
1096 buf2,
1097 [Point::new(0, 0)..Point::new(0, 1)],
1098 0,
1099 cx,
1100 );
1101 });
1102
1103 buffer.update(cx, |buffer, cx| {
1104 buffer.edit([(0..0, "a\nb\nc")], None, cx);
1105 diff.update(cx, |diff, cx| {
1106 diff.recalculate_diff_sync(&buffer.text_snapshot(), cx);
1107 });
1108 assert_eq!(buffer.text(), "a\nb\nc")
1109 });
1110 cx.run_until_parked();
1111
1112 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1113 assert_eq!(snapshot.text(), "a\nb\nc\nX");
1114
1115 buffer.update(cx, |buffer, cx| {
1116 buffer.undo(cx);
1117 diff.update(cx, |diff, cx| {
1118 diff.recalculate_diff_sync(&buffer.text_snapshot(), cx);
1119 });
1120 assert_eq!(buffer.text(), "")
1121 });
1122 cx.run_until_parked();
1123
1124 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
1125 assert_eq!(snapshot.text(), "a\nb\nc\n\nX");
1126}
1127
1128#[gpui::test]
1129fn test_singleton_multibuffer_anchors(cx: &mut App) {
1130 let buffer = cx.new(|cx| Buffer::local("abcd", cx));
1131 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
1132 let old_snapshot = multibuffer.read(cx).snapshot(cx);
1133 buffer.update(cx, |buffer, cx| {
1134 buffer.edit([(0..0, "X")], None, cx);
1135 buffer.edit([(5..5, "Y")], None, cx);
1136 });
1137 let new_snapshot = multibuffer.read(cx).snapshot(cx);
1138
1139 assert_eq!(old_snapshot.text(), "abcd");
1140 assert_eq!(new_snapshot.text(), "XabcdY");
1141
1142 assert_eq!(
1143 old_snapshot
1144 .anchor_before(MultiBufferOffset(0))
1145 .to_offset(&new_snapshot),
1146 MultiBufferOffset(0)
1147 );
1148 assert_eq!(
1149 old_snapshot
1150 .anchor_after(MultiBufferOffset(0))
1151 .to_offset(&new_snapshot),
1152 MultiBufferOffset(1)
1153 );
1154 assert_eq!(
1155 old_snapshot
1156 .anchor_before(MultiBufferOffset(4))
1157 .to_offset(&new_snapshot),
1158 MultiBufferOffset(5)
1159 );
1160 assert_eq!(
1161 old_snapshot
1162 .anchor_after(MultiBufferOffset(4))
1163 .to_offset(&new_snapshot),
1164 MultiBufferOffset(6)
1165 );
1166}
1167
1168#[gpui::test]
1169fn test_multibuffer_anchors(cx: &mut App) {
1170 let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
1171 let buffer_2 = cx.new(|cx| Buffer::local("efghi", cx));
1172 let multibuffer = cx.new(|cx| {
1173 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1174 multibuffer.set_excerpts_for_path(
1175 PathKey::sorted(0),
1176 buffer_1.clone(),
1177 [Point::new(0, 0)..Point::new(0, 4)],
1178 0,
1179 cx,
1180 );
1181 multibuffer.set_excerpts_for_path(
1182 PathKey::sorted(1),
1183 buffer_2.clone(),
1184 [Point::new(0, 0)..Point::new(0, 5)],
1185 0,
1186 cx,
1187 );
1188 multibuffer
1189 });
1190 let old_snapshot = multibuffer.read(cx).snapshot(cx);
1191
1192 assert_eq!(
1193 old_snapshot
1194 .anchor_before(MultiBufferOffset(0))
1195 .to_offset(&old_snapshot),
1196 MultiBufferOffset(0)
1197 );
1198 assert_eq!(
1199 old_snapshot
1200 .anchor_after(MultiBufferOffset(0))
1201 .to_offset(&old_snapshot),
1202 MultiBufferOffset(0)
1203 );
1204 assert_eq!(Anchor::Min.to_offset(&old_snapshot), MultiBufferOffset(0));
1205 assert_eq!(Anchor::Min.to_offset(&old_snapshot), MultiBufferOffset(0));
1206 assert_eq!(Anchor::Max.to_offset(&old_snapshot), MultiBufferOffset(10));
1207 assert_eq!(Anchor::Max.to_offset(&old_snapshot), MultiBufferOffset(10));
1208
1209 buffer_1.update(cx, |buffer, cx| {
1210 buffer.edit([(0..0, "W")], None, cx);
1211 buffer.edit([(5..5, "X")], None, cx);
1212 });
1213 buffer_2.update(cx, |buffer, cx| {
1214 buffer.edit([(0..0, "Y")], None, cx);
1215 buffer.edit([(6..6, "Z")], None, cx);
1216 });
1217 let new_snapshot = multibuffer.read(cx).snapshot(cx);
1218
1219 assert_eq!(old_snapshot.text(), "abcd\nefghi");
1220 assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
1221
1222 assert_eq!(
1223 old_snapshot
1224 .anchor_before(MultiBufferOffset(0))
1225 .to_offset(&new_snapshot),
1226 MultiBufferOffset(0)
1227 );
1228 assert_eq!(
1229 old_snapshot
1230 .anchor_after(MultiBufferOffset(0))
1231 .to_offset(&new_snapshot),
1232 MultiBufferOffset(1)
1233 );
1234 assert_eq!(
1235 old_snapshot
1236 .anchor_before(MultiBufferOffset(1))
1237 .to_offset(&new_snapshot),
1238 MultiBufferOffset(2)
1239 );
1240 assert_eq!(
1241 old_snapshot
1242 .anchor_after(MultiBufferOffset(1))
1243 .to_offset(&new_snapshot),
1244 MultiBufferOffset(2)
1245 );
1246 assert_eq!(
1247 old_snapshot
1248 .anchor_before(MultiBufferOffset(2))
1249 .to_offset(&new_snapshot),
1250 MultiBufferOffset(3)
1251 );
1252 assert_eq!(
1253 old_snapshot
1254 .anchor_after(MultiBufferOffset(2))
1255 .to_offset(&new_snapshot),
1256 MultiBufferOffset(3)
1257 );
1258 assert_eq!(
1259 old_snapshot
1260 .anchor_before(MultiBufferOffset(5))
1261 .to_offset(&new_snapshot),
1262 MultiBufferOffset(7)
1263 );
1264 assert_eq!(
1265 old_snapshot
1266 .anchor_after(MultiBufferOffset(5))
1267 .to_offset(&new_snapshot),
1268 MultiBufferOffset(8)
1269 );
1270 assert_eq!(
1271 old_snapshot
1272 .anchor_before(MultiBufferOffset(10))
1273 .to_offset(&new_snapshot),
1274 MultiBufferOffset(13)
1275 );
1276 assert_eq!(
1277 old_snapshot
1278 .anchor_after(MultiBufferOffset(10))
1279 .to_offset(&new_snapshot),
1280 MultiBufferOffset(14)
1281 );
1282}
1283
1284#[gpui::test]
1285async fn test_basic_diff_hunks(cx: &mut TestAppContext) {
1286 let text = indoc!(
1287 "
1288 ZERO
1289 one
1290 TWO
1291 three
1292 six
1293 "
1294 );
1295 let base_text = indoc!(
1296 "
1297 one
1298 two
1299 three
1300 four
1301 five
1302 six
1303 "
1304 );
1305
1306 let buffer = cx.new(|cx| Buffer::local(text, cx));
1307 let diff = cx
1308 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
1309 cx.run_until_parked();
1310
1311 let multibuffer = cx.new(|cx| {
1312 let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1313 multibuffer.add_diff(diff.clone(), cx);
1314 multibuffer
1315 });
1316
1317 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1318 (multibuffer.snapshot(cx), multibuffer.subscribe())
1319 });
1320 assert_eq!(
1321 snapshot.text(),
1322 indoc!(
1323 "
1324 ZERO
1325 one
1326 TWO
1327 three
1328 six
1329 "
1330 ),
1331 );
1332
1333 multibuffer.update(cx, |multibuffer, cx| {
1334 multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
1335 });
1336
1337 assert_new_snapshot(
1338 &multibuffer,
1339 &mut snapshot,
1340 &mut subscription,
1341 cx,
1342 indoc!(
1343 "
1344 + ZERO
1345 one
1346 - two
1347 + TWO
1348 three
1349 - four
1350 - five
1351 six
1352 "
1353 ),
1354 );
1355
1356 assert_eq!(
1357 snapshot
1358 .row_infos(MultiBufferRow(0))
1359 .map(|info| (info.buffer_row, info.diff_status))
1360 .collect::<Vec<_>>(),
1361 vec![
1362 (Some(0), Some(DiffHunkStatus::added_none())),
1363 (Some(1), None),
1364 (Some(1), Some(DiffHunkStatus::deleted_none())),
1365 (Some(2), Some(DiffHunkStatus::added_none())),
1366 (Some(3), None),
1367 (Some(3), Some(DiffHunkStatus::deleted_none())),
1368 (Some(4), Some(DiffHunkStatus::deleted_none())),
1369 (Some(4), None),
1370 (Some(5), None)
1371 ]
1372 );
1373
1374 assert_chunks_in_ranges(&snapshot);
1375 assert_consistent_line_numbers(&snapshot);
1376 assert_position_translation(&snapshot);
1377 assert_line_indents(&snapshot);
1378
1379 multibuffer.update(cx, |multibuffer, cx| {
1380 multibuffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx)
1381 });
1382 assert_new_snapshot(
1383 &multibuffer,
1384 &mut snapshot,
1385 &mut subscription,
1386 cx,
1387 indoc!(
1388 "
1389 ZERO
1390 one
1391 TWO
1392 three
1393 six
1394 "
1395 ),
1396 );
1397
1398 assert_chunks_in_ranges(&snapshot);
1399 assert_consistent_line_numbers(&snapshot);
1400 assert_position_translation(&snapshot);
1401 assert_line_indents(&snapshot);
1402
1403 // Expand the first diff hunk
1404 multibuffer.update(cx, |multibuffer, cx| {
1405 let position = multibuffer.read(cx).anchor_before(Point::new(2, 2));
1406 multibuffer.expand_diff_hunks(vec![position..position], cx)
1407 });
1408 assert_new_snapshot(
1409 &multibuffer,
1410 &mut snapshot,
1411 &mut subscription,
1412 cx,
1413 indoc!(
1414 "
1415 ZERO
1416 one
1417 - two
1418 + TWO
1419 three
1420 six
1421 "
1422 ),
1423 );
1424
1425 // Expand the second diff hunk
1426 multibuffer.update(cx, |multibuffer, cx| {
1427 let start = multibuffer.read(cx).anchor_before(Point::new(4, 0));
1428 let end = multibuffer.read(cx).anchor_before(Point::new(5, 0));
1429 multibuffer.expand_diff_hunks(vec![start..end], cx)
1430 });
1431 assert_new_snapshot(
1432 &multibuffer,
1433 &mut snapshot,
1434 &mut subscription,
1435 cx,
1436 indoc!(
1437 "
1438 ZERO
1439 one
1440 - two
1441 + TWO
1442 three
1443 - four
1444 - five
1445 six
1446 "
1447 ),
1448 );
1449
1450 assert_chunks_in_ranges(&snapshot);
1451 assert_consistent_line_numbers(&snapshot);
1452 assert_position_translation(&snapshot);
1453 assert_line_indents(&snapshot);
1454
1455 // Edit the buffer before the first hunk
1456 buffer.update(cx, |buffer, cx| {
1457 buffer.edit_via_marked_text(
1458 indoc!(
1459 "
1460 ZERO
1461 one« hundred
1462 thousand»
1463 TWO
1464 three
1465 six
1466 "
1467 ),
1468 None,
1469 cx,
1470 );
1471 });
1472 assert_new_snapshot(
1473 &multibuffer,
1474 &mut snapshot,
1475 &mut subscription,
1476 cx,
1477 indoc!(
1478 "
1479 ZERO
1480 one hundred
1481 thousand
1482 - two
1483 + TWO
1484 three
1485 - four
1486 - five
1487 six
1488 "
1489 ),
1490 );
1491
1492 assert_chunks_in_ranges(&snapshot);
1493 assert_consistent_line_numbers(&snapshot);
1494 assert_position_translation(&snapshot);
1495 assert_line_indents(&snapshot);
1496
1497 // Recalculate the diff, changing the first diff hunk.
1498 diff.update(cx, |diff, cx| {
1499 diff.recalculate_diff_sync(&buffer.read(cx).text_snapshot(), cx);
1500 });
1501 cx.run_until_parked();
1502 assert_new_snapshot(
1503 &multibuffer,
1504 &mut snapshot,
1505 &mut subscription,
1506 cx,
1507 indoc!(
1508 "
1509 ZERO
1510 one hundred
1511 thousand
1512 TWO
1513 three
1514 - four
1515 - five
1516 six
1517 "
1518 ),
1519 );
1520
1521 assert_eq!(
1522 snapshot
1523 .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.len())
1524 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
1525 .collect::<Vec<_>>(),
1526 &[0..4, 5..7]
1527 );
1528}
1529
1530#[gpui::test]
1531async fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
1532 let text = indoc!(
1533 "
1534 one
1535 TWO
1536 THREE
1537 four
1538 FIVE
1539 six
1540 "
1541 );
1542 let base_text = indoc!(
1543 "
1544 one
1545 four
1546 five
1547 six
1548 "
1549 );
1550
1551 let buffer = cx.new(|cx| Buffer::local(text, cx));
1552 let diff = cx
1553 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
1554 cx.run_until_parked();
1555
1556 let multibuffer = cx.new(|cx| {
1557 let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
1558 multibuffer.add_diff(diff.clone(), cx);
1559 multibuffer
1560 });
1561
1562 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
1563 (multibuffer.snapshot(cx), multibuffer.subscribe())
1564 });
1565
1566 multibuffer.update(cx, |multibuffer, cx| {
1567 multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
1568 });
1569
1570 assert_new_snapshot(
1571 &multibuffer,
1572 &mut snapshot,
1573 &mut subscription,
1574 cx,
1575 indoc!(
1576 "
1577 one
1578 + TWO
1579 + THREE
1580 four
1581 - five
1582 + FIVE
1583 six
1584 "
1585 ),
1586 );
1587
1588 // Regression test: expanding diff hunks that are already expanded should not change anything.
1589 multibuffer.update(cx, |multibuffer, cx| {
1590 multibuffer.expand_diff_hunks(
1591 vec![
1592 snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_before(Point::new(2, 0)),
1593 ],
1594 cx,
1595 );
1596 });
1597
1598 assert_new_snapshot(
1599 &multibuffer,
1600 &mut snapshot,
1601 &mut subscription,
1602 cx,
1603 indoc!(
1604 "
1605 one
1606 + TWO
1607 + THREE
1608 four
1609 - five
1610 + FIVE
1611 six
1612 "
1613 ),
1614 );
1615
1616 // Now collapse all diff hunks
1617 multibuffer.update(cx, |multibuffer, cx| {
1618 multibuffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
1619 });
1620
1621 assert_new_snapshot(
1622 &multibuffer,
1623 &mut snapshot,
1624 &mut subscription,
1625 cx,
1626 indoc!(
1627 "
1628 one
1629 TWO
1630 THREE
1631 four
1632 FIVE
1633 six
1634 "
1635 ),
1636 );
1637
1638 // Expand the hunks again, but this time provide two ranges that are both within the same hunk
1639 // Target the first hunk which is between "one" and "four"
1640 multibuffer.update(cx, |multibuffer, cx| {
1641 multibuffer.expand_diff_hunks(
1642 vec![
1643 snapshot.anchor_before(Point::new(4, 0))..snapshot.anchor_before(Point::new(4, 0)),
1644 snapshot.anchor_before(Point::new(4, 2))..snapshot.anchor_before(Point::new(4, 2)),
1645 ],
1646 cx,
1647 );
1648 });
1649 assert_new_snapshot(
1650 &multibuffer,
1651 &mut snapshot,
1652 &mut subscription,
1653 cx,
1654 indoc!(
1655 "
1656 one
1657 TWO
1658 THREE
1659 four
1660 - five
1661 + FIVE
1662 six
1663 "
1664 ),
1665 );
1666}
1667
1668#[gpui::test]
1669fn test_set_excerpts_for_buffer_ordering(cx: &mut TestAppContext) {
1670 let buf1 = cx.new(|cx| {
1671 Buffer::local(
1672 indoc! {
1673 "zero
1674 one
1675 two
1676 two.five
1677 three
1678 four
1679 five
1680 six
1681 seven
1682 eight
1683 nine
1684 ten
1685 eleven
1686 ",
1687 },
1688 cx,
1689 )
1690 });
1691 let path1: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
1692
1693 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1694 multibuffer.update(cx, |multibuffer, cx| {
1695 multibuffer.set_excerpts_for_path(
1696 path1.clone(),
1697 buf1.clone(),
1698 vec![
1699 Point::row_range(1..2),
1700 Point::row_range(6..7),
1701 Point::row_range(11..12),
1702 ],
1703 1,
1704 cx,
1705 );
1706 });
1707
1708 assert_excerpts_match(
1709 &multibuffer,
1710 cx,
1711 indoc! {
1712 "-----
1713 zero
1714 one
1715 two
1716 two.five
1717 -----
1718 four
1719 five
1720 six
1721 seven
1722 -----
1723 nine
1724 ten
1725 eleven
1726 "
1727 },
1728 );
1729
1730 buf1.update(cx, |buffer, cx| buffer.edit([(0..5, "")], None, cx));
1731
1732 multibuffer.update(cx, |multibuffer, cx| {
1733 multibuffer.set_excerpts_for_path(
1734 path1.clone(),
1735 buf1.clone(),
1736 vec![
1737 Point::row_range(0..3),
1738 Point::row_range(5..7),
1739 Point::row_range(10..11),
1740 ],
1741 1,
1742 cx,
1743 );
1744 });
1745
1746 assert_excerpts_match(
1747 &multibuffer,
1748 cx,
1749 indoc! {
1750 "-----
1751 one
1752 two
1753 two.five
1754 three
1755 four
1756 five
1757 six
1758 seven
1759 eight
1760 nine
1761 ten
1762 eleven
1763 "
1764 },
1765 );
1766}
1767
1768#[gpui::test]
1769fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
1770 let buf1 = cx.new(|cx| {
1771 Buffer::local(
1772 indoc! {
1773 "zero
1774 one
1775 two
1776 three
1777 four
1778 five
1779 six
1780 seven
1781 ",
1782 },
1783 cx,
1784 )
1785 });
1786 let path1: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
1787 let buf2 = cx.new(|cx| {
1788 Buffer::local(
1789 indoc! {
1790 "000
1791 111
1792 222
1793 333
1794 444
1795 555
1796 666
1797 777
1798 888
1799 999
1800 "
1801 },
1802 cx,
1803 )
1804 });
1805 let path2 = PathKey::with_sort_prefix(1, rel_path("root").into_arc());
1806
1807 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1808 multibuffer.update(cx, |multibuffer, cx| {
1809 multibuffer.set_excerpts_for_path(
1810 path1.clone(),
1811 buf1.clone(),
1812 vec![Point::row_range(0..1)],
1813 2,
1814 cx,
1815 );
1816 });
1817
1818 assert_excerpts_match(
1819 &multibuffer,
1820 cx,
1821 indoc! {
1822 "-----
1823 zero
1824 one
1825 two
1826 three
1827 "
1828 },
1829 );
1830
1831 multibuffer.update(cx, |multibuffer, cx| {
1832 multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1833 });
1834
1835 assert_excerpts_match(&multibuffer, cx, "");
1836
1837 multibuffer.update(cx, |multibuffer, cx| {
1838 multibuffer.set_excerpts_for_path(
1839 path1.clone(),
1840 buf1.clone(),
1841 vec![Point::row_range(0..1), Point::row_range(7..8)],
1842 2,
1843 cx,
1844 );
1845 });
1846
1847 assert_excerpts_match(
1848 &multibuffer,
1849 cx,
1850 indoc! {"-----
1851 zero
1852 one
1853 two
1854 three
1855 -----
1856 five
1857 six
1858 seven
1859 "},
1860 );
1861
1862 multibuffer.update(cx, |multibuffer, cx| {
1863 multibuffer.set_excerpts_for_path(
1864 path1.clone(),
1865 buf1.clone(),
1866 vec![Point::row_range(0..1), Point::row_range(5..6)],
1867 2,
1868 cx,
1869 );
1870 });
1871
1872 assert_excerpts_match(
1873 &multibuffer,
1874 cx,
1875 indoc! {"-----
1876 zero
1877 one
1878 two
1879 three
1880 four
1881 five
1882 six
1883 seven
1884 "},
1885 );
1886
1887 multibuffer.update(cx, |multibuffer, cx| {
1888 multibuffer.set_excerpts_for_path(
1889 path2.clone(),
1890 buf2.clone(),
1891 vec![Point::row_range(2..3)],
1892 2,
1893 cx,
1894 );
1895 });
1896
1897 assert_excerpts_match(
1898 &multibuffer,
1899 cx,
1900 indoc! {"-----
1901 zero
1902 one
1903 two
1904 three
1905 four
1906 five
1907 six
1908 seven
1909 -----
1910 000
1911 111
1912 222
1913 333
1914 444
1915 555
1916 "},
1917 );
1918
1919 multibuffer.update(cx, |multibuffer, cx| {
1920 multibuffer.set_excerpts_for_path(path1.clone(), buf1.clone(), vec![], 2, cx);
1921 });
1922
1923 multibuffer.update(cx, |multibuffer, cx| {
1924 multibuffer.set_excerpts_for_path(
1925 path1.clone(),
1926 buf1.clone(),
1927 vec![Point::row_range(3..4)],
1928 2,
1929 cx,
1930 );
1931 });
1932
1933 assert_excerpts_match(
1934 &multibuffer,
1935 cx,
1936 indoc! {"-----
1937 one
1938 two
1939 three
1940 four
1941 five
1942 six
1943 -----
1944 000
1945 111
1946 222
1947 333
1948 444
1949 555
1950 "},
1951 );
1952
1953 multibuffer.update(cx, |multibuffer, cx| {
1954 multibuffer.set_excerpts_for_path(
1955 path1.clone(),
1956 buf1.clone(),
1957 vec![Point::row_range(3..4)],
1958 2,
1959 cx,
1960 );
1961 });
1962}
1963
1964#[gpui::test]
1965fn test_update_excerpt_ranges_for_path(cx: &mut TestAppContext) {
1966 let buffer = cx.new(|cx| {
1967 Buffer::local(
1968 indoc! {
1969 "row 0
1970 row 1
1971 row 2
1972 row 3
1973 row 4
1974 row 5
1975 row 6
1976 row 7
1977 row 8
1978 row 9
1979 row 10
1980 row 11
1981 row 12
1982 row 13
1983 row 14
1984 "},
1985 cx,
1986 )
1987 });
1988 let path = PathKey::with_sort_prefix(0, rel_path("test.rs").into_arc());
1989
1990 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
1991 multibuffer.update(cx, |multibuffer, cx| {
1992 multibuffer.set_excerpts_for_path(
1993 path.clone(),
1994 buffer.clone(),
1995 vec![Point::row_range(2..4), Point::row_range(8..10)],
1996 0,
1997 cx,
1998 );
1999 });
2000 assert_excerpts_match(
2001 &multibuffer,
2002 cx,
2003 indoc! {"-----
2004 row 2
2005 row 3
2006 row 4
2007 -----
2008 row 8
2009 row 9
2010 row 10
2011 "},
2012 );
2013
2014 multibuffer.update(cx, |multibuffer, cx| {
2015 multibuffer.update_excerpts_for_path(
2016 path.clone(),
2017 buffer.clone(),
2018 vec![Point::row_range(12..13)],
2019 0,
2020 cx,
2021 );
2022 });
2023 assert_excerpts_match(
2024 &multibuffer,
2025 cx,
2026 indoc! {"-----
2027 row 12
2028 row 13
2029 "},
2030 );
2031
2032 multibuffer.update(cx, |multibuffer, cx| {
2033 multibuffer.set_excerpts_for_path(
2034 path.clone(),
2035 buffer.clone(),
2036 vec![Point::row_range(2..4)],
2037 0,
2038 cx,
2039 );
2040 });
2041 assert_excerpts_match(
2042 &multibuffer,
2043 cx,
2044 indoc! {"-----
2045 row 2
2046 row 3
2047 row 4
2048 "},
2049 );
2050 multibuffer.update(cx, |multibuffer, cx| {
2051 multibuffer.update_excerpts_for_path(
2052 path.clone(),
2053 buffer.clone(),
2054 vec![Point::row_range(3..5)],
2055 0,
2056 cx,
2057 );
2058 });
2059 assert_excerpts_match(
2060 &multibuffer,
2061 cx,
2062 indoc! {"-----
2063 row 2
2064 row 3
2065 row 4
2066 row 5
2067 "},
2068 );
2069
2070 multibuffer.update(cx, |multibuffer, cx| {
2071 multibuffer.set_excerpts_for_path(
2072 path.clone(),
2073 buffer.clone(),
2074 vec![
2075 Point::row_range(0..1),
2076 Point::row_range(6..8),
2077 Point::row_range(12..13),
2078 ],
2079 0,
2080 cx,
2081 );
2082 });
2083 assert_excerpts_match(
2084 &multibuffer,
2085 cx,
2086 indoc! {"-----
2087 row 0
2088 row 1
2089 -----
2090 row 6
2091 row 7
2092 row 8
2093 -----
2094 row 12
2095 row 13
2096 "},
2097 );
2098 multibuffer.update(cx, |multibuffer, cx| {
2099 multibuffer.update_excerpts_for_path(
2100 path.clone(),
2101 buffer.clone(),
2102 vec![Point::row_range(7..9)],
2103 0,
2104 cx,
2105 );
2106 });
2107 assert_excerpts_match(
2108 &multibuffer,
2109 cx,
2110 indoc! {"-----
2111 row 6
2112 row 7
2113 row 8
2114 row 9
2115 "},
2116 );
2117
2118 multibuffer.update(cx, |multibuffer, cx| {
2119 multibuffer.set_excerpts_for_path(
2120 path.clone(),
2121 buffer.clone(),
2122 vec![Point::row_range(2..3), Point::row_range(6..7)],
2123 0,
2124 cx,
2125 );
2126 });
2127 assert_excerpts_match(
2128 &multibuffer,
2129 cx,
2130 indoc! {"-----
2131 row 2
2132 row 3
2133 -----
2134 row 6
2135 row 7
2136 "},
2137 );
2138 multibuffer.update(cx, |multibuffer, cx| {
2139 multibuffer.update_excerpts_for_path(
2140 path.clone(),
2141 buffer.clone(),
2142 vec![Point::row_range(3..6)],
2143 0,
2144 cx,
2145 );
2146 });
2147 assert_excerpts_match(
2148 &multibuffer,
2149 cx,
2150 indoc! {"-----
2151 row 2
2152 row 3
2153 row 4
2154 row 5
2155 row 6
2156 row 7
2157 "},
2158 );
2159}
2160
2161#[gpui::test]
2162fn test_set_excerpts_for_buffer_rename(cx: &mut TestAppContext) {
2163 let buf1 = cx.new(|cx| {
2164 Buffer::local(
2165 indoc! {
2166 "zero
2167 one
2168 two
2169 three
2170 four
2171 five
2172 six
2173 seven
2174 ",
2175 },
2176 cx,
2177 )
2178 });
2179 let path: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
2180 let buf2 = cx.new(|cx| {
2181 Buffer::local(
2182 indoc! {
2183 "000
2184 111
2185 222
2186 333
2187 "
2188 },
2189 cx,
2190 )
2191 });
2192
2193 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2194 multibuffer.update(cx, |multibuffer, cx| {
2195 multibuffer.set_excerpts_for_path(
2196 path.clone(),
2197 buf1.clone(),
2198 vec![Point::row_range(1..1), Point::row_range(4..5)],
2199 1,
2200 cx,
2201 );
2202 });
2203
2204 assert_excerpts_match(
2205 &multibuffer,
2206 cx,
2207 indoc! {
2208 "-----
2209 zero
2210 one
2211 two
2212 three
2213 four
2214 five
2215 six
2216 "
2217 },
2218 );
2219
2220 multibuffer.update(cx, |multibuffer, cx| {
2221 multibuffer.set_excerpts_for_path(
2222 path.clone(),
2223 buf2.clone(),
2224 vec![Point::row_range(0..1)],
2225 2,
2226 cx,
2227 );
2228 });
2229
2230 assert_excerpts_match(
2231 &multibuffer,
2232 cx,
2233 indoc! {"-----
2234 000
2235 111
2236 222
2237 333
2238 "},
2239 );
2240}
2241
2242#[gpui::test]
2243fn test_set_excerpts_for_path_replaces_previous_buffer(cx: &mut TestAppContext) {
2244 let buffer_a = cx.new(|cx| {
2245 Buffer::local(
2246 indoc! {
2247 "alpha
2248 beta
2249 gamma
2250 delta
2251 epsilon
2252 ",
2253 },
2254 cx,
2255 )
2256 });
2257 let buffer_b = cx.new(|cx| {
2258 Buffer::local(
2259 indoc! {
2260 "one
2261 two
2262 three
2263 four
2264 ",
2265 },
2266 cx,
2267 )
2268 });
2269 let path: PathKey = PathKey::with_sort_prefix(0, rel_path("shared/path").into_arc());
2270
2271 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2272 let removed_buffer_ids: Arc<RwLock<Vec<BufferId>>> = Default::default();
2273 multibuffer.update(cx, |_, cx| {
2274 let removed_buffer_ids = removed_buffer_ids.clone();
2275 cx.subscribe(&multibuffer, move |_, _, event, _| {
2276 if let Event::BuffersRemoved {
2277 removed_buffer_ids: ids,
2278 } = event
2279 {
2280 removed_buffer_ids.write().extend(ids.iter().copied());
2281 }
2282 })
2283 .detach();
2284 });
2285
2286 let ranges_a = vec![Point::row_range(0..1), Point::row_range(3..4)];
2287 multibuffer.update(cx, |multibuffer, cx| {
2288 multibuffer.set_excerpts_for_path(path.clone(), buffer_a.clone(), ranges_a.clone(), 0, cx);
2289 });
2290 let (anchor_a1, anchor_a2) = multibuffer.read_with(cx, |multibuffer, cx| {
2291 let snapshot = multibuffer.snapshot(cx);
2292 let buffer_snapshot = buffer_a.read(cx).snapshot();
2293 let mut anchors = ranges_a.into_iter().filter_map(|range| {
2294 let text_range = buffer_snapshot.anchor_range_inside(range);
2295 let start = snapshot.anchor_in_buffer(text_range.start)?;
2296 let end = snapshot.anchor_in_buffer(text_range.end)?;
2297 Some(start..end)
2298 });
2299 (
2300 anchors.next().expect("should have first anchor"),
2301 anchors.next().expect("should have second anchor"),
2302 )
2303 });
2304
2305 assert_excerpts_match(
2306 &multibuffer,
2307 cx,
2308 indoc! {
2309 "-----
2310 alpha
2311 beta
2312 -----
2313 delta
2314 epsilon
2315 "
2316 },
2317 );
2318
2319 let buffer_a_id = buffer_a.read_with(cx, |buffer, _| buffer.remote_id());
2320 multibuffer.read_with(cx, |multibuffer, cx| {
2321 let snapshot = multibuffer.snapshot(cx);
2322 assert!(
2323 snapshot
2324 .excerpts()
2325 .any(|excerpt| excerpt.context.start.buffer_id == buffer_a_id),
2326 );
2327 });
2328
2329 let ranges_b = vec![Point::row_range(1..2)];
2330 multibuffer.update(cx, |multibuffer, cx| {
2331 multibuffer.set_excerpts_for_path(path.clone(), buffer_b.clone(), ranges_b.clone(), 1, cx);
2332 });
2333 let anchor_b = multibuffer.read_with(cx, |multibuffer, cx| {
2334 let snapshot = multibuffer.snapshot(cx);
2335 let buffer_snapshot = buffer_b.read(cx).snapshot();
2336 ranges_b
2337 .into_iter()
2338 .filter_map(|range| {
2339 let text_range = buffer_snapshot.anchor_range_inside(range);
2340 let start = snapshot.anchor_in_buffer(text_range.start)?;
2341 let end = snapshot.anchor_in_buffer(text_range.end)?;
2342 Some(start..end)
2343 })
2344 .next()
2345 .expect("should have an anchor")
2346 });
2347
2348 let buffer_b_id = buffer_b.read_with(cx, |buffer, _| buffer.remote_id());
2349 multibuffer.read_with(cx, |multibuffer, cx| {
2350 let snapshot = multibuffer.snapshot(cx);
2351 assert!(
2352 !snapshot
2353 .excerpts()
2354 .any(|excerpt| excerpt.context.start.buffer_id == buffer_a_id),
2355 );
2356 assert!(
2357 snapshot
2358 .excerpts()
2359 .any(|excerpt| excerpt.context.start.buffer_id == buffer_b_id),
2360 );
2361 assert!(
2362 multibuffer.buffer(buffer_a_id).is_none(),
2363 "old buffer should be fully removed from the multibuffer"
2364 );
2365 assert!(
2366 multibuffer.buffer(buffer_b_id).is_some(),
2367 "new buffer should be present in the multibuffer"
2368 );
2369 });
2370 assert!(
2371 removed_buffer_ids.read().contains(&buffer_a_id),
2372 "BuffersRemoved event should have been emitted for the old buffer"
2373 );
2374
2375 assert_excerpts_match(
2376 &multibuffer,
2377 cx,
2378 indoc! {
2379 "-----
2380 one
2381 two
2382 three
2383 four
2384 "
2385 },
2386 );
2387
2388 multibuffer.read_with(cx, |multibuffer, cx| {
2389 let snapshot = multibuffer.snapshot(cx);
2390 anchor_a1.start.cmp(&anchor_b.start, &snapshot);
2391 anchor_a1.end.cmp(&anchor_b.end, &snapshot);
2392 anchor_a1.start.cmp(&anchor_a2.start, &snapshot);
2393 anchor_a1.end.cmp(&anchor_a2.end, &snapshot);
2394 });
2395}
2396
2397#[gpui::test]
2398fn test_stale_anchor_after_buffer_removal_and_path_reuse(cx: &mut TestAppContext) {
2399 let buffer_a = cx.new(|cx| Buffer::local("aaa\nbbb\nccc\n", cx));
2400 let buffer_b = cx.new(|cx| Buffer::local("xxx\nyyy\nzzz\n", cx));
2401 let buffer_other = cx.new(|cx| Buffer::local("111\n222\n333\n", cx));
2402 let path = PathKey::with_sort_prefix(0, rel_path("the/path").into_arc());
2403 let other_path = PathKey::with_sort_prefix(1, rel_path("other/path").into_arc());
2404
2405 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2406
2407 multibuffer.update(cx, |multibuffer, cx| {
2408 multibuffer.set_excerpts_for_path(
2409 path.clone(),
2410 buffer_a.clone(),
2411 [Point::new(0, 0)..Point::new(2, 3)],
2412 0,
2413 cx,
2414 );
2415 multibuffer.set_excerpts_for_path(
2416 other_path.clone(),
2417 buffer_other.clone(),
2418 [Point::new(0, 0)..Point::new(2, 3)],
2419 0,
2420 cx,
2421 );
2422 });
2423
2424 buffer_a.update(cx, |buffer, cx| {
2425 buffer.edit(
2426 [(Point::new(1, 0)..Point::new(1, 0), "INSERTED ")],
2427 None,
2428 cx,
2429 );
2430 });
2431
2432 let stale_anchor = multibuffer.read_with(cx, |multibuffer, cx| {
2433 let snapshot = multibuffer.snapshot(cx);
2434 snapshot.anchor_before(Point::new(1, 5))
2435 });
2436
2437 multibuffer.update(cx, |multibuffer, cx| {
2438 multibuffer.remove_excerpts(path.clone(), cx);
2439 });
2440
2441 multibuffer.read_with(cx, |multibuffer, cx| {
2442 let snapshot = multibuffer.snapshot(cx);
2443 let offset = stale_anchor.to_offset(&snapshot);
2444 assert!(
2445 offset.0 <= snapshot.len().0,
2446 "stale anchor resolved to offset {offset:?} but multibuffer len is {:?}",
2447 snapshot.len()
2448 );
2449 });
2450
2451 multibuffer.update(cx, |multibuffer, cx| {
2452 multibuffer.set_excerpts_for_path(
2453 path.clone(),
2454 buffer_b.clone(),
2455 [Point::new(0, 0)..Point::new(2, 3)],
2456 0,
2457 cx,
2458 );
2459 });
2460
2461 multibuffer.read_with(cx, |multibuffer, cx| {
2462 let snapshot = multibuffer.snapshot(cx);
2463 let offset = stale_anchor.to_offset(&snapshot);
2464 assert!(
2465 offset.0 <= snapshot.len().0,
2466 "stale anchor resolved to offset {offset:?} but multibuffer len is {:?}",
2467 snapshot.len()
2468 );
2469 });
2470}
2471
2472#[gpui::test]
2473async fn test_map_excerpt_ranges(cx: &mut TestAppContext) {
2474 let base_text = indoc!(
2475 "
2476 {
2477 (aaa)
2478 (bbb)
2479 (ccc)
2480 }
2481 xxx
2482 yyy
2483 zzz
2484 [
2485 (ddd)
2486 (EEE)
2487 ]
2488 "
2489 );
2490 let text = indoc!(
2491 "
2492 {
2493 (aaa)
2494 (CCC)
2495 }
2496 xxx
2497 yyy
2498 zzz
2499 [
2500 (ddd)
2501 (EEE)
2502 ]
2503 "
2504 );
2505
2506 let buffer = cx.new(|cx| Buffer::local(text, cx));
2507 let diff = cx
2508 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
2509 cx.run_until_parked();
2510
2511 let multibuffer = cx.new(|cx| {
2512 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
2513 multibuffer.set_excerpts_for_path(
2514 PathKey::sorted(0),
2515 buffer.clone(),
2516 [
2517 Point::new(0, 0)..Point::new(3, 1),
2518 Point::new(7, 0)..Point::new(10, 1),
2519 ],
2520 0,
2521 cx,
2522 );
2523 multibuffer.add_diff(diff.clone(), cx);
2524 multibuffer
2525 });
2526
2527 multibuffer.update(cx, |multibuffer, cx| {
2528 multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
2529 });
2530 cx.run_until_parked();
2531
2532 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2533
2534 let actual_diff = format_diff(
2535 &snapshot.text(),
2536 &snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
2537 &Default::default(),
2538 None,
2539 );
2540 pretty_assertions::assert_eq!(
2541 actual_diff,
2542 indoc!(
2543 "
2544 {
2545 (aaa)
2546 - (bbb)
2547 - (ccc)
2548 + (CCC)
2549 } [\u{2193}]
2550 [ [\u{2191}]
2551 (ddd)
2552 (EEE)
2553 ] [\u{2193}]"
2554 )
2555 );
2556
2557 assert_eq!(
2558 snapshot.map_excerpt_ranges(
2559 snapshot.point_to_offset(Point::new(1, 3))..snapshot.point_to_offset(Point::new(1, 3)),
2560 |buffer, excerpt_range, input_range| {
2561 assert_eq!(
2562 buffer.offset_to_point(input_range.start.0)
2563 ..buffer.offset_to_point(input_range.end.0),
2564 Point::new(1, 3)..Point::new(1, 3),
2565 );
2566 assert_eq!(
2567 buffer.offset_to_point(excerpt_range.context.start.0)
2568 ..buffer.offset_to_point(excerpt_range.context.end.0),
2569 Point::new(0, 0)..Point::new(3, 1),
2570 );
2571 vec![
2572 (input_range.start..BufferOffset(input_range.start.0 + 3), ()),
2573 (excerpt_range.context, ()),
2574 (
2575 BufferOffset(text::ToOffset::to_offset(&Point::new(2, 2), buffer))
2576 ..BufferOffset(text::ToOffset::to_offset(&Point::new(2, 7), buffer)),
2577 (),
2578 ),
2579 (
2580 BufferOffset(text::ToOffset::to_offset(&Point::new(0, 0), buffer))
2581 ..BufferOffset(text::ToOffset::to_offset(&Point::new(2, 0), buffer)),
2582 (),
2583 ),
2584 ]
2585 },
2586 ),
2587 Some(vec![
2588 (
2589 snapshot.point_to_offset(Point::new(1, 3))
2590 ..snapshot.point_to_offset(Point::new(1, 6)),
2591 (),
2592 ),
2593 (
2594 snapshot.point_to_offset(Point::zero())..snapshot.point_to_offset(Point::new(5, 1)),
2595 ()
2596 ),
2597 (
2598 snapshot.point_to_offset(Point::new(4, 2))
2599 ..snapshot.point_to_offset(Point::new(4, 7)),
2600 (),
2601 ),
2602 (
2603 snapshot.point_to_offset(Point::zero())..snapshot.point_to_offset(Point::new(4, 0)),
2604 ()
2605 ),
2606 ]),
2607 );
2608
2609 assert_eq!(
2610 snapshot.map_excerpt_ranges(
2611 snapshot.point_to_offset(Point::new(5, 0))..snapshot.point_to_offset(Point::new(7, 0)),
2612 |_, _, range| vec![(range, ())],
2613 ),
2614 None,
2615 );
2616
2617 assert_eq!(
2618 snapshot.map_excerpt_ranges(
2619 snapshot.point_to_offset(Point::new(7, 3))..snapshot.point_to_offset(Point::new(7, 6)),
2620 |buffer, excerpt_range, input_range| {
2621 assert_eq!(
2622 buffer.offset_to_point(input_range.start.0)
2623 ..buffer.offset_to_point(input_range.end.0),
2624 Point::new(8, 3)..Point::new(8, 6),
2625 );
2626 assert_eq!(
2627 buffer.offset_to_point(excerpt_range.context.start.0)
2628 ..buffer.offset_to_point(excerpt_range.context.end.0),
2629 Point::new(7, 0)..Point::new(10, 1),
2630 );
2631 vec![(input_range, ())]
2632 },
2633 ),
2634 Some(vec![(
2635 snapshot.point_to_offset(Point::new(7, 3))..snapshot.point_to_offset(Point::new(7, 6)),
2636 (),
2637 )]),
2638 );
2639}
2640
2641#[gpui::test]
2642async fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
2643 let base_text_1 = indoc!(
2644 "
2645 one
2646 two
2647 three
2648 four
2649 five
2650 six
2651 "
2652 );
2653 let text_1 = indoc!(
2654 "
2655 ZERO
2656 one
2657 TWO
2658 three
2659 six
2660 "
2661 );
2662 let base_text_2 = indoc!(
2663 "
2664 seven
2665 eight
2666 nine
2667 ten
2668 eleven
2669 twelve
2670 "
2671 );
2672 let text_2 = indoc!(
2673 "
2674 eight
2675 nine
2676 eleven
2677 THIRTEEN
2678 FOURTEEN
2679 "
2680 );
2681
2682 let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
2683 let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
2684 let diff_1 = cx.new(|cx| {
2685 BufferDiff::new_with_base_text(base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
2686 });
2687 let diff_2 = cx.new(|cx| {
2688 BufferDiff::new_with_base_text(base_text_2, &buffer_2.read(cx).text_snapshot(), cx)
2689 });
2690 cx.run_until_parked();
2691
2692 let multibuffer = cx.new(|cx| {
2693 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
2694 multibuffer.set_excerpts_for_path(
2695 PathKey::sorted(0),
2696 buffer_1.clone(),
2697 [Point::zero()..buffer_1.read(cx).max_point()],
2698 0,
2699 cx,
2700 );
2701 multibuffer.set_excerpts_for_path(
2702 PathKey::sorted(1),
2703 buffer_2.clone(),
2704 [Point::zero()..buffer_2.read(cx).max_point()],
2705 0,
2706 cx,
2707 );
2708 multibuffer.add_diff(diff_1.clone(), cx);
2709 multibuffer.add_diff(diff_2.clone(), cx);
2710 multibuffer
2711 });
2712
2713 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
2714 (multibuffer.snapshot(cx), multibuffer.subscribe())
2715 });
2716 assert_eq!(
2717 snapshot.text(),
2718 indoc!(
2719 "
2720 ZERO
2721 one
2722 TWO
2723 three
2724 six
2725
2726 eight
2727 nine
2728 eleven
2729 THIRTEEN
2730 FOURTEEN
2731 "
2732 ),
2733 );
2734
2735 multibuffer.update(cx, |multibuffer, cx| {
2736 multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
2737 });
2738
2739 assert_new_snapshot(
2740 &multibuffer,
2741 &mut snapshot,
2742 &mut subscription,
2743 cx,
2744 indoc!(
2745 "
2746 + ZERO
2747 one
2748 - two
2749 + TWO
2750 three
2751 - four
2752 - five
2753 six
2754
2755 - seven
2756 eight
2757 nine
2758 - ten
2759 eleven
2760 - twelve
2761 + THIRTEEN
2762 + FOURTEEN
2763 "
2764 ),
2765 );
2766
2767 let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
2768 let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
2769 let base_id_1 = diff_1.read_with(cx, |diff, cx| diff.base_text(cx).remote_id());
2770 let base_id_2 = diff_2.read_with(cx, |diff, cx| diff.base_text(cx).remote_id());
2771
2772 let buffer_lines = (0..=snapshot.max_row().0)
2773 .map(|row| {
2774 let (buffer, range) = snapshot.buffer_line_for_row(MultiBufferRow(row))?;
2775 Some((
2776 buffer.remote_id(),
2777 buffer.text_for_range(range).collect::<String>(),
2778 ))
2779 })
2780 .collect::<Vec<_>>();
2781 pretty_assertions::assert_eq!(
2782 buffer_lines,
2783 [
2784 Some((id_1, "ZERO".into())),
2785 Some((id_1, "one".into())),
2786 Some((base_id_1, "two".into())),
2787 Some((id_1, "TWO".into())),
2788 Some((id_1, " three".into())),
2789 Some((base_id_1, "four".into())),
2790 Some((base_id_1, "five".into())),
2791 Some((id_1, "six".into())),
2792 Some((id_1, "".into())),
2793 Some((base_id_2, "seven".into())),
2794 Some((id_2, " eight".into())),
2795 Some((id_2, "nine".into())),
2796 Some((base_id_2, "ten".into())),
2797 Some((id_2, "eleven".into())),
2798 Some((base_id_2, "twelve".into())),
2799 Some((id_2, "THIRTEEN".into())),
2800 Some((id_2, "FOURTEEN".into())),
2801 Some((id_2, "".into())),
2802 ]
2803 );
2804
2805 let buffer_ids_by_range = [
2806 (Point::new(0, 0)..Point::new(0, 0), &[id_1] as &[_]),
2807 (Point::new(0, 0)..Point::new(2, 0), &[id_1]),
2808 (Point::new(2, 0)..Point::new(2, 0), &[id_1]),
2809 (Point::new(3, 0)..Point::new(3, 0), &[id_1]),
2810 (Point::new(8, 0)..Point::new(9, 0), &[id_1]),
2811 (Point::new(8, 0)..Point::new(10, 0), &[id_1, id_2]),
2812 (Point::new(9, 0)..Point::new(9, 0), &[id_2]),
2813 ];
2814 for (range, buffer_ids) in buffer_ids_by_range {
2815 assert_eq!(
2816 snapshot
2817 .buffer_ids_for_range(range.clone())
2818 .collect::<Vec<_>>(),
2819 buffer_ids,
2820 "buffer_ids_for_range({range:?}"
2821 );
2822 }
2823
2824 assert_position_translation(&snapshot);
2825 assert_line_indents(&snapshot);
2826
2827 assert_eq!(
2828 snapshot
2829 .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.len())
2830 .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
2831 .collect::<Vec<_>>(),
2832 &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
2833 );
2834
2835 buffer_2.update(cx, |buffer, cx| {
2836 buffer.edit_via_marked_text(
2837 indoc!(
2838 "
2839 eight
2840 «»eleven
2841 THIRTEEN
2842 FOURTEEN
2843 "
2844 ),
2845 None,
2846 cx,
2847 );
2848 });
2849
2850 assert_new_snapshot(
2851 &multibuffer,
2852 &mut snapshot,
2853 &mut subscription,
2854 cx,
2855 indoc!(
2856 "
2857 + ZERO
2858 one
2859 - two
2860 + TWO
2861 three
2862 - four
2863 - five
2864 six
2865
2866 - seven
2867 eight
2868 eleven
2869 - twelve
2870 + THIRTEEN
2871 + FOURTEEN
2872 "
2873 ),
2874 );
2875
2876 assert_line_indents(&snapshot);
2877}
2878
2879/// A naive implementation of a multi-buffer that does not maintain
2880/// any derived state, used for comparison in a randomized test.
2881#[derive(Default)]
2882struct ReferenceMultibuffer {
2883 excerpts: Vec<ReferenceExcerpt>,
2884 diffs: HashMap<BufferId, Entity<BufferDiff>>,
2885 inverted_diffs: HashMap<BufferId, (Entity<BufferDiff>, Entity<language::Buffer>)>,
2886 expanded_diff_hunks_by_buffer: HashMap<BufferId, Vec<text::Anchor>>,
2887}
2888
2889#[derive(Clone, Debug)]
2890struct ReferenceExcerpt {
2891 path_key: PathKey,
2892 path_key_index: PathKeyIndex,
2893 buffer: Entity<Buffer>,
2894 range: Range<text::Anchor>,
2895}
2896
2897#[derive(Clone, Debug)]
2898struct ReferenceRegion {
2899 buffer_id: Option<BufferId>,
2900 range: Range<usize>,
2901 buffer_range: Option<Range<Point>>,
2902 status: Option<DiffHunkStatus>,
2903 excerpt_range: Option<Range<text::Anchor>>,
2904 excerpt_path_key_index: Option<PathKeyIndex>,
2905}
2906
2907impl ReferenceMultibuffer {
2908 fn expand_excerpts(
2909 &mut self,
2910 excerpts: &HashSet<ExcerptRange<text::Anchor>>,
2911 line_count: u32,
2912 cx: &mut App,
2913 ) {
2914 use text::AnchorRangeExt as _;
2915
2916 if line_count == 0 || excerpts.is_empty() {
2917 return;
2918 }
2919
2920 let mut excerpts_by_buffer: HashMap<BufferId, Vec<ExcerptRange<text::Anchor>>> =
2921 HashMap::default();
2922 for excerpt in excerpts {
2923 excerpts_by_buffer
2924 .entry(excerpt.context.start.buffer_id)
2925 .or_default()
2926 .push(excerpt.clone())
2927 }
2928
2929 for (buffer_id, excerpts_to_expand) in excerpts_by_buffer {
2930 let mut buffer = None;
2931 let mut buffer_snapshot = None;
2932 let mut path = None;
2933 let mut path_key_index = None;
2934 let mut new_ranges =
2935 self.excerpts
2936 .iter()
2937 .filter(|excerpt| excerpt.range.start.buffer_id == buffer_id)
2938 .map(|excerpt| {
2939 let snapshot = excerpt.buffer.read(cx).snapshot();
2940 let mut range = excerpt.range.to_point(&snapshot);
2941 if excerpts_to_expand.iter().any(|info| {
2942 excerpt.range.contains_anchor(info.context.start, &snapshot)
2943 }) {
2944 range.start = Point::new(range.start.row.saturating_sub(line_count), 0);
2945 range.end = snapshot
2946 .clip_point(Point::new(range.end.row + line_count, 0), Bias::Left);
2947 range.end.column = snapshot.line_len(range.end.row);
2948 }
2949 buffer = Some(excerpt.buffer.clone());
2950 buffer_snapshot = Some(snapshot);
2951 path = Some(excerpt.path_key.clone());
2952 path_key_index = Some(excerpt.path_key_index);
2953 ExcerptRange::new(range)
2954 })
2955 .collect::<Vec<_>>();
2956
2957 new_ranges.sort_by(|l, r| l.context.start.cmp(&r.context.start));
2958
2959 self.set_excerpts(
2960 path.unwrap(),
2961 path_key_index.unwrap(),
2962 buffer.unwrap(),
2963 &buffer_snapshot.unwrap(),
2964 new_ranges,
2965 cx,
2966 );
2967 }
2968 }
2969
2970 fn set_excerpts(
2971 &mut self,
2972 path_key: PathKey,
2973 path_key_index: PathKeyIndex,
2974 buffer: Entity<Buffer>,
2975 buffer_snapshot: &BufferSnapshot,
2976 ranges: Vec<ExcerptRange<Point>>,
2977 cx: &mut App,
2978 ) {
2979 self.excerpts.retain(|excerpt| {
2980 excerpt.path_key != path_key && excerpt.buffer.entity_id() != buffer.entity_id()
2981 });
2982
2983 let ranges = MultiBuffer::merge_excerpt_ranges(&ranges);
2984
2985 let (Ok(ix) | Err(ix)) = self
2986 .excerpts
2987 .binary_search_by(|probe| probe.path_key.cmp(&path_key));
2988 self.excerpts.splice(
2989 ix..ix,
2990 ranges.into_iter().map(|range| ReferenceExcerpt {
2991 path_key: path_key.clone(),
2992 path_key_index,
2993 buffer: buffer.clone(),
2994 range: buffer_snapshot.anchor_before(range.context.start)
2995 ..buffer_snapshot.anchor_after(range.context.end),
2996 }),
2997 );
2998 self.update_expanded_diff_hunks_for_buffer(buffer_snapshot.remote_id(), cx);
2999 }
3000
3001 fn expand_diff_hunks(&mut self, path_key: PathKey, range: Range<text::Anchor>, cx: &App) {
3002 let excerpt = self
3003 .excerpts
3004 .iter_mut()
3005 .find(|e| {
3006 e.path_key == path_key
3007 && e.range
3008 .start
3009 .cmp(&range.start, &e.buffer.read(cx).snapshot())
3010 .is_le()
3011 && e.range
3012 .end
3013 .cmp(&range.end, &e.buffer.read(cx).snapshot())
3014 .is_ge()
3015 })
3016 .unwrap();
3017 let buffer = excerpt.buffer.read(cx).snapshot();
3018 let buffer_id = buffer.remote_id();
3019
3020 // Skip inverted excerpts - hunks are always expanded
3021 if self.inverted_diffs.contains_key(&buffer_id) {
3022 return;
3023 }
3024
3025 let Some(diff) = self.diffs.get(&buffer_id) else {
3026 return;
3027 };
3028 let excerpt_range = excerpt.range.to_point(&buffer);
3029 let expanded_diff_hunks = self
3030 .expanded_diff_hunks_by_buffer
3031 .entry(buffer_id)
3032 .or_default();
3033 for hunk in diff
3034 .read(cx)
3035 .snapshot(cx)
3036 .hunks_intersecting_range(range, &buffer)
3037 {
3038 let hunk_range = hunk.buffer_range.to_point(&buffer);
3039 if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end {
3040 continue;
3041 }
3042 if let Err(ix) = expanded_diff_hunks
3043 .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
3044 {
3045 log::info!(
3046 "expanding diff hunk {:?}. excerpt range: {:?}, buffer {:?}",
3047 hunk_range,
3048 excerpt_range,
3049 buffer.remote_id()
3050 );
3051 expanded_diff_hunks.insert(ix, hunk.buffer_range.start);
3052 } else {
3053 log::trace!("hunk {hunk_range:?} already expanded in excerpt");
3054 }
3055 }
3056 }
3057
3058 fn expected_content(&self, cx: &App) -> (String, Vec<RowInfo>, HashSet<MultiBufferRow>) {
3059 use util::maybe;
3060
3061 let mut text = String::new();
3062 let mut regions = Vec::<ReferenceRegion>::new();
3063 let mut excerpt_boundary_rows = HashSet::default();
3064 for excerpt in &self.excerpts {
3065 excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
3066 let buffer = excerpt.buffer.read(cx);
3067 let buffer_id = buffer.remote_id();
3068 let buffer_range = excerpt.range.to_offset(buffer);
3069
3070 if let Some((diff, main_buffer)) = self.inverted_diffs.get(&buffer_id) {
3071 let diff_snapshot = diff.read(cx).snapshot(cx);
3072 let main_buffer_snapshot = main_buffer.read(cx).snapshot();
3073
3074 let mut offset = buffer_range.start;
3075 for hunk in diff_snapshot.hunks_intersecting_base_text_range(
3076 buffer_range.clone(),
3077 &main_buffer_snapshot.text,
3078 ) {
3079 let mut hunk_base_range = hunk.diff_base_byte_range.clone();
3080
3081 hunk_base_range.end = hunk_base_range.end.min(buffer_range.end);
3082 if hunk_base_range.start > buffer_range.end
3083 || hunk_base_range.start < buffer_range.start
3084 {
3085 continue;
3086 }
3087
3088 // Add the text before the hunk
3089 if hunk_base_range.start >= offset {
3090 let len = text.len();
3091 text.extend(buffer.text_for_range(offset..hunk_base_range.start));
3092 if text.len() > len {
3093 regions.push(ReferenceRegion {
3094 buffer_id: Some(buffer_id),
3095 range: len..text.len(),
3096 buffer_range: Some(
3097 (offset..hunk_base_range.start).to_point(&buffer),
3098 ),
3099 status: None,
3100 excerpt_range: Some(excerpt.range.clone()),
3101 excerpt_path_key_index: Some(excerpt.path_key_index),
3102 });
3103 }
3104 }
3105
3106 // Add the "deleted" region (base text that's not in main)
3107 if !hunk_base_range.is_empty() {
3108 let len = text.len();
3109 text.extend(buffer.text_for_range(hunk_base_range.clone()));
3110 regions.push(ReferenceRegion {
3111 buffer_id: Some(buffer_id),
3112 range: len..text.len(),
3113 buffer_range: Some(hunk_base_range.to_point(&buffer)),
3114 status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
3115 excerpt_range: Some(excerpt.range.clone()),
3116 excerpt_path_key_index: Some(excerpt.path_key_index),
3117 });
3118 }
3119
3120 offset = hunk_base_range.end;
3121 }
3122
3123 // Add remaining buffer text
3124 let len = text.len();
3125 text.extend(buffer.text_for_range(offset..buffer_range.end));
3126 text.push('\n');
3127 regions.push(ReferenceRegion {
3128 buffer_id: Some(buffer_id),
3129 range: len..text.len(),
3130 buffer_range: Some((offset..buffer_range.end).to_point(&buffer)),
3131 status: None,
3132 excerpt_range: Some(excerpt.range.clone()),
3133 excerpt_path_key_index: Some(excerpt.path_key_index),
3134 });
3135 } else {
3136 let diff = self.diffs.get(&buffer_id).unwrap().read(cx).snapshot(cx);
3137 let base_buffer = diff.base_text();
3138
3139 let mut offset = buffer_range.start;
3140 let hunks = diff
3141 .hunks_intersecting_range(excerpt.range.clone(), buffer)
3142 .peekable();
3143
3144 for hunk in hunks {
3145 // Ignore hunks that are outside the excerpt range.
3146 let mut hunk_range = hunk.buffer_range.to_offset(buffer);
3147
3148 hunk_range.end = hunk_range.end.min(buffer_range.end);
3149 if hunk_range.start > buffer_range.end || hunk_range.start < buffer_range.start
3150 {
3151 log::trace!("skipping hunk outside excerpt range");
3152 continue;
3153 }
3154
3155 if !self
3156 .expanded_diff_hunks_by_buffer
3157 .get(&buffer_id)
3158 .cloned()
3159 .into_iter()
3160 .flatten()
3161 .any(|expanded_anchor| {
3162 expanded_anchor
3163 .cmp(&hunk.buffer_range.start, buffer)
3164 .is_eq()
3165 })
3166 {
3167 log::trace!("skipping a hunk that's not marked as expanded");
3168 continue;
3169 }
3170
3171 if !hunk.buffer_range.start.is_valid(buffer) {
3172 log::trace!("skipping hunk with deleted start: {:?}", hunk.range);
3173 continue;
3174 }
3175
3176 if hunk_range.start >= offset {
3177 // Add the buffer text before the hunk
3178 let len = text.len();
3179 text.extend(buffer.text_for_range(offset..hunk_range.start));
3180 if text.len() > len {
3181 regions.push(ReferenceRegion {
3182 buffer_id: Some(buffer_id),
3183 range: len..text.len(),
3184 buffer_range: Some((offset..hunk_range.start).to_point(&buffer)),
3185 status: None,
3186 excerpt_range: Some(excerpt.range.clone()),
3187 excerpt_path_key_index: Some(excerpt.path_key_index),
3188 });
3189 }
3190
3191 // Add the deleted text for the hunk.
3192 if !hunk.diff_base_byte_range.is_empty() {
3193 let mut base_text = base_buffer
3194 .text_for_range(hunk.diff_base_byte_range.clone())
3195 .collect::<String>();
3196 if !base_text.ends_with('\n') {
3197 base_text.push('\n');
3198 }
3199 let len = text.len();
3200 text.push_str(&base_text);
3201 regions.push(ReferenceRegion {
3202 buffer_id: Some(base_buffer.remote_id()),
3203 range: len..text.len(),
3204 buffer_range: Some(
3205 hunk.diff_base_byte_range.to_point(&base_buffer),
3206 ),
3207 status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
3208 excerpt_range: Some(excerpt.range.clone()),
3209 excerpt_path_key_index: Some(excerpt.path_key_index),
3210 });
3211 }
3212
3213 offset = hunk_range.start;
3214 }
3215
3216 // Add the inserted text for the hunk.
3217 if hunk_range.end > offset {
3218 let len = text.len();
3219 text.extend(buffer.text_for_range(offset..hunk_range.end));
3220 let range = len..text.len();
3221 let region = ReferenceRegion {
3222 buffer_id: Some(buffer_id),
3223 range,
3224 buffer_range: Some((offset..hunk_range.end).to_point(&buffer)),
3225 status: Some(DiffHunkStatus::added(hunk.secondary_status)),
3226 excerpt_range: Some(excerpt.range.clone()),
3227 excerpt_path_key_index: Some(excerpt.path_key_index),
3228 };
3229 offset = hunk_range.end;
3230 regions.push(region);
3231 }
3232 }
3233
3234 // Add the buffer text for the rest of the excerpt.
3235 let len = text.len();
3236 text.extend(buffer.text_for_range(offset..buffer_range.end));
3237 text.push('\n');
3238 regions.push(ReferenceRegion {
3239 buffer_id: Some(buffer_id),
3240 range: len..text.len(),
3241 buffer_range: Some((offset..buffer_range.end).to_point(&buffer)),
3242 status: None,
3243 excerpt_range: Some(excerpt.range.clone()),
3244 excerpt_path_key_index: Some(excerpt.path_key_index),
3245 });
3246 }
3247 }
3248
3249 // Remove final trailing newline.
3250 if self.excerpts.is_empty() {
3251 regions.push(ReferenceRegion {
3252 buffer_id: None,
3253 range: 0..1,
3254 buffer_range: Some(Point::new(0, 0)..Point::new(0, 1)),
3255 status: None,
3256 excerpt_range: None,
3257 excerpt_path_key_index: None,
3258 });
3259 } else {
3260 text.pop();
3261 }
3262
3263 // Retrieve the row info using the region that contains
3264 // the start of each multi-buffer line.
3265 let mut ix = 0;
3266 let row_infos = text
3267 .split('\n')
3268 .map(|line| {
3269 let row_info = regions
3270 .iter()
3271 .position(|region| region.range.contains(&ix))
3272 .map_or(RowInfo::default(), |region_ix| {
3273 let region = regions[region_ix].clone();
3274 let buffer_row = region.buffer_range.as_ref().map(|buffer_range| {
3275 buffer_range.start.row
3276 + text[region.range.start..ix].matches('\n').count() as u32
3277 });
3278 let main_buffer = self
3279 .excerpts
3280 .iter()
3281 .find(|e| e.range == region.excerpt_range.clone().unwrap())
3282 .map(|e| e.buffer.clone());
3283 let is_excerpt_start = region_ix == 0
3284 || ®ions[region_ix - 1].excerpt_range != ®ion.excerpt_range
3285 || regions[region_ix - 1].range.is_empty();
3286 let mut is_excerpt_end = region_ix == regions.len() - 1
3287 || ®ions[region_ix + 1].excerpt_range != ®ion.excerpt_range;
3288 let is_start = !text[region.range.start..ix].contains('\n');
3289 let mut is_end = if region.range.end > text.len() {
3290 !text[ix..].contains('\n')
3291 } else {
3292 text[ix..region.range.end.min(text.len())]
3293 .matches('\n')
3294 .count()
3295 == 1
3296 };
3297 if region_ix < regions.len() - 1
3298 && !text[ix..].contains("\n")
3299 && (region.status == Some(DiffHunkStatus::added_none())
3300 || region.status.is_some_and(|s| s.is_deleted()))
3301 && regions[region_ix + 1].excerpt_range == region.excerpt_range
3302 && regions[region_ix + 1].range.start == text.len()
3303 {
3304 is_end = true;
3305 is_excerpt_end = true;
3306 }
3307 let multibuffer_row =
3308 MultiBufferRow(text[..ix].matches('\n').count() as u32);
3309 let mut expand_direction = None;
3310 if let Some(buffer) = &main_buffer {
3311 let buffer_row = buffer_row.unwrap();
3312 let needs_expand_up = is_excerpt_start && is_start && buffer_row > 0;
3313 let needs_expand_down = is_excerpt_end
3314 && is_end
3315 && buffer.read(cx).max_point().row > buffer_row;
3316 expand_direction = if needs_expand_up && needs_expand_down {
3317 Some(ExpandExcerptDirection::UpAndDown)
3318 } else if needs_expand_up {
3319 Some(ExpandExcerptDirection::Up)
3320 } else if needs_expand_down {
3321 Some(ExpandExcerptDirection::Down)
3322 } else {
3323 None
3324 };
3325 }
3326 RowInfo {
3327 buffer_id: region.buffer_id,
3328 diff_status: region.status,
3329 buffer_row,
3330 wrapped_buffer_row: None,
3331
3332 multibuffer_row: Some(multibuffer_row),
3333 expand_info: maybe!({
3334 let direction = expand_direction?;
3335 let excerpt_range = region.excerpt_range?;
3336 let path_key_index = region.excerpt_path_key_index?;
3337 Some(ExpandInfo {
3338 direction,
3339 start_anchor: Anchor::in_buffer(
3340 path_key_index,
3341 excerpt_range.start,
3342 ),
3343 })
3344 }),
3345 }
3346 });
3347 ix += line.len() + 1;
3348 row_info
3349 })
3350 .collect();
3351
3352 (text, row_infos, excerpt_boundary_rows)
3353 }
3354
3355 fn diffs_updated(&mut self, cx: &mut App) {
3356 let buffer_ids = self.diffs.keys().copied().collect::<Vec<_>>();
3357 for buffer_id in buffer_ids {
3358 self.update_expanded_diff_hunks_for_buffer(buffer_id, cx);
3359 }
3360 }
3361
3362 fn add_diff(&mut self, diff: Entity<BufferDiff>, cx: &mut App) {
3363 let buffer_id = diff.read(cx).buffer_id;
3364 self.diffs.insert(buffer_id, diff);
3365 }
3366
3367 fn add_inverted_diff(
3368 &mut self,
3369 diff: Entity<BufferDiff>,
3370 main_buffer: Entity<language::Buffer>,
3371 cx: &App,
3372 ) {
3373 let base_text_buffer_id = diff.read(cx).base_text(cx).remote_id();
3374 self.inverted_diffs
3375 .insert(base_text_buffer_id, (diff, main_buffer));
3376 }
3377
3378 fn update_expanded_diff_hunks_for_buffer(&mut self, buffer_id: BufferId, cx: &mut App) {
3379 let excerpts = self
3380 .excerpts
3381 .iter()
3382 .filter(|excerpt| excerpt.buffer.read(cx).remote_id() == buffer_id)
3383 .collect::<Vec<_>>();
3384 let Some(buffer) = excerpts.first().map(|excerpt| excerpt.buffer.clone()) else {
3385 self.expanded_diff_hunks_by_buffer.remove(&buffer_id);
3386 return;
3387 };
3388 let buffer_snapshot = buffer.read(cx).snapshot();
3389 let Some(diff) = self.diffs.get(&buffer_id) else {
3390 self.expanded_diff_hunks_by_buffer.remove(&buffer_id);
3391 return;
3392 };
3393 let diff = diff.read(cx).snapshot(cx);
3394 let hunks = diff
3395 .hunks_in_row_range(0..u32::MAX, &buffer_snapshot)
3396 .collect::<Vec<_>>();
3397 self.expanded_diff_hunks_by_buffer
3398 .entry(buffer_id)
3399 .or_default()
3400 .retain(|hunk_anchor| {
3401 if !hunk_anchor.is_valid(&buffer_snapshot) {
3402 return false;
3403 }
3404
3405 let Ok(ix) = hunks.binary_search_by(|hunk| {
3406 hunk.buffer_range.start.cmp(hunk_anchor, &buffer_snapshot)
3407 }) else {
3408 return false;
3409 };
3410 let hunk_range = hunks[ix].buffer_range.to_point(&buffer_snapshot);
3411 excerpts.iter().any(|excerpt| {
3412 let excerpt_range = excerpt.range.to_point(&buffer_snapshot);
3413 hunk_range.start >= excerpt_range.start && hunk_range.start <= excerpt_range.end
3414 })
3415 });
3416 }
3417}
3418
3419#[gpui::test(iterations = 100)]
3420async fn test_random_set_ranges(cx: &mut TestAppContext, mut rng: StdRng) {
3421 let base_text = "a\n".repeat(100);
3422 let buf = cx.update(|cx| cx.new(|cx| Buffer::local(base_text, cx)));
3423 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
3424
3425 let operations = env::var("OPERATIONS")
3426 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3427 .unwrap_or(10);
3428
3429 fn row_ranges(ranges: &Vec<Range<Point>>) -> Vec<Range<u32>> {
3430 ranges
3431 .iter()
3432 .map(|range| range.start.row..range.end.row)
3433 .collect()
3434 }
3435
3436 for _ in 0..operations {
3437 let snapshot = buf.update(cx, |buf, _| buf.snapshot());
3438 let num_ranges = rng.random_range(0..=10);
3439 let max_row = snapshot.max_point().row;
3440 let mut ranges = (0..num_ranges)
3441 .map(|_| {
3442 let start = rng.random_range(0..max_row);
3443 let end = rng.random_range(start + 1..max_row + 1);
3444 Point::row_range(start..end)
3445 })
3446 .collect::<Vec<_>>();
3447 ranges.sort_by_key(|range| range.start);
3448 log::info!("Setting ranges: {:?}", row_ranges(&ranges));
3449 multibuffer.update(cx, |multibuffer, cx| {
3450 multibuffer.set_excerpts_for_path(
3451 PathKey::for_buffer(&buf, cx),
3452 buf.clone(),
3453 ranges.clone(),
3454 2,
3455 cx,
3456 )
3457 });
3458
3459 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3460 let mut last_end = None;
3461 let mut seen_ranges = Vec::default();
3462
3463 for info in snapshot.excerpts() {
3464 let buffer_snapshot = snapshot
3465 .buffer_for_id(info.context.start.buffer_id)
3466 .unwrap();
3467 let start = info.context.start.to_point(buffer_snapshot);
3468 let end = info.context.end.to_point(buffer_snapshot);
3469 seen_ranges.push(start..end);
3470
3471 if let Some(last_end) = last_end.take() {
3472 assert!(
3473 start > last_end,
3474 "multibuffer has out-of-order ranges: {:?}; {:?} <= {:?}",
3475 row_ranges(&seen_ranges),
3476 start,
3477 last_end
3478 )
3479 }
3480
3481 ranges.retain(|range| range.start < start || range.end > end);
3482
3483 last_end = Some(end)
3484 }
3485
3486 assert!(
3487 ranges.is_empty(),
3488 "multibuffer {:?} did not include all ranges: {:?}",
3489 row_ranges(&seen_ranges),
3490 row_ranges(&ranges)
3491 );
3492 }
3493}
3494
3495#[gpui::test(iterations = 100)]
3496async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
3497 let operations = env::var("OPERATIONS")
3498 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3499 .unwrap_or(10);
3500 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
3501 let mut buffers: Vec<Entity<Buffer>> = Vec::new();
3502 let mut base_texts: HashMap<BufferId, String> = HashMap::default();
3503 let mut reference = ReferenceMultibuffer::default();
3504 let mut anchors = Vec::new();
3505 let mut old_versions = Vec::new();
3506 let mut needs_diff_calculation = false;
3507 let mut inverted_diff_main_buffers: HashMap<BufferId, Entity<BufferDiff>> = HashMap::default();
3508 for _ in 0..operations {
3509 match rng.random_range(0..100) {
3510 0..=14 if !buffers.is_empty() => {
3511 let buffer = buffers.choose(&mut rng).unwrap();
3512 buffer.update(cx, |buf, cx| {
3513 let edit_count = rng.random_range(1..5);
3514 buf.randomly_edit(&mut rng, edit_count, cx);
3515 log::info!("buffer text:\n{}", buf.text());
3516 needs_diff_calculation = true;
3517 });
3518 cx.update(|cx| reference.diffs_updated(cx));
3519 }
3520 15..=24 if !reference.excerpts.is_empty() => {
3521 multibuffer.update(cx, |multibuffer, cx| {
3522 let snapshot = multibuffer.snapshot(cx);
3523 let infos = snapshot.excerpts().collect::<Vec<_>>();
3524 let mut excerpts = HashSet::default();
3525 for _ in 0..rng.random_range(0..infos.len()) {
3526 excerpts.extend(infos.choose(&mut rng).cloned());
3527 }
3528
3529 let line_count = rng.random_range(0..5);
3530
3531 let excerpt_ixs = excerpts
3532 .iter()
3533 .map(|info| {
3534 reference
3535 .excerpts
3536 .iter()
3537 .position(|e| e.range == info.context)
3538 .unwrap()
3539 })
3540 .collect::<Vec<_>>();
3541 log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
3542 multibuffer.expand_excerpts(
3543 excerpts
3544 .iter()
3545 .map(|info| snapshot.anchor_in_excerpt(info.context.end).unwrap()),
3546 line_count,
3547 ExpandExcerptDirection::UpAndDown,
3548 cx,
3549 );
3550
3551 reference.expand_excerpts(&excerpts, line_count, cx);
3552 });
3553 }
3554 25..=34 if !reference.excerpts.is_empty() => {
3555 let multibuffer =
3556 multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3557 let offset = multibuffer.clip_offset(
3558 MultiBufferOffset(rng.random_range(0..=multibuffer.len().0)),
3559 Bias::Left,
3560 );
3561 let bias = if rng.random() {
3562 Bias::Left
3563 } else {
3564 Bias::Right
3565 };
3566 log::info!("Creating anchor at {} with bias {:?}", offset.0, bias);
3567 anchors.push(multibuffer.anchor_at(offset, bias));
3568 anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
3569 }
3570 35..=45 if !reference.excerpts.is_empty() => {
3571 multibuffer.update(cx, |multibuffer, cx| {
3572 let snapshot = multibuffer.snapshot(cx);
3573 let excerpt_ix = rng.random_range(0..reference.excerpts.len());
3574 let excerpt = &reference.excerpts[excerpt_ix];
3575
3576 // Skip inverted excerpts - hunks can't be collapsed
3577 let buffer_id = excerpt.buffer.read(cx).remote_id();
3578 if reference.inverted_diffs.contains_key(&buffer_id) {
3579 return;
3580 }
3581
3582 let start = excerpt.range.start;
3583 let end = excerpt.range.end;
3584 let range = snapshot.anchor_in_excerpt(start).unwrap()
3585 ..snapshot.anchor_in_excerpt(end).unwrap();
3586
3587 log::info!(
3588 "expanding diff hunks in range {:?} (excerpt index {excerpt_ix:?}, buffer id {:?})",
3589 range.to_point(&snapshot),
3590 buffer_id,
3591 );
3592 reference.expand_diff_hunks(excerpt.path_key.clone(), start..end, cx);
3593 multibuffer.expand_diff_hunks(vec![range], cx);
3594 });
3595 }
3596 46..=75 if needs_diff_calculation => {
3597 multibuffer.update(cx, |multibuffer, cx| {
3598 for buffer in multibuffer.all_buffers() {
3599 let snapshot = buffer.read(cx).snapshot();
3600 let buffer_id = snapshot.remote_id();
3601
3602 if let Some(diff) = multibuffer.diff_for(buffer_id) {
3603 diff.update(cx, |diff, cx| {
3604 log::info!("recalculating diff for buffer {:?}", buffer_id,);
3605 diff.recalculate_diff_sync(&snapshot.text, cx);
3606 });
3607 }
3608
3609 if let Some(inverted_diff) = inverted_diff_main_buffers.get(&buffer_id) {
3610 inverted_diff.update(cx, |diff, cx| {
3611 log::info!(
3612 "recalculating inverted diff for main buffer {:?}",
3613 buffer_id,
3614 );
3615 diff.recalculate_diff_sync(&snapshot.text, cx);
3616 });
3617 }
3618 }
3619 reference.diffs_updated(cx);
3620 needs_diff_calculation = false;
3621 });
3622 }
3623 _ => {
3624 // Decide if we're creating a new buffer or reusing an existing one
3625 let create_new_buffer = buffers.is_empty() || rng.random_bool(0.4);
3626
3627 let (excerpt_buffer, diff, inverted_main_buffer) = if create_new_buffer {
3628 let create_inverted = rng.random_bool(0.3);
3629
3630 if create_inverted {
3631 let mut main_buffer_text = util::RandomCharIter::new(&mut rng)
3632 .take(256)
3633 .collect::<String>();
3634 let main_buffer = cx.new(|cx| Buffer::local(main_buffer_text.clone(), cx));
3635 text::LineEnding::normalize(&mut main_buffer_text);
3636 let main_buffer_id =
3637 main_buffer.read_with(cx, |buffer, _| buffer.remote_id());
3638 base_texts.insert(main_buffer_id, main_buffer_text.clone());
3639 buffers.push(main_buffer.clone());
3640
3641 let diff = cx.new(|cx| {
3642 BufferDiff::new_with_base_text(
3643 &main_buffer_text,
3644 &main_buffer.read(cx).text_snapshot(),
3645 cx,
3646 )
3647 });
3648
3649 let base_text_buffer =
3650 diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
3651
3652 // Track for recalculation when main buffer is edited
3653 inverted_diff_main_buffers.insert(main_buffer_id, diff.clone());
3654
3655 (base_text_buffer, diff, Some(main_buffer))
3656 } else {
3657 let mut base_text = util::RandomCharIter::new(&mut rng)
3658 .take(256)
3659 .collect::<String>();
3660
3661 let buffer_handle = cx.new(|cx| Buffer::local(base_text.clone(), cx));
3662 text::LineEnding::normalize(&mut base_text);
3663 let buffer_id = buffer_handle.read_with(cx, |buffer, _| buffer.remote_id());
3664 base_texts.insert(buffer_id, base_text.clone());
3665 buffers.push(buffer_handle.clone());
3666
3667 let diff = cx.new(|cx| {
3668 BufferDiff::new_with_base_text(
3669 &base_text,
3670 &buffer_handle.read(cx).text_snapshot(),
3671 cx,
3672 )
3673 });
3674
3675 (buffer_handle, diff, None)
3676 }
3677 } else {
3678 // Reuse an existing buffer
3679 let buffer_handle = buffers.choose(&mut rng).unwrap().clone();
3680 let buffer_id = buffer_handle.read_with(cx, |buffer, _| buffer.remote_id());
3681
3682 if let Some(diff) = inverted_diff_main_buffers.get(&buffer_id) {
3683 let base_text_buffer =
3684 diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
3685 (base_text_buffer, diff.clone(), Some(buffer_handle))
3686 } else {
3687 // Get existing diff or create new one for regular buffer
3688 let diff = multibuffer
3689 .read_with(cx, |mb, _| mb.diff_for(buffer_id))
3690 .unwrap_or_else(|| {
3691 let base_text = base_texts.get(&buffer_id).unwrap();
3692 cx.new(|cx| {
3693 BufferDiff::new_with_base_text(
3694 base_text,
3695 &buffer_handle.read(cx).text_snapshot(),
3696 cx,
3697 )
3698 })
3699 });
3700 (buffer_handle, diff, None)
3701 }
3702 };
3703
3704 let excerpt_buffer_snapshot =
3705 excerpt_buffer.read_with(cx, |excerpt_buffer, _| excerpt_buffer.snapshot());
3706 let mut ranges = reference
3707 .excerpts
3708 .iter()
3709 .filter(|excerpt| excerpt.buffer == excerpt_buffer)
3710 .map(|excerpt| excerpt.range.to_point(&excerpt_buffer_snapshot))
3711 .collect::<Vec<_>>();
3712 mutate_excerpt_ranges(&mut rng, &mut ranges, &excerpt_buffer_snapshot, 1);
3713 let ranges = ranges
3714 .iter()
3715 .cloned()
3716 .map(ExcerptRange::new)
3717 .collect::<Vec<_>>();
3718 let path = cx.update(|cx| PathKey::for_buffer(&excerpt_buffer, cx));
3719 let path_key_index = multibuffer.update(cx, |multibuffer, _| {
3720 multibuffer.get_or_create_path_key_index(&path)
3721 });
3722
3723 multibuffer.update(cx, |multibuffer, cx| {
3724 multibuffer.set_excerpt_ranges_for_path(
3725 path.clone(),
3726 excerpt_buffer.clone(),
3727 &excerpt_buffer_snapshot,
3728 ranges.clone(),
3729 cx,
3730 )
3731 });
3732
3733 cx.update(|cx| {
3734 reference.set_excerpts(
3735 path,
3736 path_key_index,
3737 excerpt_buffer.clone(),
3738 &excerpt_buffer_snapshot,
3739 ranges,
3740 cx,
3741 )
3742 });
3743
3744 let excerpt_buffer_id =
3745 excerpt_buffer.read_with(cx, |buffer, _| buffer.remote_id());
3746 multibuffer.update(cx, |multibuffer, cx| {
3747 if multibuffer.diff_for(excerpt_buffer_id).is_none() {
3748 if let Some(main_buffer) = inverted_main_buffer {
3749 reference.add_inverted_diff(diff.clone(), main_buffer.clone(), cx);
3750 multibuffer.add_inverted_diff(diff, main_buffer, cx);
3751 } else {
3752 reference.add_diff(diff.clone(), cx);
3753 multibuffer.add_diff(diff, cx);
3754 }
3755 }
3756 });
3757 }
3758 }
3759
3760 if rng.random_bool(0.3) {
3761 multibuffer.update(cx, |multibuffer, cx| {
3762 old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
3763 })
3764 }
3765
3766 multibuffer.read_with(cx, |multibuffer, cx| {
3767 check_multibuffer(multibuffer, &reference, &anchors, cx, &mut rng);
3768 });
3769 }
3770 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3771 for (old_snapshot, subscription) in old_versions {
3772 check_multibuffer_edits(&snapshot, &old_snapshot, subscription);
3773 }
3774}
3775
3776fn mutate_excerpt_ranges(
3777 rng: &mut StdRng,
3778 existing_ranges: &mut Vec<Range<Point>>,
3779 buffer: &BufferSnapshot,
3780 operations: u32,
3781) {
3782 let mut ranges_to_add = Vec::new();
3783
3784 for _ in 0..operations {
3785 match rng.random_range(0..5) {
3786 0..=1 if !existing_ranges.is_empty() => {
3787 let index = rng.random_range(0..existing_ranges.len());
3788 log::info!("Removing excerpt at index {index}");
3789 existing_ranges.remove(index);
3790 }
3791 _ => {
3792 let end_row = rng.random_range(0..=buffer.max_point().row);
3793 let start_row = rng.random_range(0..=end_row);
3794 log::info!(
3795 "Inserting excerpt for buffer {:?}, row range {:?}",
3796 buffer.remote_id(),
3797 start_row..end_row
3798 );
3799 ranges_to_add.push(Point::new(start_row, 0)..Point::new(end_row, 0));
3800 }
3801 }
3802 }
3803
3804 existing_ranges.extend(ranges_to_add);
3805 existing_ranges.sort_by(|l, r| l.start.cmp(&r.start));
3806}
3807
3808fn check_multibuffer(
3809 multibuffer: &MultiBuffer,
3810 reference: &ReferenceMultibuffer,
3811 anchors: &[Anchor],
3812 cx: &App,
3813 rng: &mut StdRng,
3814) {
3815 let snapshot = multibuffer.snapshot(cx);
3816 let actual_text = snapshot.text();
3817 let actual_boundary_rows = snapshot
3818 .excerpt_boundaries_in_range(MultiBufferOffset(0)..)
3819 .map(|b| b.row)
3820 .collect::<HashSet<_>>();
3821 let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3822
3823 let (expected_text, expected_row_infos, expected_boundary_rows) =
3824 reference.expected_content(cx);
3825
3826 let has_diff = actual_row_infos
3827 .iter()
3828 .any(|info| info.diff_status.is_some())
3829 || expected_row_infos
3830 .iter()
3831 .any(|info| info.diff_status.is_some());
3832 let actual_diff = format_diff(
3833 &actual_text,
3834 &actual_row_infos,
3835 &actual_boundary_rows,
3836 Some(has_diff),
3837 );
3838 let expected_diff = format_diff(
3839 &expected_text,
3840 &expected_row_infos,
3841 &expected_boundary_rows,
3842 Some(has_diff),
3843 );
3844
3845 log::info!("Multibuffer content:\n{}", actual_diff);
3846
3847 assert_eq!(
3848 actual_row_infos.len(),
3849 actual_text.split('\n').count(),
3850 "line count: {}",
3851 actual_text.split('\n').count()
3852 );
3853 pretty_assertions::assert_eq!(actual_diff, expected_diff);
3854 pretty_assertions::assert_eq!(actual_text, expected_text);
3855 pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
3856
3857 for _ in 0..5 {
3858 let start_row = rng.random_range(0..=expected_row_infos.len());
3859 assert_eq!(
3860 snapshot
3861 .row_infos(MultiBufferRow(start_row as u32))
3862 .collect::<Vec<_>>(),
3863 &expected_row_infos[start_row..],
3864 "buffer_rows({})",
3865 start_row
3866 );
3867 }
3868
3869 assert_eq!(
3870 snapshot.widest_line_number(),
3871 expected_row_infos
3872 .into_iter()
3873 .filter_map(|info| {
3874 // For inverted diffs, deleted rows are visible and should be counted.
3875 // Only filter out deleted rows that are NOT from inverted diffs.
3876 let is_inverted_diff = info
3877 .buffer_id
3878 .is_some_and(|id| reference.inverted_diffs.contains_key(&id));
3879 if info.diff_status.is_some_and(|status| status.is_deleted()) && !is_inverted_diff {
3880 None
3881 } else {
3882 info.buffer_row
3883 }
3884 })
3885 .max()
3886 .unwrap()
3887 + 1
3888 );
3889 for i in 0..snapshot.len().0 {
3890 let (_, excerpt_range) = snapshot
3891 .excerpt_containing(MultiBufferOffset(i)..MultiBufferOffset(i))
3892 .unwrap();
3893 reference
3894 .excerpts
3895 .iter()
3896 .find(|reference_excerpt| reference_excerpt.range == excerpt_range.context)
3897 .expect("corresponding excerpt should exist in reference multibuffer");
3898 }
3899
3900 assert_consistent_line_numbers(&snapshot);
3901 assert_position_translation(&snapshot);
3902
3903 for (row, line) in expected_text.split('\n').enumerate() {
3904 assert_eq!(
3905 snapshot.line_len(MultiBufferRow(row as u32)),
3906 line.len() as u32,
3907 "line_len({}).",
3908 row
3909 );
3910 }
3911
3912 let text_rope = Rope::from(expected_text.as_str());
3913 for _ in 0..10 {
3914 let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
3915 let start_ix = text_rope.clip_offset(rng.random_range(0..=end_ix), Bias::Left);
3916
3917 let text_for_range = snapshot
3918 .text_for_range(MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix))
3919 .collect::<String>();
3920 assert_eq!(
3921 text_for_range,
3922 &expected_text[start_ix..end_ix],
3923 "incorrect text for range {:?}",
3924 start_ix..end_ix
3925 );
3926
3927 let expected_summary =
3928 MBTextSummary::from(TextSummary::from(&expected_text[start_ix..end_ix]));
3929 assert_eq!(
3930 snapshot.text_summary_for_range::<MBTextSummary, _>(
3931 MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix)
3932 ),
3933 expected_summary,
3934 "incorrect summary for range {:?}",
3935 start_ix..end_ix
3936 );
3937 }
3938
3939 // Anchor resolution
3940 let summaries = snapshot.summaries_for_anchors::<MultiBufferOffset, _>(anchors);
3941 assert_eq!(anchors.len(), summaries.len());
3942 for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
3943 assert!(resolved_offset <= snapshot.len());
3944 assert_eq!(
3945 snapshot.summary_for_anchor::<MultiBufferOffset>(anchor),
3946 resolved_offset,
3947 "anchor: {:?}",
3948 anchor
3949 );
3950 }
3951
3952 for _ in 0..10 {
3953 let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
3954 assert_eq!(
3955 snapshot
3956 .reversed_chars_at(MultiBufferOffset(end_ix))
3957 .collect::<String>(),
3958 expected_text[..end_ix].chars().rev().collect::<String>(),
3959 );
3960 }
3961
3962 for _ in 0..10 {
3963 let end_ix = rng.random_range(0..=text_rope.len());
3964 let end_ix = text_rope.floor_char_boundary(end_ix);
3965 let start_ix = rng.random_range(0..=end_ix);
3966 let start_ix = text_rope.floor_char_boundary(start_ix);
3967 assert_eq!(
3968 snapshot
3969 .bytes_in_range(MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix))
3970 .flatten()
3971 .copied()
3972 .collect::<Vec<_>>(),
3973 expected_text.as_bytes()[start_ix..end_ix].to_vec(),
3974 "bytes_in_range({:?})",
3975 start_ix..end_ix,
3976 );
3977 }
3978}
3979
3980fn check_multibuffer_edits(
3981 snapshot: &MultiBufferSnapshot,
3982 old_snapshot: &MultiBufferSnapshot,
3983 subscription: Subscription<MultiBufferOffset>,
3984) {
3985 let edits = subscription.consume().into_inner();
3986
3987 log::info!(
3988 "applying subscription edits to old text: {:?}: {:#?}",
3989 old_snapshot.text(),
3990 edits,
3991 );
3992
3993 let mut text = old_snapshot.text();
3994 for edit in edits {
3995 let new_text: String = snapshot
3996 .text_for_range(edit.new.start..edit.new.end)
3997 .collect();
3998 text.replace_range(
3999 (edit.new.start.0..edit.new.start.0 + (edit.old.end.0 - edit.old.start.0)).clone(),
4000 &new_text,
4001 );
4002 pretty_assertions::assert_eq!(
4003 &text[0..edit.new.end.0],
4004 snapshot
4005 .text_for_range(MultiBufferOffset(0)..edit.new.end)
4006 .collect::<String>()
4007 );
4008 }
4009 pretty_assertions::assert_eq!(text, snapshot.text());
4010}
4011
4012#[gpui::test]
4013fn test_history(cx: &mut App) {
4014 let test_settings = SettingsStore::test(cx);
4015 cx.set_global(test_settings);
4016
4017 let group_interval: Duration = Duration::from_millis(1);
4018 let buffer_1 = cx.new(|cx| {
4019 let mut buf = Buffer::local("1234", cx);
4020 buf.set_group_interval(group_interval);
4021 buf
4022 });
4023 let buffer_2 = cx.new(|cx| {
4024 let mut buf = Buffer::local("5678", cx);
4025 buf.set_group_interval(group_interval);
4026 buf
4027 });
4028 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
4029 multibuffer.update(cx, |this, cx| {
4030 this.set_group_interval(group_interval, cx);
4031 });
4032 multibuffer.update(cx, |multibuffer, cx| {
4033 multibuffer.set_excerpts_for_path(
4034 PathKey::sorted(0),
4035 buffer_1.clone(),
4036 [Point::zero()..buffer_1.read(cx).max_point()],
4037 0,
4038 cx,
4039 );
4040 multibuffer.set_excerpts_for_path(
4041 PathKey::sorted(1),
4042 buffer_2.clone(),
4043 [Point::zero()..buffer_2.read(cx).max_point()],
4044 0,
4045 cx,
4046 );
4047 });
4048
4049 let mut now = Instant::now();
4050
4051 multibuffer.update(cx, |multibuffer, cx| {
4052 let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
4053 multibuffer.edit(
4054 [
4055 (Point::new(0, 0)..Point::new(0, 0), "A"),
4056 (Point::new(1, 0)..Point::new(1, 0), "A"),
4057 ],
4058 None,
4059 cx,
4060 );
4061 multibuffer.edit(
4062 [
4063 (Point::new(0, 1)..Point::new(0, 1), "B"),
4064 (Point::new(1, 1)..Point::new(1, 1), "B"),
4065 ],
4066 None,
4067 cx,
4068 );
4069 multibuffer.end_transaction_at(now, cx);
4070 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4071
4072 // Verify edited ranges for transaction 1
4073 assert_eq!(
4074 multibuffer.edited_ranges_for_transaction(transaction_1, cx),
4075 &[
4076 MultiBufferOffset(0)..MultiBufferOffset(2),
4077 MultiBufferOffset(7)..MultiBufferOffset(9),
4078 ]
4079 );
4080
4081 // Edit buffer 1 through the multibuffer
4082 now += 2 * group_interval;
4083 multibuffer.start_transaction_at(now, cx);
4084 multibuffer.edit(
4085 [(MultiBufferOffset(2)..MultiBufferOffset(2), "C")],
4086 None,
4087 cx,
4088 );
4089 multibuffer.end_transaction_at(now, cx);
4090 assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
4091
4092 // Edit buffer 1 independently
4093 buffer_1.update(cx, |buffer_1, cx| {
4094 buffer_1.start_transaction_at(now);
4095 buffer_1.edit([(3..3, "D")], None, cx);
4096 buffer_1.end_transaction_at(now, cx);
4097
4098 now += 2 * group_interval;
4099 buffer_1.start_transaction_at(now);
4100 buffer_1.edit([(4..4, "E")], None, cx);
4101 buffer_1.end_transaction_at(now, cx);
4102 });
4103 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
4104
4105 // An undo in the multibuffer undoes the multibuffer transaction
4106 // and also any individual buffer edits that have occurred since
4107 // that transaction.
4108 multibuffer.undo(cx);
4109 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4110
4111 multibuffer.undo(cx);
4112 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4113
4114 multibuffer.redo(cx);
4115 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4116
4117 multibuffer.redo(cx);
4118 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
4119
4120 // Undo buffer 2 independently.
4121 buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
4122 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
4123
4124 // An undo in the multibuffer undoes the components of the
4125 // the last multibuffer transaction that are not already undone.
4126 multibuffer.undo(cx);
4127 assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
4128
4129 multibuffer.undo(cx);
4130 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4131
4132 multibuffer.redo(cx);
4133 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4134
4135 buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
4136 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
4137
4138 // Redo stack gets cleared after an edit.
4139 now += 2 * group_interval;
4140 multibuffer.start_transaction_at(now, cx);
4141 multibuffer.edit(
4142 [(MultiBufferOffset(0)..MultiBufferOffset(0), "X")],
4143 None,
4144 cx,
4145 );
4146 multibuffer.end_transaction_at(now, cx);
4147 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4148 multibuffer.redo(cx);
4149 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4150 multibuffer.undo(cx);
4151 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
4152 multibuffer.undo(cx);
4153 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4154
4155 // Transactions can be grouped manually.
4156 multibuffer.redo(cx);
4157 multibuffer.redo(cx);
4158 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4159 multibuffer.group_until_transaction(transaction_1, cx);
4160 multibuffer.undo(cx);
4161 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4162 multibuffer.redo(cx);
4163 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4164 });
4165}
4166
4167#[gpui::test]
4168async fn test_enclosing_indent(cx: &mut TestAppContext) {
4169 async fn enclosing_indent(
4170 text: &str,
4171 buffer_row: u32,
4172 cx: &mut TestAppContext,
4173 ) -> Option<(Range<u32>, LineIndent)> {
4174 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
4175 let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
4176 let (range, indent) = snapshot
4177 .enclosing_indent(MultiBufferRow(buffer_row))
4178 .await?;
4179 Some((range.start.0..range.end.0, indent))
4180 }
4181
4182 assert_eq!(
4183 enclosing_indent(
4184 indoc!(
4185 "
4186 fn b() {
4187 if c {
4188 let d = 2;
4189 }
4190 }
4191 "
4192 ),
4193 1,
4194 cx,
4195 )
4196 .await,
4197 Some((
4198 1..2,
4199 LineIndent {
4200 tabs: 0,
4201 spaces: 4,
4202 line_blank: false,
4203 }
4204 ))
4205 );
4206
4207 assert_eq!(
4208 enclosing_indent(
4209 indoc!(
4210 "
4211 fn b() {
4212 if c {
4213 let d = 2;
4214 }
4215 }
4216 "
4217 ),
4218 2,
4219 cx,
4220 )
4221 .await,
4222 Some((
4223 1..2,
4224 LineIndent {
4225 tabs: 0,
4226 spaces: 4,
4227 line_blank: false,
4228 }
4229 ))
4230 );
4231
4232 assert_eq!(
4233 enclosing_indent(
4234 indoc!(
4235 "
4236 fn b() {
4237 if c {
4238 let d = 2;
4239
4240 let e = 5;
4241 }
4242 }
4243 "
4244 ),
4245 3,
4246 cx,
4247 )
4248 .await,
4249 Some((
4250 1..4,
4251 LineIndent {
4252 tabs: 0,
4253 spaces: 4,
4254 line_blank: false,
4255 }
4256 ))
4257 );
4258}
4259
4260#[gpui::test]
4261async fn test_summaries_for_anchors(cx: &mut TestAppContext) {
4262 let base_text_1 = indoc!(
4263 "
4264 bar
4265 "
4266 );
4267 let text_1 = indoc!(
4268 "
4269 BAR
4270 "
4271 );
4272 let base_text_2 = indoc!(
4273 "
4274 foo
4275 "
4276 );
4277 let text_2 = indoc!(
4278 "
4279 FOO
4280 "
4281 );
4282
4283 let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
4284 let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
4285 let diff_1 = cx.new(|cx| {
4286 BufferDiff::new_with_base_text(base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
4287 });
4288 let diff_2 = cx.new(|cx| {
4289 BufferDiff::new_with_base_text(base_text_2, &buffer_2.read(cx).text_snapshot(), cx)
4290 });
4291 cx.run_until_parked();
4292
4293 let multibuffer = cx.new(|cx| {
4294 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
4295 multibuffer.set_all_diff_hunks_expanded(cx);
4296 multibuffer.set_excerpts_for_path(
4297 PathKey::sorted(0),
4298 buffer_1.clone(),
4299 [Point::zero()..buffer_1.read(cx).max_point()],
4300 0,
4301 cx,
4302 );
4303 multibuffer.set_excerpts_for_path(
4304 PathKey::sorted(1),
4305 buffer_2.clone(),
4306 [Point::zero()..buffer_2.read(cx).max_point()],
4307 0,
4308 cx,
4309 );
4310 multibuffer.add_diff(diff_1.clone(), cx);
4311 multibuffer.add_diff(diff_2.clone(), cx);
4312 multibuffer
4313 });
4314
4315 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4316 (multibuffer.snapshot(cx), multibuffer.subscribe())
4317 });
4318
4319 assert_new_snapshot(
4320 &multibuffer,
4321 &mut snapshot,
4322 &mut subscription,
4323 cx,
4324 indoc!(
4325 "
4326 - bar
4327 + BAR
4328
4329 - foo
4330 + FOO
4331 "
4332 ),
4333 );
4334
4335 let anchor_1 = multibuffer.read_with(cx, |multibuffer, cx| {
4336 multibuffer
4337 .snapshot(cx)
4338 .anchor_in_excerpt(text::Anchor::min_for_buffer(buffer_1.read(cx).remote_id()))
4339 .unwrap()
4340 });
4341 let point_1 = snapshot.summaries_for_anchors::<Point, _>([&anchor_1])[0];
4342 assert_eq!(point_1, Point::new(0, 0));
4343
4344 let anchor_2 = multibuffer.read_with(cx, |multibuffer, cx| {
4345 multibuffer
4346 .snapshot(cx)
4347 .anchor_in_excerpt(text::Anchor::min_for_buffer(buffer_2.read(cx).remote_id()))
4348 .unwrap()
4349 });
4350 let point_2 = snapshot.summaries_for_anchors::<Point, _>([&anchor_2])[0];
4351 assert_eq!(point_2, Point::new(3, 0));
4352}
4353
4354#[gpui::test]
4355async fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
4356 let base_text_1 = "one\ntwo".to_owned();
4357 let text_1 = "one\n".to_owned();
4358
4359 let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
4360 let diff_1 = cx.new(|cx| {
4361 BufferDiff::new_with_base_text(&base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
4362 });
4363 cx.run_until_parked();
4364
4365 let multibuffer = cx.new(|cx| {
4366 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
4367 multibuffer.set_excerpts_for_path(
4368 PathKey::sorted(0),
4369 buffer_1.clone(),
4370 [Point::zero()..buffer_1.read(cx).max_point()],
4371 0,
4372 cx,
4373 );
4374 multibuffer.add_diff(diff_1.clone(), cx);
4375 multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
4376 multibuffer
4377 });
4378
4379 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4380 (multibuffer.snapshot(cx), multibuffer.subscribe())
4381 });
4382
4383 assert_new_snapshot(
4384 &multibuffer,
4385 &mut snapshot,
4386 &mut subscription,
4387 cx,
4388 indoc!(
4389 "
4390 one
4391 - two
4392 "
4393 ),
4394 );
4395
4396 assert_eq!(snapshot.max_point(), Point::new(2, 0));
4397 assert_eq!(snapshot.len().0, 8);
4398
4399 assert_eq!(
4400 snapshot
4401 .dimensions_from_points::<Point>([Point::new(2, 0)])
4402 .collect::<Vec<_>>(),
4403 vec![Point::new(2, 0)]
4404 );
4405
4406 let (_, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
4407 assert_eq!(translated_offset.0, "one\n".len());
4408 let (_, translated_point) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
4409 assert_eq!(translated_point, Point::new(1, 0));
4410
4411 // The same, for an excerpt that's not at the end of the multibuffer.
4412
4413 let text_2 = "foo\n".to_owned();
4414 let buffer_2 = cx.new(|cx| Buffer::local(&text_2, cx));
4415 multibuffer.update(cx, |multibuffer, cx| {
4416 multibuffer.set_excerpt_ranges_for_path(
4417 PathKey::sorted(1),
4418 buffer_2.clone(),
4419 &buffer_2.read(cx).snapshot(),
4420 vec![ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
4421 cx,
4422 );
4423 });
4424
4425 assert_new_snapshot(
4426 &multibuffer,
4427 &mut snapshot,
4428 &mut subscription,
4429 cx,
4430 indoc!(
4431 "
4432 one
4433 - two
4434
4435 foo
4436 "
4437 ),
4438 );
4439
4440 assert_eq!(
4441 snapshot
4442 .dimensions_from_points::<Point>([Point::new(2, 0)])
4443 .collect::<Vec<_>>(),
4444 vec![Point::new(2, 0)]
4445 );
4446
4447 let buffer_1_id = buffer_1.read_with(cx, |buffer_1, _| buffer_1.remote_id());
4448 let (buffer, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
4449 assert_eq!(buffer.remote_id(), buffer_1_id);
4450 assert_eq!(translated_offset.0, "one\n".len());
4451 let (buffer, translated_point) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
4452 assert_eq!(buffer.remote_id(), buffer_1_id);
4453 assert_eq!(translated_point, Point::new(1, 0));
4454}
4455
4456fn format_diff(
4457 text: &str,
4458 row_infos: &Vec<RowInfo>,
4459 boundary_rows: &HashSet<MultiBufferRow>,
4460 has_diff: Option<bool>,
4461) -> String {
4462 let has_diff =
4463 has_diff.unwrap_or_else(|| row_infos.iter().any(|info| info.diff_status.is_some()));
4464 text.split('\n')
4465 .enumerate()
4466 .zip(row_infos)
4467 .map(|((ix, line), info)| {
4468 let marker = match info.diff_status.map(|status| status.kind) {
4469 Some(DiffHunkStatusKind::Added) => "+ ",
4470 Some(DiffHunkStatusKind::Deleted) => "- ",
4471 Some(DiffHunkStatusKind::Modified) => unreachable!(),
4472 None => {
4473 if has_diff && !line.is_empty() {
4474 " "
4475 } else {
4476 ""
4477 }
4478 }
4479 };
4480 let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
4481 if has_diff {
4482 " ----------\n"
4483 } else {
4484 "---------\n"
4485 }
4486 } else {
4487 ""
4488 };
4489 let expand = info
4490 .expand_info
4491 .as_ref()
4492 .map(|expand_info| match expand_info.direction {
4493 ExpandExcerptDirection::Up => " [↑]",
4494 ExpandExcerptDirection::Down => " [↓]",
4495 ExpandExcerptDirection::UpAndDown => " [↕]",
4496 })
4497 .unwrap_or_default();
4498
4499 format!("{boundary_row}{marker}{line}{expand}")
4500 // let mbr = info
4501 // .multibuffer_row
4502 // .map(|row| format!("{:0>3}", row.0))
4503 // .unwrap_or_else(|| "???".to_string());
4504 // let byte_range = format!("{byte_range_start:0>3}..{byte_range_end:0>3}");
4505 // format!("{boundary_row}Row: {mbr}, Bytes: {byte_range} | {marker}{line}{expand}")
4506 })
4507 .collect::<Vec<_>>()
4508 .join("\n")
4509}
4510
4511// fn format_transforms(snapshot: &MultiBufferSnapshot) -> String {
4512// snapshot
4513// .diff_transforms
4514// .iter()
4515// .map(|transform| {
4516// let (kind, summary) = match transform {
4517// DiffTransform::DeletedHunk { summary, .. } => (" Deleted", (*summary).into()),
4518// DiffTransform::FilteredInsertedHunk { summary, .. } => (" Filtered", *summary),
4519// DiffTransform::InsertedHunk { summary, .. } => (" Inserted", *summary),
4520// DiffTransform::Unmodified { summary, .. } => ("Unmodified", *summary),
4521// };
4522// format!("{kind}(len: {}, lines: {:?})", summary.len, summary.lines)
4523// })
4524// .join("\n")
4525// }
4526
4527// fn format_excerpts(snapshot: &MultiBufferSnapshot) -> String {
4528// snapshot
4529// .excerpts
4530// .iter()
4531// .map(|excerpt| {
4532// format!(
4533// "Excerpt(buffer_range = {:?}, lines = {:?}, has_trailing_newline = {:?})",
4534// excerpt.range.context.to_point(&excerpt.buffer),
4535// excerpt.text_summary.lines,
4536// excerpt.has_trailing_newline
4537// )
4538// })
4539// .join("\n")
4540// }
4541
4542#[gpui::test]
4543async fn test_singleton_with_inverted_diff(cx: &mut TestAppContext) {
4544 let text = indoc!(
4545 "
4546 ZERO
4547 one
4548 TWO
4549 three
4550 six
4551 "
4552 );
4553 let base_text = indoc!(
4554 "
4555 one
4556 two
4557 three
4558 four
4559 five
4560 six
4561 "
4562 );
4563
4564 let buffer = cx.new(|cx| Buffer::local(text, cx));
4565 let diff = cx
4566 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
4567 cx.run_until_parked();
4568
4569 let base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4570
4571 let multibuffer = cx.new(|cx| {
4572 let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4573 multibuffer.set_all_diff_hunks_expanded(cx);
4574 multibuffer.add_inverted_diff(diff.clone(), buffer.clone(), cx);
4575 multibuffer
4576 });
4577
4578 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4579 (multibuffer.snapshot(cx), multibuffer.subscribe())
4580 });
4581
4582 assert_eq!(snapshot.text(), base_text);
4583 assert_new_snapshot(
4584 &multibuffer,
4585 &mut snapshot,
4586 &mut subscription,
4587 cx,
4588 indoc!(
4589 "
4590 one
4591 - two
4592 three
4593 - four
4594 - five
4595 six
4596 "
4597 ),
4598 );
4599
4600 buffer.update(cx, |buffer, cx| {
4601 buffer.edit_via_marked_text(
4602 indoc!(
4603 "
4604 ZERO
4605 one
4606 «<inserted>»W«O
4607 T»hree
4608 six
4609 "
4610 ),
4611 None,
4612 cx,
4613 );
4614 });
4615 cx.run_until_parked();
4616 let update = diff
4617 .update(cx, |diff, cx| {
4618 diff.update_diff(
4619 buffer.read(cx).text_snapshot(),
4620 Some(base_text.into()),
4621 None,
4622 None,
4623 cx,
4624 )
4625 })
4626 .await;
4627 diff.update(cx, |diff, cx| {
4628 diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4629 })
4630 .await;
4631 cx.run_until_parked();
4632
4633 assert_new_snapshot(
4634 &multibuffer,
4635 &mut snapshot,
4636 &mut subscription,
4637 cx,
4638 indoc! {
4639 "
4640 one
4641 - two
4642 - three
4643 - four
4644 - five
4645 six
4646 "
4647 },
4648 );
4649
4650 buffer.update(cx, |buffer, cx| {
4651 buffer.set_text("ZERO\nONE\nTWO\n", cx);
4652 });
4653 cx.run_until_parked();
4654 let update = diff
4655 .update(cx, |diff, cx| {
4656 diff.update_diff(
4657 buffer.read(cx).text_snapshot(),
4658 Some(base_text.into()),
4659 None,
4660 None,
4661 cx,
4662 )
4663 })
4664 .await;
4665 diff.update(cx, |diff, cx| {
4666 diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4667 })
4668 .await;
4669 cx.run_until_parked();
4670
4671 assert_new_snapshot(
4672 &multibuffer,
4673 &mut snapshot,
4674 &mut subscription,
4675 cx,
4676 indoc! {
4677 "
4678 - one
4679 - two
4680 - three
4681 - four
4682 - five
4683 - six
4684 "
4685 },
4686 );
4687
4688 diff.update(cx, |diff, cx| {
4689 diff.set_base_text(
4690 Some("new base\n".into()),
4691 None,
4692 buffer.read(cx).text_snapshot(),
4693 cx,
4694 )
4695 })
4696 .await
4697 .unwrap();
4698 cx.run_until_parked();
4699
4700 assert_new_snapshot(
4701 &multibuffer,
4702 &mut snapshot,
4703 &mut subscription,
4704 cx,
4705 indoc! {"
4706 - new base
4707 "},
4708 );
4709}
4710
4711#[gpui::test]
4712async fn test_inverted_diff_base_text_change(cx: &mut TestAppContext) {
4713 let base_text = "aaa\nbbb\nccc\n";
4714 let text = "ddd\n";
4715 let buffer = cx.new(|cx| Buffer::local(text, cx));
4716 let diff = cx
4717 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
4718 cx.run_until_parked();
4719
4720 let base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4721
4722 let multibuffer = cx.new(|cx| {
4723 let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4724 multibuffer.set_all_diff_hunks_expanded(cx);
4725 multibuffer.add_inverted_diff(diff.clone(), buffer.clone(), cx);
4726 multibuffer
4727 });
4728
4729 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4730 (multibuffer.snapshot(cx), multibuffer.subscribe())
4731 });
4732
4733 assert_eq!(snapshot.text(), base_text);
4734 assert_new_snapshot(
4735 &multibuffer,
4736 &mut snapshot,
4737 &mut subscription,
4738 cx,
4739 indoc!(
4740 "
4741 - aaa
4742 - bbb
4743 - ccc
4744 "
4745 ),
4746 );
4747
4748 let update = diff
4749 .update(cx, |diff, cx| {
4750 diff.update_diff(
4751 buffer.read(cx).text_snapshot(),
4752 Some("ddd\n".into()),
4753 Some(true),
4754 None,
4755 cx,
4756 )
4757 })
4758 .await;
4759 diff.update(cx, |diff, cx| {
4760 diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4761 })
4762 .detach();
4763
4764 let _hunks: Vec<_> = multibuffer
4765 .read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx))
4766 .diff_hunks()
4767 .collect();
4768}
4769
4770#[gpui::test]
4771async fn test_inverted_diff_secondary_version_mismatch(cx: &mut TestAppContext) {
4772 let base_text = "one\ntwo\nthree\nfour\nfive\n";
4773 let index_text = "one\nTWO\nthree\nfour\nfive\n";
4774 let buffer_text = "one\nTWO\nthree\nFOUR\nfive\n";
4775
4776 let buffer = cx.new(|cx| Buffer::local(buffer_text, cx));
4777
4778 let unstaged_diff = cx
4779 .new(|cx| BufferDiff::new_with_base_text(index_text, &buffer.read(cx).text_snapshot(), cx));
4780 cx.run_until_parked();
4781
4782 let uncommitted_diff = cx.new(|cx| {
4783 let mut diff =
4784 BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx);
4785 diff.set_secondary_diff(unstaged_diff.clone());
4786 diff
4787 });
4788 cx.run_until_parked();
4789
4790 buffer.update(cx, |buffer, cx| {
4791 buffer.edit([(0..0, "ZERO\n")], None, cx);
4792 });
4793
4794 let update = unstaged_diff
4795 .update(cx, |diff, cx| {
4796 diff.update_diff(
4797 buffer.read(cx).text_snapshot(),
4798 Some(index_text.into()),
4799 None,
4800 None,
4801 cx,
4802 )
4803 })
4804 .await;
4805 unstaged_diff
4806 .update(cx, |diff, cx| {
4807 diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4808 })
4809 .await;
4810
4811 let base_text_buffer =
4812 uncommitted_diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4813
4814 let multibuffer = cx.new(|cx| {
4815 let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4816 multibuffer.set_all_diff_hunks_expanded(cx);
4817 multibuffer.add_inverted_diff(uncommitted_diff.clone(), buffer.clone(), cx);
4818 multibuffer
4819 });
4820
4821 let _hunks: Vec<_> = multibuffer
4822 .read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx))
4823 .diff_hunks()
4824 .collect();
4825}
4826
4827#[track_caller]
4828fn assert_excerpts_match(
4829 multibuffer: &Entity<MultiBuffer>,
4830 cx: &mut TestAppContext,
4831 expected: &str,
4832) {
4833 let mut output = String::new();
4834 multibuffer.read_with(cx, |multibuffer, cx| {
4835 let snapshot = multibuffer.snapshot(cx);
4836 for excerpt in multibuffer.snapshot(cx).excerpts() {
4837 output.push_str("-----\n");
4838 output.extend(
4839 snapshot
4840 .buffer_for_id(excerpt.context.start.buffer_id)
4841 .unwrap()
4842 .text_for_range(excerpt.context),
4843 );
4844 if !output.ends_with('\n') {
4845 output.push('\n');
4846 }
4847 }
4848 });
4849 assert_eq!(output, expected);
4850}
4851
4852#[track_caller]
4853fn assert_new_snapshot(
4854 multibuffer: &Entity<MultiBuffer>,
4855 snapshot: &mut MultiBufferSnapshot,
4856 subscription: &mut Subscription<MultiBufferOffset>,
4857 cx: &mut TestAppContext,
4858 expected_diff: &str,
4859) {
4860 let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
4861 let actual_text = new_snapshot.text();
4862 let line_infos = new_snapshot
4863 .row_infos(MultiBufferRow(0))
4864 .collect::<Vec<_>>();
4865 let actual_diff = format_diff(&actual_text, &line_infos, &Default::default(), None);
4866 pretty_assertions::assert_eq!(actual_diff, expected_diff);
4867 check_edits(
4868 snapshot,
4869 &new_snapshot,
4870 &subscription.consume().into_inner(),
4871 );
4872 *snapshot = new_snapshot;
4873}
4874
4875#[track_caller]
4876fn check_edits(
4877 old_snapshot: &MultiBufferSnapshot,
4878 new_snapshot: &MultiBufferSnapshot,
4879 edits: &[Edit<MultiBufferOffset>],
4880) {
4881 let mut text = old_snapshot.text();
4882 let new_text = new_snapshot.text();
4883 for edit in edits.iter().rev() {
4884 if !text.is_char_boundary(edit.old.start.0)
4885 || !text.is_char_boundary(edit.old.end.0)
4886 || !new_text.is_char_boundary(edit.new.start.0)
4887 || !new_text.is_char_boundary(edit.new.end.0)
4888 {
4889 panic!(
4890 "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
4891 edits, text, new_text
4892 );
4893 }
4894
4895 text.replace_range(
4896 edit.old.start.0..edit.old.end.0,
4897 &new_text[edit.new.start.0..edit.new.end.0],
4898 );
4899 }
4900
4901 pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
4902}
4903
4904#[track_caller]
4905fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
4906 let full_text = snapshot.text();
4907 for ix in 0..full_text.len() {
4908 let mut chunks = snapshot.chunks(MultiBufferOffset(0)..snapshot.len(), false);
4909 chunks.seek(MultiBufferOffset(ix)..snapshot.len());
4910 let tail = chunks.map(|chunk| chunk.text).collect::<String>();
4911 assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
4912 }
4913}
4914
4915#[track_caller]
4916fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
4917 let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
4918 for start_row in 1..all_line_numbers.len() {
4919 let line_numbers = snapshot
4920 .row_infos(MultiBufferRow(start_row as u32))
4921 .collect::<Vec<_>>();
4922 assert_eq!(
4923 line_numbers,
4924 all_line_numbers[start_row..],
4925 "start_row: {start_row}"
4926 );
4927 }
4928}
4929
4930#[track_caller]
4931fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
4932 let text = Rope::from(snapshot.text());
4933
4934 let mut left_anchors = Vec::new();
4935 let mut right_anchors = Vec::new();
4936 let mut offsets = Vec::new();
4937 let mut points = Vec::new();
4938 for offset in 0..=text.len() + 1 {
4939 let offset = MultiBufferOffset(offset);
4940 let clipped_left = snapshot.clip_offset(offset, Bias::Left);
4941 let clipped_right = snapshot.clip_offset(offset, Bias::Right);
4942 assert_eq!(
4943 clipped_left.0,
4944 text.clip_offset(offset.0, Bias::Left),
4945 "clip_offset({offset:?}, Left)"
4946 );
4947 assert_eq!(
4948 clipped_right.0,
4949 text.clip_offset(offset.0, Bias::Right),
4950 "clip_offset({offset:?}, Right)"
4951 );
4952 assert_eq!(
4953 snapshot.offset_to_point(clipped_left),
4954 text.offset_to_point(clipped_left.0),
4955 "offset_to_point({})",
4956 clipped_left.0
4957 );
4958 assert_eq!(
4959 snapshot.offset_to_point(clipped_right),
4960 text.offset_to_point(clipped_right.0),
4961 "offset_to_point({})",
4962 clipped_right.0
4963 );
4964 let anchor_after = snapshot.anchor_after(clipped_left);
4965 assert_eq!(
4966 anchor_after.to_offset(snapshot),
4967 clipped_left,
4968 "anchor_after({}).to_offset {anchor_after:?}",
4969 clipped_left.0
4970 );
4971 let anchor_before = snapshot.anchor_before(clipped_left);
4972 assert_eq!(
4973 anchor_before.to_offset(snapshot),
4974 clipped_left,
4975 "anchor_before({}).to_offset",
4976 clipped_left.0
4977 );
4978 left_anchors.push(anchor_before);
4979 right_anchors.push(anchor_after);
4980 offsets.push(clipped_left);
4981 points.push(text.offset_to_point(clipped_left.0));
4982 }
4983
4984 for row in 0..text.max_point().row {
4985 for column in 0..text.line_len(row) + 1 {
4986 let point = Point { row, column };
4987 let clipped_left = snapshot.clip_point(point, Bias::Left);
4988 let clipped_right = snapshot.clip_point(point, Bias::Right);
4989 assert_eq!(
4990 clipped_left,
4991 text.clip_point(point, Bias::Left),
4992 "clip_point({point:?}, Left)"
4993 );
4994 assert_eq!(
4995 clipped_right,
4996 text.clip_point(point, Bias::Right),
4997 "clip_point({point:?}, Right)"
4998 );
4999 assert_eq!(
5000 snapshot.point_to_offset(clipped_left).0,
5001 text.point_to_offset(clipped_left),
5002 "point_to_offset({clipped_left:?})"
5003 );
5004 assert_eq!(
5005 snapshot.point_to_offset(clipped_right).0,
5006 text.point_to_offset(clipped_right),
5007 "point_to_offset({clipped_right:?})"
5008 );
5009 }
5010 }
5011
5012 assert_eq!(
5013 snapshot.summaries_for_anchors::<MultiBufferOffset, _>(&left_anchors),
5014 offsets,
5015 "left_anchors <-> offsets"
5016 );
5017 assert_eq!(
5018 snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
5019 points,
5020 "left_anchors <-> points"
5021 );
5022 assert_eq!(
5023 snapshot.summaries_for_anchors::<MultiBufferOffset, _>(&right_anchors),
5024 offsets,
5025 "right_anchors <-> offsets"
5026 );
5027 assert_eq!(
5028 snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
5029 points,
5030 "right_anchors <-> points"
5031 );
5032
5033 for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
5034 for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
5035 if ix > 0 && *offset == MultiBufferOffset(252) && offset > &offsets[ix - 1] {
5036 let prev_anchor = left_anchors[ix - 1];
5037 assert!(
5038 anchor.cmp(&prev_anchor, snapshot).is_gt(),
5039 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
5040 offsets[ix],
5041 offsets[ix - 1],
5042 );
5043 assert!(
5044 prev_anchor.cmp(anchor, snapshot).is_lt(),
5045 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
5046 offsets[ix - 1],
5047 offsets[ix],
5048 );
5049 }
5050 }
5051 }
5052
5053 if let Some((buffer, offset)) = snapshot.point_to_buffer_offset(snapshot.max_point()) {
5054 assert!(offset.0 <= buffer.len());
5055 }
5056 if let Some((buffer, point)) = snapshot.point_to_buffer_point(snapshot.max_point()) {
5057 assert!(point <= buffer.max_point());
5058 }
5059}
5060
5061fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
5062 let max_row = snapshot.max_point().row;
5063 let buffer_id = snapshot.excerpts().next().unwrap().context.start.buffer_id;
5064 let text = text::Buffer::new(ReplicaId::LOCAL, buffer_id, snapshot.text());
5065 let mut line_indents = text
5066 .line_indents_in_row_range(0..max_row + 1)
5067 .collect::<Vec<_>>();
5068 for start_row in 0..snapshot.max_point().row {
5069 pretty_assertions::assert_eq!(
5070 snapshot
5071 .line_indents(MultiBufferRow(start_row), |_| true)
5072 .map(|(row, indent, _)| (row.0, indent))
5073 .collect::<Vec<_>>(),
5074 &line_indents[(start_row as usize)..],
5075 "line_indents({start_row})"
5076 );
5077 }
5078
5079 line_indents.reverse();
5080 pretty_assertions::assert_eq!(
5081 snapshot
5082 .reversed_line_indents(MultiBufferRow(max_row), |_| true)
5083 .map(|(row, indent, _)| (row.0, indent))
5084 .collect::<Vec<_>>(),
5085 &line_indents[..],
5086 "reversed_line_indents({max_row})"
5087 );
5088}
5089
5090#[gpui::test]
5091fn test_new_empty_buffer_uses_untitled_title(cx: &mut App) {
5092 let buffer = cx.new(|cx| Buffer::local("", cx));
5093 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5094
5095 assert_eq!(multibuffer.read(cx).title(cx), "untitled");
5096}
5097
5098#[gpui::test]
5099fn test_new_empty_buffer_uses_untitled_title_when_only_contains_whitespace(cx: &mut App) {
5100 let buffer = cx.new(|cx| Buffer::local("\n ", cx));
5101 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5102
5103 assert_eq!(multibuffer.read(cx).title(cx), "untitled");
5104}
5105
5106#[gpui::test]
5107fn test_new_empty_buffer_takes_first_line_for_title(cx: &mut App) {
5108 let buffer = cx.new(|cx| Buffer::local("Hello World\nSecond line", cx));
5109 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5110
5111 assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
5112}
5113
5114#[gpui::test]
5115fn test_new_empty_buffer_takes_trimmed_first_line_for_title(cx: &mut App) {
5116 let buffer = cx.new(|cx| Buffer::local("\nHello, World ", cx));
5117 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5118
5119 assert_eq!(multibuffer.read(cx).title(cx), "Hello, World");
5120}
5121
5122#[gpui::test]
5123fn test_new_empty_buffer_uses_truncated_first_line_for_title(cx: &mut App) {
5124 let title = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee";
5125 let title_after = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd";
5126 let buffer = cx.new(|cx| Buffer::local(title, cx));
5127 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5128
5129 assert_eq!(multibuffer.read(cx).title(cx), title_after);
5130}
5131
5132#[gpui::test]
5133fn test_new_empty_buffer_uses_truncated_first_line_for_title_after_merging_adjacent_spaces(
5134 cx: &mut App,
5135) {
5136 let title = "aaaaaaaaaabbbbbbbbbb ccccccccccddddddddddeeeeeeeeee";
5137 let title_after = "aaaaaaaaaabbbbbbbbbb ccccccccccddddddddd";
5138 let buffer = cx.new(|cx| Buffer::local(title, cx));
5139 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5140
5141 assert_eq!(multibuffer.read(cx).title(cx), title_after);
5142}
5143
5144#[gpui::test]
5145fn test_new_empty_buffers_title_can_be_set(cx: &mut App) {
5146 let buffer = cx.new(|cx| Buffer::local("Hello World", cx));
5147 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5148 assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
5149
5150 multibuffer.update(cx, |multibuffer, cx| {
5151 multibuffer.set_title("Hey".into(), cx)
5152 });
5153 assert_eq!(multibuffer.read(cx).title(cx), "Hey");
5154}
5155
5156#[gpui::test(iterations = 100)]
5157fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) {
5158 let multibuffer = if rng.random() {
5159 let len = rng.random_range(0..10000);
5160 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
5161 let buffer = cx.new(|cx| Buffer::local(text, cx));
5162 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
5163 } else {
5164 MultiBuffer::build_random(&mut rng, cx)
5165 };
5166
5167 let snapshot = multibuffer.read(cx).snapshot(cx);
5168
5169 let chunks = snapshot.chunks(MultiBufferOffset(0)..snapshot.len(), false);
5170
5171 for chunk in chunks {
5172 let chunk_text = chunk.text;
5173 let chars_bitmap = chunk.chars;
5174 let tabs_bitmap = chunk.tabs;
5175
5176 if chunk_text.is_empty() {
5177 assert_eq!(
5178 chars_bitmap, 0,
5179 "Empty chunk should have empty chars bitmap"
5180 );
5181 assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
5182 continue;
5183 }
5184
5185 assert!(
5186 chunk_text.len() <= 128,
5187 "Chunk text length {} exceeds 128 bytes",
5188 chunk_text.len()
5189 );
5190
5191 // Verify chars bitmap
5192 let char_indices = chunk_text
5193 .char_indices()
5194 .map(|(i, _)| i)
5195 .collect::<Vec<_>>();
5196
5197 for byte_idx in 0..chunk_text.len() {
5198 let should_have_bit = char_indices.contains(&byte_idx);
5199 let has_bit = chars_bitmap & (1 << byte_idx) != 0;
5200
5201 if has_bit != should_have_bit {
5202 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5203 eprintln!("Char indices: {:?}", char_indices);
5204 eprintln!("Chars bitmap: {:#b}", chars_bitmap);
5205 }
5206
5207 assert_eq!(
5208 has_bit, should_have_bit,
5209 "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
5210 byte_idx, chunk_text, should_have_bit, has_bit
5211 );
5212 }
5213
5214 for (byte_idx, byte) in chunk_text.bytes().enumerate() {
5215 let is_tab = byte == b'\t';
5216 let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
5217
5218 if has_bit != is_tab {
5219 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5220 eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
5221 assert_eq!(
5222 has_bit, is_tab,
5223 "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
5224 byte_idx, chunk_text, byte as char, is_tab, has_bit
5225 );
5226 }
5227 }
5228 }
5229}
5230
5231#[gpui::test(iterations = 10)]
5232fn test_random_chunk_bitmaps_with_diffs(cx: &mut App, mut rng: StdRng) {
5233 let settings_store = SettingsStore::test(cx);
5234 cx.set_global(settings_store);
5235 use buffer_diff::BufferDiff;
5236 use util::RandomCharIter;
5237
5238 let multibuffer = if rng.random() {
5239 let len = rng.random_range(100..10000);
5240 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
5241 let buffer = cx.new(|cx| Buffer::local(text, cx));
5242 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
5243 } else {
5244 MultiBuffer::build_random(&mut rng, cx)
5245 };
5246
5247 let _diff_count = rng.random_range(1..5);
5248 let mut diffs = Vec::new();
5249
5250 multibuffer.update(cx, |multibuffer, cx| {
5251 let snapshot = multibuffer.snapshot(cx);
5252 for buffer_id in snapshot.all_buffer_ids() {
5253 if rng.random_bool(0.7) {
5254 if let Some(buffer_handle) = multibuffer.buffer(buffer_id) {
5255 let buffer_text = buffer_handle.read(cx).text();
5256 let mut base_text = String::new();
5257
5258 for line in buffer_text.lines() {
5259 if rng.random_bool(0.3) {
5260 continue;
5261 } else if rng.random_bool(0.3) {
5262 let line_len = rng.random_range(0..50);
5263 let modified_line = RandomCharIter::new(&mut rng)
5264 .take(line_len)
5265 .collect::<String>();
5266 base_text.push_str(&modified_line);
5267 base_text.push('\n');
5268 } else {
5269 base_text.push_str(line);
5270 base_text.push('\n');
5271 }
5272 }
5273
5274 if rng.random_bool(0.5) {
5275 let extra_lines = rng.random_range(1..5);
5276 for _ in 0..extra_lines {
5277 let line_len = rng.random_range(0..50);
5278 let extra_line = RandomCharIter::new(&mut rng)
5279 .take(line_len)
5280 .collect::<String>();
5281 base_text.push_str(&extra_line);
5282 base_text.push('\n');
5283 }
5284 }
5285
5286 let diff = cx.new(|cx| {
5287 BufferDiff::new_with_base_text(
5288 &base_text,
5289 &buffer_handle.read(cx).text_snapshot(),
5290 cx,
5291 )
5292 });
5293 diffs.push(diff.clone());
5294 multibuffer.add_diff(diff, cx);
5295 }
5296 }
5297 }
5298 });
5299
5300 multibuffer.update(cx, |multibuffer, cx| {
5301 if rng.random_bool(0.5) {
5302 multibuffer.set_all_diff_hunks_expanded(cx);
5303 } else {
5304 let snapshot = multibuffer.snapshot(cx);
5305 let text = snapshot.text();
5306
5307 let mut ranges = Vec::new();
5308 for _ in 0..rng.random_range(1..5) {
5309 if snapshot.len().0 == 0 {
5310 break;
5311 }
5312
5313 let diff_size = rng.random_range(5..1000);
5314 let mut start = rng.random_range(0..snapshot.len().0);
5315
5316 while !text.is_char_boundary(start) {
5317 start = start.saturating_sub(1);
5318 }
5319
5320 let mut end = rng.random_range(start..snapshot.len().0.min(start + diff_size));
5321
5322 while !text.is_char_boundary(end) {
5323 end = end.saturating_add(1);
5324 }
5325 let start_anchor = snapshot.anchor_after(MultiBufferOffset(start));
5326 let end_anchor = snapshot.anchor_before(MultiBufferOffset(end));
5327 ranges.push(start_anchor..end_anchor);
5328 }
5329 multibuffer.expand_diff_hunks(ranges, cx);
5330 }
5331 });
5332
5333 let snapshot = multibuffer.read(cx).snapshot(cx);
5334
5335 let chunks = snapshot.chunks(MultiBufferOffset(0)..snapshot.len(), false);
5336
5337 for chunk in chunks {
5338 let chunk_text = chunk.text;
5339 let chars_bitmap = chunk.chars;
5340 let tabs_bitmap = chunk.tabs;
5341
5342 if chunk_text.is_empty() {
5343 assert_eq!(
5344 chars_bitmap, 0,
5345 "Empty chunk should have empty chars bitmap"
5346 );
5347 assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
5348 continue;
5349 }
5350
5351 assert!(
5352 chunk_text.len() <= 128,
5353 "Chunk text length {} exceeds 128 bytes",
5354 chunk_text.len()
5355 );
5356
5357 let char_indices = chunk_text
5358 .char_indices()
5359 .map(|(i, _)| i)
5360 .collect::<Vec<_>>();
5361
5362 for byte_idx in 0..chunk_text.len() {
5363 let should_have_bit = char_indices.contains(&byte_idx);
5364 let has_bit = chars_bitmap & (1 << byte_idx) != 0;
5365
5366 if has_bit != should_have_bit {
5367 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5368 eprintln!("Char indices: {:?}", char_indices);
5369 eprintln!("Chars bitmap: {:#b}", chars_bitmap);
5370 }
5371
5372 assert_eq!(
5373 has_bit, should_have_bit,
5374 "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
5375 byte_idx, chunk_text, should_have_bit, has_bit
5376 );
5377 }
5378
5379 for (byte_idx, byte) in chunk_text.bytes().enumerate() {
5380 let is_tab = byte == b'\t';
5381 let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
5382
5383 if has_bit != is_tab {
5384 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5385 eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
5386 assert_eq!(
5387 has_bit, is_tab,
5388 "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
5389 byte_idx, chunk_text, byte as char, is_tab, has_bit
5390 );
5391 }
5392 }
5393 }
5394}
5395
5396fn collect_word_diffs(
5397 base_text: &str,
5398 modified_text: &str,
5399 cx: &mut TestAppContext,
5400) -> Vec<String> {
5401 let buffer = cx.new(|cx| Buffer::local(modified_text, cx));
5402 let diff = cx
5403 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
5404 cx.run_until_parked();
5405
5406 let multibuffer = cx.new(|cx| {
5407 let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
5408 multibuffer.add_diff(diff.clone(), cx);
5409 multibuffer
5410 });
5411
5412 multibuffer.update(cx, |multibuffer, cx| {
5413 multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
5414 });
5415
5416 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
5417 let text = snapshot.text();
5418
5419 snapshot
5420 .diff_hunks()
5421 .flat_map(|hunk| hunk.word_diffs)
5422 .map(|range| text[range.start.0..range.end.0].to_string())
5423 .collect()
5424}
5425
5426#[gpui::test]
5427async fn test_word_diff_simple_replacement(cx: &mut TestAppContext) {
5428 let settings_store = cx.update(|cx| SettingsStore::test(cx));
5429 cx.set_global(settings_store);
5430
5431 let base_text = "hello world foo bar\n";
5432 let modified_text = "hello WORLD foo BAR\n";
5433
5434 let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5435
5436 assert_eq!(word_diffs, vec!["world", "bar", "WORLD", "BAR"]);
5437}
5438
5439#[gpui::test]
5440async fn test_word_diff_white_space(cx: &mut TestAppContext) {
5441 let settings_store = cx.update(|cx| SettingsStore::test(cx));
5442 cx.set_global(settings_store);
5443
5444 let base_text = "hello world foo bar\n";
5445 let modified_text = " hello world foo bar\n";
5446
5447 let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5448
5449 assert_eq!(word_diffs, vec![" "]);
5450}
5451
5452#[gpui::test]
5453async fn test_word_diff_consecutive_modified_lines(cx: &mut TestAppContext) {
5454 let settings_store = cx.update(|cx| SettingsStore::test(cx));
5455 cx.set_global(settings_store);
5456
5457 let base_text = "aaa bbb\nccc ddd\n";
5458 let modified_text = "aaa BBB\nccc DDD\n";
5459
5460 let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5461
5462 assert_eq!(
5463 word_diffs,
5464 vec!["bbb", "ddd", "BBB", "DDD"],
5465 "consecutive modified lines should produce word diffs when line counts match"
5466 );
5467}
5468
5469#[gpui::test]
5470async fn test_word_diff_modified_lines_with_deletion_between(cx: &mut TestAppContext) {
5471 let settings_store = cx.update(|cx| SettingsStore::test(cx));
5472 cx.set_global(settings_store);
5473
5474 let base_text = "aaa bbb\ndeleted line\nccc ddd\n";
5475 let modified_text = "aaa BBB\nccc DDD\n";
5476
5477 let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5478
5479 assert_eq!(
5480 word_diffs,
5481 Vec::<String>::new(),
5482 "modified lines with a deleted line between should not produce word diffs"
5483 );
5484}
5485
5486#[gpui::test]
5487async fn test_word_diff_disabled(cx: &mut TestAppContext) {
5488 let settings_store = cx.update(|cx| {
5489 let mut settings_store = SettingsStore::test(cx);
5490 settings_store.update_user_settings(cx, |settings| {
5491 settings.project.all_languages.defaults.word_diff_enabled = Some(false);
5492 });
5493 settings_store
5494 });
5495 cx.set_global(settings_store);
5496
5497 let base_text = "hello world\n";
5498 let modified_text = "hello WORLD\n";
5499
5500 let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5501
5502 assert_eq!(
5503 word_diffs,
5504 Vec::<String>::new(),
5505 "word diffs should be empty when disabled"
5506 );
5507}
5508
5509/// Tests `excerpt_containing` and `excerpts_for_range` (functions mapping multi-buffer text-coordinates to excerpts)
5510#[gpui::test]
5511fn test_excerpts_containment_functions(cx: &mut App) {
5512 // Multibuffer content for these tests:
5513 // 0123
5514 // 0: aa0
5515 // 1: aa1
5516 // -----
5517 // 2: bb0
5518 // 3: bb1
5519 // -----MultiBufferOffset(0)..
5520 // 4: cc0
5521
5522 let buffer_1 = cx.new(|cx| Buffer::local("aa0\naa1", cx));
5523 let buffer_2 = cx.new(|cx| Buffer::local("bb0\nbb1", cx));
5524 let buffer_3 = cx.new(|cx| Buffer::local("cc0", cx));
5525
5526 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5527
5528 let (excerpt_1_info, excerpt_2_info, excerpt_3_info) =
5529 multibuffer.update(cx, |multibuffer, cx| {
5530 multibuffer.set_excerpts_for_path(
5531 PathKey::sorted(0),
5532 buffer_1.clone(),
5533 [Point::new(0, 0)..Point::new(1, 3)],
5534 0,
5535 cx,
5536 );
5537
5538 multibuffer.set_excerpts_for_path(
5539 PathKey::sorted(1),
5540 buffer_2.clone(),
5541 [Point::new(0, 0)..Point::new(1, 3)],
5542 0,
5543 cx,
5544 );
5545
5546 multibuffer.set_excerpts_for_path(
5547 PathKey::sorted(2),
5548 buffer_3.clone(),
5549 [Point::new(0, 0)..Point::new(0, 3)],
5550 0,
5551 cx,
5552 );
5553
5554 let snapshot = multibuffer.snapshot(cx);
5555 let mut excerpts = snapshot.excerpts();
5556 (
5557 excerpts.next().unwrap(),
5558 excerpts.next().unwrap(),
5559 excerpts.next().unwrap(),
5560 )
5561 });
5562
5563 let snapshot = multibuffer.read(cx).snapshot(cx);
5564
5565 assert_eq!(snapshot.text(), "aa0\naa1\nbb0\nbb1\ncc0");
5566
5567 //// Test `excerpts_for_range`
5568
5569 let p00 = snapshot.point_to_offset(Point::new(0, 0));
5570 let p10 = snapshot.point_to_offset(Point::new(1, 0));
5571 let p20 = snapshot.point_to_offset(Point::new(2, 0));
5572 let p23 = snapshot.point_to_offset(Point::new(2, 3));
5573 let p13 = snapshot.point_to_offset(Point::new(1, 3));
5574 let p40 = snapshot.point_to_offset(Point::new(4, 0));
5575 let p43 = snapshot.point_to_offset(Point::new(4, 3));
5576
5577 let excerpts: Vec<_> = snapshot.excerpts_for_range(p00..p00).collect();
5578 assert_eq!(excerpts.len(), 1);
5579 assert_eq!(excerpts[0].range, excerpt_1_info);
5580
5581 // Cursor at very end of excerpt 3
5582 let excerpts: Vec<_> = snapshot.excerpts_for_range(p43..p43).collect();
5583 assert_eq!(excerpts.len(), 1);
5584 assert_eq!(excerpts[0].range, excerpt_3_info);
5585
5586 let excerpts: Vec<_> = snapshot.excerpts_for_range(p00..p23).collect();
5587 assert_eq!(excerpts.len(), 2);
5588 assert_eq!(excerpts[0].range, excerpt_1_info);
5589 assert_eq!(excerpts[1].range, excerpt_2_info);
5590
5591 // This range represent an selection with end-point just inside excerpt_2
5592 // Today we only expand the first excerpt, but another interpretation that
5593 // we could consider is expanding both here
5594 let excerpts: Vec<_> = snapshot.excerpts_for_range(p10..p20).collect();
5595 assert_eq!(excerpts.len(), 1);
5596 assert_eq!(excerpts[0].range, excerpt_1_info);
5597
5598 //// Test that `excerpts_for_range` and `excerpt_containing` agree for all single offsets (cursor positions)
5599 for offset in 0..=snapshot.len().0 {
5600 let offset = MultiBufferOffset(offset);
5601 let excerpts_for_range: Vec<_> = snapshot.excerpts_for_range(offset..offset).collect();
5602 assert_eq!(
5603 excerpts_for_range.len(),
5604 1,
5605 "Expected exactly one excerpt for offset {offset}",
5606 );
5607
5608 let (_, excerpt_containing) =
5609 snapshot
5610 .excerpt_containing(offset..offset)
5611 .unwrap_or_else(|| {
5612 panic!("Expected excerpt_containing to find excerpt for offset {offset}")
5613 });
5614
5615 assert_eq!(
5616 excerpts_for_range[0].range, excerpt_containing,
5617 "excerpts_for_range and excerpt_containing should agree for offset {offset}",
5618 );
5619 }
5620
5621 //// Test `excerpt_containing` behavior with ranges:
5622
5623 // Ranges intersecting a single-excerpt
5624 let (_, containing) = snapshot.excerpt_containing(p00..p13).unwrap();
5625 assert_eq!(containing, excerpt_1_info);
5626
5627 // Ranges intersecting multiple excerpts (should return None)
5628 let containing = snapshot.excerpt_containing(p20..p40);
5629 assert!(
5630 containing.is_none(),
5631 "excerpt_containing should return None for ranges spanning multiple excerpts"
5632 );
5633}
5634
5635#[gpui::test]
5636fn test_range_to_buffer_ranges(cx: &mut App) {
5637 let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb", cx));
5638 let buffer_2 = cx.new(|cx| Buffer::local("ccc", cx));
5639
5640 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5641 multibuffer.update(cx, |multibuffer, cx| {
5642 multibuffer.set_excerpts_for_path(
5643 PathKey::sorted(0),
5644 buffer_1.clone(),
5645 [Point::new(0, 0)..Point::new(1, 3)],
5646 0,
5647 cx,
5648 );
5649
5650 multibuffer.set_excerpts_for_path(
5651 PathKey::sorted(1),
5652 buffer_2.clone(),
5653 [Point::new(0, 0)..Point::new(0, 3)],
5654 0,
5655 cx,
5656 );
5657 });
5658
5659 let snapshot = multibuffer.read(cx).snapshot(cx);
5660 assert_eq!(snapshot.text(), "aaa\nbbb\nccc");
5661
5662 let excerpt_2_start = Point::new(2, 0);
5663
5664 let ranges_half_open = snapshot.range_to_buffer_ranges(Point::zero()..excerpt_2_start);
5665 assert_eq!(
5666 ranges_half_open.len(),
5667 1,
5668 "Half-open range ending at excerpt start should EXCLUDE that excerpt"
5669 );
5670 assert_eq!(ranges_half_open[0].1, BufferOffset(0)..BufferOffset(7));
5671 assert_eq!(
5672 ranges_half_open[0].0.remote_id(),
5673 buffer_1.read(cx).remote_id()
5674 );
5675
5676 let buffer_empty = cx.new(|cx| Buffer::local("", cx));
5677 let multibuffer_trailing_empty = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5678 let (_te_excerpt_1_info, _te_excerpt_2_info) =
5679 multibuffer_trailing_empty.update(cx, |multibuffer, cx| {
5680 multibuffer.set_excerpts_for_path(
5681 PathKey::sorted(0),
5682 buffer_1.clone(),
5683 [Point::new(0, 0)..Point::new(1, 3)],
5684 0,
5685 cx,
5686 );
5687
5688 multibuffer.set_excerpts_for_path(
5689 PathKey::sorted(1),
5690 buffer_empty.clone(),
5691 [Point::new(0, 0)..Point::new(0, 0)],
5692 0,
5693 cx,
5694 );
5695
5696 let snapshot = multibuffer.snapshot(cx);
5697 let mut infos = snapshot.excerpts();
5698 (infos.next().unwrap(), infos.next().unwrap())
5699 });
5700
5701 let snapshot_trailing = multibuffer_trailing_empty.read(cx).snapshot(cx);
5702 assert_eq!(snapshot_trailing.text(), "aaa\nbbb\n");
5703
5704 let max_point = snapshot_trailing.max_point();
5705
5706 let ranges_half_open_max = snapshot_trailing.range_to_buffer_ranges(Point::zero()..max_point);
5707 assert_eq!(
5708 ranges_half_open_max.len(),
5709 2,
5710 "Should include trailing empty excerpts"
5711 );
5712 assert_eq!(ranges_half_open_max[1].1, BufferOffset(0)..BufferOffset(0));
5713}
5714
5715#[gpui::test]
5716async fn test_buffer_range_to_excerpt_ranges(cx: &mut TestAppContext) {
5717 let base_text = indoc!(
5718 "
5719 aaa
5720 bbb
5721 ccc
5722 ddd
5723 eee
5724 ppp
5725 qqq
5726 rrr
5727 fff
5728 ggg
5729 hhh
5730 "
5731 );
5732 let text = indoc!(
5733 "
5734 aaa
5735 BBB
5736 ddd
5737 eee
5738 ppp
5739 qqq
5740 rrr
5741 FFF
5742 ggg
5743 hhh
5744 "
5745 );
5746
5747 let buffer = cx.new(|cx| Buffer::local(text, cx));
5748 let diff = cx
5749 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
5750 cx.run_until_parked();
5751
5752 let multibuffer = cx.new(|cx| {
5753 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
5754 multibuffer.set_excerpts_for_path(
5755 PathKey::sorted(0),
5756 buffer.clone(),
5757 [
5758 Point::new(0, 0)..Point::new(3, 3),
5759 Point::new(7, 0)..Point::new(9, 3),
5760 ],
5761 0,
5762 cx,
5763 );
5764 multibuffer.add_diff(diff.clone(), cx);
5765 multibuffer
5766 });
5767
5768 multibuffer.update(cx, |multibuffer, cx| {
5769 multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
5770 });
5771 cx.run_until_parked();
5772
5773 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
5774
5775 let actual_diff = format_diff(
5776 &snapshot.text(),
5777 &snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
5778 &Default::default(),
5779 None,
5780 );
5781 let expected_diff = indoc!(
5782 "
5783 aaa
5784 - bbb
5785 - ccc
5786 + BBB
5787 ddd
5788 eee [\u{2193}]
5789 - fff [\u{2191}]
5790 + FFF
5791 ggg
5792 hhh [\u{2193}]"
5793 );
5794 pretty_assertions::assert_eq!(actual_diff, expected_diff);
5795
5796 let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
5797
5798 let query_spanning_deleted_hunk = buffer_snapshot.anchor_after(Point::new(0, 0))
5799 ..buffer_snapshot.anchor_before(Point::new(1, 3));
5800 assert_eq!(
5801 snapshot
5802 .buffer_range_to_excerpt_ranges(query_spanning_deleted_hunk)
5803 .map(|range| range.to_point(&snapshot))
5804 .collect::<Vec<_>>(),
5805 vec![
5806 Point::new(0, 0)..Point::new(1, 0),
5807 Point::new(3, 0)..Point::new(3, 3),
5808 ],
5809 );
5810
5811 let query_within_contiguous_main_buffer = buffer_snapshot.anchor_after(Point::new(1, 0))
5812 ..buffer_snapshot.anchor_before(Point::new(2, 3));
5813 assert_eq!(
5814 snapshot
5815 .buffer_range_to_excerpt_ranges(query_within_contiguous_main_buffer)
5816 .map(|range| range.to_point(&snapshot))
5817 .collect::<Vec<_>>(),
5818 vec![Point::new(3, 0)..Point::new(4, 3)],
5819 );
5820
5821 let query_spanning_both_excerpts = buffer_snapshot.anchor_after(Point::new(2, 0))
5822 ..buffer_snapshot.anchor_before(Point::new(8, 3));
5823 assert_eq!(
5824 snapshot
5825 .buffer_range_to_excerpt_ranges(query_spanning_both_excerpts)
5826 .map(|range| range.to_point(&snapshot))
5827 .collect::<Vec<_>>(),
5828 vec![
5829 Point::new(4, 0)..Point::new(5, 3),
5830 Point::new(7, 0)..Point::new(8, 3),
5831 ],
5832 );
5833}
5834
5835#[gpui::test]
5836fn test_cannot_seek_backward_after_excerpt_replacement(cx: &mut TestAppContext) {
5837 let buffer_b_text: String = (0..50).map(|i| format!("line_b {i}\n")).collect();
5838 let buffer_b = cx.new(|cx| Buffer::local(buffer_b_text, cx));
5839
5840 let buffer_c_text: String = (0..10).map(|i| format!("line_c {i}\n")).collect();
5841 let buffer_c = cx.new(|cx| Buffer::local(buffer_c_text, cx));
5842
5843 let buffer_d_text: String = (0..10).map(|i| format!("line_d {i}\n")).collect();
5844 let buffer_d = cx.new(|cx| Buffer::local(buffer_d_text, cx));
5845
5846 let path_b = PathKey::with_sort_prefix(0, rel_path("bbb.rs").into_arc());
5847 let path_c = PathKey::with_sort_prefix(0, rel_path("ddd.rs").into_arc());
5848 let path_d = PathKey::with_sort_prefix(0, rel_path("ccc.rs").into_arc());
5849
5850 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5851
5852 multibuffer.update(cx, |multibuffer, cx| {
5853 multibuffer.set_excerpts_for_path(
5854 path_b.clone(),
5855 buffer_b.clone(),
5856 vec![
5857 Point::row_range(0..3),
5858 Point::row_range(15..18),
5859 Point::row_range(30..33),
5860 ],
5861 0,
5862 cx,
5863 );
5864 });
5865
5866 multibuffer.update(cx, |multibuffer, cx| {
5867 multibuffer.set_excerpts_for_path(
5868 path_c.clone(),
5869 buffer_c.clone(),
5870 vec![Point::row_range(0..3)],
5871 0,
5872 cx,
5873 );
5874 });
5875
5876 let (anchor_in_e_b2, anchor_in_e_b3) = multibuffer.read_with(cx, |multibuffer, cx| {
5877 let snapshot = multibuffer.snapshot(cx);
5878 let excerpt_infos = snapshot.excerpts().collect::<Vec<_>>();
5879 assert_eq!(excerpt_infos.len(), 4, "expected 4 excerpts (3×B + 1×C)");
5880
5881 let e_b2_info = excerpt_infos[1].clone();
5882 let e_b3_info = excerpt_infos[2].clone();
5883
5884 let anchor_b2 = snapshot.anchor_in_excerpt(e_b2_info.context.start).unwrap();
5885 let anchor_b3 = snapshot.anchor_in_excerpt(e_b3_info.context.start).unwrap();
5886 (anchor_b2, anchor_b3)
5887 });
5888
5889 multibuffer.update(cx, |multibuffer, cx| {
5890 multibuffer.set_excerpts_for_path(
5891 path_b.clone(),
5892 buffer_b.clone(),
5893 vec![Point::row_range(0..3), Point::row_range(28..36)],
5894 0,
5895 cx,
5896 );
5897 });
5898
5899 multibuffer.update(cx, |multibuffer, cx| {
5900 multibuffer.set_excerpts_for_path(
5901 path_d.clone(),
5902 buffer_d.clone(),
5903 vec![Point::row_range(0..3)],
5904 0,
5905 cx,
5906 );
5907 });
5908
5909 multibuffer.read_with(cx, |multibuffer, cx| {
5910 let snapshot = multibuffer.snapshot(cx);
5911 snapshot.summaries_for_anchors::<Point, _>(&[anchor_in_e_b2, anchor_in_e_b3]);
5912 });
5913}