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