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.random_range(0..=10);
2495 let max_row = snapshot.max_point().row;
2496 let mut ranges = (0..num_ranges)
2497 .map(|_| {
2498 let start = rng.random_range(0..max_row);
2499 let end = rng.random_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.random_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.random_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.random_range(0..ids.len()) {
2581 excerpts.extend(ids.choose(&mut rng).copied());
2582 }
2583
2584 let line_count = rng.random_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.random_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.random_range(0..=multibuffer.len()), Bias::Left);
2624 let bias = if rng.random() {
2625 Bias::Left
2626 } else {
2627 Bias::Right
2628 };
2629 log::info!("Creating anchor at {} with bias {:?}", offset, bias);
2630 anchors.push(multibuffer.anchor_at(offset, bias));
2631 anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
2632 }
2633 40..=44 if !anchors.is_empty() => {
2634 let multibuffer =
2635 multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2636 let prev_len = anchors.len();
2637 anchors = multibuffer
2638 .refresh_anchors(&anchors)
2639 .into_iter()
2640 .map(|a| a.1)
2641 .collect();
2642
2643 // Ensure the newly-refreshed anchors point to a valid excerpt and don't
2644 // overshoot its boundaries.
2645 assert_eq!(anchors.len(), prev_len);
2646 for anchor in &anchors {
2647 if anchor.excerpt_id == ExcerptId::min()
2648 || anchor.excerpt_id == ExcerptId::max()
2649 {
2650 continue;
2651 }
2652
2653 let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
2654 assert_eq!(excerpt.id, anchor.excerpt_id);
2655 assert!(excerpt.contains(anchor));
2656 }
2657 }
2658 45..=55 if !reference.excerpts.is_empty() => {
2659 multibuffer.update(cx, |multibuffer, cx| {
2660 let snapshot = multibuffer.snapshot(cx);
2661 let excerpt_ix = rng.random_range(0..reference.excerpts.len());
2662 let excerpt = &reference.excerpts[excerpt_ix];
2663 let start = excerpt.range.start;
2664 let end = excerpt.range.end;
2665 let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
2666 ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
2667
2668 log::info!(
2669 "expanding diff hunks in range {:?} (excerpt id {:?}, index {excerpt_ix:?}, buffer id {:?})",
2670 range.to_offset(&snapshot),
2671 excerpt.id,
2672 excerpt.buffer.read(cx).remote_id(),
2673 );
2674 reference.expand_diff_hunks(excerpt.id, start..end, cx);
2675 multibuffer.expand_diff_hunks(vec![range], cx);
2676 });
2677 }
2678 56..=85 if needs_diff_calculation => {
2679 multibuffer.update(cx, |multibuffer, cx| {
2680 for buffer in multibuffer.all_buffers() {
2681 let snapshot = buffer.read(cx).snapshot();
2682 multibuffer.diff_for(snapshot.remote_id()).unwrap().update(
2683 cx,
2684 |diff, cx| {
2685 log::info!(
2686 "recalculating diff for buffer {:?}",
2687 snapshot.remote_id(),
2688 );
2689 diff.recalculate_diff_sync(snapshot.text, cx);
2690 },
2691 );
2692 }
2693 reference.diffs_updated(cx);
2694 needs_diff_calculation = false;
2695 });
2696 }
2697 _ => {
2698 let buffer_handle = if buffers.is_empty() || rng.random_bool(0.4) {
2699 let mut base_text = util::RandomCharIter::new(&mut rng)
2700 .take(256)
2701 .collect::<String>();
2702
2703 let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
2704 text::LineEnding::normalize(&mut base_text);
2705 base_texts.insert(
2706 buffer.read_with(cx, |buffer, _| buffer.remote_id()),
2707 base_text,
2708 );
2709 buffers.push(buffer);
2710 buffers.last().unwrap()
2711 } else {
2712 buffers.choose(&mut rng).unwrap()
2713 };
2714
2715 let prev_excerpt_ix = rng.random_range(0..=reference.excerpts.len());
2716 let prev_excerpt_id = reference
2717 .excerpts
2718 .get(prev_excerpt_ix)
2719 .map_or(ExcerptId::max(), |e| e.id);
2720 let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
2721
2722 let (range, anchor_range) = buffer_handle.read_with(cx, |buffer, _| {
2723 let end_row = rng.random_range(0..=buffer.max_point().row);
2724 let start_row = rng.random_range(0..=end_row);
2725 let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
2726 let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
2727 let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
2728
2729 log::info!(
2730 "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
2731 excerpt_ix,
2732 reference.excerpts.len(),
2733 buffer.remote_id(),
2734 buffer.text(),
2735 start_ix..end_ix,
2736 &buffer.text()[start_ix..end_ix]
2737 );
2738
2739 (start_ix..end_ix, anchor_range)
2740 });
2741
2742 multibuffer.update(cx, |multibuffer, cx| {
2743 let id = buffer_handle.read(cx).remote_id();
2744 if multibuffer.diff_for(id).is_none() {
2745 let base_text = base_texts.get(&id).unwrap();
2746 let diff = cx
2747 .new(|cx| BufferDiff::new_with_base_text(base_text, buffer_handle, cx));
2748 reference.add_diff(diff.clone(), cx);
2749 multibuffer.add_diff(diff, cx)
2750 }
2751 });
2752
2753 let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
2754 multibuffer
2755 .insert_excerpts_after(
2756 prev_excerpt_id,
2757 buffer_handle.clone(),
2758 [ExcerptRange::new(range.clone())],
2759 cx,
2760 )
2761 .pop()
2762 .unwrap()
2763 });
2764
2765 reference.insert_excerpt_after(
2766 prev_excerpt_id,
2767 excerpt_id,
2768 (buffer_handle.clone(), anchor_range),
2769 );
2770 }
2771 }
2772
2773 if rng.random_bool(0.3) {
2774 multibuffer.update(cx, |multibuffer, cx| {
2775 old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
2776 })
2777 }
2778
2779 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2780 let actual_text = snapshot.text();
2781 let actual_boundary_rows = snapshot
2782 .excerpt_boundaries_in_range(0..)
2783 .map(|b| b.row)
2784 .collect::<HashSet<_>>();
2785 let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
2786
2787 let (expected_text, expected_row_infos, expected_boundary_rows) =
2788 cx.update(|cx| reference.expected_content(cx));
2789
2790 let has_diff = actual_row_infos
2791 .iter()
2792 .any(|info| info.diff_status.is_some())
2793 || expected_row_infos
2794 .iter()
2795 .any(|info| info.diff_status.is_some());
2796 let actual_diff = format_diff(
2797 &actual_text,
2798 &actual_row_infos,
2799 &actual_boundary_rows,
2800 Some(has_diff),
2801 );
2802 let expected_diff = format_diff(
2803 &expected_text,
2804 &expected_row_infos,
2805 &expected_boundary_rows,
2806 Some(has_diff),
2807 );
2808
2809 log::info!("Multibuffer content:\n{}", actual_diff);
2810
2811 assert_eq!(
2812 actual_row_infos.len(),
2813 actual_text.split('\n').count(),
2814 "line count: {}",
2815 actual_text.split('\n').count()
2816 );
2817 pretty_assertions::assert_eq!(actual_diff, expected_diff);
2818 pretty_assertions::assert_eq!(actual_text, expected_text);
2819 pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
2820
2821 for _ in 0..5 {
2822 let start_row = rng.random_range(0..=expected_row_infos.len());
2823 assert_eq!(
2824 snapshot
2825 .row_infos(MultiBufferRow(start_row as u32))
2826 .collect::<Vec<_>>(),
2827 &expected_row_infos[start_row..],
2828 "buffer_rows({})",
2829 start_row
2830 );
2831 }
2832
2833 assert_eq!(
2834 snapshot.widest_line_number(),
2835 expected_row_infos
2836 .into_iter()
2837 .filter_map(|info| {
2838 if info.diff_status.is_some_and(|status| status.is_deleted()) {
2839 None
2840 } else {
2841 info.buffer_row
2842 }
2843 })
2844 .max()
2845 .unwrap()
2846 + 1
2847 );
2848 let reference_ranges = cx.update(|cx| {
2849 reference
2850 .excerpts
2851 .iter()
2852 .map(|excerpt| {
2853 (
2854 excerpt.id,
2855 excerpt.range.to_offset(&excerpt.buffer.read(cx).snapshot()),
2856 )
2857 })
2858 .collect::<HashMap<_, _>>()
2859 });
2860 for i in 0..snapshot.len() {
2861 let excerpt = snapshot.excerpt_containing(i..i).unwrap();
2862 assert_eq!(excerpt.buffer_range(), reference_ranges[&excerpt.id()]);
2863 }
2864
2865 assert_consistent_line_numbers(&snapshot);
2866 assert_position_translation(&snapshot);
2867
2868 for (row, line) in expected_text.split('\n').enumerate() {
2869 assert_eq!(
2870 snapshot.line_len(MultiBufferRow(row as u32)),
2871 line.len() as u32,
2872 "line_len({}).",
2873 row
2874 );
2875 }
2876
2877 let text_rope = Rope::from(expected_text.as_str());
2878 for _ in 0..10 {
2879 let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
2880 let start_ix = text_rope.clip_offset(rng.random_range(0..=end_ix), Bias::Left);
2881
2882 let text_for_range = snapshot
2883 .text_for_range(start_ix..end_ix)
2884 .collect::<String>();
2885 assert_eq!(
2886 text_for_range,
2887 &expected_text[start_ix..end_ix],
2888 "incorrect text for range {:?}",
2889 start_ix..end_ix
2890 );
2891
2892 let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
2893 assert_eq!(
2894 snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
2895 expected_summary,
2896 "incorrect summary for range {:?}",
2897 start_ix..end_ix
2898 );
2899 }
2900
2901 // Anchor resolution
2902 let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
2903 assert_eq!(anchors.len(), summaries.len());
2904 for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
2905 assert!(resolved_offset <= snapshot.len());
2906 assert_eq!(
2907 snapshot.summary_for_anchor::<usize>(anchor),
2908 resolved_offset,
2909 "anchor: {:?}",
2910 anchor
2911 );
2912 }
2913
2914 for _ in 0..10 {
2915 let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
2916 assert_eq!(
2917 snapshot.reversed_chars_at(end_ix).collect::<String>(),
2918 expected_text[..end_ix].chars().rev().collect::<String>(),
2919 );
2920 }
2921
2922 for _ in 0..10 {
2923 let end_ix = rng.random_range(0..=text_rope.len());
2924 let start_ix = rng.random_range(0..=end_ix);
2925 assert_eq!(
2926 snapshot
2927 .bytes_in_range(start_ix..end_ix)
2928 .flatten()
2929 .copied()
2930 .collect::<Vec<_>>(),
2931 expected_text.as_bytes()[start_ix..end_ix].to_vec(),
2932 "bytes_in_range({:?})",
2933 start_ix..end_ix,
2934 );
2935 }
2936 }
2937
2938 let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
2939 for (old_snapshot, subscription) in old_versions {
2940 let edits = subscription.consume().into_inner();
2941
2942 log::info!(
2943 "applying subscription edits to old text: {:?}: {:?}",
2944 old_snapshot.text(),
2945 edits,
2946 );
2947
2948 let mut text = old_snapshot.text();
2949 for edit in edits {
2950 let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
2951 text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
2952 }
2953 assert_eq!(text.to_string(), snapshot.text());
2954 }
2955}
2956
2957#[gpui::test]
2958fn test_history(cx: &mut App) {
2959 let test_settings = SettingsStore::test(cx);
2960 cx.set_global(test_settings);
2961 let group_interval: Duration = Duration::from_millis(1);
2962 let buffer_1 = cx.new(|cx| {
2963 let mut buf = Buffer::local("1234", cx);
2964 buf.set_group_interval(group_interval);
2965 buf
2966 });
2967 let buffer_2 = cx.new(|cx| {
2968 let mut buf = Buffer::local("5678", cx);
2969 buf.set_group_interval(group_interval);
2970 buf
2971 });
2972 let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2973 multibuffer.update(cx, |this, _| {
2974 this.history.group_interval = group_interval;
2975 });
2976 multibuffer.update(cx, |multibuffer, cx| {
2977 multibuffer.push_excerpts(
2978 buffer_1.clone(),
2979 [ExcerptRange::new(0..buffer_1.read(cx).len())],
2980 cx,
2981 );
2982 multibuffer.push_excerpts(
2983 buffer_2.clone(),
2984 [ExcerptRange::new(0..buffer_2.read(cx).len())],
2985 cx,
2986 );
2987 });
2988
2989 let mut now = Instant::now();
2990
2991 multibuffer.update(cx, |multibuffer, cx| {
2992 let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
2993 multibuffer.edit(
2994 [
2995 (Point::new(0, 0)..Point::new(0, 0), "A"),
2996 (Point::new(1, 0)..Point::new(1, 0), "A"),
2997 ],
2998 None,
2999 cx,
3000 );
3001 multibuffer.edit(
3002 [
3003 (Point::new(0, 1)..Point::new(0, 1), "B"),
3004 (Point::new(1, 1)..Point::new(1, 1), "B"),
3005 ],
3006 None,
3007 cx,
3008 );
3009 multibuffer.end_transaction_at(now, cx);
3010 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3011
3012 // Verify edited ranges for transaction 1
3013 assert_eq!(
3014 multibuffer.edited_ranges_for_transaction(transaction_1, cx),
3015 &[
3016 Point::new(0, 0)..Point::new(0, 2),
3017 Point::new(1, 0)..Point::new(1, 2)
3018 ]
3019 );
3020
3021 // Edit buffer 1 through the multibuffer
3022 now += 2 * group_interval;
3023 multibuffer.start_transaction_at(now, cx);
3024 multibuffer.edit([(2..2, "C")], None, cx);
3025 multibuffer.end_transaction_at(now, cx);
3026 assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
3027
3028 // Edit buffer 1 independently
3029 buffer_1.update(cx, |buffer_1, cx| {
3030 buffer_1.start_transaction_at(now);
3031 buffer_1.edit([(3..3, "D")], None, cx);
3032 buffer_1.end_transaction_at(now, cx);
3033
3034 now += 2 * group_interval;
3035 buffer_1.start_transaction_at(now);
3036 buffer_1.edit([(4..4, "E")], None, cx);
3037 buffer_1.end_transaction_at(now, cx);
3038 });
3039 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
3040
3041 // An undo in the multibuffer undoes the multibuffer transaction
3042 // and also any individual buffer edits that have occurred since
3043 // that transaction.
3044 multibuffer.undo(cx);
3045 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3046
3047 multibuffer.undo(cx);
3048 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3049
3050 multibuffer.redo(cx);
3051 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3052
3053 multibuffer.redo(cx);
3054 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
3055
3056 // Undo buffer 2 independently.
3057 buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx));
3058 assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678");
3059
3060 // An undo in the multibuffer undoes the components of the
3061 // the last multibuffer transaction that are not already undone.
3062 multibuffer.undo(cx);
3063 assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678");
3064
3065 multibuffer.undo(cx);
3066 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3067
3068 multibuffer.redo(cx);
3069 assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
3070
3071 buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
3072 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
3073
3074 // Redo stack gets cleared after an edit.
3075 now += 2 * group_interval;
3076 multibuffer.start_transaction_at(now, cx);
3077 multibuffer.edit([(0..0, "X")], None, cx);
3078 multibuffer.end_transaction_at(now, cx);
3079 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3080 multibuffer.redo(cx);
3081 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3082 multibuffer.undo(cx);
3083 assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
3084 multibuffer.undo(cx);
3085 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3086
3087 // Transactions can be grouped manually.
3088 multibuffer.redo(cx);
3089 multibuffer.redo(cx);
3090 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3091 multibuffer.group_until_transaction(transaction_1, cx);
3092 multibuffer.undo(cx);
3093 assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
3094 multibuffer.redo(cx);
3095 assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
3096 });
3097}
3098
3099#[gpui::test]
3100async fn test_enclosing_indent(cx: &mut TestAppContext) {
3101 async fn enclosing_indent(
3102 text: &str,
3103 buffer_row: u32,
3104 cx: &mut TestAppContext,
3105 ) -> Option<(Range<u32>, LineIndent)> {
3106 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3107 let snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
3108 let (range, indent) = snapshot
3109 .enclosing_indent(MultiBufferRow(buffer_row))
3110 .await?;
3111 Some((range.start.0..range.end.0, indent))
3112 }
3113
3114 assert_eq!(
3115 enclosing_indent(
3116 indoc!(
3117 "
3118 fn b() {
3119 if c {
3120 let d = 2;
3121 }
3122 }
3123 "
3124 ),
3125 1,
3126 cx,
3127 )
3128 .await,
3129 Some((
3130 1..2,
3131 LineIndent {
3132 tabs: 0,
3133 spaces: 4,
3134 line_blank: false,
3135 }
3136 ))
3137 );
3138
3139 assert_eq!(
3140 enclosing_indent(
3141 indoc!(
3142 "
3143 fn b() {
3144 if c {
3145 let d = 2;
3146 }
3147 }
3148 "
3149 ),
3150 2,
3151 cx,
3152 )
3153 .await,
3154 Some((
3155 1..2,
3156 LineIndent {
3157 tabs: 0,
3158 spaces: 4,
3159 line_blank: false,
3160 }
3161 ))
3162 );
3163
3164 assert_eq!(
3165 enclosing_indent(
3166 indoc!(
3167 "
3168 fn b() {
3169 if c {
3170 let d = 2;
3171
3172 let e = 5;
3173 }
3174 }
3175 "
3176 ),
3177 3,
3178 cx,
3179 )
3180 .await,
3181 Some((
3182 1..4,
3183 LineIndent {
3184 tabs: 0,
3185 spaces: 4,
3186 line_blank: false,
3187 }
3188 ))
3189 );
3190}
3191
3192#[gpui::test]
3193fn test_summaries_for_anchors(cx: &mut TestAppContext) {
3194 let base_text_1 = indoc!(
3195 "
3196 bar
3197 "
3198 );
3199 let text_1 = indoc!(
3200 "
3201 BAR
3202 "
3203 );
3204 let base_text_2 = indoc!(
3205 "
3206 foo
3207 "
3208 );
3209 let text_2 = indoc!(
3210 "
3211 FOO
3212 "
3213 );
3214
3215 let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3216 let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
3217 let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_1, &buffer_1, cx));
3218 let diff_2 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_2, &buffer_2, cx));
3219 cx.run_until_parked();
3220
3221 let mut ids = vec![];
3222 let multibuffer = cx.new(|cx| {
3223 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
3224 multibuffer.set_all_diff_hunks_expanded(cx);
3225 ids.extend(multibuffer.push_excerpts(
3226 buffer_1.clone(),
3227 [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
3228 cx,
3229 ));
3230 ids.extend(multibuffer.push_excerpts(
3231 buffer_2.clone(),
3232 [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
3233 cx,
3234 ));
3235 multibuffer.add_diff(diff_1.clone(), cx);
3236 multibuffer.add_diff(diff_2.clone(), cx);
3237 multibuffer
3238 });
3239
3240 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3241 (multibuffer.snapshot(cx), multibuffer.subscribe())
3242 });
3243
3244 assert_new_snapshot(
3245 &multibuffer,
3246 &mut snapshot,
3247 &mut subscription,
3248 cx,
3249 indoc!(
3250 "
3251 - bar
3252 + BAR
3253
3254 - foo
3255 + FOO
3256 "
3257 ),
3258 );
3259
3260 let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
3261 let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
3262
3263 let anchor_1 = Anchor::in_buffer(ids[0], id_1, text::Anchor::MIN);
3264 let point_1 = snapshot.summaries_for_anchors::<Point, _>([&anchor_1])[0];
3265 assert_eq!(point_1, Point::new(0, 0));
3266
3267 let anchor_2 = Anchor::in_buffer(ids[1], id_2, text::Anchor::MIN);
3268 let point_2 = snapshot.summaries_for_anchors::<Point, _>([&anchor_2])[0];
3269 assert_eq!(point_2, Point::new(3, 0));
3270}
3271
3272#[gpui::test]
3273fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
3274 let base_text_1 = "one\ntwo".to_owned();
3275 let text_1 = "one\n".to_owned();
3276
3277 let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
3278 let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(&base_text_1, &buffer_1, cx));
3279 cx.run_until_parked();
3280
3281 let multibuffer = cx.new(|cx| {
3282 let mut multibuffer = MultiBuffer::singleton(buffer_1.clone(), cx);
3283 multibuffer.add_diff(diff_1.clone(), cx);
3284 multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
3285 multibuffer
3286 });
3287
3288 let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
3289 (multibuffer.snapshot(cx), multibuffer.subscribe())
3290 });
3291
3292 assert_new_snapshot(
3293 &multibuffer,
3294 &mut snapshot,
3295 &mut subscription,
3296 cx,
3297 indoc!(
3298 "
3299 one
3300 - two
3301 "
3302 ),
3303 );
3304
3305 assert_eq!(snapshot.max_point(), Point::new(2, 0));
3306 assert_eq!(snapshot.len(), 8);
3307
3308 assert_eq!(
3309 snapshot
3310 .dimensions_from_points::<Point>([Point::new(2, 0)])
3311 .collect::<Vec<_>>(),
3312 vec![Point::new(2, 0)]
3313 );
3314
3315 let (_, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3316 assert_eq!(translated_offset, "one\n".len());
3317 let (_, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3318 assert_eq!(translated_point, Point::new(1, 0));
3319
3320 // The same, for an excerpt that's not at the end of the multibuffer.
3321
3322 let text_2 = "foo\n".to_owned();
3323 let buffer_2 = cx.new(|cx| Buffer::local(&text_2, cx));
3324 multibuffer.update(cx, |multibuffer, cx| {
3325 multibuffer.push_excerpts(
3326 buffer_2.clone(),
3327 [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
3328 cx,
3329 );
3330 });
3331
3332 assert_new_snapshot(
3333 &multibuffer,
3334 &mut snapshot,
3335 &mut subscription,
3336 cx,
3337 indoc!(
3338 "
3339 one
3340 - two
3341
3342 foo
3343 "
3344 ),
3345 );
3346
3347 assert_eq!(
3348 snapshot
3349 .dimensions_from_points::<Point>([Point::new(2, 0)])
3350 .collect::<Vec<_>>(),
3351 vec![Point::new(2, 0)]
3352 );
3353
3354 let buffer_1_id = buffer_1.read_with(cx, |buffer_1, _| buffer_1.remote_id());
3355 let (buffer, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
3356 assert_eq!(buffer.remote_id(), buffer_1_id);
3357 assert_eq!(translated_offset, "one\n".len());
3358 let (buffer, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
3359 assert_eq!(buffer.remote_id(), buffer_1_id);
3360 assert_eq!(translated_point, Point::new(1, 0));
3361}
3362
3363fn format_diff(
3364 text: &str,
3365 row_infos: &Vec<RowInfo>,
3366 boundary_rows: &HashSet<MultiBufferRow>,
3367 has_diff: Option<bool>,
3368) -> String {
3369 let has_diff =
3370 has_diff.unwrap_or_else(|| row_infos.iter().any(|info| info.diff_status.is_some()));
3371 text.split('\n')
3372 .enumerate()
3373 .zip(row_infos)
3374 .map(|((ix, line), info)| {
3375 let marker = match info.diff_status.map(|status| status.kind) {
3376 Some(DiffHunkStatusKind::Added) => "+ ",
3377 Some(DiffHunkStatusKind::Deleted) => "- ",
3378 Some(DiffHunkStatusKind::Modified) => unreachable!(),
3379 None => {
3380 if has_diff && !line.is_empty() {
3381 " "
3382 } else {
3383 ""
3384 }
3385 }
3386 };
3387 let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
3388 if has_diff {
3389 " ----------\n"
3390 } else {
3391 "---------\n"
3392 }
3393 } else {
3394 ""
3395 };
3396 format!("{boundary_row}{marker}{line}")
3397 })
3398 .collect::<Vec<_>>()
3399 .join("\n")
3400}
3401
3402#[track_caller]
3403fn assert_excerpts_match(
3404 multibuffer: &Entity<MultiBuffer>,
3405 cx: &mut TestAppContext,
3406 expected: &str,
3407) {
3408 let mut output = String::new();
3409 multibuffer.read_with(cx, |multibuffer, cx| {
3410 for (_, buffer, range) in multibuffer.snapshot(cx).excerpts() {
3411 output.push_str("-----\n");
3412 output.extend(buffer.text_for_range(range.context));
3413 if !output.ends_with('\n') {
3414 output.push('\n');
3415 }
3416 }
3417 });
3418 assert_eq!(output, expected);
3419}
3420
3421#[track_caller]
3422fn assert_new_snapshot(
3423 multibuffer: &Entity<MultiBuffer>,
3424 snapshot: &mut MultiBufferSnapshot,
3425 subscription: &mut Subscription,
3426 cx: &mut TestAppContext,
3427 expected_diff: &str,
3428) {
3429 let new_snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
3430 let actual_text = new_snapshot.text();
3431 let line_infos = new_snapshot
3432 .row_infos(MultiBufferRow(0))
3433 .collect::<Vec<_>>();
3434 let actual_diff = format_diff(&actual_text, &line_infos, &Default::default(), None);
3435 pretty_assertions::assert_eq!(actual_diff, expected_diff);
3436 check_edits(
3437 snapshot,
3438 &new_snapshot,
3439 &subscription.consume().into_inner(),
3440 );
3441 *snapshot = new_snapshot;
3442}
3443
3444#[track_caller]
3445fn check_edits(
3446 old_snapshot: &MultiBufferSnapshot,
3447 new_snapshot: &MultiBufferSnapshot,
3448 edits: &[Edit<usize>],
3449) {
3450 let mut text = old_snapshot.text();
3451 let new_text = new_snapshot.text();
3452 for edit in edits.iter().rev() {
3453 if !text.is_char_boundary(edit.old.start)
3454 || !text.is_char_boundary(edit.old.end)
3455 || !new_text.is_char_boundary(edit.new.start)
3456 || !new_text.is_char_boundary(edit.new.end)
3457 {
3458 panic!(
3459 "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
3460 edits, text, new_text
3461 );
3462 }
3463
3464 text.replace_range(
3465 edit.old.start..edit.old.end,
3466 &new_text[edit.new.start..edit.new.end],
3467 );
3468 }
3469
3470 pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits);
3471}
3472
3473#[track_caller]
3474fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
3475 let full_text = snapshot.text();
3476 for ix in 0..full_text.len() {
3477 let mut chunks = snapshot.chunks(0..snapshot.len(), false);
3478 chunks.seek(ix..snapshot.len());
3479 let tail = chunks.map(|chunk| chunk.text).collect::<String>();
3480 assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
3481 }
3482}
3483
3484#[track_caller]
3485fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
3486 let all_line_numbers = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
3487 for start_row in 1..all_line_numbers.len() {
3488 let line_numbers = snapshot
3489 .row_infos(MultiBufferRow(start_row as u32))
3490 .collect::<Vec<_>>();
3491 assert_eq!(
3492 line_numbers,
3493 all_line_numbers[start_row..],
3494 "start_row: {start_row}"
3495 );
3496 }
3497}
3498
3499#[track_caller]
3500fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
3501 let text = Rope::from(snapshot.text());
3502
3503 let mut left_anchors = Vec::new();
3504 let mut right_anchors = Vec::new();
3505 let mut offsets = Vec::new();
3506 let mut points = Vec::new();
3507 for offset in 0..=text.len() + 1 {
3508 let clipped_left = snapshot.clip_offset(offset, Bias::Left);
3509 let clipped_right = snapshot.clip_offset(offset, Bias::Right);
3510 assert_eq!(
3511 clipped_left,
3512 text.clip_offset(offset, Bias::Left),
3513 "clip_offset({offset:?}, Left)"
3514 );
3515 assert_eq!(
3516 clipped_right,
3517 text.clip_offset(offset, Bias::Right),
3518 "clip_offset({offset:?}, Right)"
3519 );
3520 assert_eq!(
3521 snapshot.offset_to_point(clipped_left),
3522 text.offset_to_point(clipped_left),
3523 "offset_to_point({clipped_left})"
3524 );
3525 assert_eq!(
3526 snapshot.offset_to_point(clipped_right),
3527 text.offset_to_point(clipped_right),
3528 "offset_to_point({clipped_right})"
3529 );
3530 let anchor_after = snapshot.anchor_after(clipped_left);
3531 assert_eq!(
3532 anchor_after.to_offset(snapshot),
3533 clipped_left,
3534 "anchor_after({clipped_left}).to_offset {anchor_after:?}"
3535 );
3536 let anchor_before = snapshot.anchor_before(clipped_left);
3537 assert_eq!(
3538 anchor_before.to_offset(snapshot),
3539 clipped_left,
3540 "anchor_before({clipped_left}).to_offset"
3541 );
3542 left_anchors.push(anchor_before);
3543 right_anchors.push(anchor_after);
3544 offsets.push(clipped_left);
3545 points.push(text.offset_to_point(clipped_left));
3546 }
3547
3548 for row in 0..text.max_point().row {
3549 for column in 0..text.line_len(row) + 1 {
3550 let point = Point { row, column };
3551 let clipped_left = snapshot.clip_point(point, Bias::Left);
3552 let clipped_right = snapshot.clip_point(point, Bias::Right);
3553 assert_eq!(
3554 clipped_left,
3555 text.clip_point(point, Bias::Left),
3556 "clip_point({point:?}, Left)"
3557 );
3558 assert_eq!(
3559 clipped_right,
3560 text.clip_point(point, Bias::Right),
3561 "clip_point({point:?}, Right)"
3562 );
3563 assert_eq!(
3564 snapshot.point_to_offset(clipped_left),
3565 text.point_to_offset(clipped_left),
3566 "point_to_offset({clipped_left:?})"
3567 );
3568 assert_eq!(
3569 snapshot.point_to_offset(clipped_right),
3570 text.point_to_offset(clipped_right),
3571 "point_to_offset({clipped_right:?})"
3572 );
3573 }
3574 }
3575
3576 assert_eq!(
3577 snapshot.summaries_for_anchors::<usize, _>(&left_anchors),
3578 offsets,
3579 "left_anchors <-> offsets"
3580 );
3581 assert_eq!(
3582 snapshot.summaries_for_anchors::<Point, _>(&left_anchors),
3583 points,
3584 "left_anchors <-> points"
3585 );
3586 assert_eq!(
3587 snapshot.summaries_for_anchors::<usize, _>(&right_anchors),
3588 offsets,
3589 "right_anchors <-> offsets"
3590 );
3591 assert_eq!(
3592 snapshot.summaries_for_anchors::<Point, _>(&right_anchors),
3593 points,
3594 "right_anchors <-> points"
3595 );
3596
3597 for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
3598 for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
3599 if ix > 0 && *offset == 252 && offset > &offsets[ix - 1] {
3600 let prev_anchor = left_anchors[ix - 1];
3601 assert!(
3602 anchor.cmp(&prev_anchor, snapshot).is_gt(),
3603 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_gt()",
3604 offsets[ix],
3605 offsets[ix - 1],
3606 );
3607 assert!(
3608 prev_anchor.cmp(anchor, snapshot).is_lt(),
3609 "anchor({}, {bias:?}).cmp(&anchor({}, {bias:?}).is_lt()",
3610 offsets[ix - 1],
3611 offsets[ix],
3612 );
3613 }
3614 }
3615 }
3616
3617 if let Some((buffer, offset)) = snapshot.point_to_buffer_offset(snapshot.max_point()) {
3618 assert!(offset <= buffer.len());
3619 }
3620 if let Some((buffer, point, _)) = snapshot.point_to_buffer_point(snapshot.max_point()) {
3621 assert!(point <= buffer.max_point());
3622 }
3623}
3624
3625fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
3626 let max_row = snapshot.max_point().row;
3627 let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
3628 let text = text::Buffer::new(0, buffer_id, snapshot.text());
3629 let mut line_indents = text
3630 .line_indents_in_row_range(0..max_row + 1)
3631 .collect::<Vec<_>>();
3632 for start_row in 0..snapshot.max_point().row {
3633 pretty_assertions::assert_eq!(
3634 snapshot
3635 .line_indents(MultiBufferRow(start_row), |_| true)
3636 .map(|(row, indent, _)| (row.0, indent))
3637 .collect::<Vec<_>>(),
3638 &line_indents[(start_row as usize)..],
3639 "line_indents({start_row})"
3640 );
3641 }
3642
3643 line_indents.reverse();
3644 pretty_assertions::assert_eq!(
3645 snapshot
3646 .reversed_line_indents(MultiBufferRow(max_row), |_| true)
3647 .map(|(row, indent, _)| (row.0, indent))
3648 .collect::<Vec<_>>(),
3649 &line_indents[..],
3650 "reversed_line_indents({max_row})"
3651 );
3652}
3653
3654#[gpui::test]
3655fn test_new_empty_buffer_uses_untitled_title(cx: &mut App) {
3656 let buffer = cx.new(|cx| Buffer::local("", cx));
3657 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3658
3659 assert_eq!(multibuffer.read(cx).title(cx), "untitled");
3660}
3661
3662#[gpui::test]
3663fn test_new_empty_buffer_uses_untitled_title_when_only_contains_whitespace(cx: &mut App) {
3664 let buffer = cx.new(|cx| Buffer::local("\n ", cx));
3665 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3666
3667 assert_eq!(multibuffer.read(cx).title(cx), "untitled");
3668}
3669
3670#[gpui::test]
3671fn test_new_empty_buffer_takes_first_line_for_title(cx: &mut App) {
3672 let buffer = cx.new(|cx| Buffer::local("Hello World\nSecond line", cx));
3673 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3674
3675 assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
3676}
3677
3678#[gpui::test]
3679fn test_new_empty_buffer_takes_trimmed_first_line_for_title(cx: &mut App) {
3680 let buffer = cx.new(|cx| Buffer::local("\nHello, World ", cx));
3681 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3682
3683 assert_eq!(multibuffer.read(cx).title(cx), "Hello, World");
3684}
3685
3686#[gpui::test]
3687fn test_new_empty_buffer_uses_truncated_first_line_for_title(cx: &mut App) {
3688 let title = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee";
3689 let title_after = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd";
3690 let buffer = cx.new(|cx| Buffer::local(title, cx));
3691 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3692
3693 assert_eq!(multibuffer.read(cx).title(cx), title_after);
3694}
3695
3696#[gpui::test]
3697fn test_new_empty_buffer_uses_truncated_first_line_for_title_after_merging_adjacent_spaces(
3698 cx: &mut App,
3699) {
3700 let title = "aaaaaaaaaabbbbbbbbbb ccccccccccddddddddddeeeeeeeeee";
3701 let title_after = "aaaaaaaaaabbbbbbbbbb ccccccccccddddddddd";
3702 let buffer = cx.new(|cx| Buffer::local(title, cx));
3703 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3704
3705 assert_eq!(multibuffer.read(cx).title(cx), title_after);
3706}
3707
3708#[gpui::test]
3709fn test_new_empty_buffers_title_can_be_set(cx: &mut App) {
3710 let buffer = cx.new(|cx| Buffer::local("Hello World", cx));
3711 let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
3712 assert_eq!(multibuffer.read(cx).title(cx), "Hello World");
3713
3714 multibuffer.update(cx, |multibuffer, cx| {
3715 multibuffer.set_title("Hey".into(), cx)
3716 });
3717 assert_eq!(multibuffer.read(cx).title(cx), "Hey");
3718}