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