1use super::{
2 Highlights,
3 fold_map::Chunk,
4 wrap_map::{self, WrapEdit, WrapPatch, WrapPoint, WrapSnapshot},
5};
6use crate::{
7 EditorStyle, GutterDimensions,
8 display_map::{Companion, dimensions::RowDelta, wrap_map::WrapRow},
9};
10use collections::{Bound, HashMap, HashSet};
11use gpui::{AnyElement, App, EntityId, Pixels, Window};
12use language::{Patch, Point};
13use multi_buffer::{
14 Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferOffset, MultiBufferPoint,
15 MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset, ToPoint as _,
16};
17use parking_lot::Mutex;
18use std::{
19 cell::RefCell,
20 cmp::{self, Ordering},
21 fmt::Debug,
22 ops::{Deref, DerefMut, Not, Range, RangeBounds, RangeInclusive},
23 sync::{
24 Arc,
25 atomic::{AtomicUsize, Ordering::SeqCst},
26 },
27};
28use sum_tree::{Bias, ContextLessSummary, Dimensions, SumTree, TreeMap};
29use text::{BufferId, Edit};
30use ui::ElementId;
31
32const NEWLINES: &[u8; rope::Chunk::MASK_BITS] = &[b'\n'; _];
33const BULLETS: &[u8; rope::Chunk::MASK_BITS] = &[b'*'; _];
34
35/// Tracks custom blocks such as diagnostics that should be displayed within buffer.
36///
37/// See the [`display_map` module documentation](crate::display_map) for more information.
38pub struct BlockMap {
39 pub(super) wrap_snapshot: RefCell<WrapSnapshot>,
40 next_block_id: AtomicUsize,
41 custom_blocks: Vec<Arc<CustomBlock>>,
42 custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
43 transforms: RefCell<SumTree<Transform>>,
44 buffer_header_height: u32,
45 excerpt_header_height: u32,
46 pub(super) folded_buffers: HashSet<BufferId>,
47 buffers_with_disabled_headers: HashSet<BufferId>,
48}
49
50pub struct BlockMapReader<'a> {
51 blocks: &'a Vec<Arc<CustomBlock>>,
52 pub snapshot: BlockSnapshot,
53}
54
55pub struct BlockMapWriter<'a> {
56 block_map: &'a mut BlockMap,
57 companion: Option<BlockMapWriterCompanion<'a>>,
58}
59
60struct BlockMapWriterCompanion<'a>(CompanionView<'a>);
61
62impl<'a> Deref for BlockMapWriterCompanion<'a> {
63 type Target = CompanionView<'a>;
64
65 fn deref(&self) -> &Self::Target {
66 &self.0
67 }
68}
69
70#[derive(Clone)]
71pub struct BlockSnapshot {
72 pub(super) wrap_snapshot: WrapSnapshot,
73 transforms: SumTree<Transform>,
74 custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
75 pub(super) buffer_header_height: u32,
76 pub(super) excerpt_header_height: u32,
77}
78
79impl Deref for BlockSnapshot {
80 type Target = WrapSnapshot;
81
82 fn deref(&self) -> &Self::Target {
83 &self.wrap_snapshot
84 }
85}
86
87#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
88pub struct CustomBlockId(pub usize);
89
90impl From<CustomBlockId> for ElementId {
91 fn from(val: CustomBlockId) -> Self {
92 val.0.into()
93 }
94}
95
96#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
97pub struct SpacerId(pub usize);
98
99/// A zero-indexed point in a text buffer consisting of a row and column
100/// adjusted for inserted blocks, wrapped rows, tabs, folds and inlays.
101#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
102pub struct BlockPoint(pub Point);
103
104#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
105pub struct BlockRow(pub u32);
106
107impl_for_row_types! {
108 BlockRow => RowDelta
109}
110
111impl BlockPoint {
112 pub fn row(&self) -> BlockRow {
113 BlockRow(self.0.row)
114 }
115}
116
117pub type RenderBlock = Arc<dyn Send + Sync + Fn(&mut BlockContext) -> AnyElement>;
118
119/// Where to place a block.
120#[derive(Clone, Debug, Eq, PartialEq)]
121pub enum BlockPlacement<T> {
122 /// Place the block above the given position.
123 Above(T),
124 /// Place the block below the given position.
125 Below(T),
126 /// Place the block next the given position.
127 Near(T),
128 /// Replace the given range of positions with the block.
129 Replace(RangeInclusive<T>),
130}
131
132impl<T> BlockPlacement<T> {
133 pub fn start(&self) -> &T {
134 match self {
135 BlockPlacement::Above(position) => position,
136 BlockPlacement::Below(position) => position,
137 BlockPlacement::Near(position) => position,
138 BlockPlacement::Replace(range) => range.start(),
139 }
140 }
141
142 fn end(&self) -> &T {
143 match self {
144 BlockPlacement::Above(position) => position,
145 BlockPlacement::Below(position) => position,
146 BlockPlacement::Near(position) => position,
147 BlockPlacement::Replace(range) => range.end(),
148 }
149 }
150
151 pub fn as_ref(&self) -> BlockPlacement<&T> {
152 match self {
153 BlockPlacement::Above(position) => BlockPlacement::Above(position),
154 BlockPlacement::Below(position) => BlockPlacement::Below(position),
155 BlockPlacement::Near(position) => BlockPlacement::Near(position),
156 BlockPlacement::Replace(range) => BlockPlacement::Replace(range.start()..=range.end()),
157 }
158 }
159
160 pub fn map<R>(self, mut f: impl FnMut(T) -> R) -> BlockPlacement<R> {
161 match self {
162 BlockPlacement::Above(position) => BlockPlacement::Above(f(position)),
163 BlockPlacement::Below(position) => BlockPlacement::Below(f(position)),
164 BlockPlacement::Near(position) => BlockPlacement::Near(f(position)),
165 BlockPlacement::Replace(range) => {
166 let (start, end) = range.into_inner();
167 BlockPlacement::Replace(f(start)..=f(end))
168 }
169 }
170 }
171
172 fn tie_break(&self) -> u8 {
173 match self {
174 BlockPlacement::Replace(_) => 0,
175 BlockPlacement::Above(_) => 1,
176 BlockPlacement::Near(_) => 2,
177 BlockPlacement::Below(_) => 3,
178 }
179 }
180}
181
182impl BlockPlacement<Anchor> {
183 #[ztracing::instrument(skip_all)]
184 fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
185 self.start()
186 .cmp(other.start(), buffer)
187 .then_with(|| other.end().cmp(self.end(), buffer))
188 .then_with(|| self.tie_break().cmp(&other.tie_break()))
189 }
190
191 #[ztracing::instrument(skip_all)]
192 fn to_wrap_row(&self, wrap_snapshot: &WrapSnapshot) -> Option<BlockPlacement<WrapRow>> {
193 let buffer_snapshot = wrap_snapshot.buffer_snapshot();
194 match self {
195 BlockPlacement::Above(position) => {
196 let mut position = position.to_point(buffer_snapshot);
197 position.column = 0;
198 let wrap_row = wrap_snapshot.make_wrap_point(position, Bias::Left).row();
199 Some(BlockPlacement::Above(wrap_row))
200 }
201 BlockPlacement::Near(position) => {
202 let mut position = position.to_point(buffer_snapshot);
203 position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
204 let wrap_row = wrap_snapshot.make_wrap_point(position, Bias::Left).row();
205 Some(BlockPlacement::Near(wrap_row))
206 }
207 BlockPlacement::Below(position) => {
208 let mut position = position.to_point(buffer_snapshot);
209 position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
210 let wrap_row = wrap_snapshot.make_wrap_point(position, Bias::Left).row();
211 Some(BlockPlacement::Below(wrap_row))
212 }
213 BlockPlacement::Replace(range) => {
214 let mut start = range.start().to_point(buffer_snapshot);
215 let mut end = range.end().to_point(buffer_snapshot);
216 if start == end {
217 None
218 } else {
219 start.column = 0;
220 let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
221 end.column = buffer_snapshot.line_len(MultiBufferRow(end.row));
222 let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
223 Some(BlockPlacement::Replace(start_wrap_row..=end_wrap_row))
224 }
225 }
226 }
227 }
228}
229
230pub struct CustomBlock {
231 pub id: CustomBlockId,
232 pub placement: BlockPlacement<Anchor>,
233 pub height: Option<u32>,
234 style: BlockStyle,
235 render: Arc<Mutex<RenderBlock>>,
236 priority: usize,
237}
238
239#[derive(Clone)]
240pub struct BlockProperties<P> {
241 pub placement: BlockPlacement<P>,
242 // None if the block takes up no space
243 // (e.g. a horizontal line)
244 pub height: Option<u32>,
245 pub style: BlockStyle,
246 pub render: RenderBlock,
247 pub priority: usize,
248}
249
250impl<P: Debug> Debug for BlockProperties<P> {
251 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252 f.debug_struct("BlockProperties")
253 .field("placement", &self.placement)
254 .field("height", &self.height)
255 .field("style", &self.style)
256 .finish()
257 }
258}
259
260#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
261pub enum BlockStyle {
262 Fixed,
263 Flex,
264 Sticky,
265}
266
267#[derive(Debug, Default, Copy, Clone)]
268pub struct EditorMargins {
269 pub gutter: GutterDimensions,
270 pub right: Pixels,
271}
272
273#[derive(gpui::AppContext, gpui::VisualContext)]
274pub struct BlockContext<'a, 'b> {
275 #[window]
276 pub window: &'a mut Window,
277 #[app]
278 pub app: &'b mut App,
279 pub anchor_x: Pixels,
280 pub max_width: Pixels,
281 pub margins: &'b EditorMargins,
282 pub em_width: Pixels,
283 pub line_height: Pixels,
284 pub block_id: BlockId,
285 pub selected: bool,
286 pub editor_style: &'b EditorStyle,
287}
288
289#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
290pub enum BlockId {
291 ExcerptBoundary(ExcerptId),
292 FoldedBuffer(ExcerptId),
293 Custom(CustomBlockId),
294 Spacer(SpacerId),
295}
296
297impl From<BlockId> for ElementId {
298 fn from(value: BlockId) -> Self {
299 match value {
300 BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
301 BlockId::ExcerptBoundary(excerpt_id) => {
302 ("ExcerptBoundary", EntityId::from(excerpt_id)).into()
303 }
304 BlockId::FoldedBuffer(id) => ("FoldedBuffer", EntityId::from(id)).into(),
305 BlockId::Spacer(SpacerId(id)) => ("Spacer", id).into(),
306 }
307 }
308}
309
310impl std::fmt::Display for BlockId {
311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312 match self {
313 Self::Custom(id) => write!(f, "Block({id:?})"),
314 Self::ExcerptBoundary(id) => write!(f, "ExcerptHeader({id:?})"),
315 Self::FoldedBuffer(id) => write!(f, "FoldedBuffer({id:?})"),
316 Self::Spacer(id) => write!(f, "Spacer({id:?})"),
317 }
318 }
319}
320
321#[derive(Clone, Debug)]
322struct Transform {
323 summary: TransformSummary,
324 block: Option<Block>,
325}
326
327#[derive(Clone)]
328pub enum Block {
329 Custom(Arc<CustomBlock>),
330 FoldedBuffer {
331 first_excerpt: ExcerptInfo,
332 height: u32,
333 },
334 ExcerptBoundary {
335 excerpt: ExcerptInfo,
336 height: u32,
337 },
338 BufferHeader {
339 excerpt: ExcerptInfo,
340 height: u32,
341 },
342 Spacer {
343 id: SpacerId,
344 height: u32,
345 is_below: bool,
346 },
347}
348
349impl Block {
350 pub fn id(&self) -> BlockId {
351 match self {
352 Block::Custom(block) => BlockId::Custom(block.id),
353 Block::ExcerptBoundary {
354 excerpt: next_excerpt,
355 ..
356 } => BlockId::ExcerptBoundary(next_excerpt.id),
357 Block::FoldedBuffer { first_excerpt, .. } => BlockId::FoldedBuffer(first_excerpt.id),
358 Block::BufferHeader {
359 excerpt: next_excerpt,
360 ..
361 } => BlockId::ExcerptBoundary(next_excerpt.id),
362 Block::Spacer { id, .. } => BlockId::Spacer(*id),
363 }
364 }
365
366 pub fn has_height(&self) -> bool {
367 match self {
368 Block::Custom(block) => block.height.is_some(),
369 Block::ExcerptBoundary { .. }
370 | Block::FoldedBuffer { .. }
371 | Block::BufferHeader { .. }
372 | Block::Spacer { .. } => true,
373 }
374 }
375
376 pub fn height(&self) -> u32 {
377 match self {
378 Block::Custom(block) => block.height.unwrap_or(0),
379 Block::ExcerptBoundary { height, .. }
380 | Block::FoldedBuffer { height, .. }
381 | Block::BufferHeader { height, .. }
382 | Block::Spacer { height, .. } => *height,
383 }
384 }
385
386 pub fn style(&self) -> BlockStyle {
387 match self {
388 Block::Custom(block) => block.style,
389 Block::ExcerptBoundary { .. }
390 | Block::FoldedBuffer { .. }
391 | Block::BufferHeader { .. }
392 | Block::Spacer { .. } => BlockStyle::Sticky,
393 }
394 }
395
396 fn place_above(&self) -> bool {
397 match self {
398 Block::Custom(block) => matches!(block.placement, BlockPlacement::Above(_)),
399 Block::FoldedBuffer { .. } => false,
400 Block::ExcerptBoundary { .. } => true,
401 Block::BufferHeader { .. } => true,
402 Block::Spacer { is_below, .. } => !*is_below,
403 }
404 }
405
406 pub fn place_near(&self) -> bool {
407 match self {
408 Block::Custom(block) => matches!(block.placement, BlockPlacement::Near(_)),
409 Block::FoldedBuffer { .. } => false,
410 Block::ExcerptBoundary { .. } => false,
411 Block::BufferHeader { .. } => false,
412 Block::Spacer { .. } => false,
413 }
414 }
415
416 fn place_below(&self) -> bool {
417 match self {
418 Block::Custom(block) => matches!(
419 block.placement,
420 BlockPlacement::Below(_) | BlockPlacement::Near(_)
421 ),
422 Block::FoldedBuffer { .. } => false,
423 Block::ExcerptBoundary { .. } => false,
424 Block::BufferHeader { .. } => false,
425 Block::Spacer { is_below, .. } => *is_below,
426 }
427 }
428
429 fn is_replacement(&self) -> bool {
430 match self {
431 Block::Custom(block) => matches!(block.placement, BlockPlacement::Replace(_)),
432 Block::FoldedBuffer { .. } => true,
433 Block::ExcerptBoundary { .. } => false,
434 Block::BufferHeader { .. } => false,
435 Block::Spacer { .. } => false,
436 }
437 }
438
439 fn is_header(&self) -> bool {
440 match self {
441 Block::Custom(_) => false,
442 Block::FoldedBuffer { .. } => true,
443 Block::ExcerptBoundary { .. } => true,
444 Block::BufferHeader { .. } => true,
445 Block::Spacer { .. } => false,
446 }
447 }
448
449 pub fn is_buffer_header(&self) -> bool {
450 match self {
451 Block::Custom(_) => false,
452 Block::FoldedBuffer { .. } => true,
453 Block::ExcerptBoundary { .. } => false,
454 Block::BufferHeader { .. } => true,
455 Block::Spacer { .. } => false,
456 }
457 }
458}
459
460impl Debug for Block {
461 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
462 match self {
463 Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
464 Self::FoldedBuffer {
465 first_excerpt,
466 height,
467 } => f
468 .debug_struct("FoldedBuffer")
469 .field("first_excerpt", &first_excerpt)
470 .field("height", height)
471 .finish(),
472 Self::ExcerptBoundary { excerpt, height } => f
473 .debug_struct("ExcerptBoundary")
474 .field("excerpt", excerpt)
475 .field("height", height)
476 .finish(),
477 Self::BufferHeader { excerpt, height } => f
478 .debug_struct("BufferHeader")
479 .field("excerpt", excerpt)
480 .field("height", height)
481 .finish(),
482 Self::Spacer {
483 id,
484 height,
485 is_below: _,
486 } => f
487 .debug_struct("Spacer")
488 .field("id", id)
489 .field("height", height)
490 .finish(),
491 }
492 }
493}
494
495#[derive(Clone, Debug, Default)]
496struct TransformSummary {
497 input_rows: WrapRow,
498 output_rows: BlockRow,
499 longest_row: BlockRow,
500 longest_row_chars: u32,
501}
502
503pub struct BlockChunks<'a> {
504 transforms: sum_tree::Cursor<'a, 'static, Transform, Dimensions<BlockRow, WrapRow>>,
505 input_chunks: wrap_map::WrapChunks<'a>,
506 input_chunk: Chunk<'a>,
507 output_row: BlockRow,
508 max_output_row: BlockRow,
509 line_count_overflow: RowDelta,
510 masked: bool,
511}
512
513#[derive(Clone)]
514pub struct BlockRows<'a> {
515 transforms: sum_tree::Cursor<'a, 'static, Transform, Dimensions<BlockRow, WrapRow>>,
516 input_rows: wrap_map::WrapRows<'a>,
517 output_row: BlockRow,
518 started: bool,
519}
520
521#[derive(Clone, Copy)]
522pub struct CompanionView<'a> {
523 entity_id: EntityId,
524 wrap_snapshot: &'a WrapSnapshot,
525 wrap_edits: &'a WrapPatch,
526 companion: &'a Companion,
527}
528
529impl<'a> CompanionView<'a> {
530 pub(crate) fn new(
531 entity_id: EntityId,
532 wrap_snapshot: &'a WrapSnapshot,
533 wrap_edits: &'a WrapPatch,
534 companion: &'a Companion,
535 ) -> Self {
536 Self {
537 entity_id,
538 wrap_snapshot,
539 wrap_edits,
540 companion,
541 }
542 }
543}
544
545impl BlockMap {
546 #[ztracing::instrument(skip_all)]
547 pub fn new(
548 wrap_snapshot: WrapSnapshot,
549 buffer_header_height: u32,
550 excerpt_header_height: u32,
551 ) -> Self {
552 let row_count = wrap_snapshot.max_point().row() + WrapRow(1);
553 let mut transforms = SumTree::default();
554 push_isomorphic(&mut transforms, row_count - WrapRow(0), &wrap_snapshot);
555 let map = Self {
556 next_block_id: AtomicUsize::new(0),
557 custom_blocks: Vec::new(),
558 custom_blocks_by_id: TreeMap::default(),
559 folded_buffers: HashSet::default(),
560 buffers_with_disabled_headers: HashSet::default(),
561 transforms: RefCell::new(transforms),
562 wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
563 buffer_header_height,
564 excerpt_header_height,
565 };
566 map.sync(
567 &wrap_snapshot,
568 Patch::new(vec![Edit {
569 old: WrapRow(0)..row_count,
570 new: WrapRow(0)..row_count,
571 }]),
572 None,
573 );
574 map
575 }
576
577 #[ztracing::instrument(skip_all)]
578 pub(crate) fn read(
579 &self,
580 wrap_snapshot: WrapSnapshot,
581 edits: WrapPatch,
582 companion_view: Option<CompanionView>,
583 ) -> BlockMapReader<'_> {
584 self.sync(&wrap_snapshot, edits, companion_view);
585 *self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
586 BlockMapReader {
587 blocks: &self.custom_blocks,
588 snapshot: BlockSnapshot {
589 wrap_snapshot,
590 transforms: self.transforms.borrow().clone(),
591 custom_blocks_by_id: self.custom_blocks_by_id.clone(),
592 buffer_header_height: self.buffer_header_height,
593 excerpt_header_height: self.excerpt_header_height,
594 },
595 }
596 }
597
598 #[ztracing::instrument(skip_all)]
599 pub(crate) fn write<'a>(
600 &'a mut self,
601 wrap_snapshot: WrapSnapshot,
602 edits: WrapPatch,
603 companion_view: Option<CompanionView<'a>>,
604 ) -> BlockMapWriter<'a> {
605 self.sync(&wrap_snapshot, edits, companion_view);
606 *self.wrap_snapshot.borrow_mut() = wrap_snapshot;
607 BlockMapWriter {
608 block_map: self,
609 companion: companion_view.map(BlockMapWriterCompanion),
610 }
611 }
612
613 #[ztracing::instrument(skip_all, fields(edits = ?edits))]
614 fn sync(
615 &self,
616 wrap_snapshot: &WrapSnapshot,
617 mut edits: WrapPatch,
618 companion_view: Option<CompanionView>,
619 ) {
620 let buffer = wrap_snapshot.buffer_snapshot();
621
622 // Handle changing the last excerpt if it is empty.
623 if buffer.trailing_excerpt_update_count()
624 != self
625 .wrap_snapshot
626 .borrow()
627 .buffer_snapshot()
628 .trailing_excerpt_update_count()
629 {
630 let max_point = wrap_snapshot.max_point();
631 let edit_start = wrap_snapshot.prev_row_boundary(max_point);
632 let edit_end = max_point.row() + WrapRow(1); // this is end of file
633 edits = edits.compose([WrapEdit {
634 old: edit_start..edit_end,
635 new: edit_start..edit_end,
636 }]);
637 }
638
639 // Pull in companion edits to ensure we recompute spacers in ranges that have changed in the companion.
640 if let Some(CompanionView {
641 wrap_snapshot: companion_new_snapshot,
642 wrap_edits: companion_edits,
643 companion,
644 entity_id: display_map_id,
645 ..
646 }) = companion_view
647 {
648 let mut companion_edits_in_my_space: Vec<WrapEdit> = companion_edits
649 .clone()
650 .into_inner()
651 .iter()
652 .map(|edit| {
653 let companion_start = companion_new_snapshot
654 .to_point(WrapPoint::new(edit.new.start, 0), Bias::Left);
655 let companion_end = companion_new_snapshot
656 .to_point(WrapPoint::new(edit.new.end, 0), Bias::Left);
657
658 let my_start = companion
659 .convert_point_from_companion(
660 display_map_id,
661 wrap_snapshot.buffer_snapshot(),
662 companion_new_snapshot.buffer_snapshot(),
663 companion_start,
664 )
665 .start;
666 let my_end = companion
667 .convert_point_from_companion(
668 display_map_id,
669 wrap_snapshot.buffer_snapshot(),
670 companion_new_snapshot.buffer_snapshot(),
671 companion_end,
672 )
673 .end;
674
675 let mut my_start = wrap_snapshot.make_wrap_point(my_start, Bias::Left);
676 let mut my_end = wrap_snapshot.make_wrap_point(my_end, Bias::Left);
677 // TODO(split-diff) should use trailing_excerpt_update_count for the second case
678 if my_end.column() > 0 || my_end == wrap_snapshot.max_point() {
679 *my_end.row_mut() += 1;
680 *my_end.column_mut() = 0;
681 }
682
683 // Empty edits won't survive Patch::compose, but we still need to make sure
684 // we recompute spacers when we get them.
685 if my_start.row() == my_end.row() {
686 if my_end.row() <= wrap_snapshot.max_point().row() {
687 *my_end.row_mut() += 1;
688 *my_end.column_mut() = 0;
689 } else if my_start.row() > WrapRow(0) {
690 *my_start.row_mut() += 1;
691 *my_start.column_mut() = 0;
692 }
693 }
694
695 WrapEdit {
696 old: my_start.row()..my_end.row(),
697 new: my_start.row()..my_end.row(),
698 }
699 })
700 .collect();
701
702 companion_edits_in_my_space.sort_by_key(|edit| edit.old.start);
703 let mut merged_edits: Vec<WrapEdit> = Vec::new();
704 for edit in companion_edits_in_my_space {
705 if let Some(last) = merged_edits.last_mut() {
706 if edit.old.start <= last.old.end {
707 last.old.end = last.old.end.max(edit.old.end);
708 last.new.end = last.new.end.max(edit.new.end);
709 continue;
710 }
711 }
712 merged_edits.push(edit);
713 }
714
715 edits = edits.compose(merged_edits);
716 }
717
718 let edits = edits.into_inner();
719 if edits.is_empty() {
720 return;
721 }
722
723 let mut transforms = self.transforms.borrow_mut();
724 let mut new_transforms = SumTree::default();
725 let mut cursor = transforms.cursor::<WrapRow>(());
726 let mut last_block_ix = 0;
727 let mut blocks_in_edit = Vec::new();
728 let mut edits = edits.into_iter().peekable();
729
730 let mut inlay_point_cursor = wrap_snapshot.inlay_point_cursor();
731 let mut tab_point_cursor = wrap_snapshot.tab_point_cursor();
732 let mut fold_point_cursor = wrap_snapshot.fold_point_cursor();
733 let mut wrap_point_cursor = wrap_snapshot.wrap_point_cursor();
734
735 while let Some(edit) = edits.next() {
736 let span = ztracing::debug_span!("while edits", edit = ?edit);
737 let _enter = span.enter();
738
739 let mut old_start = edit.old.start;
740 let mut new_start = edit.new.start;
741
742 // Only preserve transforms that:
743 // * Strictly precedes this edit
744 // * Isomorphic transforms that end *at* the start of the edit
745 // * Below blocks that end at the start of the edit
746 // However, if we hit a replace block that ends at the start of the edit we want to reconstruct it.
747 new_transforms.append(cursor.slice(&old_start, Bias::Left), ());
748 if let Some(transform) = cursor.item()
749 && transform.summary.input_rows > WrapRow(0)
750 && cursor.end() == old_start
751 && transform.block.as_ref().is_none_or(|b| !b.is_replacement())
752 {
753 // Preserve the transform (push and next)
754 new_transforms.push(transform.clone(), ());
755 cursor.next();
756
757 // Preserve below blocks at start of edit
758 while let Some(transform) = cursor.item() {
759 if transform.block.as_ref().is_some_and(|b| b.place_below()) {
760 new_transforms.push(transform.clone(), ());
761 cursor.next();
762 } else {
763 break;
764 }
765 }
766 }
767
768 // Ensure the edit starts at a transform boundary.
769 // If the edit starts within an isomorphic transform, preserve its prefix
770 // If the edit lands within a replacement block, expand the edit to include the start of the replaced input range
771 let transform = cursor.item().unwrap();
772 let transform_rows_before_edit = old_start - *cursor.start();
773 if transform_rows_before_edit > RowDelta(0) {
774 if transform.block.is_none() {
775 // Preserve any portion of the old isomorphic transform that precedes this edit.
776 push_isomorphic(
777 &mut new_transforms,
778 transform_rows_before_edit,
779 wrap_snapshot,
780 );
781 } else {
782 // We landed within a block that replaces some lines, so we
783 // extend the edit to start at the beginning of the
784 // replacement.
785 debug_assert!(transform.summary.input_rows > WrapRow(0));
786 old_start -= transform_rows_before_edit;
787 new_start -= transform_rows_before_edit;
788 }
789 }
790
791 // Decide where the edit ends
792 // * It should end at a transform boundary
793 // * Coalesce edits that intersect the same transform
794 let mut old_end = edit.old.end;
795 let mut new_end = edit.new.end;
796 loop {
797 let span = ztracing::debug_span!("decide where edit ends loop");
798 let _enter = span.enter();
799 // Seek to the transform starting at or after the end of the edit
800 cursor.seek(&old_end, Bias::Left);
801 cursor.next();
802
803 // Extend edit to the end of the discarded transform so it is reconstructed in full
804 let transform_rows_after_edit = *cursor.start() - old_end;
805 old_end += transform_rows_after_edit;
806 new_end += transform_rows_after_edit;
807
808 // Combine this edit with any subsequent edits that intersect the same transform.
809 while let Some(next_edit) = edits.peek() {
810 if next_edit.old.start <= *cursor.start() {
811 old_end = next_edit.old.end;
812 new_end = next_edit.new.end;
813 cursor.seek(&old_end, Bias::Left);
814 cursor.next();
815 edits.next();
816 } else {
817 break;
818 }
819 }
820
821 if *cursor.start() == old_end {
822 break;
823 }
824 }
825
826 // Discard below blocks at the end of the edit. They'll be reconstructed.
827 while let Some(transform) = cursor.item() {
828 if transform
829 .block
830 .as_ref()
831 .is_some_and(|b| b.place_below() || matches!(b, Block::Spacer { .. }))
832 {
833 cursor.next();
834 } else {
835 break;
836 }
837 }
838
839 // Find the blocks within this edited region.
840 let new_buffer_start = wrap_snapshot.to_point(WrapPoint::new(new_start, 0), Bias::Left);
841 let start_bound = Bound::Included(new_buffer_start);
842 let start_block_ix =
843 match self.custom_blocks[last_block_ix..].binary_search_by(|probe| {
844 probe
845 .start()
846 .to_point(buffer)
847 .cmp(&new_buffer_start)
848 // Move left until we find the index of the first block starting within this edit
849 .then(Ordering::Greater)
850 }) {
851 Ok(ix) | Err(ix) => last_block_ix + ix,
852 };
853
854 let end_bound;
855 let end_block_ix = if new_end > wrap_snapshot.max_point().row() {
856 end_bound = Bound::Unbounded;
857 self.custom_blocks.len()
858 } else {
859 let new_buffer_end = wrap_snapshot.to_point(WrapPoint::new(new_end, 0), Bias::Left);
860 end_bound = Bound::Excluded(new_buffer_end);
861 match self.custom_blocks[start_block_ix..].binary_search_by(|probe| {
862 probe
863 .start()
864 .to_point(buffer)
865 .cmp(&new_buffer_end)
866 .then(Ordering::Greater)
867 }) {
868 Ok(ix) | Err(ix) => start_block_ix + ix,
869 }
870 };
871 last_block_ix = end_block_ix;
872
873 debug_assert!(blocks_in_edit.is_empty());
874 // + 8 is chosen arbitrarily to cover some multibuffer headers
875 blocks_in_edit
876 .reserve(end_block_ix - start_block_ix + if buffer.is_singleton() { 0 } else { 8 });
877
878 blocks_in_edit.extend(
879 self.custom_blocks[start_block_ix..end_block_ix]
880 .iter()
881 .filter_map(|block| {
882 let placement = block.placement.to_wrap_row(wrap_snapshot)?;
883 if let BlockPlacement::Above(row) = placement
884 && row < new_start
885 {
886 return None;
887 }
888 Some((placement, Block::Custom(block.clone())))
889 }),
890 );
891
892 blocks_in_edit.extend(self.header_and_footer_blocks(
893 buffer,
894 (start_bound, end_bound),
895 |point, bias| {
896 wrap_point_cursor
897 .map(
898 tab_point_cursor
899 .map(fold_point_cursor.map(inlay_point_cursor.map(point), bias)),
900 )
901 .row()
902 },
903 ));
904
905 if let Some(CompanionView {
906 wrap_snapshot: companion_snapshot,
907 companion,
908 entity_id: display_map_id,
909 ..
910 }) = companion_view
911 {
912 blocks_in_edit.extend(self.spacer_blocks(
913 (start_bound, end_bound),
914 wrap_snapshot,
915 companion_snapshot,
916 companion,
917 display_map_id,
918 ));
919 }
920
921 BlockMap::sort_blocks(&mut blocks_in_edit);
922
923 // For each of these blocks, insert a new isomorphic transform preceding the block,
924 // and then insert the block itself.
925 let mut just_processed_folded_buffer = false;
926 for (block_placement, block) in blocks_in_edit.drain(..) {
927 let span =
928 ztracing::debug_span!("for block in edits", block_height = block.height());
929 let _enter = span.enter();
930
931 let mut summary = TransformSummary {
932 input_rows: WrapRow(0),
933 output_rows: BlockRow(block.height()),
934 longest_row: BlockRow(0),
935 longest_row_chars: 0,
936 };
937
938 let rows_before_block;
939 match block_placement {
940 BlockPlacement::Above(position) => {
941 rows_before_block = position - new_transforms.summary().input_rows;
942 just_processed_folded_buffer = false;
943 }
944 BlockPlacement::Near(position) | BlockPlacement::Below(position) => {
945 if just_processed_folded_buffer {
946 continue;
947 }
948 if position + RowDelta(1) < new_transforms.summary().input_rows {
949 continue;
950 }
951 rows_before_block =
952 (position + RowDelta(1)) - new_transforms.summary().input_rows;
953 }
954 BlockPlacement::Replace(ref range) => {
955 rows_before_block = *range.start() - new_transforms.summary().input_rows;
956 summary.input_rows = WrapRow(1) + (*range.end() - *range.start());
957 just_processed_folded_buffer = matches!(block, Block::FoldedBuffer { .. });
958 }
959 }
960
961 push_isomorphic(&mut new_transforms, rows_before_block, wrap_snapshot);
962 new_transforms.push(
963 Transform {
964 summary,
965 block: Some(block),
966 },
967 (),
968 );
969 }
970
971 // Insert an isomorphic transform after the final block.
972 let rows_after_last_block =
973 RowDelta(new_end.0).saturating_sub(RowDelta(new_transforms.summary().input_rows.0));
974 push_isomorphic(&mut new_transforms, rows_after_last_block, wrap_snapshot);
975 }
976
977 new_transforms.append(cursor.suffix(), ());
978 debug_assert_eq!(
979 new_transforms.summary().input_rows,
980 wrap_snapshot.max_point().row() + WrapRow(1),
981 );
982
983 drop(cursor);
984 *transforms = new_transforms;
985 }
986
987 #[ztracing::instrument(skip_all)]
988 pub fn replace_blocks(&mut self, mut renderers: HashMap<CustomBlockId, RenderBlock>) {
989 for block in &mut self.custom_blocks {
990 if let Some(render) = renderers.remove(&block.id) {
991 *block.render.lock() = render;
992 }
993 }
994 }
995
996 /// Guarantees that `wrap_row_for` is called with points in increasing order.
997 #[ztracing::instrument(skip_all)]
998 fn header_and_footer_blocks<'a, R, T>(
999 &'a self,
1000 buffer: &'a multi_buffer::MultiBufferSnapshot,
1001 range: R,
1002 mut wrap_row_for: impl 'a + FnMut(Point, Bias) -> WrapRow,
1003 ) -> impl Iterator<Item = (BlockPlacement<WrapRow>, Block)> + 'a
1004 where
1005 R: RangeBounds<T>,
1006 T: multi_buffer::ToOffset,
1007 {
1008 let mut boundaries = buffer.excerpt_boundaries_in_range(range).peekable();
1009
1010 std::iter::from_fn(move || {
1011 loop {
1012 let excerpt_boundary = boundaries.next()?;
1013 let wrap_row = wrap_row_for(Point::new(excerpt_boundary.row.0, 0), Bias::Left);
1014
1015 let new_buffer_id = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
1016 (None, next) => Some(next.buffer_id),
1017 (Some(prev), next) => {
1018 if prev.buffer_id != next.buffer_id {
1019 Some(next.buffer_id)
1020 } else {
1021 None
1022 }
1023 }
1024 };
1025
1026 let mut height = 0;
1027
1028 if let Some(new_buffer_id) = new_buffer_id {
1029 let first_excerpt = excerpt_boundary.next.clone();
1030 if self.buffers_with_disabled_headers.contains(&new_buffer_id) {
1031 continue;
1032 }
1033 if self.folded_buffers.contains(&new_buffer_id) && buffer.show_headers() {
1034 let mut last_excerpt_end_row = first_excerpt.end_row;
1035
1036 while let Some(next_boundary) = boundaries.peek() {
1037 if next_boundary.next.buffer_id == new_buffer_id {
1038 last_excerpt_end_row = next_boundary.next.end_row;
1039 } else {
1040 break;
1041 }
1042
1043 boundaries.next();
1044 }
1045 let wrap_end_row = wrap_row_for(
1046 Point::new(
1047 last_excerpt_end_row.0,
1048 buffer.line_len(last_excerpt_end_row),
1049 ),
1050 Bias::Right,
1051 );
1052
1053 return Some((
1054 BlockPlacement::Replace(wrap_row..=wrap_end_row),
1055 Block::FoldedBuffer {
1056 height: height + self.buffer_header_height,
1057 first_excerpt,
1058 },
1059 ));
1060 }
1061 }
1062
1063 let starts_new_buffer = new_buffer_id.is_some();
1064 let block = if starts_new_buffer && buffer.show_headers() {
1065 height += self.buffer_header_height;
1066 Block::BufferHeader {
1067 excerpt: excerpt_boundary.next,
1068 height,
1069 }
1070 } else if excerpt_boundary.prev.is_some() {
1071 height += self.excerpt_header_height;
1072 Block::ExcerptBoundary {
1073 excerpt: excerpt_boundary.next,
1074 height,
1075 }
1076 } else {
1077 continue;
1078 };
1079
1080 return Some((BlockPlacement::Above(wrap_row), block));
1081 }
1082 })
1083 }
1084
1085 fn spacer_blocks(
1086 &self,
1087 bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
1088 wrap_snapshot: &WrapSnapshot,
1089 companion_snapshot: &WrapSnapshot,
1090 companion: &Companion,
1091 display_map_id: EntityId,
1092 ) -> Vec<(BlockPlacement<WrapRow>, Block)> {
1093 let our_buffer = wrap_snapshot.buffer_snapshot();
1094 let companion_buffer = companion_snapshot.buffer_snapshot();
1095
1096 let patches = companion.convert_rows_to_companion(
1097 display_map_id,
1098 companion_buffer,
1099 our_buffer,
1100 bounds,
1101 );
1102
1103 let mut our_inlay_point_cursor = wrap_snapshot.inlay_point_cursor();
1104 let mut our_fold_point_cursor = wrap_snapshot.fold_point_cursor();
1105 let mut our_tab_point_cursor = wrap_snapshot.tab_point_cursor();
1106 let mut our_wrap_point_cursor = wrap_snapshot.wrap_point_cursor();
1107
1108 let mut companion_inlay_point_cursor = companion_snapshot.inlay_point_cursor();
1109 let mut companion_fold_point_cursor = companion_snapshot.fold_point_cursor();
1110 let mut companion_tab_point_cursor = companion_snapshot.tab_point_cursor();
1111 let mut companion_wrap_point_cursor = companion_snapshot.wrap_point_cursor();
1112
1113 let mut our_wrapper = |our_point: Point| {
1114 our_wrap_point_cursor
1115 .map(our_tab_point_cursor.map(
1116 our_fold_point_cursor.map(our_inlay_point_cursor.map(our_point), Bias::Left),
1117 ))
1118 .row()
1119 };
1120 let mut companion_wrapper = |their_point: Point| {
1121 companion_wrap_point_cursor
1122 .map(
1123 companion_tab_point_cursor.map(
1124 companion_fold_point_cursor
1125 .map(companion_inlay_point_cursor.map(their_point), Bias::Left),
1126 ),
1127 )
1128 .row()
1129 };
1130 fn determine_spacer(
1131 our_wrapper: &mut impl FnMut(Point) -> WrapRow,
1132 companion_wrapper: &mut impl FnMut(Point) -> WrapRow,
1133 our_point: Point,
1134 their_point: Point,
1135 delta: i32,
1136 ) -> (i32, Option<(WrapRow, u32)>) {
1137 let our_wrap = our_wrapper(our_point);
1138 let companion_wrap = companion_wrapper(their_point);
1139 let new_delta = companion_wrap.0 as i32 - our_wrap.0 as i32;
1140
1141 let spacer = if new_delta > delta {
1142 let height = (new_delta - delta) as u32;
1143 Some((our_wrap, height))
1144 } else {
1145 None
1146 };
1147 (new_delta, spacer)
1148 }
1149
1150 let mut result = Vec::new();
1151
1152 for excerpt in patches {
1153 let mut source_points = (excerpt.edited_range.start.row..=excerpt.edited_range.end.row)
1154 .map(|row| MultiBufferPoint::new(row, 0))
1155 .chain(if excerpt.edited_range.end.column > 0 {
1156 Some(excerpt.edited_range.end)
1157 } else {
1158 None
1159 })
1160 .peekable();
1161 let last_source_point = if excerpt.edited_range.end.column > 0 {
1162 excerpt.edited_range.end
1163 } else {
1164 MultiBufferPoint::new(excerpt.edited_range.end.row, 0)
1165 };
1166
1167 let Some(first_point) = source_points.peek().copied() else {
1168 continue;
1169 };
1170 let edit_for_first_point = excerpt.patch.edit_for_old_position(first_point);
1171
1172 // Because we calculate spacers based on differences in wrap row
1173 // counts between the RHS and LHS for corresponding buffer points,
1174 // we need to calibrate our expectations based on the difference
1175 // in counts before the start of the edit. This difference in
1176 // counts should have been balanced already by spacers above this
1177 // edit, so we only need to insert spacers for when the difference
1178 // in counts diverges from that baseline value.
1179 let (our_baseline, their_baseline) = if edit_for_first_point.old.start < first_point {
1180 // Case 1: We are inside a hunk/group--take the start of the hunk/group on both sides as the baseline.
1181 (
1182 edit_for_first_point.old.start,
1183 edit_for_first_point.new.start,
1184 )
1185 } else if first_point.row > excerpt.source_excerpt_range.start.row {
1186 // Case 2: We are not inside a hunk/group--go back by one row to find the baseline.
1187 let prev_point = Point::new(first_point.row - 1, 0);
1188 let edit_for_prev_point = excerpt.patch.edit_for_old_position(prev_point);
1189 (prev_point, edit_for_prev_point.new.end)
1190 } else {
1191 // Case 3: We are at the start of the excerpt--no previous row to use as the baseline.
1192 (first_point, edit_for_first_point.new.start)
1193 };
1194 let our_baseline = our_wrapper(our_baseline);
1195 let their_baseline = companion_wrapper(their_baseline);
1196
1197 let mut delta = their_baseline.0 as i32 - our_baseline.0 as i32;
1198
1199 // If we started out in the middle of a hunk/group, work up to the end of that group to set up the main loop below.
1200 if edit_for_first_point.old.start < first_point {
1201 let mut current_boundary = first_point;
1202 let current_range = edit_for_first_point.new;
1203 while let Some(next_point) = source_points.peek().cloned() {
1204 let edit_for_next_point = excerpt.patch.edit_for_old_position(next_point);
1205 if edit_for_next_point.new.end > current_range.end {
1206 break;
1207 }
1208 source_points.next();
1209 current_boundary = next_point;
1210 }
1211
1212 let (new_delta, spacer) = determine_spacer(
1213 &mut our_wrapper,
1214 &mut companion_wrapper,
1215 current_boundary,
1216 current_range.end,
1217 delta,
1218 );
1219
1220 delta = new_delta;
1221 if let Some((wrap_row, height)) = spacer {
1222 result.push((
1223 BlockPlacement::Above(wrap_row),
1224 Block::Spacer {
1225 id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1226 height,
1227 is_below: false,
1228 },
1229 ));
1230 }
1231 }
1232
1233 // Main loop: process one hunk/group at a time, possibly inserting spacers before and after.
1234 while let Some(source_point) = source_points.next() {
1235 let mut current_boundary = source_point;
1236 let current_range = excerpt.patch.edit_for_old_position(current_boundary).new;
1237
1238 // This can only occur at the end of an excerpt.
1239 if current_boundary.column > 0 {
1240 debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end);
1241 break;
1242 }
1243
1244 // Align the two sides at the start of this group.
1245 let (delta_at_start, mut spacer_at_start) = determine_spacer(
1246 &mut our_wrapper,
1247 &mut companion_wrapper,
1248 current_boundary,
1249 current_range.start,
1250 delta,
1251 );
1252 delta = delta_at_start;
1253
1254 while let Some(next_point) = source_points.peek().copied() {
1255 let edit_for_next_point = excerpt.patch.edit_for_old_position(next_point);
1256 if edit_for_next_point.new.end > current_range.end {
1257 break;
1258 }
1259
1260 if let Some((wrap_row, height)) = spacer_at_start.take() {
1261 result.push((
1262 BlockPlacement::Above(wrap_row),
1263 Block::Spacer {
1264 id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1265 height,
1266 is_below: false,
1267 },
1268 ));
1269 }
1270
1271 current_boundary = next_point;
1272 source_points.next();
1273 }
1274
1275 // This can only occur at the end of an excerpt.
1276 if current_boundary.column > 0 {
1277 debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end);
1278 break;
1279 }
1280
1281 let (delta_at_end, spacer_at_end) = determine_spacer(
1282 &mut our_wrapper,
1283 &mut companion_wrapper,
1284 current_boundary,
1285 current_range.end,
1286 delta,
1287 );
1288 delta = delta_at_end;
1289
1290 if let Some((wrap_row, mut height)) = spacer_at_start {
1291 if let Some((_, additional_height)) = spacer_at_end {
1292 height += additional_height;
1293 }
1294 result.push((
1295 BlockPlacement::Above(wrap_row),
1296 Block::Spacer {
1297 id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1298 height,
1299 is_below: false,
1300 },
1301 ));
1302 } else if let Some((wrap_row, height)) = spacer_at_end {
1303 result.push((
1304 BlockPlacement::Above(wrap_row),
1305 Block::Spacer {
1306 id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1307 height,
1308 is_below: false,
1309 },
1310 ));
1311 }
1312 }
1313
1314 if last_source_point == excerpt.source_excerpt_range.end {
1315 let (_new_delta, spacer) = determine_spacer(
1316 &mut our_wrapper,
1317 &mut companion_wrapper,
1318 last_source_point,
1319 excerpt.target_excerpt_range.end,
1320 delta,
1321 );
1322 if let Some((wrap_row, height)) = spacer {
1323 result.push((
1324 BlockPlacement::Below(wrap_row),
1325 Block::Spacer {
1326 id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1327 height,
1328 is_below: true,
1329 },
1330 ));
1331 }
1332 }
1333 }
1334
1335 result
1336 }
1337
1338 #[ztracing::instrument(skip_all)]
1339 fn sort_blocks(blocks: &mut Vec<(BlockPlacement<WrapRow>, Block)>) {
1340 blocks.sort_unstable_by(|(placement_a, block_a), (placement_b, block_b)| {
1341 placement_a
1342 .start()
1343 .cmp(placement_b.start())
1344 .then_with(|| placement_b.end().cmp(placement_a.end()))
1345 .then_with(|| placement_a.tie_break().cmp(&placement_b.tie_break()))
1346 .then_with(|| {
1347 if block_a.is_header() {
1348 Ordering::Less
1349 } else if block_b.is_header() {
1350 Ordering::Greater
1351 } else {
1352 Ordering::Equal
1353 }
1354 })
1355 .then_with(|| match (block_a, block_b) {
1356 (
1357 Block::ExcerptBoundary {
1358 excerpt: excerpt_a, ..
1359 }
1360 | Block::BufferHeader {
1361 excerpt: excerpt_a, ..
1362 },
1363 Block::ExcerptBoundary {
1364 excerpt: excerpt_b, ..
1365 }
1366 | Block::BufferHeader {
1367 excerpt: excerpt_b, ..
1368 },
1369 ) => Some(excerpt_a.id).cmp(&Some(excerpt_b.id)),
1370 (
1371 Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
1372 Block::Spacer { .. } | Block::Custom(_),
1373 ) => Ordering::Less,
1374 (
1375 Block::Spacer { .. } | Block::Custom(_),
1376 Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
1377 ) => Ordering::Greater,
1378 (Block::Spacer { .. }, Block::Custom(_)) => Ordering::Less,
1379 (Block::Custom(_), Block::Spacer { .. }) => Ordering::Greater,
1380 (Block::Custom(block_a), Block::Custom(block_b)) => block_a
1381 .priority
1382 .cmp(&block_b.priority)
1383 .then_with(|| block_a.id.cmp(&block_b.id)),
1384 _ => {
1385 unreachable!("comparing blocks: {block_a:?} vs {block_b:?}")
1386 }
1387 })
1388 });
1389 blocks.dedup_by(|right, left| match (left.0.clone(), right.0.clone()) {
1390 (BlockPlacement::Replace(range), BlockPlacement::Above(row))
1391 | (BlockPlacement::Replace(range), BlockPlacement::Below(row)) => range.contains(&row),
1392 (BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => {
1393 if range_a.end() >= range_b.start() && range_a.start() <= range_b.end() {
1394 left.0 = BlockPlacement::Replace(
1395 *range_a.start()..=*range_a.end().max(range_b.end()),
1396 );
1397 true
1398 } else {
1399 false
1400 }
1401 }
1402 _ => false,
1403 });
1404 }
1405}
1406
1407#[ztracing::instrument(skip(tree, wrap_snapshot))]
1408fn push_isomorphic(tree: &mut SumTree<Transform>, rows: RowDelta, wrap_snapshot: &WrapSnapshot) {
1409 if rows == RowDelta(0) {
1410 return;
1411 }
1412
1413 let wrap_row_start = tree.summary().input_rows;
1414 let wrap_row_end = wrap_row_start + rows;
1415 let wrap_summary = wrap_snapshot.text_summary_for_range(wrap_row_start..wrap_row_end);
1416 let summary = TransformSummary {
1417 input_rows: WrapRow(rows.0),
1418 output_rows: BlockRow(rows.0),
1419 longest_row: BlockRow(wrap_summary.longest_row),
1420 longest_row_chars: wrap_summary.longest_row_chars,
1421 };
1422 let mut merged = false;
1423 tree.update_last(
1424 |last_transform| {
1425 if last_transform.block.is_none() {
1426 last_transform.summary.add_summary(&summary);
1427 merged = true;
1428 }
1429 },
1430 (),
1431 );
1432 if !merged {
1433 tree.push(
1434 Transform {
1435 summary,
1436 block: None,
1437 },
1438 (),
1439 );
1440 }
1441}
1442
1443impl BlockPoint {
1444 pub fn new(row: BlockRow, column: u32) -> Self {
1445 Self(Point::new(row.0, column))
1446 }
1447}
1448
1449impl Deref for BlockPoint {
1450 type Target = Point;
1451
1452 fn deref(&self) -> &Self::Target {
1453 &self.0
1454 }
1455}
1456
1457impl std::ops::DerefMut for BlockPoint {
1458 fn deref_mut(&mut self) -> &mut Self::Target {
1459 &mut self.0
1460 }
1461}
1462
1463impl Deref for BlockMapReader<'_> {
1464 type Target = BlockSnapshot;
1465
1466 fn deref(&self) -> &Self::Target {
1467 &self.snapshot
1468 }
1469}
1470
1471impl DerefMut for BlockMapReader<'_> {
1472 fn deref_mut(&mut self) -> &mut Self::Target {
1473 &mut self.snapshot
1474 }
1475}
1476
1477impl BlockMapReader<'_> {
1478 #[ztracing::instrument(skip_all)]
1479 pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
1480 let block = self.blocks.iter().find(|block| block.id == block_id)?;
1481 let buffer_row = block
1482 .start()
1483 .to_point(self.wrap_snapshot.buffer_snapshot())
1484 .row;
1485 let wrap_row = self
1486 .wrap_snapshot
1487 .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
1488 .row();
1489 let start_wrap_row = self
1490 .wrap_snapshot
1491 .prev_row_boundary(WrapPoint::new(wrap_row, 0));
1492 let end_wrap_row = self
1493 .wrap_snapshot
1494 .next_row_boundary(WrapPoint::new(wrap_row, 0))
1495 .unwrap_or(self.wrap_snapshot.max_point().row() + WrapRow(1));
1496
1497 let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(());
1498 cursor.seek(&start_wrap_row, Bias::Left);
1499 while let Some(transform) = cursor.item() {
1500 if cursor.start().0 > end_wrap_row {
1501 break;
1502 }
1503
1504 if let Some(BlockId::Custom(id)) = transform.block.as_ref().map(|block| block.id())
1505 && id == block_id
1506 {
1507 return Some(cursor.start().1);
1508 }
1509 cursor.next();
1510 }
1511
1512 None
1513 }
1514}
1515
1516impl BlockMapWriter<'_> {
1517 #[ztracing::instrument(skip_all)]
1518 pub fn insert(
1519 &mut self,
1520 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
1521 ) -> Vec<CustomBlockId> {
1522 let blocks = blocks.into_iter();
1523 let mut ids = Vec::with_capacity(blocks.size_hint().1.unwrap_or(0));
1524 let mut edits = Patch::default();
1525 let wrap_snapshot = &*self.block_map.wrap_snapshot.borrow();
1526 let buffer = wrap_snapshot.buffer_snapshot();
1527
1528 let mut previous_wrap_row_range: Option<Range<WrapRow>> = None;
1529 for block in blocks {
1530 if let BlockPlacement::Replace(_) = &block.placement {
1531 debug_assert!(block.height.unwrap() > 0);
1532 }
1533
1534 let id = CustomBlockId(self.block_map.next_block_id.fetch_add(1, SeqCst));
1535 ids.push(id);
1536
1537 let start = block.placement.start().to_point(buffer);
1538 let end = block.placement.end().to_point(buffer);
1539 let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1540 let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1541
1542 let (start_row, end_row) = {
1543 previous_wrap_row_range.take_if(|range| {
1544 !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1545 });
1546 let range = previous_wrap_row_range.get_or_insert_with(|| {
1547 let start_row =
1548 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1549 let end_row = wrap_snapshot
1550 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1551 .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1552 start_row..end_row
1553 });
1554 (range.start, range.end)
1555 };
1556 let block_ix = match self
1557 .block_map
1558 .custom_blocks
1559 .binary_search_by(|probe| probe.placement.cmp(&block.placement, buffer))
1560 {
1561 Ok(ix) | Err(ix) => ix,
1562 };
1563 let new_block = Arc::new(CustomBlock {
1564 id,
1565 placement: block.placement,
1566 height: block.height,
1567 render: Arc::new(Mutex::new(block.render)),
1568 style: block.style,
1569 priority: block.priority,
1570 });
1571 self.block_map
1572 .custom_blocks
1573 .insert(block_ix, new_block.clone());
1574 self.block_map.custom_blocks_by_id.insert(id, new_block);
1575
1576 edits = edits.compose([Edit {
1577 old: start_row..end_row,
1578 new: start_row..end_row,
1579 }]);
1580 }
1581
1582 let default_patch = Patch::default();
1583 self.block_map.sync(
1584 wrap_snapshot,
1585 edits,
1586 self.companion.as_deref().map(
1587 |&CompanionView {
1588 entity_id,
1589 wrap_snapshot,
1590 companion,
1591 ..
1592 }| {
1593 CompanionView::new(entity_id, wrap_snapshot, &default_patch, companion)
1594 },
1595 ),
1596 );
1597 ids
1598 }
1599
1600 #[ztracing::instrument(skip_all)]
1601 pub fn resize(&mut self, mut heights: HashMap<CustomBlockId, u32>) {
1602 let wrap_snapshot = &*self.block_map.wrap_snapshot.borrow();
1603 let buffer = wrap_snapshot.buffer_snapshot();
1604 let mut edits = Patch::default();
1605 let mut last_block_buffer_row = None;
1606
1607 for block in &mut self.block_map.custom_blocks {
1608 if let Some(new_height) = heights.remove(&block.id) {
1609 if let BlockPlacement::Replace(_) = &block.placement {
1610 debug_assert!(new_height > 0);
1611 }
1612
1613 if block.height != Some(new_height) {
1614 let new_block = CustomBlock {
1615 id: block.id,
1616 placement: block.placement.clone(),
1617 height: Some(new_height),
1618 style: block.style,
1619 render: block.render.clone(),
1620 priority: block.priority,
1621 };
1622 let new_block = Arc::new(new_block);
1623 *block = new_block.clone();
1624 self.block_map
1625 .custom_blocks_by_id
1626 .insert(block.id, new_block);
1627
1628 let start_row = block.placement.start().to_point(buffer).row;
1629 let end_row = block.placement.end().to_point(buffer).row;
1630 if last_block_buffer_row != Some(end_row) {
1631 last_block_buffer_row = Some(end_row);
1632 let start_wrap_row = wrap_snapshot
1633 .make_wrap_point(Point::new(start_row, 0), Bias::Left)
1634 .row();
1635 let end_wrap_row = wrap_snapshot
1636 .make_wrap_point(Point::new(end_row, 0), Bias::Left)
1637 .row();
1638 let start =
1639 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1640 let end = wrap_snapshot
1641 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1642 .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1643 edits.push(Edit {
1644 old: start..end,
1645 new: start..end,
1646 })
1647 }
1648 }
1649 }
1650 }
1651
1652 let default_patch = Patch::default();
1653 self.block_map.sync(
1654 wrap_snapshot,
1655 edits,
1656 self.companion.as_deref().map(
1657 |&CompanionView {
1658 entity_id,
1659 wrap_snapshot,
1660 companion,
1661 ..
1662 }| {
1663 CompanionView::new(entity_id, wrap_snapshot, &default_patch, companion)
1664 },
1665 ),
1666 );
1667 }
1668
1669 #[ztracing::instrument(skip_all)]
1670 pub fn remove(&mut self, block_ids: HashSet<CustomBlockId>) {
1671 let wrap_snapshot = &*self.block_map.wrap_snapshot.borrow();
1672 let buffer = wrap_snapshot.buffer_snapshot();
1673 let mut edits = Patch::default();
1674 let mut last_block_buffer_row = None;
1675 let mut previous_wrap_row_range: Option<Range<WrapRow>> = None;
1676 self.block_map.custom_blocks.retain(|block| {
1677 if block_ids.contains(&block.id) {
1678 let start = block.placement.start().to_point(buffer);
1679 let end = block.placement.end().to_point(buffer);
1680 if last_block_buffer_row != Some(end.row) {
1681 last_block_buffer_row = Some(end.row);
1682 let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1683 let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1684 let (start_row, end_row) = {
1685 previous_wrap_row_range.take_if(|range| {
1686 !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1687 });
1688 let range = previous_wrap_row_range.get_or_insert_with(|| {
1689 let start_row =
1690 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1691 let end_row = wrap_snapshot
1692 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1693 .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1694 start_row..end_row
1695 });
1696 (range.start, range.end)
1697 };
1698
1699 edits.push(Edit {
1700 old: start_row..end_row,
1701 new: start_row..end_row,
1702 })
1703 }
1704 false
1705 } else {
1706 true
1707 }
1708 });
1709 self.block_map
1710 .custom_blocks_by_id
1711 .retain(|id, _| !block_ids.contains(id));
1712 let default_patch = Patch::default();
1713 self.block_map.sync(
1714 wrap_snapshot,
1715 edits,
1716 self.companion.as_deref().map(
1717 |&CompanionView {
1718 entity_id,
1719 wrap_snapshot,
1720 companion,
1721 ..
1722 }| {
1723 CompanionView::new(entity_id, wrap_snapshot, &default_patch, companion)
1724 },
1725 ),
1726 );
1727 }
1728
1729 #[ztracing::instrument(skip_all)]
1730 pub fn remove_intersecting_replace_blocks(
1731 &mut self,
1732 ranges: impl IntoIterator<Item = Range<MultiBufferOffset>>,
1733 inclusive: bool,
1734 ) {
1735 let wrap_snapshot = self.block_map.wrap_snapshot.borrow();
1736 let mut blocks_to_remove = HashSet::default();
1737 for range in ranges {
1738 for block in self.blocks_intersecting_buffer_range(range, inclusive) {
1739 if matches!(block.placement, BlockPlacement::Replace(_)) {
1740 blocks_to_remove.insert(block.id);
1741 }
1742 }
1743 }
1744 drop(wrap_snapshot);
1745 self.remove(blocks_to_remove);
1746 }
1747
1748 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId) {
1749 self.block_map
1750 .buffers_with_disabled_headers
1751 .insert(buffer_id);
1752 }
1753
1754 #[ztracing::instrument(skip_all)]
1755 pub fn fold_buffers(
1756 &mut self,
1757 buffer_ids: impl IntoIterator<Item = BufferId>,
1758 multi_buffer: &MultiBuffer,
1759 cx: &App,
1760 ) {
1761 self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx);
1762 }
1763
1764 #[ztracing::instrument(skip_all)]
1765 pub fn unfold_buffers(
1766 &mut self,
1767 buffer_ids: impl IntoIterator<Item = BufferId>,
1768 multi_buffer: &MultiBuffer,
1769 cx: &App,
1770 ) {
1771 self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx);
1772 }
1773
1774 #[ztracing::instrument(skip_all)]
1775 fn fold_or_unfold_buffers(
1776 &mut self,
1777 fold: bool,
1778 buffer_ids: impl IntoIterator<Item = BufferId>,
1779 multi_buffer: &MultiBuffer,
1780 cx: &App,
1781 ) {
1782 let mut ranges = Vec::new();
1783 for buffer_id in buffer_ids {
1784 if fold {
1785 self.block_map.folded_buffers.insert(buffer_id);
1786 } else {
1787 self.block_map.folded_buffers.remove(&buffer_id);
1788 }
1789 ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
1790 }
1791 ranges.sort_unstable_by_key(|range| range.start);
1792
1793 let mut edits = Patch::default();
1794 let wrap_snapshot = self.block_map.wrap_snapshot.borrow().clone();
1795 for range in ranges {
1796 let last_edit_row = cmp::min(
1797 wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + WrapRow(1),
1798 wrap_snapshot.max_point().row(),
1799 ) + WrapRow(1);
1800 let range = wrap_snapshot.make_wrap_point(range.start, Bias::Left).row()..last_edit_row;
1801 edits.push(Edit {
1802 old: range.clone(),
1803 new: range,
1804 });
1805 }
1806
1807 let default_patch = Patch::default();
1808 self.block_map.sync(
1809 &wrap_snapshot,
1810 edits,
1811 self.companion.as_deref().map(
1812 |&CompanionView {
1813 entity_id,
1814 wrap_snapshot,
1815 companion,
1816 ..
1817 }| {
1818 CompanionView::new(entity_id, wrap_snapshot, &default_patch, companion)
1819 },
1820 ),
1821 );
1822 }
1823
1824 #[ztracing::instrument(skip_all)]
1825 fn blocks_intersecting_buffer_range(
1826 &self,
1827 range: Range<MultiBufferOffset>,
1828 inclusive: bool,
1829 ) -> &[Arc<CustomBlock>] {
1830 if range.is_empty() && !inclusive {
1831 return &[];
1832 }
1833 let wrap_snapshot = self.block_map.wrap_snapshot.borrow();
1834 let buffer = wrap_snapshot.buffer_snapshot();
1835
1836 let start_block_ix = match self.block_map.custom_blocks.binary_search_by(|block| {
1837 let block_end = block.end().to_offset(buffer);
1838 block_end.cmp(&range.start).then(Ordering::Greater)
1839 }) {
1840 Ok(ix) | Err(ix) => ix,
1841 };
1842 let end_block_ix =
1843 match self.block_map.custom_blocks[start_block_ix..].binary_search_by(|block| {
1844 let block_start = block.start().to_offset(buffer);
1845 block_start.cmp(&range.end).then(if inclusive {
1846 Ordering::Less
1847 } else {
1848 Ordering::Greater
1849 })
1850 }) {
1851 Ok(ix) | Err(ix) => ix,
1852 };
1853
1854 &self.block_map.custom_blocks[start_block_ix..][..end_block_ix]
1855 }
1856}
1857
1858impl BlockSnapshot {
1859 #[cfg(test)]
1860 #[ztracing::instrument(skip_all)]
1861 pub fn text(&self) -> String {
1862 self.chunks(
1863 BlockRow(0)..self.transforms.summary().output_rows,
1864 false,
1865 false,
1866 Highlights::default(),
1867 )
1868 .map(|chunk| chunk.text)
1869 .collect()
1870 }
1871
1872 #[ztracing::instrument(skip_all)]
1873 pub(crate) fn chunks<'a>(
1874 &'a self,
1875 rows: Range<BlockRow>,
1876 language_aware: bool,
1877 masked: bool,
1878 highlights: Highlights<'a>,
1879 ) -> BlockChunks<'a> {
1880 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
1881
1882 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1883 cursor.seek(&rows.start, Bias::Right);
1884 let transform_output_start = cursor.start().0;
1885 let transform_input_start = cursor.start().1;
1886
1887 let mut input_start = transform_input_start;
1888 let mut input_end = transform_input_start;
1889 if let Some(transform) = cursor.item()
1890 && transform.block.is_none()
1891 {
1892 input_start += rows.start - transform_output_start;
1893 input_end += cmp::min(
1894 rows.end - transform_output_start,
1895 RowDelta(transform.summary.input_rows.0),
1896 );
1897 }
1898
1899 BlockChunks {
1900 input_chunks: self.wrap_snapshot.chunks(
1901 input_start..input_end,
1902 language_aware,
1903 highlights,
1904 ),
1905 input_chunk: Default::default(),
1906 transforms: cursor,
1907 output_row: rows.start,
1908 line_count_overflow: RowDelta(0),
1909 max_output_row,
1910 masked,
1911 }
1912 }
1913
1914 #[ztracing::instrument(skip_all)]
1915 pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
1916 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1917 cursor.seek(&start_row, Bias::Right);
1918 let Dimensions(output_start, input_start, _) = cursor.start();
1919 let overshoot = if cursor
1920 .item()
1921 .is_some_and(|transform| transform.block.is_none())
1922 {
1923 start_row - *output_start
1924 } else {
1925 RowDelta(0)
1926 };
1927 let input_start_row = *input_start + overshoot;
1928 BlockRows {
1929 transforms: cursor,
1930 input_rows: self.wrap_snapshot.row_infos(input_start_row),
1931 output_row: start_row,
1932 started: false,
1933 }
1934 }
1935
1936 #[ztracing::instrument(skip_all)]
1937 pub fn blocks_in_range(
1938 &self,
1939 rows: Range<BlockRow>,
1940 ) -> impl Iterator<Item = (BlockRow, &Block)> {
1941 let mut cursor = self.transforms.cursor::<BlockRow>(());
1942 cursor.seek(&rows.start, Bias::Left);
1943 while *cursor.start() < rows.start && cursor.end() <= rows.start {
1944 cursor.next();
1945 }
1946
1947 std::iter::from_fn(move || {
1948 while let Some(transform) = cursor.item() {
1949 let start_row = *cursor.start();
1950 if start_row > rows.end
1951 || (start_row == rows.end
1952 && transform
1953 .block
1954 .as_ref()
1955 .is_some_and(|block| block.height() > 0))
1956 {
1957 break;
1958 }
1959 if let Some(block) = &transform.block {
1960 cursor.next();
1961 return Some((start_row, block));
1962 } else {
1963 cursor.next();
1964 }
1965 }
1966 None
1967 })
1968 }
1969
1970 #[ztracing::instrument(skip_all)]
1971 pub(crate) fn sticky_header_excerpt(&self, position: f64) -> Option<StickyHeaderExcerpt<'_>> {
1972 let top_row = position as u32;
1973 let mut cursor = self.transforms.cursor::<BlockRow>(());
1974 cursor.seek(&BlockRow(top_row), Bias::Right);
1975
1976 while let Some(transform) = cursor.item() {
1977 match &transform.block {
1978 Some(
1979 Block::ExcerptBoundary { excerpt, .. } | Block::BufferHeader { excerpt, .. },
1980 ) => {
1981 return Some(StickyHeaderExcerpt { excerpt });
1982 }
1983 Some(block) if block.is_buffer_header() => return None,
1984 _ => {
1985 cursor.prev();
1986 continue;
1987 }
1988 }
1989 }
1990
1991 None
1992 }
1993
1994 #[ztracing::instrument(skip_all)]
1995 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
1996 let buffer = self.wrap_snapshot.buffer_snapshot();
1997 let wrap_point = match block_id {
1998 BlockId::Custom(custom_block_id) => {
1999 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
2000 return Some(Block::Custom(custom_block.clone()));
2001 }
2002 BlockId::ExcerptBoundary(next_excerpt_id) => {
2003 let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
2004 self.wrap_snapshot
2005 .make_wrap_point(excerpt_range.start, Bias::Left)
2006 }
2007 BlockId::FoldedBuffer(excerpt_id) => self
2008 .wrap_snapshot
2009 .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
2010 BlockId::Spacer(_) => return None,
2011 };
2012 let wrap_row = wrap_point.row();
2013
2014 let mut cursor = self.transforms.cursor::<WrapRow>(());
2015 cursor.seek(&wrap_row, Bias::Left);
2016
2017 while let Some(transform) = cursor.item() {
2018 if let Some(block) = transform.block.as_ref() {
2019 if block.id() == block_id {
2020 return Some(block.clone());
2021 }
2022 } else if *cursor.start() > wrap_row {
2023 break;
2024 }
2025
2026 cursor.next();
2027 }
2028
2029 None
2030 }
2031
2032 #[ztracing::instrument(skip_all)]
2033 pub fn max_point(&self) -> BlockPoint {
2034 let row = self
2035 .transforms
2036 .summary()
2037 .output_rows
2038 .saturating_sub(RowDelta(1));
2039 BlockPoint::new(row, self.line_len(row))
2040 }
2041
2042 #[ztracing::instrument(skip_all)]
2043 pub fn longest_row(&self) -> BlockRow {
2044 self.transforms.summary().longest_row
2045 }
2046
2047 #[ztracing::instrument(skip_all)]
2048 pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
2049 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2050 cursor.seek(&range.start, Bias::Right);
2051
2052 let mut longest_row = range.start;
2053 let mut longest_row_chars = 0;
2054 if let Some(transform) = cursor.item() {
2055 if transform.block.is_none() {
2056 let &Dimensions(output_start, input_start, _) = cursor.start();
2057 let overshoot = range.start - output_start;
2058 let wrap_start_row = input_start + WrapRow(overshoot.0);
2059 let wrap_end_row = cmp::min(
2060 input_start + WrapRow((range.end - output_start).0),
2061 cursor.end().1,
2062 );
2063 let summary = self
2064 .wrap_snapshot
2065 .text_summary_for_range(wrap_start_row..wrap_end_row);
2066 longest_row = BlockRow(range.start.0 + summary.longest_row);
2067 longest_row_chars = summary.longest_row_chars;
2068 }
2069 cursor.next();
2070 }
2071
2072 let cursor_start_row = cursor.start().0;
2073 if range.end > cursor_start_row {
2074 let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right);
2075 if summary.longest_row_chars > longest_row_chars {
2076 longest_row = cursor_start_row + summary.longest_row;
2077 longest_row_chars = summary.longest_row_chars;
2078 }
2079
2080 if let Some(transform) = cursor.item()
2081 && transform.block.is_none()
2082 {
2083 let &Dimensions(output_start, input_start, _) = cursor.start();
2084 let overshoot = range.end - output_start;
2085 let wrap_start_row = input_start;
2086 let wrap_end_row = input_start + overshoot;
2087 let summary = self
2088 .wrap_snapshot
2089 .text_summary_for_range(wrap_start_row..wrap_end_row);
2090 if summary.longest_row_chars > longest_row_chars {
2091 longest_row = output_start + RowDelta(summary.longest_row);
2092 }
2093 }
2094 }
2095
2096 longest_row
2097 }
2098
2099 #[ztracing::instrument(skip_all)]
2100 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
2101 let (start, _, item) =
2102 self.transforms
2103 .find::<Dimensions<BlockRow, WrapRow>, _>((), &row, Bias::Right);
2104 if let Some(transform) = item {
2105 let Dimensions(output_start, input_start, _) = start;
2106 let overshoot = row - output_start;
2107 if transform.block.is_some() {
2108 0
2109 } else {
2110 self.wrap_snapshot.line_len(input_start + overshoot)
2111 }
2112 } else if row == BlockRow(0) {
2113 0
2114 } else {
2115 panic!("row out of range");
2116 }
2117 }
2118
2119 #[ztracing::instrument(skip_all)]
2120 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
2121 let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
2122 item.is_some_and(|t| t.block.is_some())
2123 }
2124
2125 #[ztracing::instrument(skip_all)]
2126 pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
2127 let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
2128 let Some(transform) = item else {
2129 return false;
2130 };
2131 matches!(transform.block, Some(Block::FoldedBuffer { .. }))
2132 }
2133
2134 #[ztracing::instrument(skip_all)]
2135 pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
2136 let wrap_point = self
2137 .wrap_snapshot
2138 .make_wrap_point(Point::new(row.0, 0), Bias::Left);
2139 let (_, _, item) = self
2140 .transforms
2141 .find::<WrapRow, _>((), &wrap_point.row(), Bias::Right);
2142 item.is_some_and(|transform| {
2143 transform
2144 .block
2145 .as_ref()
2146 .is_some_and(|block| block.is_replacement())
2147 })
2148 }
2149
2150 #[ztracing::instrument(skip_all)]
2151 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
2152 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2153 cursor.seek(&BlockRow(point.row), Bias::Right);
2154
2155 let max_input_row = self.transforms.summary().input_rows;
2156 let mut search_left = (bias == Bias::Left && cursor.start().1 > WrapRow(0))
2157 || cursor.end().1 == max_input_row;
2158 let mut reversed = false;
2159
2160 loop {
2161 if let Some(transform) = cursor.item() {
2162 let Dimensions(output_start_row, input_start_row, _) = cursor.start();
2163 let Dimensions(output_end_row, input_end_row, _) = cursor.end();
2164 let output_start = Point::new(output_start_row.0, 0);
2165 let input_start = Point::new(input_start_row.0, 0);
2166 let input_end = Point::new(input_end_row.0, 0);
2167
2168 match transform.block.as_ref() {
2169 Some(block) => {
2170 if block.is_replacement()
2171 && (((bias == Bias::Left || search_left) && output_start <= point.0)
2172 || (!search_left && output_start >= point.0))
2173 {
2174 return BlockPoint(output_start);
2175 }
2176 }
2177 None => {
2178 let input_point = if point.row >= output_end_row.0 {
2179 let line_len = self.wrap_snapshot.line_len(input_end_row - RowDelta(1));
2180 self.wrap_snapshot.clip_point(
2181 WrapPoint::new(input_end_row - RowDelta(1), line_len),
2182 bias,
2183 )
2184 } else {
2185 let output_overshoot = point.0.saturating_sub(output_start);
2186 self.wrap_snapshot
2187 .clip_point(WrapPoint(input_start + output_overshoot), bias)
2188 };
2189
2190 if (input_start..input_end).contains(&input_point.0) {
2191 let input_overshoot = input_point.0.saturating_sub(input_start);
2192 return BlockPoint(output_start + input_overshoot);
2193 }
2194 }
2195 }
2196
2197 if search_left {
2198 cursor.prev();
2199 } else {
2200 cursor.next();
2201 }
2202 } else if reversed {
2203 return self.max_point();
2204 } else {
2205 reversed = true;
2206 search_left = !search_left;
2207 cursor.seek(&BlockRow(point.row), Bias::Right);
2208 }
2209 }
2210 }
2211
2212 #[ztracing::instrument(skip_all)]
2213 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
2214 let (start, _, item) = self.transforms.find::<Dimensions<WrapRow, BlockRow>, _>(
2215 (),
2216 &wrap_point.row(),
2217 Bias::Right,
2218 );
2219 if let Some(transform) = item {
2220 if transform.block.is_some() {
2221 BlockPoint::new(start.1, 0)
2222 } else {
2223 let Dimensions(input_start_row, output_start_row, _) = start;
2224 let input_start = Point::new(input_start_row.0, 0);
2225 let output_start = Point::new(output_start_row.0, 0);
2226 let input_overshoot = wrap_point.0 - input_start;
2227 BlockPoint(output_start + input_overshoot)
2228 }
2229 } else {
2230 self.max_point()
2231 }
2232 }
2233
2234 #[ztracing::instrument(skip_all)]
2235 pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
2236 let (start, end, item) = self.transforms.find::<Dimensions<BlockRow, WrapRow>, _>(
2237 (),
2238 &BlockRow(block_point.row),
2239 Bias::Right,
2240 );
2241 if let Some(transform) = item {
2242 match transform.block.as_ref() {
2243 Some(block) => {
2244 if block.place_below() {
2245 let wrap_row = start.1 - RowDelta(1);
2246 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
2247 } else if block.place_above() {
2248 WrapPoint::new(start.1, 0)
2249 } else if bias == Bias::Left {
2250 WrapPoint::new(start.1, 0)
2251 } else {
2252 let wrap_row = end.1 - RowDelta(1);
2253 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
2254 }
2255 }
2256 None => {
2257 let overshoot = block_point.row() - start.0;
2258 let wrap_row = start.1 + RowDelta(overshoot.0);
2259 WrapPoint::new(wrap_row, block_point.column)
2260 }
2261 }
2262 } else {
2263 self.wrap_snapshot.max_point()
2264 }
2265 }
2266}
2267
2268impl BlockChunks<'_> {
2269 /// Go to the next transform
2270 #[ztracing::instrument(skip_all)]
2271 fn advance(&mut self) {
2272 self.input_chunk = Chunk::default();
2273 self.transforms.next();
2274 while let Some(transform) = self.transforms.item() {
2275 if transform
2276 .block
2277 .as_ref()
2278 .is_some_and(|block| block.height() == 0)
2279 {
2280 self.transforms.next();
2281 } else {
2282 break;
2283 }
2284 }
2285
2286 if self
2287 .transforms
2288 .item()
2289 .is_some_and(|transform| transform.block.is_none())
2290 {
2291 let start_input_row = self.transforms.start().1;
2292 let start_output_row = self.transforms.start().0;
2293 if start_output_row < self.max_output_row {
2294 let end_input_row = cmp::min(
2295 self.transforms.end().1,
2296 start_input_row + (self.max_output_row - start_output_row),
2297 );
2298 self.input_chunks.seek(start_input_row..end_input_row);
2299 }
2300 }
2301 }
2302}
2303
2304pub struct StickyHeaderExcerpt<'a> {
2305 pub excerpt: &'a ExcerptInfo,
2306}
2307
2308impl<'a> Iterator for BlockChunks<'a> {
2309 type Item = Chunk<'a>;
2310
2311 #[ztracing::instrument(skip_all)]
2312 fn next(&mut self) -> Option<Self::Item> {
2313 if self.output_row >= self.max_output_row {
2314 return None;
2315 }
2316
2317 if self.line_count_overflow > RowDelta(0) {
2318 let lines = self.line_count_overflow.0.min(u128::BITS);
2319 self.line_count_overflow.0 -= lines;
2320 self.output_row += RowDelta(lines);
2321 return Some(Chunk {
2322 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines as usize]) },
2323 chars: 1u128.unbounded_shl(lines).wrapping_sub(1),
2324 ..Default::default()
2325 });
2326 }
2327
2328 let transform = self.transforms.item()?;
2329 if transform.block.is_some() {
2330 let block_start = self.transforms.start().0;
2331 let mut block_end = self.transforms.end().0;
2332 self.advance();
2333 if self.transforms.item().is_none() {
2334 block_end -= RowDelta(1);
2335 }
2336
2337 let start_in_block = self.output_row - block_start;
2338 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
2339 let line_count = end_in_block - start_in_block;
2340 let lines = RowDelta(line_count.0.min(u128::BITS));
2341 self.line_count_overflow = line_count - lines;
2342 self.output_row += lines;
2343
2344 return Some(Chunk {
2345 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines.0 as usize]) },
2346 chars: 1u128.unbounded_shl(lines.0).wrapping_sub(1),
2347 ..Default::default()
2348 });
2349 }
2350
2351 if self.input_chunk.text.is_empty() {
2352 if let Some(input_chunk) = self.input_chunks.next() {
2353 self.input_chunk = input_chunk;
2354 } else {
2355 if self.output_row < self.max_output_row {
2356 self.output_row.0 += 1;
2357 self.advance();
2358 if self.transforms.item().is_some() {
2359 return Some(Chunk {
2360 text: "\n",
2361 chars: 1,
2362 ..Default::default()
2363 });
2364 }
2365 }
2366 return None;
2367 }
2368 }
2369
2370 let transform_end = self.transforms.end().0;
2371 let (prefix_rows, prefix_bytes) =
2372 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
2373 self.output_row += prefix_rows;
2374
2375 let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
2376 self.input_chunk.text = suffix;
2377 self.input_chunk.tabs >>= prefix_bytes.saturating_sub(1);
2378 self.input_chunk.chars >>= prefix_bytes.saturating_sub(1);
2379
2380 let mut tabs = self.input_chunk.tabs;
2381 let mut chars = self.input_chunk.chars;
2382
2383 if self.masked {
2384 // Not great for multibyte text because to keep cursor math correct we
2385 // need to have the same number of chars in the input as output.
2386 let chars_count = prefix.chars().count();
2387 let bullet_len = chars_count;
2388 prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
2389 chars = 1u128.unbounded_shl(bullet_len as u32).wrapping_sub(1);
2390 tabs = 0;
2391 }
2392
2393 let chunk = Chunk {
2394 text: prefix,
2395 tabs,
2396 chars,
2397 ..self.input_chunk.clone()
2398 };
2399
2400 if self.output_row == transform_end {
2401 self.advance();
2402 }
2403
2404 Some(chunk)
2405 }
2406}
2407
2408impl Iterator for BlockRows<'_> {
2409 type Item = RowInfo;
2410
2411 #[ztracing::instrument(skip_all)]
2412 fn next(&mut self) -> Option<Self::Item> {
2413 if self.started {
2414 self.output_row.0 += 1;
2415 } else {
2416 self.started = true;
2417 }
2418
2419 if self.output_row >= self.transforms.end().0 {
2420 self.transforms.next();
2421 while let Some(transform) = self.transforms.item() {
2422 if transform
2423 .block
2424 .as_ref()
2425 .is_some_and(|block| block.height() == 0)
2426 {
2427 self.transforms.next();
2428 } else {
2429 break;
2430 }
2431 }
2432
2433 let transform = self.transforms.item()?;
2434 if transform
2435 .block
2436 .as_ref()
2437 .is_none_or(|block| block.is_replacement())
2438 {
2439 self.input_rows.seek(self.transforms.start().1);
2440 }
2441 }
2442
2443 let transform = self.transforms.item()?;
2444 if transform.block.as_ref().is_none_or(|block| {
2445 block.is_replacement()
2446 && self.transforms.start().0 == self.output_row
2447 && matches!(block, Block::FoldedBuffer { .. }).not()
2448 }) {
2449 self.input_rows.next()
2450 } else {
2451 Some(RowInfo::default())
2452 }
2453 }
2454}
2455
2456impl sum_tree::Item for Transform {
2457 type Summary = TransformSummary;
2458
2459 fn summary(&self, _cx: ()) -> Self::Summary {
2460 self.summary.clone()
2461 }
2462}
2463
2464impl sum_tree::ContextLessSummary for TransformSummary {
2465 fn zero() -> Self {
2466 Default::default()
2467 }
2468
2469 fn add_summary(&mut self, summary: &Self) {
2470 if summary.longest_row_chars > self.longest_row_chars {
2471 self.longest_row = self.output_rows + summary.longest_row;
2472 self.longest_row_chars = summary.longest_row_chars;
2473 }
2474 self.input_rows += summary.input_rows;
2475 self.output_rows += summary.output_rows;
2476 }
2477}
2478
2479impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
2480 fn zero(_cx: ()) -> Self {
2481 Default::default()
2482 }
2483
2484 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
2485 *self += summary.input_rows;
2486 }
2487}
2488
2489impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
2490 fn zero(_cx: ()) -> Self {
2491 Default::default()
2492 }
2493
2494 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
2495 *self += summary.output_rows;
2496 }
2497}
2498
2499impl Deref for BlockContext<'_, '_> {
2500 type Target = App;
2501
2502 fn deref(&self) -> &Self::Target {
2503 self.app
2504 }
2505}
2506
2507impl DerefMut for BlockContext<'_, '_> {
2508 fn deref_mut(&mut self) -> &mut Self::Target {
2509 self.app
2510 }
2511}
2512
2513impl CustomBlock {
2514 #[ztracing::instrument(skip_all)]
2515 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
2516 self.render.lock()(cx)
2517 }
2518
2519 #[ztracing::instrument(skip_all)]
2520 pub fn start(&self) -> Anchor {
2521 *self.placement.start()
2522 }
2523
2524 #[ztracing::instrument(skip_all)]
2525 pub fn end(&self) -> Anchor {
2526 *self.placement.end()
2527 }
2528
2529 pub fn style(&self) -> BlockStyle {
2530 self.style
2531 }
2532}
2533
2534impl Debug for CustomBlock {
2535 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2536 f.debug_struct("Block")
2537 .field("id", &self.id)
2538 .field("placement", &self.placement)
2539 .field("height", &self.height)
2540 .field("style", &self.style)
2541 .field("priority", &self.priority)
2542 .finish_non_exhaustive()
2543 }
2544}
2545
2546// Count the number of bytes prior to a target point. If the string doesn't contain the target
2547// point, return its total extent. Otherwise return the target point itself.
2548fn offset_for_row(s: &str, target: RowDelta) -> (RowDelta, usize) {
2549 let mut row = 0;
2550 let mut offset = 0;
2551 for (ix, line) in s.split('\n').enumerate() {
2552 if ix > 0 {
2553 row += 1;
2554 offset += 1;
2555 }
2556 if row >= target.0 {
2557 break;
2558 }
2559 offset += line.len();
2560 }
2561 (RowDelta(row), offset)
2562}
2563
2564#[cfg(test)]
2565mod tests {
2566 use super::*;
2567 use crate::{
2568 display_map::{
2569 Companion, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
2570 },
2571 split::{convert_lhs_rows_to_rhs, convert_rhs_rows_to_lhs},
2572 test::test_font,
2573 };
2574 use buffer_diff::BufferDiff;
2575 use gpui::{App, AppContext as _, Element, div, font, px};
2576 use itertools::Itertools;
2577 use language::{Buffer, Capability};
2578 use multi_buffer::{ExcerptRange, MultiBuffer};
2579 use rand::prelude::*;
2580 use settings::SettingsStore;
2581 use std::env;
2582 use util::RandomCharIter;
2583
2584 #[gpui::test]
2585 fn test_offset_for_row() {
2586 assert_eq!(offset_for_row("", RowDelta(0)), (RowDelta(0), 0));
2587 assert_eq!(offset_for_row("", RowDelta(1)), (RowDelta(0), 0));
2588 assert_eq!(offset_for_row("abcd", RowDelta(0)), (RowDelta(0), 0));
2589 assert_eq!(offset_for_row("abcd", RowDelta(1)), (RowDelta(0), 4));
2590 assert_eq!(offset_for_row("\n", RowDelta(0)), (RowDelta(0), 0));
2591 assert_eq!(offset_for_row("\n", RowDelta(1)), (RowDelta(1), 1));
2592 assert_eq!(
2593 offset_for_row("abc\ndef\nghi", RowDelta(0)),
2594 (RowDelta(0), 0)
2595 );
2596 assert_eq!(
2597 offset_for_row("abc\ndef\nghi", RowDelta(1)),
2598 (RowDelta(1), 4)
2599 );
2600 assert_eq!(
2601 offset_for_row("abc\ndef\nghi", RowDelta(2)),
2602 (RowDelta(2), 8)
2603 );
2604 assert_eq!(
2605 offset_for_row("abc\ndef\nghi", RowDelta(3)),
2606 (RowDelta(2), 11)
2607 );
2608 }
2609
2610 #[gpui::test]
2611 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
2612 cx.update(init_test);
2613
2614 let text = "aaa\nbbb\nccc\nddd";
2615
2616 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2617 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2618 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2619 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2620 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2621 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2622 let (wrap_map, wraps_snapshot) =
2623 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2624 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2625
2626 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
2627 let block_ids = writer.insert(vec![
2628 BlockProperties {
2629 style: BlockStyle::Fixed,
2630 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2631 height: Some(1),
2632 render: Arc::new(|_| div().into_any()),
2633 priority: 0,
2634 },
2635 BlockProperties {
2636 style: BlockStyle::Fixed,
2637 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2638 height: Some(2),
2639 render: Arc::new(|_| div().into_any()),
2640 priority: 0,
2641 },
2642 BlockProperties {
2643 style: BlockStyle::Fixed,
2644 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2645 height: Some(3),
2646 render: Arc::new(|_| div().into_any()),
2647 priority: 0,
2648 },
2649 ]);
2650
2651 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
2652 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2653
2654 let blocks = snapshot
2655 .blocks_in_range(BlockRow(0)..BlockRow(8))
2656 .map(|(start_row, block)| {
2657 let block = block.as_custom().unwrap();
2658 (start_row.0..start_row.0 + block.height.unwrap(), block.id)
2659 })
2660 .collect::<Vec<_>>();
2661
2662 // When multiple blocks are on the same line, the newer blocks appear first.
2663 assert_eq!(
2664 blocks,
2665 &[
2666 (1..2, block_ids[0]),
2667 (2..4, block_ids[1]),
2668 (7..10, block_ids[2]),
2669 ]
2670 );
2671
2672 assert_eq!(
2673 snapshot.to_block_point(WrapPoint::new(WrapRow(0), 3)),
2674 BlockPoint::new(BlockRow(0), 3)
2675 );
2676 assert_eq!(
2677 snapshot.to_block_point(WrapPoint::new(WrapRow(1), 0)),
2678 BlockPoint::new(BlockRow(4), 0)
2679 );
2680 assert_eq!(
2681 snapshot.to_block_point(WrapPoint::new(WrapRow(3), 3)),
2682 BlockPoint::new(BlockRow(6), 3)
2683 );
2684
2685 assert_eq!(
2686 snapshot.to_wrap_point(BlockPoint::new(BlockRow(0), 3), Bias::Left),
2687 WrapPoint::new(WrapRow(0), 3)
2688 );
2689 assert_eq!(
2690 snapshot.to_wrap_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2691 WrapPoint::new(WrapRow(1), 0)
2692 );
2693 assert_eq!(
2694 snapshot.to_wrap_point(BlockPoint::new(BlockRow(3), 0), Bias::Left),
2695 WrapPoint::new(WrapRow(1), 0)
2696 );
2697 assert_eq!(
2698 snapshot.to_wrap_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
2699 WrapPoint::new(WrapRow(3), 3)
2700 );
2701
2702 assert_eq!(
2703 snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2704 BlockPoint::new(BlockRow(0), 3)
2705 );
2706 assert_eq!(
2707 snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Right),
2708 BlockPoint::new(BlockRow(4), 0)
2709 );
2710 assert_eq!(
2711 snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Left),
2712 BlockPoint::new(BlockRow(0), 3)
2713 );
2714 assert_eq!(
2715 snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Right),
2716 BlockPoint::new(BlockRow(4), 0)
2717 );
2718 assert_eq!(
2719 snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Left),
2720 BlockPoint::new(BlockRow(4), 0)
2721 );
2722 assert_eq!(
2723 snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Right),
2724 BlockPoint::new(BlockRow(4), 0)
2725 );
2726 assert_eq!(
2727 snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Left),
2728 BlockPoint::new(BlockRow(6), 3)
2729 );
2730 assert_eq!(
2731 snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Right),
2732 BlockPoint::new(BlockRow(6), 3)
2733 );
2734 assert_eq!(
2735 snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
2736 BlockPoint::new(BlockRow(6), 3)
2737 );
2738 assert_eq!(
2739 snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Right),
2740 BlockPoint::new(BlockRow(6), 3)
2741 );
2742
2743 assert_eq!(
2744 snapshot
2745 .row_infos(BlockRow(0))
2746 .map(|row_info| row_info.buffer_row)
2747 .collect::<Vec<_>>(),
2748 &[
2749 Some(0),
2750 None,
2751 None,
2752 None,
2753 Some(1),
2754 Some(2),
2755 Some(3),
2756 None,
2757 None,
2758 None
2759 ]
2760 );
2761
2762 // Insert a line break, separating two block decorations into separate lines.
2763 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2764 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
2765 buffer.snapshot(cx)
2766 });
2767
2768 let (inlay_snapshot, inlay_edits) =
2769 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
2770 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2771 let (tab_snapshot, tab_edits) =
2772 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
2773 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2774 wrap_map.sync(tab_snapshot, tab_edits, cx)
2775 });
2776 let snapshot = block_map.read(wraps_snapshot, wrap_edits, None);
2777 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
2778 }
2779
2780 #[gpui::test]
2781 fn test_multibuffer_headers_and_footers(cx: &mut App) {
2782 init_test(cx);
2783
2784 let buffer1 = cx.new(|cx| Buffer::local("Buffer 1", cx));
2785 let buffer2 = cx.new(|cx| Buffer::local("Buffer 2", cx));
2786 let buffer3 = cx.new(|cx| Buffer::local("Buffer 3", cx));
2787
2788 let mut excerpt_ids = Vec::new();
2789 let multi_buffer = cx.new(|cx| {
2790 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
2791 excerpt_ids.extend(multi_buffer.push_excerpts(
2792 buffer1.clone(),
2793 [ExcerptRange::new(0..buffer1.read(cx).len())],
2794 cx,
2795 ));
2796 excerpt_ids.extend(multi_buffer.push_excerpts(
2797 buffer2.clone(),
2798 [ExcerptRange::new(0..buffer2.read(cx).len())],
2799 cx,
2800 ));
2801 excerpt_ids.extend(multi_buffer.push_excerpts(
2802 buffer3.clone(),
2803 [ExcerptRange::new(0..buffer3.read(cx).len())],
2804 cx,
2805 ));
2806
2807 multi_buffer
2808 });
2809
2810 let font = test_font();
2811 let font_size = px(14.);
2812 let font_id = cx.text_system().resolve_font(&font);
2813 let mut wrap_width = px(0.);
2814 for c in "Buff".chars() {
2815 wrap_width += cx
2816 .text_system()
2817 .advance(font_id, font_size, c)
2818 .unwrap()
2819 .width;
2820 }
2821
2822 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2823 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
2824 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2825 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2826 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
2827
2828 let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2829 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
2830
2831 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
2832 assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
2833
2834 let blocks: Vec<_> = snapshot
2835 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
2836 .map(|(row, block)| (row.0..row.0 + block.height(), block.id()))
2837 .collect();
2838 assert_eq!(
2839 blocks,
2840 vec![
2841 (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
2842 (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
2843 (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
2844 ]
2845 );
2846 }
2847
2848 #[gpui::test]
2849 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
2850 cx.update(init_test);
2851
2852 let text = "aaa\nbbb\nccc\nddd";
2853
2854 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2855 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2856 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2857 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2858 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2859 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2860 let (_wrap_map, wraps_snapshot) =
2861 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2862 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2863
2864 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
2865 let block_ids = writer.insert(vec![
2866 BlockProperties {
2867 style: BlockStyle::Fixed,
2868 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2869 height: Some(1),
2870 render: Arc::new(|_| div().into_any()),
2871 priority: 0,
2872 },
2873 BlockProperties {
2874 style: BlockStyle::Fixed,
2875 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2876 height: Some(2),
2877 render: Arc::new(|_| div().into_any()),
2878 priority: 0,
2879 },
2880 BlockProperties {
2881 style: BlockStyle::Fixed,
2882 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2883 height: Some(3),
2884 render: Arc::new(|_| div().into_any()),
2885 priority: 0,
2886 },
2887 ]);
2888
2889 {
2890 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
2891 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2892
2893 let mut block_map_writer =
2894 block_map.write(wraps_snapshot.clone(), Default::default(), None);
2895
2896 let mut new_heights = HashMap::default();
2897 new_heights.insert(block_ids[0], 2);
2898 block_map_writer.resize(new_heights);
2899 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
2900 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2901 }
2902
2903 {
2904 let mut block_map_writer =
2905 block_map.write(wraps_snapshot.clone(), Default::default(), None);
2906
2907 let mut new_heights = HashMap::default();
2908 new_heights.insert(block_ids[0], 1);
2909 block_map_writer.resize(new_heights);
2910
2911 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
2912 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2913 }
2914
2915 {
2916 let mut block_map_writer =
2917 block_map.write(wraps_snapshot.clone(), Default::default(), None);
2918
2919 let mut new_heights = HashMap::default();
2920 new_heights.insert(block_ids[0], 0);
2921 block_map_writer.resize(new_heights);
2922
2923 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
2924 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
2925 }
2926
2927 {
2928 let mut block_map_writer =
2929 block_map.write(wraps_snapshot.clone(), Default::default(), None);
2930
2931 let mut new_heights = HashMap::default();
2932 new_heights.insert(block_ids[0], 3);
2933 block_map_writer.resize(new_heights);
2934
2935 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
2936 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2937 }
2938
2939 {
2940 let mut block_map_writer =
2941 block_map.write(wraps_snapshot.clone(), Default::default(), None);
2942
2943 let mut new_heights = HashMap::default();
2944 new_heights.insert(block_ids[0], 3);
2945 block_map_writer.resize(new_heights);
2946
2947 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
2948 // Same height as before, should remain the same
2949 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2950 }
2951 }
2952
2953 #[gpui::test]
2954 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
2955 cx.update(init_test);
2956
2957 let text = "one two three\nfour five six\nseven eight";
2958
2959 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2960 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2961 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2962 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2963 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2964 let (_, wraps_snapshot) = cx.update(|cx| {
2965 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
2966 });
2967 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2968
2969 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
2970 writer.insert(vec![
2971 BlockProperties {
2972 style: BlockStyle::Fixed,
2973 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
2974 render: Arc::new(|_| div().into_any()),
2975 height: Some(1),
2976 priority: 0,
2977 },
2978 BlockProperties {
2979 style: BlockStyle::Fixed,
2980 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
2981 render: Arc::new(|_| div().into_any()),
2982 height: Some(1),
2983 priority: 0,
2984 },
2985 ]);
2986
2987 // Blocks with an 'above' disposition go above their corresponding buffer line.
2988 // Blocks with a 'below' disposition go below their corresponding buffer line.
2989 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
2990 assert_eq!(
2991 snapshot.text(),
2992 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
2993 );
2994 }
2995
2996 #[gpui::test]
2997 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
2998 cx.update(init_test);
2999
3000 let text = "line1\nline2\nline3\nline4\nline5";
3001
3002 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3003 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
3004 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3005 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3006 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3007 let tab_size = 1.try_into().unwrap();
3008 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
3009 let (wrap_map, wraps_snapshot) =
3010 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3011 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3012
3013 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3014 let replace_block_id = writer.insert(vec![BlockProperties {
3015 style: BlockStyle::Fixed,
3016 placement: BlockPlacement::Replace(
3017 buffer_snapshot.anchor_after(Point::new(1, 3))
3018 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
3019 ),
3020 height: Some(4),
3021 render: Arc::new(|_| div().into_any()),
3022 priority: 0,
3023 }])[0];
3024
3025 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3026 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3027
3028 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3029 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
3030 buffer.snapshot(cx)
3031 });
3032 let (inlay_snapshot, inlay_edits) =
3033 inlay_map.sync(buffer_snapshot, buffer_subscription.consume().into_inner());
3034 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3035 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3036 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3037 wrap_map.sync(tab_snapshot, tab_edits, cx)
3038 });
3039 let blocks_snapshot = block_map.read(wraps_snapshot, wrap_edits, None);
3040 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3041
3042 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3043 buffer.edit(
3044 [(
3045 Point::new(1, 5)..Point::new(1, 5),
3046 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
3047 )],
3048 None,
3049 cx,
3050 );
3051 buffer.snapshot(cx)
3052 });
3053 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
3054 buffer_snapshot.clone(),
3055 buffer_subscription.consume().into_inner(),
3056 );
3057 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3058 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3059 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3060 wrap_map.sync(tab_snapshot, tab_edits, cx)
3061 });
3062 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
3063 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3064
3065 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
3066 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3067 writer.insert(vec![
3068 BlockProperties {
3069 style: BlockStyle::Fixed,
3070 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
3071 height: Some(1),
3072 render: Arc::new(|_| div().into_any()),
3073 priority: 0,
3074 },
3075 BlockProperties {
3076 style: BlockStyle::Fixed,
3077 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
3078 height: Some(1),
3079 render: Arc::new(|_| div().into_any()),
3080 priority: 0,
3081 },
3082 BlockProperties {
3083 style: BlockStyle::Fixed,
3084 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
3085 height: Some(1),
3086 render: Arc::new(|_| div().into_any()),
3087 priority: 0,
3088 },
3089 ]);
3090 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3091 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3092
3093 // Ensure blocks inserted *inside* replaced region are hidden.
3094 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3095 writer.insert(vec![
3096 BlockProperties {
3097 style: BlockStyle::Fixed,
3098 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
3099 height: Some(1),
3100 render: Arc::new(|_| div().into_any()),
3101 priority: 0,
3102 },
3103 BlockProperties {
3104 style: BlockStyle::Fixed,
3105 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
3106 height: Some(1),
3107 render: Arc::new(|_| div().into_any()),
3108 priority: 0,
3109 },
3110 BlockProperties {
3111 style: BlockStyle::Fixed,
3112 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
3113 height: Some(1),
3114 render: Arc::new(|_| div().into_any()),
3115 priority: 0,
3116 },
3117 ]);
3118 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3119 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3120
3121 // Removing the replace block shows all the hidden blocks again.
3122 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3123 writer.remove(HashSet::from_iter([replace_block_id]));
3124 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3125 assert_eq!(
3126 blocks_snapshot.text(),
3127 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
3128 );
3129 }
3130
3131 #[gpui::test]
3132 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
3133 cx.update(init_test);
3134
3135 let text = "111\n222\n333\n444\n555\n666";
3136
3137 let buffer = cx.update(|cx| {
3138 MultiBuffer::build_multi(
3139 [
3140 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
3141 (
3142 text,
3143 vec![
3144 Point::new(1, 0)..Point::new(1, 3),
3145 Point::new(2, 0)..Point::new(2, 3),
3146 Point::new(3, 0)..Point::new(3, 3),
3147 ],
3148 ),
3149 (
3150 text,
3151 vec![
3152 Point::new(4, 0)..Point::new(4, 3),
3153 Point::new(5, 0)..Point::new(5, 3),
3154 ],
3155 ),
3156 ],
3157 cx,
3158 )
3159 });
3160 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3161 let buffer_ids = buffer_snapshot
3162 .excerpts()
3163 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3164 .dedup()
3165 .collect::<Vec<_>>();
3166 assert_eq!(buffer_ids.len(), 3);
3167 let buffer_id_1 = buffer_ids[0];
3168 let buffer_id_2 = buffer_ids[1];
3169 let buffer_id_3 = buffer_ids[2];
3170
3171 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3172 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3173 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3174 let (_, wrap_snapshot) =
3175 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3176 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3177 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3178
3179 assert_eq!(
3180 blocks_snapshot.text(),
3181 "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
3182 );
3183 assert_eq!(
3184 blocks_snapshot
3185 .row_infos(BlockRow(0))
3186 .map(|i| i.buffer_row)
3187 .collect::<Vec<_>>(),
3188 vec![
3189 None,
3190 None,
3191 Some(0),
3192 None,
3193 None,
3194 Some(1),
3195 None,
3196 Some(2),
3197 None,
3198 Some(3),
3199 None,
3200 None,
3201 Some(4),
3202 None,
3203 Some(5),
3204 ]
3205 );
3206
3207 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3208 let excerpt_blocks_2 = writer.insert(vec![
3209 BlockProperties {
3210 style: BlockStyle::Fixed,
3211 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3212 height: Some(1),
3213 render: Arc::new(|_| div().into_any()),
3214 priority: 0,
3215 },
3216 BlockProperties {
3217 style: BlockStyle::Fixed,
3218 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
3219 height: Some(1),
3220 render: Arc::new(|_| div().into_any()),
3221 priority: 0,
3222 },
3223 BlockProperties {
3224 style: BlockStyle::Fixed,
3225 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
3226 height: Some(1),
3227 render: Arc::new(|_| div().into_any()),
3228 priority: 0,
3229 },
3230 ]);
3231 let excerpt_blocks_3 = writer.insert(vec![
3232 BlockProperties {
3233 style: BlockStyle::Fixed,
3234 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
3235 height: Some(1),
3236 render: Arc::new(|_| div().into_any()),
3237 priority: 0,
3238 },
3239 BlockProperties {
3240 style: BlockStyle::Fixed,
3241 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
3242 height: Some(1),
3243 render: Arc::new(|_| div().into_any()),
3244 priority: 0,
3245 },
3246 ]);
3247
3248 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3249 assert_eq!(
3250 blocks_snapshot.text(),
3251 "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3252 );
3253 assert_eq!(
3254 blocks_snapshot
3255 .row_infos(BlockRow(0))
3256 .map(|i| i.buffer_row)
3257 .collect::<Vec<_>>(),
3258 vec![
3259 None,
3260 None,
3261 Some(0),
3262 None,
3263 None,
3264 None,
3265 Some(1),
3266 None,
3267 None,
3268 Some(2),
3269 None,
3270 Some(3),
3271 None,
3272 None,
3273 None,
3274 None,
3275 Some(4),
3276 None,
3277 Some(5),
3278 None,
3279 ]
3280 );
3281
3282 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3283 buffer.read_with(cx, |buffer, cx| {
3284 writer.fold_buffers([buffer_id_1], buffer, cx);
3285 });
3286 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
3287 style: BlockStyle::Fixed,
3288 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
3289 height: Some(1),
3290 render: Arc::new(|_| div().into_any()),
3291 priority: 0,
3292 }]);
3293 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3294 let blocks = blocks_snapshot
3295 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3296 .collect::<Vec<_>>();
3297 for (_, block) in &blocks {
3298 if let BlockId::Custom(custom_block_id) = block.id() {
3299 assert!(
3300 !excerpt_blocks_1.contains(&custom_block_id),
3301 "Should have no blocks from the folded buffer"
3302 );
3303 assert!(
3304 excerpt_blocks_2.contains(&custom_block_id)
3305 || excerpt_blocks_3.contains(&custom_block_id),
3306 "Should have only blocks from unfolded buffers"
3307 );
3308 }
3309 }
3310 assert_eq!(
3311 1,
3312 blocks
3313 .iter()
3314 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3315 .count(),
3316 "Should have one folded block, producing a header of the second buffer"
3317 );
3318 assert_eq!(
3319 blocks_snapshot.text(),
3320 "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3321 );
3322 assert_eq!(
3323 blocks_snapshot
3324 .row_infos(BlockRow(0))
3325 .map(|i| i.buffer_row)
3326 .collect::<Vec<_>>(),
3327 vec![
3328 None,
3329 None,
3330 None,
3331 None,
3332 None,
3333 Some(1),
3334 None,
3335 None,
3336 Some(2),
3337 None,
3338 Some(3),
3339 None,
3340 None,
3341 None,
3342 None,
3343 Some(4),
3344 None,
3345 Some(5),
3346 None,
3347 ]
3348 );
3349
3350 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3351 buffer.read_with(cx, |buffer, cx| {
3352 writer.fold_buffers([buffer_id_2], buffer, cx);
3353 });
3354 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3355 let blocks = blocks_snapshot
3356 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3357 .collect::<Vec<_>>();
3358 for (_, block) in &blocks {
3359 if let BlockId::Custom(custom_block_id) = block.id() {
3360 assert!(
3361 !excerpt_blocks_1.contains(&custom_block_id),
3362 "Should have no blocks from the folded buffer_1"
3363 );
3364 assert!(
3365 !excerpt_blocks_2.contains(&custom_block_id),
3366 "Should have no blocks from the folded buffer_2"
3367 );
3368 assert!(
3369 excerpt_blocks_3.contains(&custom_block_id),
3370 "Should have only blocks from unfolded buffers"
3371 );
3372 }
3373 }
3374 assert_eq!(
3375 2,
3376 blocks
3377 .iter()
3378 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3379 .count(),
3380 "Should have two folded blocks, producing headers"
3381 );
3382 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
3383 assert_eq!(
3384 blocks_snapshot
3385 .row_infos(BlockRow(0))
3386 .map(|i| i.buffer_row)
3387 .collect::<Vec<_>>(),
3388 vec![
3389 None,
3390 None,
3391 None,
3392 None,
3393 None,
3394 None,
3395 None,
3396 Some(4),
3397 None,
3398 Some(5),
3399 None,
3400 ]
3401 );
3402
3403 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3404 buffer.read_with(cx, |buffer, cx| {
3405 writer.unfold_buffers([buffer_id_1], buffer, cx);
3406 });
3407 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3408 let blocks = blocks_snapshot
3409 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3410 .collect::<Vec<_>>();
3411 for (_, block) in &blocks {
3412 if let BlockId::Custom(custom_block_id) = block.id() {
3413 assert!(
3414 !excerpt_blocks_2.contains(&custom_block_id),
3415 "Should have no blocks from the folded buffer_2"
3416 );
3417 assert!(
3418 excerpt_blocks_1.contains(&custom_block_id)
3419 || excerpt_blocks_3.contains(&custom_block_id),
3420 "Should have only blocks from unfolded buffers"
3421 );
3422 }
3423 }
3424 assert_eq!(
3425 1,
3426 blocks
3427 .iter()
3428 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3429 .count(),
3430 "Should be back to a single folded buffer, producing a header for buffer_2"
3431 );
3432 assert_eq!(
3433 blocks_snapshot.text(),
3434 "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
3435 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
3436 );
3437 assert_eq!(
3438 blocks_snapshot
3439 .row_infos(BlockRow(0))
3440 .map(|i| i.buffer_row)
3441 .collect::<Vec<_>>(),
3442 vec![
3443 None,
3444 None,
3445 None,
3446 Some(0),
3447 None,
3448 None,
3449 None,
3450 None,
3451 None,
3452 Some(4),
3453 None,
3454 Some(5),
3455 None,
3456 ]
3457 );
3458
3459 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3460 buffer.read_with(cx, |buffer, cx| {
3461 writer.fold_buffers([buffer_id_3], buffer, cx);
3462 });
3463 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3464 let blocks = blocks_snapshot
3465 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3466 .collect::<Vec<_>>();
3467 for (_, block) in &blocks {
3468 if let BlockId::Custom(custom_block_id) = block.id() {
3469 assert!(
3470 excerpt_blocks_1.contains(&custom_block_id),
3471 "Should have no blocks from the folded buffer_1"
3472 );
3473 assert!(
3474 !excerpt_blocks_2.contains(&custom_block_id),
3475 "Should have only blocks from unfolded buffers"
3476 );
3477 assert!(
3478 !excerpt_blocks_3.contains(&custom_block_id),
3479 "Should have only blocks from unfolded buffers"
3480 );
3481 }
3482 }
3483
3484 assert_eq!(
3485 blocks_snapshot.text(),
3486 "\n\n\n111\n\n\n\n",
3487 "Should have a single, first buffer left after folding"
3488 );
3489 assert_eq!(
3490 blocks_snapshot
3491 .row_infos(BlockRow(0))
3492 .map(|i| i.buffer_row)
3493 .collect::<Vec<_>>(),
3494 vec![None, None, None, Some(0), None, None, None, None,]
3495 );
3496 }
3497
3498 #[gpui::test]
3499 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
3500 cx.update(init_test);
3501
3502 let text = "111";
3503
3504 let buffer = cx.update(|cx| {
3505 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
3506 });
3507 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3508 let buffer_ids = buffer_snapshot
3509 .excerpts()
3510 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3511 .dedup()
3512 .collect::<Vec<_>>();
3513 assert_eq!(buffer_ids.len(), 1);
3514 let buffer_id = buffer_ids[0];
3515
3516 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
3517 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3518 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3519 let (_, wrap_snapshot) =
3520 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3521 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3522 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3523
3524 assert_eq!(blocks_snapshot.text(), "\n\n111");
3525
3526 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3527 buffer.read_with(cx, |buffer, cx| {
3528 writer.fold_buffers([buffer_id], buffer, cx);
3529 });
3530 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3531 let blocks = blocks_snapshot
3532 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3533 .collect::<Vec<_>>();
3534 assert_eq!(
3535 1,
3536 blocks
3537 .iter()
3538 .filter(|(_, block)| { matches!(block, Block::FoldedBuffer { .. }) })
3539 .count(),
3540 "Should have one folded block, producing a header of the second buffer"
3541 );
3542 assert_eq!(blocks_snapshot.text(), "\n");
3543 assert_eq!(
3544 blocks_snapshot
3545 .row_infos(BlockRow(0))
3546 .map(|i| i.buffer_row)
3547 .collect::<Vec<_>>(),
3548 vec![None, None],
3549 "When fully folded, should be no buffer rows"
3550 );
3551 }
3552
3553 #[gpui::test(iterations = 60)]
3554 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
3555 cx.update(init_test);
3556
3557 let operations = env::var("OPERATIONS")
3558 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3559 .unwrap_or(10);
3560
3561 let wrap_width = if rng.random_bool(0.2) {
3562 None
3563 } else {
3564 Some(px(rng.random_range(0.0..=100.0)))
3565 };
3566 let tab_size = 1.try_into().unwrap();
3567 let font_size = px(14.0);
3568 let buffer_start_header_height = rng.random_range(1..=5);
3569 let excerpt_header_height = rng.random_range(1..=5);
3570
3571 log::info!("Wrap width: {:?}", wrap_width);
3572 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
3573 let is_singleton = rng.random();
3574 let buffer = if is_singleton {
3575 let len = rng.random_range(0..10);
3576 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
3577 log::info!("initial singleton buffer text: {:?}", text);
3578 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
3579 } else {
3580 cx.update(|cx| {
3581 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
3582 log::info!(
3583 "initial multi-buffer text: {:?}",
3584 multibuffer.read(cx).read(cx).text()
3585 );
3586 multibuffer
3587 })
3588 };
3589
3590 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3591 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3592 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3593 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3594 let font = test_font();
3595 let (wrap_map, wraps_snapshot) =
3596 cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
3597 let mut block_map = BlockMap::new(
3598 wraps_snapshot,
3599 buffer_start_header_height,
3600 excerpt_header_height,
3601 );
3602
3603 for _ in 0..operations {
3604 let mut buffer_edits = Vec::new();
3605 match rng.random_range(0..=100) {
3606 0..=19 => {
3607 let wrap_width = if rng.random_bool(0.2) {
3608 None
3609 } else {
3610 Some(px(rng.random_range(0.0..=100.0)))
3611 };
3612 log::info!("Setting wrap width to {:?}", wrap_width);
3613 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
3614 }
3615 20..=39 => {
3616 let block_count = rng.random_range(1..=5);
3617 let block_properties = (0..block_count)
3618 .map(|_| {
3619 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
3620 let offset = buffer.clip_offset(
3621 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
3622 Bias::Left,
3623 );
3624 let mut min_height = 0;
3625 let placement = match rng.random_range(0..3) {
3626 0 => {
3627 min_height = 1;
3628 let start = buffer.anchor_after(offset);
3629 let end = buffer.anchor_after(buffer.clip_offset(
3630 rng.random_range(offset..=buffer.len()),
3631 Bias::Left,
3632 ));
3633 BlockPlacement::Replace(start..=end)
3634 }
3635 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3636 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3637 };
3638
3639 let height = rng.random_range(min_height..512);
3640 BlockProperties {
3641 style: BlockStyle::Fixed,
3642 placement,
3643 height: Some(height),
3644 render: Arc::new(|_| div().into_any()),
3645 priority: 0,
3646 }
3647 })
3648 .collect::<Vec<_>>();
3649
3650 let (inlay_snapshot, inlay_edits) =
3651 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3652 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3653 let (tab_snapshot, tab_edits) =
3654 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3655 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3656 wrap_map.sync(tab_snapshot, tab_edits, cx)
3657 });
3658 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3659 let block_ids =
3660 block_map.insert(block_properties.iter().map(|props| BlockProperties {
3661 placement: props.placement.clone(),
3662 height: props.height,
3663 style: props.style,
3664 render: Arc::new(|_| div().into_any()),
3665 priority: 0,
3666 }));
3667
3668 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3669 log::info!(
3670 "inserted block {:?} with height {:?} and id {:?}",
3671 block_properties
3672 .placement
3673 .as_ref()
3674 .map(|p| p.to_point(&buffer_snapshot)),
3675 block_properties.height,
3676 block_id
3677 );
3678 }
3679 }
3680 40..=59 if !block_map.custom_blocks.is_empty() => {
3681 let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3682 let block_ids_to_remove = block_map
3683 .custom_blocks
3684 .choose_multiple(&mut rng, block_count)
3685 .map(|block| block.id)
3686 .collect::<HashSet<_>>();
3687
3688 let (inlay_snapshot, inlay_edits) =
3689 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3690 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3691 let (tab_snapshot, tab_edits) =
3692 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3693 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3694 wrap_map.sync(tab_snapshot, tab_edits, cx)
3695 });
3696 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3697 log::info!(
3698 "removing {} blocks: {:?}",
3699 block_ids_to_remove.len(),
3700 block_ids_to_remove
3701 );
3702 block_map.remove(block_ids_to_remove);
3703 }
3704 60..=79 => {
3705 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3706 log::info!("Noop fold/unfold operation on a singleton buffer");
3707 continue;
3708 }
3709 let (inlay_snapshot, inlay_edits) =
3710 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3711 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3712 let (tab_snapshot, tab_edits) =
3713 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3714 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3715 wrap_map.sync(tab_snapshot, tab_edits, cx)
3716 });
3717 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3718 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3719 let folded_buffers: Vec<_> =
3720 block_map.block_map.folded_buffers.iter().cloned().collect();
3721 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3722 unfolded_buffers.dedup();
3723 log::debug!("All buffers {unfolded_buffers:?}");
3724 log::debug!("Folded buffers {folded_buffers:?}");
3725 unfolded_buffers.retain(|buffer_id| {
3726 !block_map.block_map.folded_buffers.contains(buffer_id)
3727 });
3728 (unfolded_buffers, folded_buffers)
3729 });
3730 let mut folded_count = folded_buffers.len();
3731 let mut unfolded_count = unfolded_buffers.len();
3732
3733 let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
3734 let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
3735 if !fold && !unfold {
3736 log::info!(
3737 "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3738 );
3739 continue;
3740 }
3741
3742 buffer.update(cx, |buffer, cx| {
3743 if fold {
3744 let buffer_to_fold =
3745 unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
3746 log::info!("Folding {buffer_to_fold:?}");
3747 let related_excerpts = buffer_snapshot
3748 .excerpts()
3749 .filter_map(|(excerpt_id, buffer, range)| {
3750 if buffer.remote_id() == buffer_to_fold {
3751 Some((
3752 excerpt_id,
3753 buffer
3754 .text_for_range(range.context)
3755 .collect::<String>(),
3756 ))
3757 } else {
3758 None
3759 }
3760 })
3761 .collect::<Vec<_>>();
3762 log::info!(
3763 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
3764 );
3765 folded_count += 1;
3766 unfolded_count -= 1;
3767 block_map.fold_buffers([buffer_to_fold], buffer, cx);
3768 }
3769 if unfold {
3770 let buffer_to_unfold =
3771 folded_buffers[rng.random_range(0..folded_buffers.len())];
3772 log::info!("Unfolding {buffer_to_unfold:?}");
3773 unfolded_count += 1;
3774 folded_count -= 1;
3775 block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
3776 }
3777 log::info!(
3778 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3779 );
3780 });
3781 }
3782 _ => {
3783 buffer.update(cx, |buffer, cx| {
3784 let mutation_count = rng.random_range(1..=5);
3785 let subscription = buffer.subscribe();
3786 buffer.randomly_mutate(&mut rng, mutation_count, cx);
3787 buffer_snapshot = buffer.snapshot(cx);
3788 buffer_edits.extend(subscription.consume());
3789 log::info!("buffer text: {:?}", buffer_snapshot.text());
3790 });
3791 }
3792 }
3793
3794 let (inlay_snapshot, inlay_edits) =
3795 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
3796 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3797 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3798 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3799 wrap_map.sync(tab_snapshot, tab_edits, cx)
3800 });
3801 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
3802 assert_eq!(
3803 blocks_snapshot.transforms.summary().input_rows,
3804 wraps_snapshot.max_point().row() + RowDelta(1)
3805 );
3806 log::info!("wrapped text: {:?}", wraps_snapshot.text());
3807 log::info!("blocks text: {:?}", blocks_snapshot.text());
3808
3809 let mut expected_blocks = Vec::new();
3810 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
3811 Some((
3812 block.placement.to_wrap_row(&wraps_snapshot)?,
3813 Block::Custom(block.clone()),
3814 ))
3815 }));
3816
3817 let mut inlay_point_cursor = wraps_snapshot.inlay_point_cursor();
3818 let mut tab_point_cursor = wraps_snapshot.tab_point_cursor();
3819 let mut fold_point_cursor = wraps_snapshot.fold_point_cursor();
3820 let mut wrap_point_cursor = wraps_snapshot.wrap_point_cursor();
3821
3822 // Note that this needs to be synced with the related section in BlockMap::sync
3823 expected_blocks.extend(block_map.header_and_footer_blocks(
3824 &buffer_snapshot,
3825 MultiBufferOffset(0)..,
3826 |point, bias| {
3827 wrap_point_cursor
3828 .map(
3829 tab_point_cursor
3830 .map(fold_point_cursor.map(inlay_point_cursor.map(point), bias)),
3831 )
3832 .row()
3833 },
3834 ));
3835
3836 BlockMap::sort_blocks(&mut expected_blocks);
3837
3838 for (placement, block) in &expected_blocks {
3839 log::info!(
3840 "Block {:?} placement: {:?} Height: {:?}",
3841 block.id(),
3842 placement,
3843 block.height()
3844 );
3845 }
3846
3847 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
3848
3849 let input_buffer_rows = buffer_snapshot
3850 .row_infos(MultiBufferRow(0))
3851 .map(|row| row.buffer_row)
3852 .collect::<Vec<_>>();
3853 let mut expected_buffer_rows = Vec::new();
3854 let mut expected_text = String::new();
3855 let mut expected_block_positions = Vec::new();
3856 let mut expected_replaced_buffer_rows = HashSet::default();
3857 let input_text = wraps_snapshot.text();
3858
3859 // Loop over the input lines, creating (N - 1) empty lines for
3860 // blocks of height N.
3861 //
3862 // It's important to note that output *starts* as one empty line,
3863 // so we special case row 0 to assume a leading '\n'.
3864 //
3865 // Linehood is the birthright of strings.
3866 let input_text_lines = input_text.split('\n').enumerate().peekable();
3867 let mut block_row = 0;
3868 for (wrap_row, input_line) in input_text_lines {
3869 let wrap_row = WrapRow(wrap_row as u32);
3870 let multibuffer_row = wraps_snapshot
3871 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
3872 .row;
3873
3874 // Create empty lines for the above block
3875 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3876 if *placement.start() == wrap_row && block.place_above() {
3877 let (_, block) = sorted_blocks_iter.next().unwrap();
3878 expected_block_positions.push((block_row, block.id()));
3879 if block.height() > 0 {
3880 let text = "\n".repeat((block.height() - 1) as usize);
3881 if block_row > 0 {
3882 expected_text.push('\n')
3883 }
3884 expected_text.push_str(&text);
3885 for _ in 0..block.height() {
3886 expected_buffer_rows.push(None);
3887 }
3888 block_row += block.height();
3889 }
3890 } else {
3891 break;
3892 }
3893 }
3894
3895 // Skip lines within replace blocks, then create empty lines for the replace block's height
3896 let mut is_in_replace_block = false;
3897 if let Some((BlockPlacement::Replace(replace_range), block)) =
3898 sorted_blocks_iter.peek()
3899 && wrap_row >= *replace_range.start()
3900 {
3901 is_in_replace_block = true;
3902
3903 if wrap_row == *replace_range.start() {
3904 if matches!(block, Block::FoldedBuffer { .. }) {
3905 expected_buffer_rows.push(None);
3906 } else {
3907 expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
3908 }
3909 }
3910
3911 if wrap_row == *replace_range.end() {
3912 expected_block_positions.push((block_row, block.id()));
3913 let text = "\n".repeat((block.height() - 1) as usize);
3914 if block_row > 0 {
3915 expected_text.push('\n');
3916 }
3917 expected_text.push_str(&text);
3918
3919 for _ in 1..block.height() {
3920 expected_buffer_rows.push(None);
3921 }
3922 block_row += block.height();
3923
3924 sorted_blocks_iter.next();
3925 }
3926 }
3927
3928 if is_in_replace_block {
3929 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
3930 } else {
3931 let buffer_row = input_buffer_rows[multibuffer_row as usize];
3932 let soft_wrapped = wraps_snapshot
3933 .to_tab_point(WrapPoint::new(wrap_row, 0))
3934 .column()
3935 > 0;
3936 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
3937 if block_row > 0 {
3938 expected_text.push('\n');
3939 }
3940 expected_text.push_str(input_line);
3941 block_row += 1;
3942 }
3943
3944 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3945 if *placement.end() == wrap_row && block.place_below() {
3946 let (_, block) = sorted_blocks_iter.next().unwrap();
3947 expected_block_positions.push((block_row, block.id()));
3948 if block.height() > 0 {
3949 let text = "\n".repeat((block.height() - 1) as usize);
3950 if block_row > 0 {
3951 expected_text.push('\n')
3952 }
3953 expected_text.push_str(&text);
3954 for _ in 0..block.height() {
3955 expected_buffer_rows.push(None);
3956 }
3957 block_row += block.height();
3958 }
3959 } else {
3960 break;
3961 }
3962 }
3963 }
3964
3965 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
3966 let expected_row_count = expected_lines.len();
3967 log::info!("expected text: {expected_text:?}");
3968
3969 assert_eq!(
3970 blocks_snapshot.max_point().row + 1,
3971 expected_row_count as u32,
3972 "actual row count != expected row count",
3973 );
3974 assert_eq!(
3975 blocks_snapshot.text(),
3976 expected_text,
3977 "actual text != expected text",
3978 );
3979
3980 for start_row in 0..expected_row_count {
3981 let end_row = rng.random_range(start_row + 1..=expected_row_count);
3982 let mut expected_text = expected_lines[start_row..end_row].join("\n");
3983 if end_row < expected_row_count {
3984 expected_text.push('\n');
3985 }
3986
3987 let actual_text = blocks_snapshot
3988 .chunks(
3989 BlockRow(start_row as u32)..BlockRow(end_row as u32),
3990 false,
3991 false,
3992 Highlights::default(),
3993 )
3994 .map(|chunk| chunk.text)
3995 .collect::<String>();
3996 assert_eq!(
3997 actual_text,
3998 expected_text,
3999 "incorrect text starting row row range {:?}",
4000 start_row..end_row
4001 );
4002 assert_eq!(
4003 blocks_snapshot
4004 .row_infos(BlockRow(start_row as u32))
4005 .map(|row_info| row_info.buffer_row)
4006 .collect::<Vec<_>>(),
4007 &expected_buffer_rows[start_row..],
4008 "incorrect buffer_rows starting at row {:?}",
4009 start_row
4010 );
4011 }
4012
4013 assert_eq!(
4014 blocks_snapshot
4015 .blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4016 .map(|(row, block)| (row.0, block.id()))
4017 .collect::<Vec<_>>(),
4018 expected_block_positions,
4019 "invalid blocks_in_range({:?})",
4020 0..expected_row_count
4021 );
4022
4023 for (_, expected_block) in
4024 blocks_snapshot.blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4025 {
4026 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
4027 assert_eq!(
4028 actual_block.map(|block| block.id()),
4029 Some(expected_block.id())
4030 );
4031 }
4032
4033 for (block_row, block_id) in expected_block_positions {
4034 if let BlockId::Custom(block_id) = block_id {
4035 assert_eq!(
4036 blocks_snapshot.row_for_block(block_id),
4037 Some(BlockRow(block_row))
4038 );
4039 }
4040 }
4041
4042 let mut expected_longest_rows = Vec::new();
4043 let mut longest_line_len = -1_isize;
4044 for (row, line) in expected_lines.iter().enumerate() {
4045 let row = row as u32;
4046
4047 assert_eq!(
4048 blocks_snapshot.line_len(BlockRow(row)),
4049 line.len() as u32,
4050 "invalid line len for row {}",
4051 row
4052 );
4053
4054 let line_char_count = line.chars().count() as isize;
4055 match line_char_count.cmp(&longest_line_len) {
4056 Ordering::Less => {}
4057 Ordering::Equal => expected_longest_rows.push(row),
4058 Ordering::Greater => {
4059 longest_line_len = line_char_count;
4060 expected_longest_rows.clear();
4061 expected_longest_rows.push(row);
4062 }
4063 }
4064 }
4065
4066 let longest_row = blocks_snapshot.longest_row();
4067 assert!(
4068 expected_longest_rows.contains(&longest_row.0),
4069 "incorrect longest row {}. expected {:?} with length {}",
4070 longest_row.0,
4071 expected_longest_rows,
4072 longest_line_len,
4073 );
4074
4075 for _ in 0..10 {
4076 let end_row = rng.random_range(1..=expected_lines.len());
4077 let start_row = rng.random_range(0..end_row);
4078
4079 let mut expected_longest_rows_in_range = vec![];
4080 let mut longest_line_len_in_range = 0;
4081
4082 let mut row = start_row as u32;
4083 for line in &expected_lines[start_row..end_row] {
4084 let line_char_count = line.chars().count() as isize;
4085 match line_char_count.cmp(&longest_line_len_in_range) {
4086 Ordering::Less => {}
4087 Ordering::Equal => expected_longest_rows_in_range.push(row),
4088 Ordering::Greater => {
4089 longest_line_len_in_range = line_char_count;
4090 expected_longest_rows_in_range.clear();
4091 expected_longest_rows_in_range.push(row);
4092 }
4093 }
4094 row += 1;
4095 }
4096
4097 let longest_row_in_range = blocks_snapshot
4098 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
4099 assert!(
4100 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
4101 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
4102 longest_row.0,
4103 start_row..end_row,
4104 expected_longest_rows_in_range,
4105 longest_line_len_in_range,
4106 );
4107 }
4108
4109 // Ensure that conversion between block points and wrap points is stable.
4110 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row().0 {
4111 let wrap_point = WrapPoint::new(WrapRow(row), 0);
4112 let block_point = blocks_snapshot.to_block_point(wrap_point);
4113 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
4114 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
4115 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
4116 assert_eq!(
4117 blocks_snapshot.to_block_point(right_wrap_point),
4118 block_point
4119 );
4120 }
4121
4122 let mut block_point = BlockPoint::new(BlockRow(0), 0);
4123 for c in expected_text.chars() {
4124 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
4125 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
4126 assert_eq!(
4127 blocks_snapshot
4128 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
4129 left_point,
4130 "block point: {:?}, wrap point: {:?}",
4131 block_point,
4132 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
4133 );
4134 assert_eq!(
4135 left_buffer_point,
4136 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
4137 "{:?} is not valid in buffer coordinates",
4138 left_point
4139 );
4140
4141 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
4142 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
4143 assert_eq!(
4144 blocks_snapshot
4145 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
4146 right_point,
4147 "block point: {:?}, wrap point: {:?}",
4148 block_point,
4149 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
4150 );
4151 assert_eq!(
4152 right_buffer_point,
4153 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
4154 "{:?} is not valid in buffer coordinates",
4155 right_point
4156 );
4157
4158 if c == '\n' {
4159 block_point.0 += Point::new(1, 0);
4160 } else {
4161 block_point.column += c.len_utf8() as u32;
4162 }
4163 }
4164
4165 for buffer_row in 0..=buffer_snapshot.max_point().row {
4166 let buffer_row = MultiBufferRow(buffer_row);
4167 assert_eq!(
4168 blocks_snapshot.is_line_replaced(buffer_row),
4169 expected_replaced_buffer_rows.contains(&buffer_row),
4170 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
4171 );
4172 }
4173 }
4174 }
4175
4176 #[gpui::test]
4177 fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
4178 cx.update(init_test);
4179
4180 let text = "abc\ndef\nghi\njkl\nmno";
4181 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
4182 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4183 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4184 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
4185 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4186 let (_wrap_map, wraps_snapshot) =
4187 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4188 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
4189
4190 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4191 let _block_id = writer.insert(vec![BlockProperties {
4192 style: BlockStyle::Fixed,
4193 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
4194 height: Some(1),
4195 render: Arc::new(|_| div().into_any()),
4196 priority: 0,
4197 }])[0];
4198
4199 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
4200 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4201
4202 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4203 writer.remove_intersecting_replace_blocks(
4204 [buffer_snapshot
4205 .anchor_after(Point::new(1, 0))
4206 .to_offset(&buffer_snapshot)
4207 ..buffer_snapshot
4208 .anchor_after(Point::new(1, 0))
4209 .to_offset(&buffer_snapshot)],
4210 false,
4211 );
4212 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
4213 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4214 }
4215
4216 #[gpui::test]
4217 fn test_folded_buffer_with_near_blocks(cx: &mut gpui::TestAppContext) {
4218 cx.update(init_test);
4219
4220 let text = "line 1\nline 2\nline 3";
4221 let buffer = cx.update(|cx| {
4222 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(2, 6)])], cx)
4223 });
4224 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4225 let buffer_ids = buffer_snapshot
4226 .excerpts()
4227 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
4228 .dedup()
4229 .collect::<Vec<_>>();
4230 assert_eq!(buffer_ids.len(), 1);
4231 let buffer_id = buffer_ids[0];
4232
4233 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4234 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4235 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4236 let (_, wrap_snapshot) =
4237 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4238 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4239
4240 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4241 writer.insert(vec![BlockProperties {
4242 style: BlockStyle::Fixed,
4243 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(0, 0))),
4244 height: Some(1),
4245 render: Arc::new(|_| div().into_any()),
4246 priority: 0,
4247 }]);
4248
4249 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4250 assert_eq!(blocks_snapshot.text(), "\nline 1\n\nline 2\nline 3");
4251
4252 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4253 buffer.read_with(cx, |buffer, cx| {
4254 writer.fold_buffers([buffer_id], buffer, cx);
4255 });
4256
4257 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4258 assert_eq!(blocks_snapshot.text(), "");
4259 }
4260
4261 #[gpui::test]
4262 fn test_folded_buffer_with_near_blocks_on_last_line(cx: &mut gpui::TestAppContext) {
4263 cx.update(init_test);
4264
4265 let text = "line 1\nline 2\nline 3\nline 4";
4266 let buffer = cx.update(|cx| {
4267 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(3, 6)])], cx)
4268 });
4269 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4270 let buffer_ids = buffer_snapshot
4271 .excerpts()
4272 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
4273 .dedup()
4274 .collect::<Vec<_>>();
4275 assert_eq!(buffer_ids.len(), 1);
4276 let buffer_id = buffer_ids[0];
4277
4278 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4279 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4280 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4281 let (_, wrap_snapshot) =
4282 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4283 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4284
4285 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4286 writer.insert(vec![BlockProperties {
4287 style: BlockStyle::Fixed,
4288 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(3, 6))),
4289 height: Some(1),
4290 render: Arc::new(|_| div().into_any()),
4291 priority: 0,
4292 }]);
4293
4294 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4295 assert_eq!(blocks_snapshot.text(), "\nline 1\nline 2\nline 3\nline 4\n");
4296
4297 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4298 buffer.read_with(cx, |buffer, cx| {
4299 writer.fold_buffers([buffer_id], buffer, cx);
4300 });
4301
4302 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4303 assert_eq!(blocks_snapshot.text(), "");
4304 }
4305
4306 #[gpui::test]
4307 fn test_companion_spacer_blocks(cx: &mut gpui::TestAppContext) {
4308 cx.update(init_test);
4309
4310 let base_text = "aaa\nbbb\nccc\nddd\nddd\nddd\neee\n";
4311 let main_text = "aaa\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n";
4312
4313 let rhs_buffer = cx.new(|cx| Buffer::local(main_text, cx));
4314 let diff = cx.new(|cx| {
4315 BufferDiff::new_with_base_text(base_text, &rhs_buffer.read(cx).text_snapshot(), cx)
4316 });
4317 let lhs_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer());
4318
4319 let lhs_multibuffer = cx.new(|cx| {
4320 let mut mb = MultiBuffer::new(Capability::ReadWrite);
4321 mb.push_excerpts(
4322 lhs_buffer.clone(),
4323 [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
4324 cx,
4325 );
4326 mb.add_inverted_diff(diff.clone(), cx);
4327 mb
4328 });
4329 let rhs_multibuffer = cx.new(|cx| {
4330 let mut mb = MultiBuffer::new(Capability::ReadWrite);
4331 mb.push_excerpts(
4332 rhs_buffer.clone(),
4333 [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
4334 cx,
4335 );
4336 mb.add_diff(diff.clone(), cx);
4337 mb
4338 });
4339 let subscription =
4340 rhs_multibuffer.update(cx, |rhs_multibuffer, _| rhs_multibuffer.subscribe());
4341
4342 let lhs_excerpt_id =
4343 lhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
4344 let rhs_excerpt_id =
4345 rhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
4346
4347 let lhs_buffer_snapshot = cx.update(|cx| lhs_multibuffer.read(cx).snapshot(cx));
4348 let (mut _lhs_inlay_map, lhs_inlay_snapshot) = InlayMap::new(lhs_buffer_snapshot);
4349 let (mut _lhs_fold_map, lhs_fold_snapshot) = FoldMap::new(lhs_inlay_snapshot);
4350 let (mut _lhs_tab_map, lhs_tab_snapshot) =
4351 TabMap::new(lhs_fold_snapshot, 4.try_into().unwrap());
4352 let (_lhs_wrap_map, lhs_wrap_snapshot) =
4353 cx.update(|cx| WrapMap::new(lhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4354 let lhs_block_map = BlockMap::new(lhs_wrap_snapshot.clone(), 0, 0);
4355
4356 let rhs_buffer_snapshot = cx.update(|cx| rhs_multibuffer.read(cx).snapshot(cx));
4357 let (mut rhs_inlay_map, rhs_inlay_snapshot) = InlayMap::new(rhs_buffer_snapshot);
4358 let (mut rhs_fold_map, rhs_fold_snapshot) = FoldMap::new(rhs_inlay_snapshot);
4359 let (mut rhs_tab_map, rhs_tab_snapshot) =
4360 TabMap::new(rhs_fold_snapshot, 4.try_into().unwrap());
4361 let (_rhs_wrap_map, rhs_wrap_snapshot) =
4362 cx.update(|cx| WrapMap::new(rhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4363 let rhs_block_map = BlockMap::new(rhs_wrap_snapshot.clone(), 0, 0);
4364
4365 let rhs_entity_id = rhs_multibuffer.entity_id();
4366
4367 let companion = cx.new(|_| {
4368 let mut c = Companion::new(
4369 rhs_entity_id,
4370 Default::default(),
4371 convert_rhs_rows_to_lhs,
4372 convert_lhs_rows_to_rhs,
4373 );
4374 c.add_excerpt_mapping(lhs_excerpt_id, rhs_excerpt_id);
4375 c
4376 });
4377
4378 let rhs_edits = Patch::new(vec![text::Edit {
4379 old: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4380 new: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4381 }]);
4382 let lhs_edits = Patch::new(vec![text::Edit {
4383 old: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4384 new: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4385 }]);
4386
4387 let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4388 rhs_block_map.read(
4389 rhs_wrap_snapshot.clone(),
4390 rhs_edits.clone(),
4391 Some(CompanionView::new(
4392 rhs_entity_id,
4393 &lhs_wrap_snapshot,
4394 &lhs_edits,
4395 companion,
4396 )),
4397 )
4398 });
4399
4400 let lhs_entity_id = lhs_multibuffer.entity_id();
4401 let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4402 lhs_block_map.read(
4403 lhs_wrap_snapshot.clone(),
4404 lhs_edits.clone(),
4405 Some(CompanionView::new(
4406 lhs_entity_id,
4407 &rhs_wrap_snapshot,
4408 &rhs_edits,
4409 companion,
4410 )),
4411 )
4412 });
4413
4414 // LHS:
4415 // aaa
4416 // - bbb
4417 // - ccc
4418 // ddd
4419 // ddd
4420 // ddd
4421 // <extra line>
4422 // <extra line>
4423 // <extra line>
4424 // *eee
4425 //
4426 // RHS:
4427 // aaa
4428 // <extra line>
4429 // <extra line>
4430 // ddd
4431 // ddd
4432 // ddd
4433 // + XXX
4434 // + YYY
4435 // + ZZZ
4436 // eee
4437
4438 assert_eq!(
4439 rhs_snapshot.snapshot.text(),
4440 "aaa\n\n\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4441 "RHS should have 2 spacer lines after 'aaa' to align with LHS's deleted lines"
4442 );
4443
4444 assert_eq!(
4445 lhs_snapshot.snapshot.text(),
4446 "aaa\nbbb\nccc\nddd\nddd\nddd\n\n\n\neee\n",
4447 "LHS should have 3 spacer lines in place of RHS's inserted lines"
4448 );
4449
4450 // LHS:
4451 // aaa
4452 // - bbb
4453 // - ccc
4454 // ddd
4455 // ddd
4456 // ddd
4457 // <extra line>
4458 // <extra line>
4459 // <extra line>
4460 // eee
4461 //
4462 // RHS:
4463 // aaa
4464 // <extra line>
4465 // <extra line>
4466 // ddd
4467 // foo
4468 // foo
4469 // foo
4470 // ddd
4471 // ddd
4472 // + XXX
4473 // + YYY
4474 // + ZZZ
4475 // eee
4476
4477 let rhs_buffer_snapshot = rhs_multibuffer.update(cx, |multibuffer, cx| {
4478 multibuffer.edit(
4479 [(Point::new(2, 0)..Point::new(2, 0), "foo\nfoo\nfoo\n")],
4480 None,
4481 cx,
4482 );
4483 multibuffer.snapshot(cx)
4484 });
4485
4486 let (rhs_inlay_snapshot, rhs_inlay_edits) =
4487 rhs_inlay_map.sync(rhs_buffer_snapshot, subscription.consume().into_inner());
4488 let (rhs_fold_snapshot, rhs_fold_edits) =
4489 rhs_fold_map.read(rhs_inlay_snapshot, rhs_inlay_edits);
4490 let (rhs_tab_snapshot, rhs_tab_edits) =
4491 rhs_tab_map.sync(rhs_fold_snapshot, rhs_fold_edits, 4.try_into().unwrap());
4492 let (rhs_wrap_snapshot, rhs_wrap_edits) = _rhs_wrap_map.update(cx, |wrap_map, cx| {
4493 wrap_map.sync(rhs_tab_snapshot, rhs_tab_edits, cx)
4494 });
4495
4496 let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4497 rhs_block_map.read(
4498 rhs_wrap_snapshot.clone(),
4499 rhs_wrap_edits.clone(),
4500 Some(CompanionView::new(
4501 rhs_entity_id,
4502 &lhs_wrap_snapshot,
4503 &Default::default(),
4504 companion,
4505 )),
4506 )
4507 });
4508
4509 let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4510 lhs_block_map.read(
4511 lhs_wrap_snapshot.clone(),
4512 Default::default(),
4513 Some(CompanionView::new(
4514 lhs_entity_id,
4515 &rhs_wrap_snapshot,
4516 &rhs_wrap_edits,
4517 companion,
4518 )),
4519 )
4520 });
4521
4522 assert_eq!(
4523 rhs_snapshot.snapshot.text(),
4524 "aaa\n\n\nddd\nfoo\nfoo\nfoo\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4525 "RHS should have the insertion"
4526 );
4527
4528 assert_eq!(
4529 lhs_snapshot.snapshot.text(),
4530 "aaa\nbbb\nccc\nddd\n\n\n\nddd\nddd\n\n\n\neee\n",
4531 "LHS should have 3 more spacer lines to balance the insertion"
4532 );
4533 }
4534
4535 fn init_test(cx: &mut gpui::App) {
4536 let settings = SettingsStore::test(cx);
4537 cx.set_global(settings);
4538 theme::init(theme::LoadThemes::JustBase, cx);
4539 assets::Assets.load_test_fonts(cx);
4540 }
4541
4542 impl Block {
4543 fn as_custom(&self) -> Option<&CustomBlock> {
4544 match self {
4545 Block::Custom(block) => Some(block),
4546 _ => None,
4547 }
4548 }
4549 }
4550
4551 impl BlockSnapshot {
4552 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
4553 self.wrap_snapshot
4554 .to_point(self.to_wrap_point(point, bias), bias)
4555 }
4556 }
4557}