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