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: Range<Point>,
2902 // if this is a deleted hunk, the main buffer anchor to which the deleted content is attached
2903 deleted_hunk_anchor: Option<text::Anchor>,
2904 status: Option<DiffHunkStatus>,
2905 excerpt: Option<ReferenceExcerpt>,
2906}
2907
2908impl ReferenceMultibuffer {
2909 fn expand_excerpts(
2910 &mut self,
2911 excerpts: &HashSet<ExcerptRange<text::Anchor>>,
2912 line_count: u32,
2913 cx: &mut App,
2914 ) {
2915 use text::AnchorRangeExt as _;
2916
2917 if line_count == 0 || excerpts.is_empty() {
2918 return;
2919 }
2920
2921 let mut excerpts_by_buffer: HashMap<BufferId, Vec<ExcerptRange<text::Anchor>>> =
2922 HashMap::default();
2923 for excerpt in excerpts {
2924 excerpts_by_buffer
2925 .entry(excerpt.context.start.buffer_id)
2926 .or_default()
2927 .push(excerpt.clone())
2928 }
2929
2930 for (buffer_id, excerpts_to_expand) in excerpts_by_buffer {
2931 let mut buffer = None;
2932 let mut buffer_snapshot = None;
2933 let mut path = None;
2934 let mut path_key_index = None;
2935 let mut new_ranges =
2936 self.excerpts
2937 .iter()
2938 .filter(|excerpt| excerpt.range.start.buffer_id == buffer_id)
2939 .map(|excerpt| {
2940 let snapshot = excerpt.buffer.read(cx).snapshot();
2941 let mut range = excerpt.range.to_point(&snapshot);
2942 if excerpts_to_expand.iter().any(|info| {
2943 excerpt.range.contains_anchor(info.context.start, &snapshot)
2944 }) {
2945 range.start = Point::new(range.start.row.saturating_sub(line_count), 0);
2946 range.end = snapshot
2947 .clip_point(Point::new(range.end.row + line_count, 0), Bias::Left);
2948 range.end.column = snapshot.line_len(range.end.row);
2949 }
2950 buffer = Some(excerpt.buffer.clone());
2951 buffer_snapshot = Some(snapshot);
2952 path = Some(excerpt.path_key.clone());
2953 path_key_index = Some(excerpt.path_key_index);
2954 ExcerptRange::new(range)
2955 })
2956 .collect::<Vec<_>>();
2957
2958 new_ranges.sort_by(|l, r| l.context.start.cmp(&r.context.start));
2959
2960 self.set_excerpts(
2961 path.unwrap(),
2962 path_key_index.unwrap(),
2963 buffer.unwrap(),
2964 &buffer_snapshot.unwrap(),
2965 new_ranges,
2966 cx,
2967 );
2968 }
2969 }
2970
2971 fn set_excerpts(
2972 &mut self,
2973 path_key: PathKey,
2974 path_key_index: PathKeyIndex,
2975 buffer: Entity<Buffer>,
2976 buffer_snapshot: &BufferSnapshot,
2977 ranges: Vec<ExcerptRange<Point>>,
2978 cx: &mut App,
2979 ) {
2980 self.excerpts.retain(|excerpt| {
2981 excerpt.path_key != path_key && excerpt.buffer.entity_id() != buffer.entity_id()
2982 });
2983
2984 let ranges = MultiBuffer::merge_excerpt_ranges(&ranges);
2985
2986 let (Ok(ix) | Err(ix)) = self
2987 .excerpts
2988 .binary_search_by(|probe| probe.path_key.cmp(&path_key));
2989 self.excerpts.splice(
2990 ix..ix,
2991 ranges.into_iter().map(|range| ReferenceExcerpt {
2992 path_key: path_key.clone(),
2993 path_key_index,
2994 buffer: buffer.clone(),
2995 range: buffer_snapshot.anchor_before(range.context.start)
2996 ..buffer_snapshot.anchor_after(range.context.end),
2997 }),
2998 );
2999 self.update_expanded_diff_hunks_for_buffer(buffer_snapshot.remote_id(), cx);
3000 }
3001
3002 fn expand_diff_hunks(&mut self, path_key: PathKey, range: Range<text::Anchor>, cx: &App) {
3003 let excerpt = self
3004 .excerpts
3005 .iter_mut()
3006 .find(|e| {
3007 e.path_key == path_key
3008 && e.range
3009 .start
3010 .cmp(&range.start, &e.buffer.read(cx).snapshot())
3011 .is_le()
3012 && e.range
3013 .end
3014 .cmp(&range.end, &e.buffer.read(cx).snapshot())
3015 .is_ge()
3016 })
3017 .unwrap();
3018 let buffer = excerpt.buffer.read(cx).snapshot();
3019 let buffer_id = buffer.remote_id();
3020
3021 // Skip inverted excerpts - hunks are always expanded
3022 if self.inverted_diffs.contains_key(&buffer_id) {
3023 return;
3024 }
3025
3026 let Some(diff) = self.diffs.get(&buffer_id) else {
3027 return;
3028 };
3029 let excerpt_range = excerpt.range.to_point(&buffer);
3030 let expanded_diff_hunks = self
3031 .expanded_diff_hunks_by_buffer
3032 .entry(buffer_id)
3033 .or_default();
3034 for hunk in diff
3035 .read(cx)
3036 .snapshot(cx)
3037 .hunks_intersecting_range(range, &buffer)
3038 {
3039 let hunk_range = hunk.buffer_range.to_point(&buffer);
3040 if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end {
3041 continue;
3042 }
3043 if let Err(ix) = expanded_diff_hunks
3044 .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
3045 {
3046 log::info!(
3047 "expanding diff hunk {:?}. excerpt range: {:?}, buffer {:?}",
3048 hunk_range,
3049 excerpt_range,
3050 buffer.remote_id()
3051 );
3052 expanded_diff_hunks.insert(ix, hunk.buffer_range.start);
3053 } else {
3054 log::trace!("hunk {hunk_range:?} already expanded in excerpt");
3055 }
3056 }
3057 }
3058
3059 fn expected_content(
3060 &self,
3061 cx: &App,
3062 ) -> (
3063 String,
3064 Vec<RowInfo>,
3065 HashSet<MultiBufferRow>,
3066 Vec<ReferenceRegion>,
3067 ) {
3068 use util::maybe;
3069
3070 let mut text = String::new();
3071 let mut regions = Vec::<ReferenceRegion>::new();
3072 let mut excerpt_boundary_rows = HashSet::default();
3073 for excerpt in &self.excerpts {
3074 excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
3075 let buffer = excerpt.buffer.read(cx);
3076 let buffer_id = buffer.remote_id();
3077 let buffer_range = excerpt.range.to_offset(buffer);
3078
3079 if let Some((diff, main_buffer)) = self.inverted_diffs.get(&buffer_id) {
3080 let diff_snapshot = diff.read(cx).snapshot(cx);
3081 let main_buffer_snapshot = main_buffer.read(cx).snapshot();
3082
3083 let mut offset = buffer_range.start;
3084 for hunk in diff_snapshot.hunks_intersecting_base_text_range(
3085 buffer_range.clone(),
3086 &main_buffer_snapshot.text,
3087 ) {
3088 let mut hunk_base_range = hunk.diff_base_byte_range.clone();
3089
3090 hunk_base_range.end = hunk_base_range.end.min(buffer_range.end);
3091 if hunk_base_range.start > buffer_range.end
3092 || hunk_base_range.start < buffer_range.start
3093 {
3094 continue;
3095 }
3096
3097 // Add the text before the hunk
3098 if hunk_base_range.start >= offset {
3099 let len = text.len();
3100 text.extend(buffer.text_for_range(offset..hunk_base_range.start));
3101 if text.len() > len {
3102 regions.push(ReferenceRegion {
3103 buffer_id: Some(buffer_id),
3104 range: len..text.len(),
3105 buffer_range: (offset..hunk_base_range.start).to_point(&buffer),
3106 status: None,
3107 excerpt: Some(excerpt.clone()),
3108 deleted_hunk_anchor: None,
3109 });
3110 }
3111 }
3112
3113 // Add the "deleted" region (base text that's not in main)
3114 if !hunk_base_range.is_empty() {
3115 let len = text.len();
3116 text.extend(buffer.text_for_range(hunk_base_range.clone()));
3117 regions.push(ReferenceRegion {
3118 buffer_id: Some(buffer_id),
3119 range: len..text.len(),
3120 buffer_range: hunk_base_range.to_point(&buffer),
3121 status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
3122 excerpt: Some(excerpt.clone()),
3123 deleted_hunk_anchor: None,
3124 });
3125 }
3126
3127 offset = hunk_base_range.end;
3128 }
3129
3130 // Add remaining buffer text
3131 let len = text.len();
3132 text.extend(buffer.text_for_range(offset..buffer_range.end));
3133 text.push('\n');
3134 regions.push(ReferenceRegion {
3135 buffer_id: Some(buffer_id),
3136 range: len..text.len(),
3137 buffer_range: (offset..buffer_range.end).to_point(&buffer),
3138 status: None,
3139 excerpt: Some(excerpt.clone()),
3140 deleted_hunk_anchor: None,
3141 });
3142 } else {
3143 let diff = self.diffs.get(&buffer_id).unwrap().read(cx).snapshot(cx);
3144 let base_buffer = diff.base_text();
3145
3146 let mut offset = buffer_range.start;
3147 let hunks = diff
3148 .hunks_intersecting_range(excerpt.range.clone(), buffer)
3149 .peekable();
3150
3151 for hunk in hunks {
3152 // Ignore hunks that are outside the excerpt range.
3153 let mut hunk_range = hunk.buffer_range.to_offset(buffer);
3154
3155 hunk_range.end = hunk_range.end.min(buffer_range.end);
3156 if hunk_range.start > buffer_range.end || hunk_range.start < buffer_range.start
3157 {
3158 log::trace!("skipping hunk outside excerpt range");
3159 continue;
3160 }
3161
3162 if !self
3163 .expanded_diff_hunks_by_buffer
3164 .get(&buffer_id)
3165 .cloned()
3166 .into_iter()
3167 .flatten()
3168 .any(|expanded_anchor| {
3169 expanded_anchor
3170 .cmp(&hunk.buffer_range.start, buffer)
3171 .is_eq()
3172 })
3173 {
3174 log::trace!("skipping a hunk that's not marked as expanded");
3175 continue;
3176 }
3177
3178 if !hunk.buffer_range.start.is_valid(buffer) {
3179 log::trace!("skipping hunk with deleted start: {:?}", hunk.range);
3180 continue;
3181 }
3182
3183 if hunk_range.start >= offset {
3184 // Add the buffer text before the hunk
3185 let len = text.len();
3186 text.extend(buffer.text_for_range(offset..hunk_range.start));
3187 if text.len() > len {
3188 regions.push(ReferenceRegion {
3189 buffer_id: Some(buffer_id),
3190 range: len..text.len(),
3191 buffer_range: (offset..hunk_range.start).to_point(&buffer),
3192 status: None,
3193 excerpt: Some(excerpt.clone()),
3194 deleted_hunk_anchor: None,
3195 });
3196 }
3197
3198 // Add the deleted text for the hunk.
3199 if !hunk.diff_base_byte_range.is_empty() {
3200 let mut base_text = base_buffer
3201 .text_for_range(hunk.diff_base_byte_range.clone())
3202 .collect::<String>();
3203 if !base_text.ends_with('\n') {
3204 base_text.push('\n');
3205 }
3206 let len = text.len();
3207 text.push_str(&base_text);
3208 regions.push(ReferenceRegion {
3209 buffer_id: Some(base_buffer.remote_id()),
3210 range: len..text.len(),
3211 buffer_range: hunk.diff_base_byte_range.to_point(&base_buffer),
3212 status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
3213 excerpt: Some(excerpt.clone()),
3214 deleted_hunk_anchor: Some(hunk.buffer_range.start),
3215 });
3216 }
3217
3218 offset = hunk_range.start;
3219 }
3220
3221 // Add the inserted text for the hunk.
3222 if hunk_range.end > offset {
3223 let len = text.len();
3224 text.extend(buffer.text_for_range(offset..hunk_range.end));
3225 let range = len..text.len();
3226 let region = ReferenceRegion {
3227 buffer_id: Some(buffer_id),
3228 range,
3229 buffer_range: (offset..hunk_range.end).to_point(&buffer),
3230 status: Some(DiffHunkStatus::added(hunk.secondary_status)),
3231 excerpt: Some(excerpt.clone()),
3232 deleted_hunk_anchor: None,
3233 };
3234 offset = hunk_range.end;
3235 regions.push(region);
3236 }
3237 }
3238
3239 // Add the buffer text for the rest of the excerpt.
3240 let len = text.len();
3241 text.extend(buffer.text_for_range(offset..buffer_range.end));
3242 text.push('\n');
3243 regions.push(ReferenceRegion {
3244 buffer_id: Some(buffer_id),
3245 range: len..text.len(),
3246 buffer_range: (offset..buffer_range.end).to_point(&buffer),
3247 status: None,
3248 excerpt: Some(excerpt.clone()),
3249 deleted_hunk_anchor: None,
3250 });
3251 }
3252 }
3253
3254 // Remove final trailing newline.
3255 if self.excerpts.is_empty() {
3256 regions.push(ReferenceRegion {
3257 buffer_id: None,
3258 range: 0..1,
3259 buffer_range: Point::new(0, 0)..Point::new(0, 1),
3260 status: None,
3261 excerpt: None,
3262 deleted_hunk_anchor: None,
3263 });
3264 } else {
3265 text.pop();
3266 let region = regions.last_mut().unwrap();
3267 assert!(region.deleted_hunk_anchor.is_none());
3268 region.range.end -= 1;
3269 }
3270
3271 // Retrieve the row info using the region that contains
3272 // the start of each multi-buffer line.
3273 let mut ix = 0;
3274 let row_infos = text
3275 .split('\n')
3276 .map(|line| {
3277 let row_info = regions
3278 .iter()
3279 .rposition(|region| {
3280 region.range.contains(&ix) || (ix == text.len() && ix == region.range.end)
3281 })
3282 .map_or(RowInfo::default(), |region_ix| {
3283 let region = regions[region_ix].clone();
3284 let buffer_row = region.buffer_range.start.row
3285 + text[region.range.start..ix].matches('\n').count() as u32;
3286 let main_buffer = region.excerpt.as_ref().map(|e| e.buffer.clone());
3287 let excerpt_range = region.excerpt.as_ref().map(|e| &e.range);
3288 let is_excerpt_start = region_ix == 0
3289 || regions[region_ix - 1].excerpt.as_ref().map(|e| &e.range)
3290 != excerpt_range
3291 || regions[region_ix - 1].range.is_empty();
3292 let mut is_excerpt_end = region_ix == regions.len() - 1
3293 || regions[region_ix + 1].excerpt.as_ref().map(|e| &e.range)
3294 != excerpt_range;
3295 let is_start = !text[region.range.start..ix].contains('\n');
3296 let is_last_region = region_ix == regions.len() - 1;
3297 let mut is_end = if region.range.end > text.len() {
3298 !text[ix..].contains('\n')
3299 } else {
3300 let remaining_newlines = text[ix..region.range.end.min(text.len())]
3301 .matches('\n')
3302 .count();
3303 remaining_newlines == if is_last_region { 0 } else { 1 }
3304 };
3305 if region_ix < regions.len() - 1
3306 && !text[ix..].contains("\n")
3307 && (region.status == Some(DiffHunkStatus::added_none())
3308 || region.status.is_some_and(|s| s.is_deleted()))
3309 && regions[region_ix + 1].excerpt.as_ref().map(|e| &e.range)
3310 == excerpt_range
3311 && regions[region_ix + 1].range.start == text.len()
3312 {
3313 is_end = true;
3314 is_excerpt_end = true;
3315 }
3316 let multibuffer_row =
3317 MultiBufferRow(text[..ix].matches('\n').count() as u32);
3318 let mut expand_direction = None;
3319 if let Some(buffer) = &main_buffer {
3320 let needs_expand_up = is_excerpt_start && is_start && buffer_row > 0;
3321 let needs_expand_down = is_excerpt_end
3322 && is_end
3323 && buffer.read(cx).max_point().row > buffer_row;
3324 expand_direction = if needs_expand_up && needs_expand_down {
3325 Some(ExpandExcerptDirection::UpAndDown)
3326 } else if needs_expand_up {
3327 Some(ExpandExcerptDirection::Up)
3328 } else if needs_expand_down {
3329 Some(ExpandExcerptDirection::Down)
3330 } else {
3331 None
3332 };
3333 }
3334 RowInfo {
3335 buffer_id: region.buffer_id,
3336 diff_status: region.status,
3337 buffer_row: Some(buffer_row),
3338 wrapped_buffer_row: None,
3339
3340 multibuffer_row: Some(multibuffer_row),
3341 expand_info: maybe!({
3342 let direction = expand_direction?;
3343 let excerpt = region.excerpt.as_ref()?;
3344 Some(ExpandInfo {
3345 direction,
3346 start_anchor: Anchor::in_buffer(
3347 excerpt.path_key_index,
3348 excerpt.range.start,
3349 ),
3350 })
3351 }),
3352 }
3353 });
3354 ix += line.len() + 1;
3355 row_info
3356 })
3357 .collect();
3358
3359 (text, row_infos, excerpt_boundary_rows, regions)
3360 }
3361
3362 fn diffs_updated(&mut self, cx: &mut App) {
3363 let buffer_ids = self.diffs.keys().copied().collect::<Vec<_>>();
3364 for buffer_id in buffer_ids {
3365 self.update_expanded_diff_hunks_for_buffer(buffer_id, cx);
3366 }
3367 }
3368
3369 fn add_diff(&mut self, diff: Entity<BufferDiff>, cx: &mut App) {
3370 let buffer_id = diff.read(cx).buffer_id;
3371 self.diffs.insert(buffer_id, diff);
3372 }
3373
3374 fn add_inverted_diff(
3375 &mut self,
3376 diff: Entity<BufferDiff>,
3377 main_buffer: Entity<language::Buffer>,
3378 cx: &App,
3379 ) {
3380 let base_text_buffer_id = diff.read(cx).base_text(cx).remote_id();
3381 self.inverted_diffs
3382 .insert(base_text_buffer_id, (diff, main_buffer));
3383 }
3384
3385 fn update_expanded_diff_hunks_for_buffer(&mut self, buffer_id: BufferId, cx: &mut App) {
3386 let excerpts = self
3387 .excerpts
3388 .iter()
3389 .filter(|excerpt| excerpt.buffer.read(cx).remote_id() == buffer_id)
3390 .collect::<Vec<_>>();
3391 let Some(buffer) = excerpts.first().map(|excerpt| excerpt.buffer.clone()) else {
3392 self.expanded_diff_hunks_by_buffer.remove(&buffer_id);
3393 return;
3394 };
3395 let buffer_snapshot = buffer.read(cx).snapshot();
3396 let Some(diff) = self.diffs.get(&buffer_id) else {
3397 self.expanded_diff_hunks_by_buffer.remove(&buffer_id);
3398 return;
3399 };
3400 let diff = diff.read(cx).snapshot(cx);
3401 let hunks = diff
3402 .hunks_in_row_range(0..u32::MAX, &buffer_snapshot)
3403 .collect::<Vec<_>>();
3404 self.expanded_diff_hunks_by_buffer
3405 .entry(buffer_id)
3406 .or_default()
3407 .retain(|hunk_anchor| {
3408 if !hunk_anchor.is_valid(&buffer_snapshot) {
3409 return false;
3410 }
3411
3412 let Ok(ix) = hunks.binary_search_by(|hunk| {
3413 hunk.buffer_range.start.cmp(hunk_anchor, &buffer_snapshot)
3414 }) else {
3415 return false;
3416 };
3417 let hunk_range = hunks[ix].buffer_range.to_point(&buffer_snapshot);
3418 excerpts.iter().any(|excerpt| {
3419 let excerpt_range = excerpt.range.to_point(&buffer_snapshot);
3420 hunk_range.start >= excerpt_range.start && hunk_range.start <= excerpt_range.end
3421 })
3422 });
3423 }
3424
3425 fn anchor_to_offset(&self, anchor: &Anchor, cx: &App) -> Option<MultiBufferOffset> {
3426 if anchor.diff_base_anchor().is_some() {
3427 panic!("reference multibuffer cannot yet resolve anchors inside deleted hunks");
3428 }
3429 let (anchor, snapshot, path_key) = self.anchor_to_buffer_anchor(anchor, cx)?;
3430 // TODO(cole) can maybe make this and expected content call a common function instead
3431 let (text, _, _, regions) = self.expected_content(cx);
3432
3433 // Locate the first region that contains or is past the putative location of the buffer anchor
3434 let ix = regions.partition_point(|region| {
3435 let excerpt = region
3436 .excerpt
3437 .as_ref()
3438 .expect("should have no buffers in empty reference multibuffer");
3439 excerpt
3440 .path_key
3441 .cmp(&path_key)
3442 .then_with(|| {
3443 if excerpt.range.end.cmp(&anchor, &snapshot).is_lt() {
3444 Ordering::Less
3445 } else if excerpt.range.start.cmp(&anchor, &snapshot).is_gt() {
3446 Ordering::Greater
3447 } else {
3448 Ordering::Equal
3449 }
3450 })
3451 .then_with(|| {
3452 if let Some(deleted_hunk_anchor) = region.deleted_hunk_anchor {
3453 deleted_hunk_anchor.cmp(&anchor, &snapshot)
3454 } else {
3455 let point = anchor.to_point(&snapshot);
3456 assert_eq!(region.buffer_id, Some(snapshot.remote_id()));
3457 if region.buffer_range.end < point {
3458 Ordering::Less
3459 } else if region.buffer_range.start > point {
3460 Ordering::Greater
3461 } else {
3462 Ordering::Equal
3463 }
3464 }
3465 })
3466 .is_lt()
3467 });
3468
3469 let Some(region) = regions.get(ix) else {
3470 return Some(MultiBufferOffset(text.len()));
3471 };
3472
3473 let offset = if region.buffer_id == Some(snapshot.remote_id()) {
3474 let buffer_offset = anchor.to_offset(&snapshot);
3475 let buffer_range = region.buffer_range.to_offset(&snapshot);
3476 assert!(buffer_offset <= buffer_range.end);
3477 let overshoot = buffer_offset.saturating_sub(buffer_range.start);
3478 region.range.start + overshoot
3479 } else {
3480 region.range.start
3481 };
3482 Some(MultiBufferOffset(offset))
3483 }
3484
3485 fn anchor_to_buffer_anchor(
3486 &self,
3487 anchor: &Anchor,
3488 cx: &App,
3489 ) -> Option<(text::Anchor, BufferSnapshot, PathKey)> {
3490 let (excerpt, anchor) = match anchor {
3491 Anchor::Min => {
3492 let excerpt = self.excerpts.first()?;
3493 (excerpt, excerpt.range.start)
3494 }
3495 Anchor::Excerpt(excerpt_anchor) => (
3496 self.excerpts.iter().find(|excerpt| {
3497 excerpt.buffer.read(cx).remote_id() == excerpt_anchor.buffer_id()
3498 })?,
3499 excerpt_anchor.text_anchor,
3500 ),
3501 Anchor::Max => {
3502 let excerpt = self.excerpts.last()?;
3503 (excerpt, excerpt.range.end)
3504 }
3505 };
3506
3507 Some((
3508 anchor,
3509 excerpt.buffer.read(cx).snapshot(),
3510 excerpt.path_key.clone(),
3511 ))
3512 }
3513}
3514
3515#[gpui::test(iterations = 100)]
3516async fn test_random_set_ranges(cx: &mut TestAppContext, mut rng: StdRng) {
3517 let base_text = "a\n".repeat(100);
3518 let buf = cx.update(|cx| cx.new(|cx| Buffer::local(base_text, cx)));
3519 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
3520
3521 let operations = env::var("OPERATIONS")
3522 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3523 .unwrap_or(10);
3524
3525 fn row_ranges(ranges: &Vec<Range<Point>>) -> Vec<Range<u32>> {
3526 ranges
3527 .iter()
3528 .map(|range| range.start.row..range.end.row)
3529 .collect()
3530 }
3531
3532 for _ in 0..operations {
3533 let snapshot = buf.update(cx, |buf, _| buf.snapshot());
3534 let num_ranges = rng.random_range(0..=10);
3535 let max_row = snapshot.max_point().row;
3536 let mut ranges = (0..num_ranges)
3537 .map(|_| {
3538 let start = rng.random_range(0..max_row);
3539 let end = rng.random_range(start + 1..max_row + 1);
3540 Point::row_range(start..end)
3541 })
3542 .collect::<Vec<_>>();
3543 ranges.sort_by_key(|range| range.start);
3544 log::info!("Setting ranges: {:?}", row_ranges(&ranges));
3545 multibuffer.update(cx, |multibuffer, cx| {
3546 multibuffer.set_excerpts_for_path(
3547 PathKey::for_buffer(&buf, cx),
3548 buf.clone(),
3549 ranges.clone(),
3550 2,
3551 cx,
3552 )
3553 });
3554
3555 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3556 let mut last_end = None;
3557 let mut seen_ranges = Vec::default();
3558
3559 for info in snapshot.excerpts() {
3560 let buffer_snapshot = snapshot
3561 .buffer_for_id(info.context.start.buffer_id)
3562 .unwrap();
3563 let start = info.context.start.to_point(buffer_snapshot);
3564 let end = info.context.end.to_point(buffer_snapshot);
3565 seen_ranges.push(start..end);
3566
3567 if let Some(last_end) = last_end.take() {
3568 assert!(
3569 start > last_end,
3570 "multibuffer has out-of-order ranges: {:?}; {:?} <= {:?}",
3571 row_ranges(&seen_ranges),
3572 start,
3573 last_end
3574 )
3575 }
3576
3577 ranges.retain(|range| range.start < start || range.end > end);
3578
3579 last_end = Some(end)
3580 }
3581
3582 assert!(
3583 ranges.is_empty(),
3584 "multibuffer {:?} did not include all ranges: {:?}",
3585 row_ranges(&seen_ranges),
3586 row_ranges(&ranges)
3587 );
3588 }
3589}
3590
3591#[gpui::test(iterations = 100)]
3592async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
3593 let operations = env::var("OPERATIONS")
3594 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3595 .unwrap_or(10);
3596 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
3597 let mut buffers: Vec<Entity<Buffer>> = Vec::new();
3598 let mut base_texts: HashMap<BufferId, String> = HashMap::default();
3599 let mut reference = ReferenceMultibuffer::default();
3600 let mut anchors = Vec::new();
3601 let mut old_versions = Vec::new();
3602 let mut needs_diff_calculation = false;
3603 let mut inverted_diff_main_buffers: HashMap<BufferId, Entity<BufferDiff>> = HashMap::default();
3604 for _ in 0..operations {
3605 match rng.random_range(0..100) {
3606 0..=14 if !buffers.is_empty() => {
3607 let buffer = buffers.choose(&mut rng).unwrap();
3608 buffer.update(cx, |buf, cx| {
3609 let edit_count = rng.random_range(1..5);
3610 buf.randomly_edit(&mut rng, edit_count, cx);
3611 log::info!("buffer text:\n{}", buf.text());
3612 needs_diff_calculation = true;
3613 });
3614 cx.update(|cx| reference.diffs_updated(cx));
3615 }
3616 15..=24 if !reference.excerpts.is_empty() => {
3617 multibuffer.update(cx, |multibuffer, cx| {
3618 let snapshot = multibuffer.snapshot(cx);
3619 let infos = snapshot.excerpts().collect::<Vec<_>>();
3620 let mut excerpts = HashSet::default();
3621 for _ in 0..rng.random_range(0..infos.len()) {
3622 excerpts.extend(infos.choose(&mut rng).cloned());
3623 }
3624
3625 let line_count = rng.random_range(0..5);
3626
3627 let excerpt_ixs = excerpts
3628 .iter()
3629 .map(|info| {
3630 reference
3631 .excerpts
3632 .iter()
3633 .position(|e| e.range == info.context)
3634 .unwrap()
3635 })
3636 .collect::<Vec<_>>();
3637 log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
3638 multibuffer.expand_excerpts(
3639 excerpts
3640 .iter()
3641 .map(|info| snapshot.anchor_in_excerpt(info.context.end).unwrap()),
3642 line_count,
3643 ExpandExcerptDirection::UpAndDown,
3644 cx,
3645 );
3646
3647 reference.expand_excerpts(&excerpts, line_count, cx);
3648 });
3649 }
3650 25..=34 if !reference.excerpts.is_empty() => {
3651 let multibuffer =
3652 multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3653 let offset = multibuffer.clip_offset(
3654 MultiBufferOffset(rng.random_range(0..=multibuffer.len().0)),
3655 Bias::Left,
3656 );
3657 let bias = if rng.random() {
3658 Bias::Left
3659 } else {
3660 Bias::Right
3661 };
3662 log::info!("Creating anchor at {} with bias {:?}", offset.0, bias);
3663 anchors.push(multibuffer.anchor_at(offset, bias));
3664 anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
3665 }
3666 35..=45 if !reference.excerpts.is_empty() => {
3667 multibuffer.update(cx, |multibuffer, cx| {
3668 let snapshot = multibuffer.snapshot(cx);
3669 let excerpt_ix = rng.random_range(0..reference.excerpts.len());
3670 let excerpt = &reference.excerpts[excerpt_ix];
3671
3672 // Skip inverted excerpts - hunks can't be collapsed
3673 let buffer_id = excerpt.buffer.read(cx).remote_id();
3674 if reference.inverted_diffs.contains_key(&buffer_id) {
3675 return;
3676 }
3677
3678 let start = excerpt.range.start;
3679 let end = excerpt.range.end;
3680 let range = snapshot.anchor_in_excerpt(start).unwrap()
3681 ..snapshot.anchor_in_excerpt(end).unwrap();
3682
3683 log::info!(
3684 "expanding diff hunks in range {:?} (excerpt index {excerpt_ix:?}, buffer id {:?})",
3685 range.to_point(&snapshot),
3686 buffer_id,
3687 );
3688 reference.expand_diff_hunks(excerpt.path_key.clone(), start..end, cx);
3689 multibuffer.expand_diff_hunks(vec![range], cx);
3690 });
3691 }
3692 46..=75 if needs_diff_calculation => {
3693 multibuffer.update(cx, |multibuffer, cx| {
3694 for buffer in multibuffer.all_buffers() {
3695 let snapshot = buffer.read(cx).snapshot();
3696 let buffer_id = snapshot.remote_id();
3697
3698 if let Some(diff) = multibuffer.diff_for(buffer_id) {
3699 diff.update(cx, |diff, cx| {
3700 log::info!("recalculating diff for buffer {:?}", buffer_id,);
3701 diff.recalculate_diff_sync(&snapshot.text, cx);
3702 });
3703 }
3704
3705 if let Some(inverted_diff) = inverted_diff_main_buffers.get(&buffer_id) {
3706 inverted_diff.update(cx, |diff, cx| {
3707 log::info!(
3708 "recalculating inverted diff for main buffer {:?}",
3709 buffer_id,
3710 );
3711 diff.recalculate_diff_sync(&snapshot.text, cx);
3712 });
3713 }
3714 }
3715 reference.diffs_updated(cx);
3716 needs_diff_calculation = false;
3717 });
3718 }
3719 _ => {
3720 // Decide if we're creating a new buffer or reusing an existing one
3721 let create_new_buffer = buffers.is_empty() || rng.random_bool(0.4);
3722
3723 let (excerpt_buffer, diff, inverted_main_buffer) = if create_new_buffer {
3724 let create_inverted = rng.random_bool(0.3);
3725
3726 if create_inverted {
3727 let mut main_buffer_text = util::RandomCharIter::new(&mut rng)
3728 .take(256)
3729 .collect::<String>();
3730 let main_buffer = cx.new(|cx| Buffer::local(main_buffer_text.clone(), cx));
3731 text::LineEnding::normalize(&mut main_buffer_text);
3732 let main_buffer_id =
3733 main_buffer.read_with(cx, |buffer, _| buffer.remote_id());
3734 base_texts.insert(main_buffer_id, main_buffer_text.clone());
3735 buffers.push(main_buffer.clone());
3736
3737 let diff = cx.new(|cx| {
3738 BufferDiff::new_with_base_text(
3739 &main_buffer_text,
3740 &main_buffer.read(cx).text_snapshot(),
3741 cx,
3742 )
3743 });
3744
3745 let base_text_buffer =
3746 diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
3747
3748 // Track for recalculation when main buffer is edited
3749 inverted_diff_main_buffers.insert(main_buffer_id, diff.clone());
3750
3751 (base_text_buffer, diff, Some(main_buffer))
3752 } else {
3753 let mut base_text = util::RandomCharIter::new(&mut rng)
3754 .take(256)
3755 .collect::<String>();
3756
3757 let buffer_handle = cx.new(|cx| Buffer::local(base_text.clone(), cx));
3758 text::LineEnding::normalize(&mut base_text);
3759 let buffer_id = buffer_handle.read_with(cx, |buffer, _| buffer.remote_id());
3760 base_texts.insert(buffer_id, base_text.clone());
3761 buffers.push(buffer_handle.clone());
3762
3763 let diff = cx.new(|cx| {
3764 BufferDiff::new_with_base_text(
3765 &base_text,
3766 &buffer_handle.read(cx).text_snapshot(),
3767 cx,
3768 )
3769 });
3770
3771 (buffer_handle, diff, None)
3772 }
3773 } else {
3774 // Reuse an existing buffer
3775 let buffer_handle = buffers.choose(&mut rng).unwrap().clone();
3776 let buffer_id = buffer_handle.read_with(cx, |buffer, _| buffer.remote_id());
3777
3778 if let Some(diff) = inverted_diff_main_buffers.get(&buffer_id) {
3779 let base_text_buffer =
3780 diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
3781 (base_text_buffer, diff.clone(), Some(buffer_handle))
3782 } else {
3783 // Get existing diff or create new one for regular buffer
3784 let diff = multibuffer
3785 .read_with(cx, |mb, _| mb.diff_for(buffer_id))
3786 .unwrap_or_else(|| {
3787 let base_text = base_texts.get(&buffer_id).unwrap();
3788 cx.new(|cx| {
3789 BufferDiff::new_with_base_text(
3790 base_text,
3791 &buffer_handle.read(cx).text_snapshot(),
3792 cx,
3793 )
3794 })
3795 });
3796 (buffer_handle, diff, None)
3797 }
3798 };
3799
3800 let excerpt_buffer_snapshot =
3801 excerpt_buffer.read_with(cx, |excerpt_buffer, _| excerpt_buffer.snapshot());
3802 let mut ranges = reference
3803 .excerpts
3804 .iter()
3805 .filter(|excerpt| excerpt.buffer == excerpt_buffer)
3806 .map(|excerpt| excerpt.range.to_point(&excerpt_buffer_snapshot))
3807 .collect::<Vec<_>>();
3808 mutate_excerpt_ranges(&mut rng, &mut ranges, &excerpt_buffer_snapshot, 1);
3809 let ranges = ranges
3810 .iter()
3811 .cloned()
3812 .map(ExcerptRange::new)
3813 .collect::<Vec<_>>();
3814 let path = cx.update(|cx| PathKey::for_buffer(&excerpt_buffer, cx));
3815 let path_key_index = multibuffer.update(cx, |multibuffer, _| {
3816 multibuffer.get_or_create_path_key_index(&path)
3817 });
3818
3819 multibuffer.update(cx, |multibuffer, cx| {
3820 multibuffer.set_excerpt_ranges_for_path(
3821 path.clone(),
3822 excerpt_buffer.clone(),
3823 &excerpt_buffer_snapshot,
3824 ranges.clone(),
3825 cx,
3826 )
3827 });
3828
3829 cx.update(|cx| {
3830 reference.set_excerpts(
3831 path,
3832 path_key_index,
3833 excerpt_buffer.clone(),
3834 &excerpt_buffer_snapshot,
3835 ranges,
3836 cx,
3837 )
3838 });
3839
3840 let excerpt_buffer_id =
3841 excerpt_buffer.read_with(cx, |buffer, _| buffer.remote_id());
3842 multibuffer.update(cx, |multibuffer, cx| {
3843 if multibuffer.diff_for(excerpt_buffer_id).is_none() {
3844 if let Some(main_buffer) = inverted_main_buffer {
3845 reference.add_inverted_diff(diff.clone(), main_buffer.clone(), cx);
3846 multibuffer.add_inverted_diff(diff, main_buffer, cx);
3847 } else {
3848 reference.add_diff(diff.clone(), cx);
3849 multibuffer.add_diff(diff, cx);
3850 }
3851 }
3852 });
3853 }
3854 }
3855
3856 if rng.random_bool(0.3) {
3857 multibuffer.update(cx, |multibuffer, cx| {
3858 old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
3859 })
3860 }
3861
3862 multibuffer.read_with(cx, |multibuffer, cx| {
3863 check_multibuffer(multibuffer, &reference, &anchors, cx, &mut rng);
3864 });
3865 }
3866 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3867 for (old_snapshot, subscription) in old_versions {
3868 check_multibuffer_edits(&snapshot, &old_snapshot, subscription);
3869 }
3870}
3871
3872fn mutate_excerpt_ranges(
3873 rng: &mut StdRng,
3874 existing_ranges: &mut Vec<Range<Point>>,
3875 buffer: &BufferSnapshot,
3876 operations: u32,
3877) {
3878 let mut ranges_to_add = Vec::new();
3879
3880 for _ in 0..operations {
3881 match rng.random_range(0..5) {
3882 0..=1 if !existing_ranges.is_empty() => {
3883 let index = rng.random_range(0..existing_ranges.len());
3884 log::info!("Removing excerpt at index {index}");
3885 existing_ranges.remove(index);
3886 }
3887 _ => {
3888 let end_row = rng.random_range(0..=buffer.max_point().row);
3889 let start_row = rng.random_range(0..=end_row);
3890 let end_col = buffer.line_len(end_row);
3891 log::info!(
3892 "Inserting excerpt for buffer {:?}, row range {:?}",
3893 buffer.remote_id(),
3894 start_row..end_row
3895 );
3896 ranges_to_add.push(Point::new(start_row, 0)..Point::new(end_row, end_col));
3897 }
3898 }
3899 }
3900
3901 existing_ranges.extend(ranges_to_add);
3902 existing_ranges.sort_by(|l, r| l.start.cmp(&r.start));
3903}
3904
3905fn check_multibuffer(
3906 multibuffer: &MultiBuffer,
3907 reference: &ReferenceMultibuffer,
3908 anchors: &[Anchor],
3909 cx: &App,
3910 rng: &mut StdRng,
3911) {
3912 let snapshot = multibuffer.snapshot(cx);
3913 let actual_text = snapshot.text();
3914 let actual_boundary_rows = snapshot
3915 .excerpt_boundaries_in_range(MultiBufferOffset(0)..)
3916 .map(|b| b.row)
3917 .collect::<HashSet<_>>();
3918 let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3919
3920 let anchors_to_check = anchors
3921 .iter()
3922 .filter_map(|anchor| {
3923 snapshot
3924 .anchor_to_buffer_anchor(*anchor)
3925 .map(|(anchor, _)| anchor)
3926 })
3927 // Intentionally mix in some anchors that are (in general) not contained in any excerpt
3928 .chain(
3929 reference
3930 .excerpts
3931 .iter()
3932 .map(|excerpt| excerpt.buffer.read(cx).remote_id())
3933 .dedup()
3934 .flat_map(|buffer_id| {
3935 [
3936 text::Anchor::min_for_buffer(buffer_id),
3937 text::Anchor::max_for_buffer(buffer_id),
3938 ]
3939 }),
3940 )
3941 .map(|anchor| snapshot.anchor_in_buffer(anchor).unwrap())
3942 .collect::<Vec<_>>();
3943
3944 let (expected_text, expected_row_infos, expected_boundary_rows, _) =
3945 reference.expected_content(cx);
3946 let expected_anchor_offsets = anchors_to_check
3947 .iter()
3948 .map(|anchor| reference.anchor_to_offset(anchor, cx).unwrap())
3949 .collect::<Vec<_>>();
3950
3951 let has_diff = actual_row_infos
3952 .iter()
3953 .any(|info| info.diff_status.is_some())
3954 || expected_row_infos
3955 .iter()
3956 .any(|info| info.diff_status.is_some());
3957 let actual_diff = format_diff(
3958 &actual_text,
3959 &actual_row_infos,
3960 &actual_boundary_rows,
3961 Some(has_diff),
3962 );
3963 let expected_diff = format_diff(
3964 &expected_text,
3965 &expected_row_infos,
3966 &expected_boundary_rows,
3967 Some(has_diff),
3968 );
3969
3970 log::info!("Multibuffer content:\n{}", actual_diff);
3971
3972 assert_eq!(
3973 actual_row_infos.len(),
3974 actual_text.split('\n').count(),
3975 "line count: {}",
3976 actual_text.split('\n').count()
3977 );
3978 pretty_assertions::assert_eq!(actual_diff, expected_diff);
3979 pretty_assertions::assert_eq!(actual_text, expected_text);
3980 pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
3981
3982 for _ in 0..5 {
3983 let start_row = rng.random_range(0..=expected_row_infos.len());
3984 assert_eq!(
3985 snapshot
3986 .row_infos(MultiBufferRow(start_row as u32))
3987 .collect::<Vec<_>>(),
3988 &expected_row_infos[start_row..],
3989 "buffer_rows({})",
3990 start_row
3991 );
3992 }
3993
3994 assert_eq!(
3995 snapshot.widest_line_number(),
3996 expected_row_infos
3997 .into_iter()
3998 .filter_map(|info| {
3999 // For inverted diffs, deleted rows are visible and should be counted.
4000 // Only filter out deleted rows that are NOT from inverted diffs.
4001 let is_inverted_diff = info
4002 .buffer_id
4003 .is_some_and(|id| reference.inverted_diffs.contains_key(&id));
4004 if info.diff_status.is_some_and(|status| status.is_deleted()) && !is_inverted_diff {
4005 None
4006 } else {
4007 info.buffer_row
4008 }
4009 })
4010 .max()
4011 .unwrap()
4012 + 1
4013 );
4014 for i in 0..snapshot.len().0 {
4015 let (_, excerpt_range) = snapshot
4016 .excerpt_containing(MultiBufferOffset(i)..MultiBufferOffset(i))
4017 .unwrap();
4018 reference
4019 .excerpts
4020 .iter()
4021 .find(|reference_excerpt| reference_excerpt.range == excerpt_range.context)
4022 .expect("corresponding excerpt should exist in reference multibuffer");
4023 }
4024
4025 assert_consistent_line_numbers(&snapshot);
4026 assert_position_translation(&snapshot);
4027
4028 for (row, line) in expected_text.split('\n').enumerate() {
4029 assert_eq!(
4030 snapshot.line_len(MultiBufferRow(row as u32)),
4031 line.len() as u32,
4032 "line_len({}).",
4033 row
4034 );
4035 }
4036
4037 let text_rope = Rope::from(expected_text.as_str());
4038 for _ in 0..10 {
4039 let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
4040 let start_ix = text_rope.clip_offset(rng.random_range(0..=end_ix), Bias::Left);
4041
4042 let text_for_range = snapshot
4043 .text_for_range(MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix))
4044 .collect::<String>();
4045 assert_eq!(
4046 text_for_range,
4047 &expected_text[start_ix..end_ix],
4048 "incorrect text for range {:?}",
4049 start_ix..end_ix
4050 );
4051
4052 let expected_summary =
4053 MBTextSummary::from(TextSummary::from(&expected_text[start_ix..end_ix]));
4054 assert_eq!(
4055 snapshot.text_summary_for_range::<MBTextSummary, _>(
4056 MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix)
4057 ),
4058 expected_summary,
4059 "incorrect summary for range {:?}",
4060 start_ix..end_ix
4061 );
4062 }
4063
4064 // Anchor resolution
4065 let summaries = snapshot.summaries_for_anchors::<MultiBufferOffset, _>(anchors);
4066 assert_eq!(anchors.len(), summaries.len());
4067 for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
4068 assert!(resolved_offset <= snapshot.len());
4069 assert_eq!(
4070 snapshot.summary_for_anchor::<MultiBufferOffset>(anchor),
4071 resolved_offset,
4072 "anchor: {:?}",
4073 anchor
4074 );
4075 }
4076
4077 let actual_anchor_offsets = anchors_to_check
4078 .into_iter()
4079 .map(|anchor| anchor.to_offset(&snapshot))
4080 .collect::<Vec<_>>();
4081 assert_eq!(
4082 actual_anchor_offsets, expected_anchor_offsets,
4083 "buffer anchor resolves to wrong offset"
4084 );
4085
4086 for _ in 0..10 {
4087 let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
4088 assert_eq!(
4089 snapshot
4090 .reversed_chars_at(MultiBufferOffset(end_ix))
4091 .collect::<String>(),
4092 expected_text[..end_ix].chars().rev().collect::<String>(),
4093 );
4094 }
4095
4096 for _ in 0..10 {
4097 let end_ix = rng.random_range(0..=text_rope.len());
4098 let end_ix = text_rope.floor_char_boundary(end_ix);
4099 let start_ix = rng.random_range(0..=end_ix);
4100 let start_ix = text_rope.floor_char_boundary(start_ix);
4101 assert_eq!(
4102 snapshot
4103 .bytes_in_range(MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix))
4104 .flatten()
4105 .copied()
4106 .collect::<Vec<_>>(),
4107 expected_text.as_bytes()[start_ix..end_ix].to_vec(),
4108 "bytes_in_range({:?})",
4109 start_ix..end_ix,
4110 );
4111 }
4112}
4113
4114fn check_multibuffer_edits(
4115 snapshot: &MultiBufferSnapshot,
4116 old_snapshot: &MultiBufferSnapshot,
4117 subscription: Subscription<MultiBufferOffset>,
4118) {
4119 let edits = subscription.consume().into_inner();
4120
4121 log::info!(
4122 "applying subscription edits to old text: {:?}: {:#?}",
4123 old_snapshot.text(),
4124 edits,
4125 );
4126
4127 let mut text = old_snapshot.text();
4128 for edit in edits {
4129 let new_text: String = snapshot
4130 .text_for_range(edit.new.start..edit.new.end)
4131 .collect();
4132 text.replace_range(
4133 (edit.new.start.0..edit.new.start.0 + (edit.old.end.0 - edit.old.start.0)).clone(),
4134 &new_text,
4135 );
4136 pretty_assertions::assert_eq!(
4137 &text[0..edit.new.end.0],
4138 snapshot
4139 .text_for_range(MultiBufferOffset(0)..edit.new.end)
4140 .collect::<String>()
4141 );
4142 }
4143 pretty_assertions::assert_eq!(text, snapshot.text());
4144}
4145
4146#[gpui::test]
4147fn test_history(cx: &mut App) {
4148 let test_settings = SettingsStore::test(cx);
4149 cx.set_global(test_settings);
4150
4151 let group_interval: Duration = Duration::from_millis(1);
4152 let buffer_1 = cx.new(|cx| {
4153 let mut buf = Buffer::local("1234", cx);
4154 buf.set_group_interval(group_interval);
4155 buf
4156 });
4157 let buffer_2 = cx.new(|cx| {
4158 let mut buf = Buffer::local("5678", cx);
4159 buf.set_group_interval(group_interval);
4160 buf
4161 });
4162 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
4163 multibuffer.update(cx, |this, cx| {
4164 this.set_group_interval(group_interval, cx);
4165 });
4166 multibuffer.update(cx, |multibuffer, cx| {
4167 multibuffer.set_excerpts_for_path(
4168 PathKey::sorted(0),
4169 buffer_1.clone(),
4170 [Point::zero()..buffer_1.read(cx).max_point()],
4171 0,
4172 cx,
4173 );
4174 multibuffer.set_excerpts_for_path(
4175 PathKey::sorted(1),
4176 buffer_2.clone(),
4177 [Point::zero()..buffer_2.read(cx).max_point()],
4178 0,
4179 cx,
4180 );
4181 });
4182
4183 let mut now = Instant::now();
4184
4185 multibuffer.update(cx, |multibuffer, cx| {
4186 let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
4187 multibuffer.edit(
4188 [
4189 (Point::new(0, 0)..Point::new(0, 0), "A"),
4190 (Point::new(1, 0)..Point::new(1, 0), "A"),
4191 ],
4192 None,
4193 cx,
4194 );
4195 multibuffer.edit(
4196 [
4197 (Point::new(0, 1)..Point::new(0, 1), "B"),
4198 (Point::new(1, 1)..Point::new(1, 1), "B"),
4199 ],
4200 None,
4201 cx,
4202 );
4203 multibuffer.end_transaction_at(now, cx);
4204 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4205
4206 // Verify edited ranges for transaction 1
4207 assert_eq!(
4208 multibuffer.edited_ranges_for_transaction(transaction_1, cx),
4209 &[
4210 MultiBufferOffset(0)..MultiBufferOffset(2),
4211 MultiBufferOffset(7)..MultiBufferOffset(9),
4212 ]
4213 );
4214
4215 // Edit buffer 1 through the multibuffer
4216 now += 2 * group_interval;
4217 multibuffer.start_transaction_at(now, cx);
4218 multibuffer.edit(
4219 [(MultiBufferOffset(2)..MultiBufferOffset(2), "C")],
4220 None,
4221 cx,
4222 );
4223 multibuffer.end_transaction_at(now, cx);
4224 assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
4225
4226 // Edit buffer 1 independently
4227 buffer_1.update(cx, |buffer_1, cx| {
4228 buffer_1.start_transaction_at(now);
4229 buffer_1.edit([(3..3, "D")], None, cx);
4230 buffer_1.end_transaction_at(now, cx);
4231
4232 now += 2 * group_interval;
4233 buffer_1.start_transaction_at(now);
4234 buffer_1.edit([(4..4, "E")], None, cx);
4235 buffer_1.end_transaction_at(now, cx);
4236 });
4237 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
4238
4239 // An undo in the multibuffer undoes the multibuffer transaction
4240 // and also any individual buffer edits that have occurred since
4241 // that transaction.
4242 multibuffer.undo(cx);
4243 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4244
4245 multibuffer.undo(cx);
4246 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4247
4248 multibuffer.redo(cx);
4249 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4250
4251 multibuffer.redo(cx);
4252 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
4253
4254 // Undo buffer 2 independently.
4255 buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
4256 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
4257
4258 // An undo in the multibuffer undoes the components of the
4259 // the last multibuffer transaction that are not already undone.
4260 multibuffer.undo(cx);
4261 assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
4262
4263 multibuffer.undo(cx);
4264 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4265
4266 multibuffer.redo(cx);
4267 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
4268
4269 buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
4270 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
4271
4272 // Redo stack gets cleared after an edit.
4273 now += 2 * group_interval;
4274 multibuffer.start_transaction_at(now, cx);
4275 multibuffer.edit(
4276 [(MultiBufferOffset(0)..MultiBufferOffset(0), "X")],
4277 None,
4278 cx,
4279 );
4280 multibuffer.end_transaction_at(now, cx);
4281 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4282 multibuffer.redo(cx);
4283 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4284 multibuffer.undo(cx);
4285 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
4286 multibuffer.undo(cx);
4287 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4288
4289 // Transactions can be grouped manually.
4290 multibuffer.redo(cx);
4291 multibuffer.redo(cx);
4292 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4293 multibuffer.group_until_transaction(transaction_1, cx);
4294 multibuffer.undo(cx);
4295 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
4296 multibuffer.redo(cx);
4297 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
4298 });
4299}
4300
4301#[gpui::test]
4302async fn test_enclosing_indent(cx: &mut TestAppContext) {
4303 async fn enclosing_indent(
4304 text: &str,
4305 buffer_row: u32,
4306 cx: &mut TestAppContext,
4307 ) -> Option<(Range<u32>, LineIndent)> {
4308 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
4309 let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
4310 let (range, indent) = snapshot
4311 .enclosing_indent(MultiBufferRow(buffer_row))
4312 .await?;
4313 Some((range.start.0..range.end.0, indent))
4314 }
4315
4316 assert_eq!(
4317 enclosing_indent(
4318 indoc!(
4319 "
4320 fn b() {
4321 if c {
4322 let d = 2;
4323 }
4324 }
4325 "
4326 ),
4327 1,
4328 cx,
4329 )
4330 .await,
4331 Some((
4332 1..2,
4333 LineIndent {
4334 tabs: 0,
4335 spaces: 4,
4336 line_blank: false,
4337 }
4338 ))
4339 );
4340
4341 assert_eq!(
4342 enclosing_indent(
4343 indoc!(
4344 "
4345 fn b() {
4346 if c {
4347 let d = 2;
4348 }
4349 }
4350 "
4351 ),
4352 2,
4353 cx,
4354 )
4355 .await,
4356 Some((
4357 1..2,
4358 LineIndent {
4359 tabs: 0,
4360 spaces: 4,
4361 line_blank: false,
4362 }
4363 ))
4364 );
4365
4366 assert_eq!(
4367 enclosing_indent(
4368 indoc!(
4369 "
4370 fn b() {
4371 if c {
4372 let d = 2;
4373
4374 let e = 5;
4375 }
4376 }
4377 "
4378 ),
4379 3,
4380 cx,
4381 )
4382 .await,
4383 Some((
4384 1..4,
4385 LineIndent {
4386 tabs: 0,
4387 spaces: 4,
4388 line_blank: false,
4389 }
4390 ))
4391 );
4392}
4393
4394#[gpui::test]
4395async fn test_summaries_for_anchors(cx: &mut TestAppContext) {
4396 let base_text_1 = indoc!(
4397 "
4398 bar
4399 "
4400 );
4401 let text_1 = indoc!(
4402 "
4403 BAR
4404 "
4405 );
4406 let base_text_2 = indoc!(
4407 "
4408 foo
4409 "
4410 );
4411 let text_2 = indoc!(
4412 "
4413 FOO
4414 "
4415 );
4416
4417 let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
4418 let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
4419 let diff_1 = cx.new(|cx| {
4420 BufferDiff::new_with_base_text(base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
4421 });
4422 let diff_2 = cx.new(|cx| {
4423 BufferDiff::new_with_base_text(base_text_2, &buffer_2.read(cx).text_snapshot(), cx)
4424 });
4425 cx.run_until_parked();
4426
4427 let multibuffer = cx.new(|cx| {
4428 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
4429 multibuffer.set_all_diff_hunks_expanded(cx);
4430 multibuffer.set_excerpts_for_path(
4431 PathKey::sorted(0),
4432 buffer_1.clone(),
4433 [Point::zero()..buffer_1.read(cx).max_point()],
4434 0,
4435 cx,
4436 );
4437 multibuffer.set_excerpts_for_path(
4438 PathKey::sorted(1),
4439 buffer_2.clone(),
4440 [Point::zero()..buffer_2.read(cx).max_point()],
4441 0,
4442 cx,
4443 );
4444 multibuffer.add_diff(diff_1.clone(), cx);
4445 multibuffer.add_diff(diff_2.clone(), cx);
4446 multibuffer
4447 });
4448
4449 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4450 (multibuffer.snapshot(cx), multibuffer.subscribe())
4451 });
4452
4453 assert_new_snapshot(
4454 &multibuffer,
4455 &mut snapshot,
4456 &mut subscription,
4457 cx,
4458 indoc!(
4459 "
4460 - bar
4461 + BAR
4462
4463 - foo
4464 + FOO
4465 "
4466 ),
4467 );
4468
4469 let anchor_1 = multibuffer.read_with(cx, |multibuffer, cx| {
4470 multibuffer
4471 .snapshot(cx)
4472 .anchor_in_excerpt(text::Anchor::min_for_buffer(buffer_1.read(cx).remote_id()))
4473 .unwrap()
4474 });
4475 let point_1 = snapshot.summaries_for_anchors::<Point, _>([&anchor_1])[0];
4476 assert_eq!(point_1, Point::new(0, 0));
4477
4478 let anchor_2 = multibuffer.read_with(cx, |multibuffer, cx| {
4479 multibuffer
4480 .snapshot(cx)
4481 .anchor_in_excerpt(text::Anchor::min_for_buffer(buffer_2.read(cx).remote_id()))
4482 .unwrap()
4483 });
4484 let point_2 = snapshot.summaries_for_anchors::<Point, _>([&anchor_2])[0];
4485 assert_eq!(point_2, Point::new(3, 0));
4486}
4487
4488#[gpui::test]
4489async fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
4490 let base_text_1 = "one\ntwo".to_owned();
4491 let text_1 = "one\n".to_owned();
4492
4493 let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
4494 let diff_1 = cx.new(|cx| {
4495 BufferDiff::new_with_base_text(&base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
4496 });
4497 cx.run_until_parked();
4498
4499 let multibuffer = cx.new(|cx| {
4500 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
4501 multibuffer.set_excerpts_for_path(
4502 PathKey::sorted(0),
4503 buffer_1.clone(),
4504 [Point::zero()..buffer_1.read(cx).max_point()],
4505 0,
4506 cx,
4507 );
4508 multibuffer.add_diff(diff_1.clone(), cx);
4509 multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
4510 multibuffer
4511 });
4512
4513 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4514 (multibuffer.snapshot(cx), multibuffer.subscribe())
4515 });
4516
4517 assert_new_snapshot(
4518 &multibuffer,
4519 &mut snapshot,
4520 &mut subscription,
4521 cx,
4522 indoc!(
4523 "
4524 one
4525 - two
4526 "
4527 ),
4528 );
4529
4530 assert_eq!(snapshot.max_point(), Point::new(2, 0));
4531 assert_eq!(snapshot.len().0, 8);
4532
4533 assert_eq!(
4534 snapshot
4535 .dimensions_from_points::<Point>([Point::new(2, 0)])
4536 .collect::<Vec<_>>(),
4537 vec![Point::new(2, 0)]
4538 );
4539
4540 let (_, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
4541 assert_eq!(translated_offset.0, "one\n".len());
4542 let (_, translated_point) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
4543 assert_eq!(translated_point, Point::new(1, 0));
4544
4545 // The same, for an excerpt that's not at the end of the multibuffer.
4546
4547 let text_2 = "foo\n".to_owned();
4548 let buffer_2 = cx.new(|cx| Buffer::local(&text_2, cx));
4549 multibuffer.update(cx, |multibuffer, cx| {
4550 multibuffer.set_excerpt_ranges_for_path(
4551 PathKey::sorted(1),
4552 buffer_2.clone(),
4553 &buffer_2.read(cx).snapshot(),
4554 vec![ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
4555 cx,
4556 );
4557 });
4558
4559 assert_new_snapshot(
4560 &multibuffer,
4561 &mut snapshot,
4562 &mut subscription,
4563 cx,
4564 indoc!(
4565 "
4566 one
4567 - two
4568
4569 foo
4570 "
4571 ),
4572 );
4573
4574 assert_eq!(
4575 snapshot
4576 .dimensions_from_points::<Point>([Point::new(2, 0)])
4577 .collect::<Vec<_>>(),
4578 vec![Point::new(2, 0)]
4579 );
4580
4581 let buffer_1_id = buffer_1.read_with(cx, |buffer_1, _| buffer_1.remote_id());
4582 let (buffer, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
4583 assert_eq!(buffer.remote_id(), buffer_1_id);
4584 assert_eq!(translated_offset.0, "one\n".len());
4585 let (buffer, translated_point) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
4586 assert_eq!(buffer.remote_id(), buffer_1_id);
4587 assert_eq!(translated_point, Point::new(1, 0));
4588}
4589
4590fn format_diff(
4591 text: &str,
4592 row_infos: &Vec<RowInfo>,
4593 boundary_rows: &HashSet<MultiBufferRow>,
4594 has_diff: Option<bool>,
4595) -> String {
4596 let has_diff =
4597 has_diff.unwrap_or_else(|| row_infos.iter().any(|info| info.diff_status.is_some()));
4598 text.split('\n')
4599 .enumerate()
4600 .zip(row_infos)
4601 .map(|((ix, line), info)| {
4602 let marker = match info.diff_status.map(|status| status.kind) {
4603 Some(DiffHunkStatusKind::Added) => "+ ",
4604 Some(DiffHunkStatusKind::Deleted) => "- ",
4605 Some(DiffHunkStatusKind::Modified) => unreachable!(),
4606 None => {
4607 if has_diff && !line.is_empty() {
4608 " "
4609 } else {
4610 ""
4611 }
4612 }
4613 };
4614 let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
4615 if has_diff {
4616 " ----------\n"
4617 } else {
4618 "---------\n"
4619 }
4620 } else {
4621 ""
4622 };
4623 let expand = info
4624 .expand_info
4625 .as_ref()
4626 .map(|expand_info| match expand_info.direction {
4627 ExpandExcerptDirection::Up => " [↑]",
4628 ExpandExcerptDirection::Down => " [↓]",
4629 ExpandExcerptDirection::UpAndDown => " [↕]",
4630 })
4631 .unwrap_or_default();
4632
4633 format!("{boundary_row}{marker}{line}{expand}")
4634 // let mbr = info
4635 // .multibuffer_row
4636 // .map(|row| format!("{:0>3}", row.0))
4637 // .unwrap_or_else(|| "???".to_string());
4638 // let byte_range = format!("{byte_range_start:0>3}..{byte_range_end:0>3}");
4639 // format!("{boundary_row}Row: {mbr}, Bytes: {byte_range} | {marker}{line}{expand}")
4640 })
4641 .collect::<Vec<_>>()
4642 .join("\n")
4643}
4644
4645// fn format_transforms(snapshot: &MultiBufferSnapshot) -> String {
4646// snapshot
4647// .diff_transforms
4648// .iter()
4649// .map(|transform| {
4650// let (kind, summary) = match transform {
4651// DiffTransform::DeletedHunk { summary, .. } => (" Deleted", (*summary).into()),
4652// DiffTransform::FilteredInsertedHunk { summary, .. } => (" Filtered", *summary),
4653// DiffTransform::InsertedHunk { summary, .. } => (" Inserted", *summary),
4654// DiffTransform::Unmodified { summary, .. } => ("Unmodified", *summary),
4655// };
4656// format!("{kind}(len: {}, lines: {:?})", summary.len, summary.lines)
4657// })
4658// .join("\n")
4659// }
4660
4661// fn format_excerpts(snapshot: &MultiBufferSnapshot) -> String {
4662// snapshot
4663// .excerpts
4664// .iter()
4665// .map(|excerpt| {
4666// format!(
4667// "Excerpt(buffer_range = {:?}, lines = {:?}, has_trailing_newline = {:?})",
4668// excerpt.range.context.to_point(&excerpt.buffer),
4669// excerpt.text_summary.lines,
4670// excerpt.has_trailing_newline
4671// )
4672// })
4673// .join("\n")
4674// }
4675
4676#[gpui::test]
4677async fn test_singleton_with_inverted_diff(cx: &mut TestAppContext) {
4678 let text = indoc!(
4679 "
4680 ZERO
4681 one
4682 TWO
4683 three
4684 six
4685 "
4686 );
4687 let base_text = indoc!(
4688 "
4689 one
4690 two
4691 three
4692 four
4693 five
4694 six
4695 "
4696 );
4697
4698 let buffer = cx.new(|cx| Buffer::local(text, cx));
4699 let diff = cx
4700 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
4701 cx.run_until_parked();
4702
4703 let base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4704
4705 let multibuffer = cx.new(|cx| {
4706 let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4707 multibuffer.set_all_diff_hunks_expanded(cx);
4708 multibuffer.add_inverted_diff(diff.clone(), buffer.clone(), cx);
4709 multibuffer
4710 });
4711
4712 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4713 (multibuffer.snapshot(cx), multibuffer.subscribe())
4714 });
4715
4716 assert_eq!(snapshot.text(), base_text);
4717 assert_new_snapshot(
4718 &multibuffer,
4719 &mut snapshot,
4720 &mut subscription,
4721 cx,
4722 indoc!(
4723 "
4724 one
4725 - two
4726 three
4727 - four
4728 - five
4729 six
4730 "
4731 ),
4732 );
4733
4734 buffer.update(cx, |buffer, cx| {
4735 buffer.edit_via_marked_text(
4736 indoc!(
4737 "
4738 ZERO
4739 one
4740 «<inserted>»W«O
4741 T»hree
4742 six
4743 "
4744 ),
4745 None,
4746 cx,
4747 );
4748 });
4749 cx.run_until_parked();
4750 let update = diff
4751 .update(cx, |diff, cx| {
4752 diff.update_diff(
4753 buffer.read(cx).text_snapshot(),
4754 Some(base_text.into()),
4755 None,
4756 None,
4757 cx,
4758 )
4759 })
4760 .await;
4761 diff.update(cx, |diff, cx| {
4762 diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4763 })
4764 .await;
4765 cx.run_until_parked();
4766
4767 assert_new_snapshot(
4768 &multibuffer,
4769 &mut snapshot,
4770 &mut subscription,
4771 cx,
4772 indoc! {
4773 "
4774 one
4775 - two
4776 - three
4777 - four
4778 - five
4779 six
4780 "
4781 },
4782 );
4783
4784 buffer.update(cx, |buffer, cx| {
4785 buffer.set_text("ZERO\nONE\nTWO\n", cx);
4786 });
4787 cx.run_until_parked();
4788 let update = diff
4789 .update(cx, |diff, cx| {
4790 diff.update_diff(
4791 buffer.read(cx).text_snapshot(),
4792 Some(base_text.into()),
4793 None,
4794 None,
4795 cx,
4796 )
4797 })
4798 .await;
4799 diff.update(cx, |diff, cx| {
4800 diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4801 })
4802 .await;
4803 cx.run_until_parked();
4804
4805 assert_new_snapshot(
4806 &multibuffer,
4807 &mut snapshot,
4808 &mut subscription,
4809 cx,
4810 indoc! {
4811 "
4812 - one
4813 - two
4814 - three
4815 - four
4816 - five
4817 - six
4818 "
4819 },
4820 );
4821
4822 diff.update(cx, |diff, cx| {
4823 diff.set_base_text(
4824 Some("new base\n".into()),
4825 None,
4826 buffer.read(cx).text_snapshot(),
4827 cx,
4828 )
4829 })
4830 .await
4831 .unwrap();
4832 cx.run_until_parked();
4833
4834 assert_new_snapshot(
4835 &multibuffer,
4836 &mut snapshot,
4837 &mut subscription,
4838 cx,
4839 indoc! {"
4840 - new base
4841 "},
4842 );
4843}
4844
4845#[gpui::test]
4846async fn test_inverted_diff_base_text_change(cx: &mut TestAppContext) {
4847 let base_text = "aaa\nbbb\nccc\n";
4848 let text = "ddd\n";
4849 let buffer = cx.new(|cx| Buffer::local(text, cx));
4850 let diff = cx
4851 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
4852 cx.run_until_parked();
4853
4854 let base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4855
4856 let multibuffer = cx.new(|cx| {
4857 let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4858 multibuffer.set_all_diff_hunks_expanded(cx);
4859 multibuffer.add_inverted_diff(diff.clone(), buffer.clone(), cx);
4860 multibuffer
4861 });
4862
4863 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
4864 (multibuffer.snapshot(cx), multibuffer.subscribe())
4865 });
4866
4867 assert_eq!(snapshot.text(), base_text);
4868 assert_new_snapshot(
4869 &multibuffer,
4870 &mut snapshot,
4871 &mut subscription,
4872 cx,
4873 indoc!(
4874 "
4875 - aaa
4876 - bbb
4877 - ccc
4878 "
4879 ),
4880 );
4881
4882 let update = diff
4883 .update(cx, |diff, cx| {
4884 diff.update_diff(
4885 buffer.read(cx).text_snapshot(),
4886 Some("ddd\n".into()),
4887 Some(true),
4888 None,
4889 cx,
4890 )
4891 })
4892 .await;
4893 diff.update(cx, |diff, cx| {
4894 diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4895 })
4896 .detach();
4897
4898 let _hunks: Vec<_> = multibuffer
4899 .read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx))
4900 .diff_hunks()
4901 .collect();
4902}
4903
4904#[gpui::test]
4905async fn test_inverted_diff_secondary_version_mismatch(cx: &mut TestAppContext) {
4906 let base_text = "one\ntwo\nthree\nfour\nfive\n";
4907 let index_text = "one\nTWO\nthree\nfour\nfive\n";
4908 let buffer_text = "one\nTWO\nthree\nFOUR\nfive\n";
4909
4910 let buffer = cx.new(|cx| Buffer::local(buffer_text, cx));
4911
4912 let unstaged_diff = cx
4913 .new(|cx| BufferDiff::new_with_base_text(index_text, &buffer.read(cx).text_snapshot(), cx));
4914 cx.run_until_parked();
4915
4916 let uncommitted_diff = cx.new(|cx| {
4917 let mut diff =
4918 BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx);
4919 diff.set_secondary_diff(unstaged_diff.clone());
4920 diff
4921 });
4922 cx.run_until_parked();
4923
4924 buffer.update(cx, |buffer, cx| {
4925 buffer.edit([(0..0, "ZERO\n")], None, cx);
4926 });
4927
4928 let update = unstaged_diff
4929 .update(cx, |diff, cx| {
4930 diff.update_diff(
4931 buffer.read(cx).text_snapshot(),
4932 Some(index_text.into()),
4933 None,
4934 None,
4935 cx,
4936 )
4937 })
4938 .await;
4939 unstaged_diff
4940 .update(cx, |diff, cx| {
4941 diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), cx)
4942 })
4943 .await;
4944
4945 let base_text_buffer =
4946 uncommitted_diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4947
4948 let multibuffer = cx.new(|cx| {
4949 let mut multibuffer = MultiBuffer::singleton(base_text_buffer.clone(), cx);
4950 multibuffer.set_all_diff_hunks_expanded(cx);
4951 multibuffer.add_inverted_diff(uncommitted_diff.clone(), buffer.clone(), cx);
4952 multibuffer
4953 });
4954
4955 let _hunks: Vec<_> = multibuffer
4956 .read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx))
4957 .diff_hunks()
4958 .collect();
4959}
4960
4961#[track_caller]
4962fn assert_excerpts_match(
4963 multibuffer: &Entity<MultiBuffer>,
4964 cx: &mut TestAppContext,
4965 expected: &str,
4966) {
4967 let mut output = String::new();
4968 multibuffer.read_with(cx, |multibuffer, cx| {
4969 let snapshot = multibuffer.snapshot(cx);
4970 for excerpt in multibuffer.snapshot(cx).excerpts() {
4971 output.push_str("-----\n");
4972 output.extend(
4973 snapshot
4974 .buffer_for_id(excerpt.context.start.buffer_id)
4975 .unwrap()
4976 .text_for_range(excerpt.context),
4977 );
4978 if !output.ends_with('\n') {
4979 output.push('\n');
4980 }
4981 }
4982 });
4983 assert_eq!(output, expected);
4984}
4985
4986#[track_caller]
4987fn assert_new_snapshot(
4988 multibuffer: &Entity<MultiBuffer>,
4989 snapshot: &mut MultiBufferSnapshot,
4990 subscription: &mut Subscription<MultiBufferOffset>,
4991 cx: &mut TestAppContext,
4992 expected_diff: &str,
4993) {
4994 let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
4995 let actual_text = new_snapshot.text();
4996 let line_infos = new_snapshot
4997 .row_infos(MultiBufferRow(0))
4998 .collect::<Vec<_>>();
4999 let actual_diff = format_diff(&actual_text, &line_infos, &Default::default(), None);
5000 pretty_assertions::assert_eq!(actual_diff, expected_diff);
5001 check_edits(
5002 snapshot,
5003 &new_snapshot,
5004 &subscription.consume().into_inner(),
5005 );
5006 *snapshot = new_snapshot;
5007}
5008
5009#[track_caller]
5010fn check_edits(
5011 old_snapshot: &MultiBufferSnapshot,
5012 new_snapshot: &MultiBufferSnapshot,
5013 edits: &[Edit<MultiBufferOffset>],
5014) {
5015 let mut text = old_snapshot.text();
5016 let new_text = new_snapshot.text();
5017 for edit in edits.iter().rev() {
5018 if !text.is_char_boundary(edit.old.start.0)
5019 || !text.is_char_boundary(edit.old.end.0)
5020 || !new_text.is_char_boundary(edit.new.start.0)
5021 || !new_text.is_char_boundary(edit.new.end.0)
5022 {
5023 panic!(
5024 "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
5025 edits, text, new_text
5026 );
5027 }
5028
5029 text.replace_range(
5030 edit.old.start.0..edit.old.end.0,
5031 &new_text[edit.new.start.0..edit.new.end.0],
5032 );
5033 }
5034
5035 pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
5036}
5037
5038#[track_caller]
5039fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
5040 let full_text = snapshot.text();
5041 for ix in 0..full_text.len() {
5042 let mut chunks = snapshot.chunks(
5043 MultiBufferOffset(0)..snapshot.len(),
5044 LanguageAwareStyling {
5045 tree_sitter: false,
5046 diagnostics: false,
5047 },
5048 );
5049 chunks.seek(MultiBufferOffset(ix)..snapshot.len());
5050 let tail = chunks.map(|chunk| chunk.text).collect::<String>();
5051 assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
5052 }
5053}
5054
5055#[track_caller]
5056fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
5057 let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
5058 for start_row in 1..all_line_numbers.len() {
5059 let line_numbers = snapshot
5060 .row_infos(MultiBufferRow(start_row as u32))
5061 .collect::<Vec<_>>();
5062 assert_eq!(
5063 line_numbers,
5064 all_line_numbers[start_row..],
5065 "start_row: {start_row}"
5066 );
5067 }
5068}
5069
5070#[track_caller]
5071fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
5072 let text = Rope::from(snapshot.text());
5073
5074 let mut left_anchors = Vec::new();
5075 let mut right_anchors = Vec::new();
5076 let mut offsets = Vec::new();
5077 let mut points = Vec::new();
5078 for offset in 0..=text.len() + 1 {
5079 let offset = MultiBufferOffset(offset);
5080 let clipped_left = snapshot.clip_offset(offset, Bias::Left);
5081 let clipped_right = snapshot.clip_offset(offset, Bias::Right);
5082 assert_eq!(
5083 clipped_left.0,
5084 text.clip_offset(offset.0, Bias::Left),
5085 "clip_offset({offset:?}, Left)"
5086 );
5087 assert_eq!(
5088 clipped_right.0,
5089 text.clip_offset(offset.0, Bias::Right),
5090 "clip_offset({offset:?}, Right)"
5091 );
5092 assert_eq!(
5093 snapshot.offset_to_point(clipped_left),
5094 text.offset_to_point(clipped_left.0),
5095 "offset_to_point({})",
5096 clipped_left.0
5097 );
5098 assert_eq!(
5099 snapshot.offset_to_point(clipped_right),
5100 text.offset_to_point(clipped_right.0),
5101 "offset_to_point({})",
5102 clipped_right.0
5103 );
5104 let anchor_after = snapshot.anchor_after(clipped_left);
5105 assert_eq!(
5106 anchor_after.to_offset(snapshot),
5107 clipped_left,
5108 "anchor_after({}).to_offset {anchor_after:?}",
5109 clipped_left.0
5110 );
5111 let anchor_before = snapshot.anchor_before(clipped_left);
5112 assert_eq!(
5113 anchor_before.to_offset(snapshot),
5114 clipped_left,
5115 "anchor_before({}).to_offset",
5116 clipped_left.0
5117 );
5118 left_anchors.push(anchor_before);
5119 right_anchors.push(anchor_after);
5120 offsets.push(clipped_left);
5121 points.push(text.offset_to_point(clipped_left.0));
5122 }
5123
5124 for row in 0..text.max_point().row {
5125 for column in 0..text.line_len(row) + 1 {
5126 let point = Point { row, column };
5127 let clipped_left = snapshot.clip_point(point, Bias::Left);
5128 let clipped_right = snapshot.clip_point(point, Bias::Right);
5129 assert_eq!(
5130 clipped_left,
5131 text.clip_point(point, Bias::Left),
5132 "clip_point({point:?}, Left)"
5133 );
5134 assert_eq!(
5135 clipped_right,
5136 text.clip_point(point, Bias::Right),
5137 "clip_point({point:?}, Right)"
5138 );
5139 assert_eq!(
5140 snapshot.point_to_offset(clipped_left).0,
5141 text.point_to_offset(clipped_left),
5142 "point_to_offset({clipped_left:?})"
5143 );
5144 assert_eq!(
5145 snapshot.point_to_offset(clipped_right).0,
5146 text.point_to_offset(clipped_right),
5147 "point_to_offset({clipped_right:?})"
5148 );
5149 }
5150 }
5151
5152 assert_eq!(
5153 snapshot.summaries_for_anchors::<MultiBufferOffset, _>(&left_anchors),
5154 offsets,
5155 "left_anchors <-> offsets"
5156 );
5157 assert_eq!(
5158 snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
5159 points,
5160 "left_anchors <-> points"
5161 );
5162 assert_eq!(
5163 snapshot.summaries_for_anchors::<MultiBufferOffset, _>(&right_anchors),
5164 offsets,
5165 "right_anchors <-> offsets"
5166 );
5167 assert_eq!(
5168 snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
5169 points,
5170 "right_anchors <-> points"
5171 );
5172
5173 for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
5174 for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
5175 if ix > 0 && *offset == MultiBufferOffset(252) && offset > &offsets[ix - 1] {
5176 let prev_anchor = left_anchors[ix - 1];
5177 assert!(
5178 anchor.cmp(&prev_anchor, snapshot).is_gt(),
5179 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
5180 offsets[ix],
5181 offsets[ix - 1],
5182 );
5183 assert!(
5184 prev_anchor.cmp(anchor, snapshot).is_lt(),
5185 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
5186 offsets[ix - 1],
5187 offsets[ix],
5188 );
5189 }
5190 }
5191 }
5192
5193 if let Some((buffer, offset)) = snapshot.point_to_buffer_offset(snapshot.max_point()) {
5194 assert!(offset.0 <= buffer.len());
5195 }
5196 if let Some((buffer, point)) = snapshot.point_to_buffer_point(snapshot.max_point()) {
5197 assert!(point <= buffer.max_point());
5198 }
5199}
5200
5201fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
5202 let max_row = snapshot.max_point().row;
5203 let buffer_id = snapshot.excerpts().next().unwrap().context.start.buffer_id;
5204 let text = text::Buffer::new(ReplicaId::LOCAL, buffer_id, snapshot.text());
5205 let mut line_indents = text
5206 .line_indents_in_row_range(0..max_row + 1)
5207 .collect::<Vec<_>>();
5208 for start_row in 0..snapshot.max_point().row {
5209 pretty_assertions::assert_eq!(
5210 snapshot
5211 .line_indents(MultiBufferRow(start_row), |_| true)
5212 .map(|(row, indent, _)| (row.0, indent))
5213 .collect::<Vec<_>>(),
5214 &line_indents[(start_row as usize)..],
5215 "line_indents({start_row})"
5216 );
5217 }
5218
5219 line_indents.reverse();
5220 pretty_assertions::assert_eq!(
5221 snapshot
5222 .reversed_line_indents(MultiBufferRow(max_row), |_| true)
5223 .map(|(row, indent, _)| (row.0, indent))
5224 .collect::<Vec<_>>(),
5225 &line_indents[..],
5226 "reversed_line_indents({max_row})"
5227 );
5228}
5229
5230#[gpui::test]
5231fn test_new_empty_buffer_uses_untitled_title(cx: &mut App) {
5232 let buffer = cx.new(|cx| Buffer::local("", cx));
5233 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5234
5235 assert_eq!(multibuffer.read(cx).title(cx), "untitled");
5236}
5237
5238#[gpui::test]
5239fn test_new_empty_buffer_uses_untitled_title_when_only_contains_whitespace(cx: &mut App) {
5240 let buffer = cx.new(|cx| Buffer::local("\n ", cx));
5241 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5242
5243 assert_eq!(multibuffer.read(cx).title(cx), "untitled");
5244}
5245
5246#[gpui::test]
5247fn test_new_empty_buffer_takes_first_line_for_title(cx: &mut App) {
5248 let buffer = cx.new(|cx| Buffer::local("Hello World\nSecond line", cx));
5249 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5250
5251 assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
5252}
5253
5254#[gpui::test]
5255fn test_new_empty_buffer_takes_trimmed_first_line_for_title(cx: &mut App) {
5256 let buffer = cx.new(|cx| Buffer::local("\nHello, World ", cx));
5257 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5258
5259 assert_eq!(multibuffer.read(cx).title(cx), "Hello, World");
5260}
5261
5262#[gpui::test]
5263fn test_new_empty_buffer_uses_truncated_first_line_for_title(cx: &mut App) {
5264 let title = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee";
5265 let title_after = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd";
5266 let buffer = cx.new(|cx| Buffer::local(title, cx));
5267 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5268
5269 assert_eq!(multibuffer.read(cx).title(cx), title_after);
5270}
5271
5272#[gpui::test]
5273fn test_new_empty_buffer_uses_truncated_first_line_for_title_after_merging_adjacent_spaces(
5274 cx: &mut App,
5275) {
5276 let title = "aaaaaaaaaabbbbbbbbbb ccccccccccddddddddddeeeeeeeeee";
5277 let title_after = "aaaaaaaaaabbbbbbbbbb ccccccccccddddddddd";
5278 let buffer = cx.new(|cx| Buffer::local(title, cx));
5279 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5280
5281 assert_eq!(multibuffer.read(cx).title(cx), title_after);
5282}
5283
5284#[gpui::test]
5285fn test_new_empty_buffers_title_can_be_set(cx: &mut App) {
5286 let buffer = cx.new(|cx| Buffer::local("Hello World", cx));
5287 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
5288 assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
5289
5290 multibuffer.update(cx, |multibuffer, cx| {
5291 multibuffer.set_title("Hey".into(), cx)
5292 });
5293 assert_eq!(multibuffer.read(cx).title(cx), "Hey");
5294}
5295
5296#[gpui::test(iterations = 100)]
5297fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) {
5298 let multibuffer = if rng.random() {
5299 let len = rng.random_range(0..10000);
5300 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
5301 let buffer = cx.new(|cx| Buffer::local(text, cx));
5302 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
5303 } else {
5304 MultiBuffer::build_random(&mut rng, cx)
5305 };
5306
5307 let snapshot = multibuffer.read(cx).snapshot(cx);
5308
5309 let chunks = snapshot.chunks(
5310 MultiBufferOffset(0)..snapshot.len(),
5311 LanguageAwareStyling {
5312 tree_sitter: false,
5313 diagnostics: false,
5314 },
5315 );
5316
5317 for chunk in chunks {
5318 let chunk_text = chunk.text;
5319 let chars_bitmap = chunk.chars;
5320 let tabs_bitmap = chunk.tabs;
5321
5322 if chunk_text.is_empty() {
5323 assert_eq!(
5324 chars_bitmap, 0,
5325 "Empty chunk should have empty chars bitmap"
5326 );
5327 assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
5328 continue;
5329 }
5330
5331 assert!(
5332 chunk_text.len() <= 128,
5333 "Chunk text length {} exceeds 128 bytes",
5334 chunk_text.len()
5335 );
5336
5337 // Verify chars bitmap
5338 let char_indices = chunk_text
5339 .char_indices()
5340 .map(|(i, _)| i)
5341 .collect::<Vec<_>>();
5342
5343 for byte_idx in 0..chunk_text.len() {
5344 let should_have_bit = char_indices.contains(&byte_idx);
5345 let has_bit = chars_bitmap & (1 << byte_idx) != 0;
5346
5347 if has_bit != should_have_bit {
5348 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5349 eprintln!("Char indices: {:?}", char_indices);
5350 eprintln!("Chars bitmap: {:#b}", chars_bitmap);
5351 }
5352
5353 assert_eq!(
5354 has_bit, should_have_bit,
5355 "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
5356 byte_idx, chunk_text, should_have_bit, has_bit
5357 );
5358 }
5359
5360 for (byte_idx, byte) in chunk_text.bytes().enumerate() {
5361 let is_tab = byte == b'\t';
5362 let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
5363
5364 if has_bit != is_tab {
5365 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5366 eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
5367 assert_eq!(
5368 has_bit, is_tab,
5369 "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
5370 byte_idx, chunk_text, byte as char, is_tab, has_bit
5371 );
5372 }
5373 }
5374 }
5375}
5376
5377#[gpui::test(iterations = 10)]
5378fn test_random_chunk_bitmaps_with_diffs(cx: &mut App, mut rng: StdRng) {
5379 let settings_store = SettingsStore::test(cx);
5380 cx.set_global(settings_store);
5381 use buffer_diff::BufferDiff;
5382 use util::RandomCharIter;
5383
5384 let multibuffer = if rng.random() {
5385 let len = rng.random_range(100..10000);
5386 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
5387 let buffer = cx.new(|cx| Buffer::local(text, cx));
5388 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
5389 } else {
5390 MultiBuffer::build_random(&mut rng, cx)
5391 };
5392
5393 let _diff_count = rng.random_range(1..5);
5394 let mut diffs = Vec::new();
5395
5396 multibuffer.update(cx, |multibuffer, cx| {
5397 let snapshot = multibuffer.snapshot(cx);
5398 for buffer_id in snapshot.all_buffer_ids() {
5399 if rng.random_bool(0.7) {
5400 if let Some(buffer_handle) = multibuffer.buffer(buffer_id) {
5401 let buffer_text = buffer_handle.read(cx).text();
5402 let mut base_text = String::new();
5403
5404 for line in buffer_text.lines() {
5405 if rng.random_bool(0.3) {
5406 continue;
5407 } else if rng.random_bool(0.3) {
5408 let line_len = rng.random_range(0..50);
5409 let modified_line = RandomCharIter::new(&mut rng)
5410 .take(line_len)
5411 .collect::<String>();
5412 base_text.push_str(&modified_line);
5413 base_text.push('\n');
5414 } else {
5415 base_text.push_str(line);
5416 base_text.push('\n');
5417 }
5418 }
5419
5420 if rng.random_bool(0.5) {
5421 let extra_lines = rng.random_range(1..5);
5422 for _ in 0..extra_lines {
5423 let line_len = rng.random_range(0..50);
5424 let extra_line = RandomCharIter::new(&mut rng)
5425 .take(line_len)
5426 .collect::<String>();
5427 base_text.push_str(&extra_line);
5428 base_text.push('\n');
5429 }
5430 }
5431
5432 let diff = cx.new(|cx| {
5433 BufferDiff::new_with_base_text(
5434 &base_text,
5435 &buffer_handle.read(cx).text_snapshot(),
5436 cx,
5437 )
5438 });
5439 diffs.push(diff.clone());
5440 multibuffer.add_diff(diff, cx);
5441 }
5442 }
5443 }
5444 });
5445
5446 multibuffer.update(cx, |multibuffer, cx| {
5447 if rng.random_bool(0.5) {
5448 multibuffer.set_all_diff_hunks_expanded(cx);
5449 } else {
5450 let snapshot = multibuffer.snapshot(cx);
5451 let text = snapshot.text();
5452
5453 let mut ranges = Vec::new();
5454 for _ in 0..rng.random_range(1..5) {
5455 if snapshot.len().0 == 0 {
5456 break;
5457 }
5458
5459 let diff_size = rng.random_range(5..1000);
5460 let mut start = rng.random_range(0..snapshot.len().0);
5461
5462 while !text.is_char_boundary(start) {
5463 start = start.saturating_sub(1);
5464 }
5465
5466 let mut end = rng.random_range(start..snapshot.len().0.min(start + diff_size));
5467
5468 while !text.is_char_boundary(end) {
5469 end = end.saturating_add(1);
5470 }
5471 let start_anchor = snapshot.anchor_after(MultiBufferOffset(start));
5472 let end_anchor = snapshot.anchor_before(MultiBufferOffset(end));
5473 ranges.push(start_anchor..end_anchor);
5474 }
5475 multibuffer.expand_diff_hunks(ranges, cx);
5476 }
5477 });
5478
5479 let snapshot = multibuffer.read(cx).snapshot(cx);
5480
5481 let chunks = snapshot.chunks(
5482 MultiBufferOffset(0)..snapshot.len(),
5483 LanguageAwareStyling {
5484 tree_sitter: false,
5485 diagnostics: false,
5486 },
5487 );
5488
5489 for chunk in chunks {
5490 let chunk_text = chunk.text;
5491 let chars_bitmap = chunk.chars;
5492 let tabs_bitmap = chunk.tabs;
5493
5494 if chunk_text.is_empty() {
5495 assert_eq!(
5496 chars_bitmap, 0,
5497 "Empty chunk should have empty chars bitmap"
5498 );
5499 assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
5500 continue;
5501 }
5502
5503 assert!(
5504 chunk_text.len() <= 128,
5505 "Chunk text length {} exceeds 128 bytes",
5506 chunk_text.len()
5507 );
5508
5509 let char_indices = chunk_text
5510 .char_indices()
5511 .map(|(i, _)| i)
5512 .collect::<Vec<_>>();
5513
5514 for byte_idx in 0..chunk_text.len() {
5515 let should_have_bit = char_indices.contains(&byte_idx);
5516 let has_bit = chars_bitmap & (1 << byte_idx) != 0;
5517
5518 if has_bit != should_have_bit {
5519 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5520 eprintln!("Char indices: {:?}", char_indices);
5521 eprintln!("Chars bitmap: {:#b}", chars_bitmap);
5522 }
5523
5524 assert_eq!(
5525 has_bit, should_have_bit,
5526 "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
5527 byte_idx, chunk_text, should_have_bit, has_bit
5528 );
5529 }
5530
5531 for (byte_idx, byte) in chunk_text.bytes().enumerate() {
5532 let is_tab = byte == b'\t';
5533 let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
5534
5535 if has_bit != is_tab {
5536 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
5537 eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
5538 assert_eq!(
5539 has_bit, is_tab,
5540 "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
5541 byte_idx, chunk_text, byte as char, is_tab, has_bit
5542 );
5543 }
5544 }
5545 }
5546}
5547
5548fn collect_word_diffs(
5549 base_text: &str,
5550 modified_text: &str,
5551 cx: &mut TestAppContext,
5552) -> Vec<String> {
5553 let buffer = cx.new(|cx| Buffer::local(modified_text, cx));
5554 let diff = cx
5555 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
5556 cx.run_until_parked();
5557
5558 let multibuffer = cx.new(|cx| {
5559 let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
5560 multibuffer.add_diff(diff.clone(), cx);
5561 multibuffer
5562 });
5563
5564 multibuffer.update(cx, |multibuffer, cx| {
5565 multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
5566 });
5567
5568 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
5569 let text = snapshot.text();
5570
5571 snapshot
5572 .diff_hunks()
5573 .flat_map(|hunk| hunk.word_diffs)
5574 .map(|range| text[range.start.0..range.end.0].to_string())
5575 .collect()
5576}
5577
5578#[gpui::test]
5579async fn test_word_diff_simple_replacement(cx: &mut TestAppContext) {
5580 let settings_store = cx.update(|cx| SettingsStore::test(cx));
5581 cx.set_global(settings_store);
5582
5583 let base_text = "hello world foo bar\n";
5584 let modified_text = "hello WORLD foo BAR\n";
5585
5586 let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5587
5588 assert_eq!(word_diffs, vec!["world", "bar", "WORLD", "BAR"]);
5589}
5590
5591#[gpui::test]
5592async fn test_word_diff_white_space(cx: &mut TestAppContext) {
5593 let settings_store = cx.update(|cx| SettingsStore::test(cx));
5594 cx.set_global(settings_store);
5595
5596 let base_text = "hello world foo bar\n";
5597 let modified_text = " hello world foo bar\n";
5598
5599 let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5600
5601 assert_eq!(word_diffs, vec![" "]);
5602}
5603
5604#[gpui::test]
5605async fn test_word_diff_consecutive_modified_lines(cx: &mut TestAppContext) {
5606 let settings_store = cx.update(|cx| SettingsStore::test(cx));
5607 cx.set_global(settings_store);
5608
5609 let base_text = "aaa bbb\nccc ddd\n";
5610 let modified_text = "aaa BBB\nccc DDD\n";
5611
5612 let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5613
5614 assert_eq!(
5615 word_diffs,
5616 vec!["bbb", "ddd", "BBB", "DDD"],
5617 "consecutive modified lines should produce word diffs when line counts match"
5618 );
5619}
5620
5621#[gpui::test]
5622async fn test_word_diff_modified_lines_with_deletion_between(cx: &mut TestAppContext) {
5623 let settings_store = cx.update(|cx| SettingsStore::test(cx));
5624 cx.set_global(settings_store);
5625
5626 let base_text = "aaa bbb\ndeleted line\nccc ddd\n";
5627 let modified_text = "aaa BBB\nccc DDD\n";
5628
5629 let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5630
5631 assert_eq!(
5632 word_diffs,
5633 Vec::<String>::new(),
5634 "modified lines with a deleted line between should not produce word diffs"
5635 );
5636}
5637
5638#[gpui::test]
5639async fn test_word_diff_disabled(cx: &mut TestAppContext) {
5640 let settings_store = cx.update(|cx| {
5641 let mut settings_store = SettingsStore::test(cx);
5642 settings_store.update_user_settings(cx, |settings| {
5643 settings.project.all_languages.defaults.word_diff_enabled = Some(false);
5644 });
5645 settings_store
5646 });
5647 cx.set_global(settings_store);
5648
5649 let base_text = "hello world\n";
5650 let modified_text = "hello WORLD\n";
5651
5652 let word_diffs = collect_word_diffs(base_text, modified_text, cx);
5653
5654 assert_eq!(
5655 word_diffs,
5656 Vec::<String>::new(),
5657 "word diffs should be empty when disabled"
5658 );
5659}
5660
5661/// Tests `excerpt_containing` and `excerpts_for_range` (functions mapping multi-buffer text-coordinates to excerpts)
5662#[gpui::test]
5663fn test_excerpts_containment_functions(cx: &mut App) {
5664 // Multibuffer content for these tests:
5665 // 0123
5666 // 0: aa0
5667 // 1: aa1
5668 // -----
5669 // 2: bb0
5670 // 3: bb1
5671 // -----MultiBufferOffset(0)..
5672 // 4: cc0
5673
5674 let buffer_1 = cx.new(|cx| Buffer::local("aa0\naa1", cx));
5675 let buffer_2 = cx.new(|cx| Buffer::local("bb0\nbb1", cx));
5676 let buffer_3 = cx.new(|cx| Buffer::local("cc0", cx));
5677
5678 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5679
5680 let (excerpt_1_info, excerpt_2_info, excerpt_3_info) =
5681 multibuffer.update(cx, |multibuffer, cx| {
5682 multibuffer.set_excerpts_for_path(
5683 PathKey::sorted(0),
5684 buffer_1.clone(),
5685 [Point::new(0, 0)..Point::new(1, 3)],
5686 0,
5687 cx,
5688 );
5689
5690 multibuffer.set_excerpts_for_path(
5691 PathKey::sorted(1),
5692 buffer_2.clone(),
5693 [Point::new(0, 0)..Point::new(1, 3)],
5694 0,
5695 cx,
5696 );
5697
5698 multibuffer.set_excerpts_for_path(
5699 PathKey::sorted(2),
5700 buffer_3.clone(),
5701 [Point::new(0, 0)..Point::new(0, 3)],
5702 0,
5703 cx,
5704 );
5705
5706 let snapshot = multibuffer.snapshot(cx);
5707 let mut excerpts = snapshot.excerpts();
5708 (
5709 excerpts.next().unwrap(),
5710 excerpts.next().unwrap(),
5711 excerpts.next().unwrap(),
5712 )
5713 });
5714
5715 let snapshot = multibuffer.read(cx).snapshot(cx);
5716
5717 assert_eq!(snapshot.text(), "aa0\naa1\nbb0\nbb1\ncc0");
5718
5719 //// Test `excerpts_for_range`
5720
5721 let p00 = snapshot.point_to_offset(Point::new(0, 0));
5722 let p10 = snapshot.point_to_offset(Point::new(1, 0));
5723 let p20 = snapshot.point_to_offset(Point::new(2, 0));
5724 let p23 = snapshot.point_to_offset(Point::new(2, 3));
5725 let p13 = snapshot.point_to_offset(Point::new(1, 3));
5726 let p40 = snapshot.point_to_offset(Point::new(4, 0));
5727 let p43 = snapshot.point_to_offset(Point::new(4, 3));
5728
5729 let excerpts: Vec<_> = snapshot.excerpts_for_range(p00..p00).collect();
5730 assert_eq!(excerpts.len(), 1);
5731 assert_eq!(excerpts[0].range, excerpt_1_info);
5732
5733 // Cursor at very end of excerpt 3
5734 let excerpts: Vec<_> = snapshot.excerpts_for_range(p43..p43).collect();
5735 assert_eq!(excerpts.len(), 1);
5736 assert_eq!(excerpts[0].range, excerpt_3_info);
5737
5738 let excerpts: Vec<_> = snapshot.excerpts_for_range(p00..p23).collect();
5739 assert_eq!(excerpts.len(), 2);
5740 assert_eq!(excerpts[0].range, excerpt_1_info);
5741 assert_eq!(excerpts[1].range, excerpt_2_info);
5742
5743 // This range represent an selection with end-point just inside excerpt_2
5744 // Today we only expand the first excerpt, but another interpretation that
5745 // we could consider is expanding both here
5746 let excerpts: Vec<_> = snapshot.excerpts_for_range(p10..p20).collect();
5747 assert_eq!(excerpts.len(), 1);
5748 assert_eq!(excerpts[0].range, excerpt_1_info);
5749
5750 //// Test that `excerpts_for_range` and `excerpt_containing` agree for all single offsets (cursor positions)
5751 for offset in 0..=snapshot.len().0 {
5752 let offset = MultiBufferOffset(offset);
5753 let excerpts_for_range: Vec<_> = snapshot.excerpts_for_range(offset..offset).collect();
5754 assert_eq!(
5755 excerpts_for_range.len(),
5756 1,
5757 "Expected exactly one excerpt for offset {offset}",
5758 );
5759
5760 let (_, excerpt_containing) =
5761 snapshot
5762 .excerpt_containing(offset..offset)
5763 .unwrap_or_else(|| {
5764 panic!("Expected excerpt_containing to find excerpt for offset {offset}")
5765 });
5766
5767 assert_eq!(
5768 excerpts_for_range[0].range, excerpt_containing,
5769 "excerpts_for_range and excerpt_containing should agree for offset {offset}",
5770 );
5771 }
5772
5773 //// Test `excerpt_containing` behavior with ranges:
5774
5775 // Ranges intersecting a single-excerpt
5776 let (_, containing) = snapshot.excerpt_containing(p00..p13).unwrap();
5777 assert_eq!(containing, excerpt_1_info);
5778
5779 // Ranges intersecting multiple excerpts (should return None)
5780 let containing = snapshot.excerpt_containing(p20..p40);
5781 assert!(
5782 containing.is_none(),
5783 "excerpt_containing should return None for ranges spanning multiple excerpts"
5784 );
5785}
5786
5787#[gpui::test]
5788fn test_range_to_buffer_ranges(cx: &mut App) {
5789 let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb", cx));
5790 let buffer_2 = cx.new(|cx| Buffer::local("ccc", cx));
5791
5792 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5793 multibuffer.update(cx, |multibuffer, cx| {
5794 multibuffer.set_excerpts_for_path(
5795 PathKey::sorted(0),
5796 buffer_1.clone(),
5797 [Point::new(0, 0)..Point::new(1, 3)],
5798 0,
5799 cx,
5800 );
5801
5802 multibuffer.set_excerpts_for_path(
5803 PathKey::sorted(1),
5804 buffer_2.clone(),
5805 [Point::new(0, 0)..Point::new(0, 3)],
5806 0,
5807 cx,
5808 );
5809 });
5810
5811 let snapshot = multibuffer.read(cx).snapshot(cx);
5812 assert_eq!(snapshot.text(), "aaa\nbbb\nccc");
5813
5814 let excerpt_2_start = Point::new(2, 0);
5815
5816 let ranges_half_open = snapshot.range_to_buffer_ranges(Point::zero()..excerpt_2_start);
5817 assert_eq!(
5818 ranges_half_open.len(),
5819 1,
5820 "Half-open range ending at excerpt start should EXCLUDE that excerpt"
5821 );
5822 assert_eq!(ranges_half_open[0].1, BufferOffset(0)..BufferOffset(7));
5823 assert_eq!(
5824 ranges_half_open[0].0.remote_id(),
5825 buffer_1.read(cx).remote_id()
5826 );
5827
5828 let buffer_empty = cx.new(|cx| Buffer::local("", cx));
5829 let multibuffer_trailing_empty = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
5830 let (_te_excerpt_1_info, _te_excerpt_2_info) =
5831 multibuffer_trailing_empty.update(cx, |multibuffer, cx| {
5832 multibuffer.set_excerpts_for_path(
5833 PathKey::sorted(0),
5834 buffer_1.clone(),
5835 [Point::new(0, 0)..Point::new(1, 3)],
5836 0,
5837 cx,
5838 );
5839
5840 multibuffer.set_excerpts_for_path(
5841 PathKey::sorted(1),
5842 buffer_empty.clone(),
5843 [Point::new(0, 0)..Point::new(0, 0)],
5844 0,
5845 cx,
5846 );
5847
5848 let snapshot = multibuffer.snapshot(cx);
5849 let mut infos = snapshot.excerpts();
5850 (infos.next().unwrap(), infos.next().unwrap())
5851 });
5852
5853 let snapshot_trailing = multibuffer_trailing_empty.read(cx).snapshot(cx);
5854 assert_eq!(snapshot_trailing.text(), "aaa\nbbb\n");
5855
5856 let max_point = snapshot_trailing.max_point();
5857
5858 let ranges_half_open_max = snapshot_trailing.range_to_buffer_ranges(Point::zero()..max_point);
5859 assert_eq!(
5860 ranges_half_open_max.len(),
5861 2,
5862 "Should include trailing empty excerpts"
5863 );
5864 assert_eq!(ranges_half_open_max[1].1, BufferOffset(0)..BufferOffset(0));
5865}
5866
5867#[gpui::test]
5868async fn test_buffer_range_to_excerpt_ranges(cx: &mut TestAppContext) {
5869 let base_text = indoc!(
5870 "
5871 aaa
5872 bbb
5873 ccc
5874 ddd
5875 eee
5876 ppp
5877 qqq
5878 rrr
5879 fff
5880 ggg
5881 hhh
5882 "
5883 );
5884 let text = indoc!(
5885 "
5886 aaa
5887 BBB
5888 ddd
5889 eee
5890 ppp
5891 qqq
5892 rrr
5893 FFF
5894 ggg
5895 hhh
5896 "
5897 );
5898
5899 let buffer = cx.new(|cx| Buffer::local(text, cx));
5900 let diff = cx
5901 .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
5902 cx.run_until_parked();
5903
5904 let multibuffer = cx.new(|cx| {
5905 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
5906 multibuffer.set_excerpts_for_path(
5907 PathKey::sorted(0),
5908 buffer.clone(),
5909 [
5910 Point::new(0, 0)..Point::new(3, 3),
5911 Point::new(7, 0)..Point::new(9, 3),
5912 ],
5913 0,
5914 cx,
5915 );
5916 multibuffer.add_diff(diff.clone(), cx);
5917 multibuffer
5918 });
5919
5920 multibuffer.update(cx, |multibuffer, cx| {
5921 multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
5922 });
5923 cx.run_until_parked();
5924
5925 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
5926
5927 let actual_diff = format_diff(
5928 &snapshot.text(),
5929 &snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
5930 &Default::default(),
5931 None,
5932 );
5933 let expected_diff = indoc!(
5934 "
5935 aaa
5936 - bbb
5937 - ccc
5938 + BBB
5939 ddd
5940 eee [\u{2193}]
5941 - fff [\u{2191}]
5942 + FFF
5943 ggg
5944 hhh [\u{2193}]"
5945 );
5946 pretty_assertions::assert_eq!(actual_diff, expected_diff);
5947
5948 let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
5949
5950 let query_spanning_deleted_hunk = buffer_snapshot.anchor_after(Point::new(0, 0))
5951 ..buffer_snapshot.anchor_before(Point::new(1, 3));
5952 assert_eq!(
5953 snapshot
5954 .buffer_range_to_excerpt_ranges(query_spanning_deleted_hunk)
5955 .map(|range| range.to_point(&snapshot))
5956 .collect::<Vec<_>>(),
5957 vec![
5958 Point::new(0, 0)..Point::new(1, 0),
5959 Point::new(3, 0)..Point::new(3, 3),
5960 ],
5961 );
5962
5963 let query_within_contiguous_main_buffer = buffer_snapshot.anchor_after(Point::new(1, 0))
5964 ..buffer_snapshot.anchor_before(Point::new(2, 3));
5965 assert_eq!(
5966 snapshot
5967 .buffer_range_to_excerpt_ranges(query_within_contiguous_main_buffer)
5968 .map(|range| range.to_point(&snapshot))
5969 .collect::<Vec<_>>(),
5970 vec![Point::new(3, 0)..Point::new(4, 3)],
5971 );
5972
5973 let query_spanning_both_excerpts = buffer_snapshot.anchor_after(Point::new(2, 0))
5974 ..buffer_snapshot.anchor_before(Point::new(8, 3));
5975 assert_eq!(
5976 snapshot
5977 .buffer_range_to_excerpt_ranges(query_spanning_both_excerpts)
5978 .map(|range| range.to_point(&snapshot))
5979 .collect::<Vec<_>>(),
5980 vec![
5981 Point::new(4, 0)..Point::new(5, 3),
5982 Point::new(7, 0)..Point::new(8, 3),
5983 ],
5984 );
5985}
5986
5987#[gpui::test]
5988fn test_cannot_seek_backward_after_excerpt_replacement(cx: &mut TestAppContext) {
5989 let buffer_b_text: String = (0..50).map(|i| format!("line_b {i}\n")).collect();
5990 let buffer_b = cx.new(|cx| Buffer::local(buffer_b_text, cx));
5991
5992 let buffer_c_text: String = (0..10).map(|i| format!("line_c {i}\n")).collect();
5993 let buffer_c = cx.new(|cx| Buffer::local(buffer_c_text, cx));
5994
5995 let buffer_d_text: String = (0..10).map(|i| format!("line_d {i}\n")).collect();
5996 let buffer_d = cx.new(|cx| Buffer::local(buffer_d_text, cx));
5997
5998 let path_b = PathKey::with_sort_prefix(0, rel_path("bbb.rs").into_arc());
5999 let path_c = PathKey::with_sort_prefix(0, rel_path("ddd.rs").into_arc());
6000 let path_d = PathKey::with_sort_prefix(0, rel_path("ccc.rs").into_arc());
6001
6002 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
6003
6004 multibuffer.update(cx, |multibuffer, cx| {
6005 multibuffer.set_excerpts_for_path(
6006 path_b.clone(),
6007 buffer_b.clone(),
6008 vec![
6009 Point::row_range(0..3),
6010 Point::row_range(15..18),
6011 Point::row_range(30..33),
6012 ],
6013 0,
6014 cx,
6015 );
6016 });
6017
6018 multibuffer.update(cx, |multibuffer, cx| {
6019 multibuffer.set_excerpts_for_path(
6020 path_c.clone(),
6021 buffer_c.clone(),
6022 vec![Point::row_range(0..3)],
6023 0,
6024 cx,
6025 );
6026 });
6027
6028 let (anchor_in_e_b2, anchor_in_e_b3) = multibuffer.read_with(cx, |multibuffer, cx| {
6029 let snapshot = multibuffer.snapshot(cx);
6030 let excerpt_infos = snapshot.excerpts().collect::<Vec<_>>();
6031 assert_eq!(excerpt_infos.len(), 4, "expected 4 excerpts (3×B + 1×C)");
6032
6033 let e_b2_info = excerpt_infos[1].clone();
6034 let e_b3_info = excerpt_infos[2].clone();
6035
6036 let anchor_b2 = snapshot.anchor_in_excerpt(e_b2_info.context.start).unwrap();
6037 let anchor_b3 = snapshot.anchor_in_excerpt(e_b3_info.context.start).unwrap();
6038 (anchor_b2, anchor_b3)
6039 });
6040
6041 multibuffer.update(cx, |multibuffer, cx| {
6042 multibuffer.set_excerpts_for_path(
6043 path_b.clone(),
6044 buffer_b.clone(),
6045 vec![Point::row_range(0..3), Point::row_range(28..36)],
6046 0,
6047 cx,
6048 );
6049 });
6050
6051 multibuffer.update(cx, |multibuffer, cx| {
6052 multibuffer.set_excerpts_for_path(
6053 path_d.clone(),
6054 buffer_d.clone(),
6055 vec![Point::row_range(0..3)],
6056 0,
6057 cx,
6058 );
6059 });
6060
6061 multibuffer.read_with(cx, |multibuffer, cx| {
6062 let snapshot = multibuffer.snapshot(cx);
6063 snapshot.summaries_for_anchors::<Point, _>(&[anchor_in_e_b2, anchor_in_e_b3]);
6064 });
6065}
6066
6067#[gpui::test]
6068fn test_resolving_max_anchor_for_buffer(cx: &mut TestAppContext) {
6069 let dock_base_text = indoc! {"
6070 0
6071 1
6072 2
6073 3
6074 4
6075 5
6076 6
6077 7
6078 8
6079 9
6080 10
6081 11
6082 12
6083 "};
6084
6085 let dock_text = indoc! {"
6086 0
6087 4
6088 5
6089 6
6090 10
6091 11
6092 12
6093 "};
6094
6095 let dock_buffer = cx.new(|cx| Buffer::local(dock_text, cx));
6096 let diff = cx.new(|cx| {
6097 BufferDiff::new_with_base_text(dock_base_text, &dock_buffer.read(cx).snapshot(), cx)
6098 });
6099
6100 let workspace_text = "second buffer\n";
6101 let workspace_buffer = cx.new(|cx| Buffer::local(workspace_text, cx));
6102
6103 let dock_path = PathKey::with_sort_prefix(0, rel_path("").into_arc());
6104 let workspace_path = PathKey::with_sort_prefix(1, rel_path("").into_arc());
6105
6106 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
6107
6108 multibuffer.update(cx, |multibuffer, cx| {
6109 multibuffer.set_excerpt_ranges_for_path(
6110 dock_path,
6111 dock_buffer.clone(),
6112 &dock_buffer.read(cx).snapshot(),
6113 vec![
6114 ExcerptRange::new(Point::zero()..Point::new(1, 1)),
6115 ExcerptRange::new(Point::new(3, 0)..Point::new(4, 2)),
6116 ],
6117 cx,
6118 );
6119 multibuffer.set_excerpt_ranges_for_path(
6120 workspace_path,
6121 workspace_buffer.clone(),
6122 &workspace_buffer.read(cx).snapshot(),
6123 vec![ExcerptRange::new(
6124 Point::zero()..workspace_buffer.read(cx).max_point(),
6125 )],
6126 cx,
6127 );
6128 multibuffer.add_diff(diff, cx);
6129 multibuffer.set_all_diff_hunks_expanded(cx);
6130 });
6131
6132 let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
6133 let diff = format_diff(
6134 &snapshot.text(),
6135 &snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
6136 &Default::default(),
6137 None,
6138 );
6139 assert_eq!(
6140 diff,
6141 indoc! {"
6142 0
6143 - 1
6144 - 2
6145 - 3
6146 4 [↓]
6147 6 [↑]
6148 - 7
6149 - 8
6150 - 9
6151 10 [↓]
6152 second buffer
6153 "}
6154 );
6155
6156 multibuffer.update(cx, |multibuffer, cx| {
6157 let snapshot = multibuffer.snapshot(cx);
6158 let point = snapshot
6159 .anchor_in_buffer(text::Anchor::max_for_buffer(
6160 dock_buffer.read(cx).remote_id(),
6161 ))
6162 .unwrap()
6163 .to_point(&snapshot);
6164 assert_eq!(point, Point::new(10, 0));
6165 })
6166}