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 edit_for_current_boundary =
1341 excerpt.patch.edit_for_old_position(current_boundary);
1342
1343 let spacer_at_end = if current_boundary == edit_for_current_boundary.old.end {
1344 let (delta_at_end, spacer_at_end) = determine_spacer(
1345 &mut our_wrapper,
1346 &mut companion_wrapper,
1347 current_boundary,
1348 current_range.end,
1349 delta,
1350 );
1351 delta = delta_at_end;
1352 spacer_at_end
1353 } else {
1354 None
1355 };
1356
1357 if let Some((wrap_row, mut height)) = spacer_at_start {
1358 if let Some((_, additional_height)) = spacer_at_end {
1359 height += additional_height;
1360 }
1361 result.push((
1362 BlockPlacement::Above(wrap_row),
1363 Block::Spacer {
1364 id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1365 height,
1366 is_below: false,
1367 },
1368 ));
1369 } else if let Some((wrap_row, height)) = spacer_at_end {
1370 result.push((
1371 BlockPlacement::Above(wrap_row),
1372 Block::Spacer {
1373 id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1374 height,
1375 is_below: false,
1376 },
1377 ));
1378 }
1379 }
1380
1381 if last_source_point == excerpt.source_excerpt_range.end {
1382 let (_new_delta, spacer) = determine_spacer(
1383 &mut our_wrapper,
1384 &mut companion_wrapper,
1385 last_source_point,
1386 excerpt.target_excerpt_range.end,
1387 delta,
1388 );
1389 if let Some((wrap_row, height)) = spacer {
1390 result.push((
1391 BlockPlacement::Below(wrap_row),
1392 Block::Spacer {
1393 id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1394 height,
1395 is_below: true,
1396 },
1397 ));
1398 }
1399 }
1400 }
1401
1402 result
1403 }
1404
1405 #[ztracing::instrument(skip_all)]
1406 fn sort_blocks(blocks: &mut Vec<(BlockPlacement<WrapRow>, Block)>) {
1407 blocks.sort_unstable_by(|(placement_a, block_a), (placement_b, block_b)| {
1408 placement_a
1409 .start()
1410 .cmp(placement_b.start())
1411 .then_with(|| placement_b.end().cmp(placement_a.end()))
1412 .then_with(|| placement_a.tie_break().cmp(&placement_b.tie_break()))
1413 .then_with(|| {
1414 if block_a.is_header() {
1415 Ordering::Less
1416 } else if block_b.is_header() {
1417 Ordering::Greater
1418 } else {
1419 Ordering::Equal
1420 }
1421 })
1422 .then_with(|| match (block_a, block_b) {
1423 (
1424 Block::ExcerptBoundary {
1425 excerpt: excerpt_a, ..
1426 }
1427 | Block::BufferHeader {
1428 excerpt: excerpt_a, ..
1429 },
1430 Block::ExcerptBoundary {
1431 excerpt: excerpt_b, ..
1432 }
1433 | Block::BufferHeader {
1434 excerpt: excerpt_b, ..
1435 },
1436 ) => Some(excerpt_a.id).cmp(&Some(excerpt_b.id)),
1437 (
1438 Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
1439 Block::Spacer { .. } | Block::Custom(_),
1440 ) => Ordering::Less,
1441 (
1442 Block::Spacer { .. } | Block::Custom(_),
1443 Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
1444 ) => Ordering::Greater,
1445 (Block::Spacer { .. }, Block::Custom(_)) => Ordering::Less,
1446 (Block::Custom(_), Block::Spacer { .. }) => Ordering::Greater,
1447 (Block::Custom(block_a), Block::Custom(block_b)) => block_a
1448 .priority
1449 .cmp(&block_b.priority)
1450 .then_with(|| block_a.id.cmp(&block_b.id)),
1451 _ => {
1452 unreachable!("comparing blocks: {block_a:?} vs {block_b:?}")
1453 }
1454 })
1455 });
1456 blocks.dedup_by(|right, left| match (left.0.clone(), right.0.clone()) {
1457 (BlockPlacement::Replace(range), BlockPlacement::Above(row))
1458 | (BlockPlacement::Replace(range), BlockPlacement::Below(row)) => range.contains(&row),
1459 (BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => {
1460 if range_a.end() >= range_b.start() && range_a.start() <= range_b.end() {
1461 left.0 = BlockPlacement::Replace(
1462 *range_a.start()..=*range_a.end().max(range_b.end()),
1463 );
1464 true
1465 } else {
1466 false
1467 }
1468 }
1469 _ => false,
1470 });
1471 }
1472
1473 pub(crate) fn insert_custom_block_into_companion(
1474 &mut self,
1475 entity_id: EntityId,
1476 snapshot: &WrapSnapshot,
1477 block: &CustomBlock,
1478 companion_snapshot: &MultiBufferSnapshot,
1479 companion: &mut Companion,
1480 ) {
1481 let their_anchor = block.placement.start();
1482 let their_point = their_anchor.to_point(companion_snapshot);
1483 let my_patches = companion.convert_rows_to_companion(
1484 entity_id,
1485 snapshot.buffer_snapshot(),
1486 companion_snapshot,
1487 (Bound::Included(their_point), Bound::Included(their_point)),
1488 );
1489 let my_excerpt = my_patches
1490 .first()
1491 .expect("at least one companion excerpt exists");
1492 let my_range = my_excerpt.patch.edit_for_old_position(their_point).new;
1493 let my_point = my_range.start;
1494 let anchor = snapshot.buffer_snapshot().anchor_before(my_point);
1495 let height = block.height.unwrap_or(1);
1496 let new_block = BlockProperties {
1497 placement: BlockPlacement::Above(anchor),
1498 height: Some(height),
1499 style: BlockStyle::Sticky,
1500 render: Arc::new(move |cx| {
1501 crate::EditorElement::render_spacer_block(
1502 cx.block_id,
1503 cx.height,
1504 cx.line_height,
1505 cx.window,
1506 cx.app,
1507 )
1508 }),
1509 priority: 0,
1510 };
1511 log::debug!("Inserting matching companion custom block: {block:#?} => {new_block:#?}");
1512 let new_block_id = self
1513 .write(snapshot.clone(), Patch::default(), None)
1514 .insert([new_block])[0];
1515 if companion.is_rhs(entity_id) {
1516 companion.add_custom_block_mapping(block.id, new_block_id);
1517 } else {
1518 companion.add_custom_block_mapping(new_block_id, block.id);
1519 }
1520 }
1521}
1522
1523#[ztracing::instrument(skip(tree, wrap_snapshot))]
1524fn push_isomorphic(tree: &mut SumTree<Transform>, rows: RowDelta, wrap_snapshot: &WrapSnapshot) {
1525 if rows == RowDelta(0) {
1526 return;
1527 }
1528
1529 let wrap_row_start = tree.summary().input_rows;
1530 let wrap_row_end = wrap_row_start + rows;
1531 let wrap_summary = wrap_snapshot.text_summary_for_range(wrap_row_start..wrap_row_end);
1532 let summary = TransformSummary {
1533 input_rows: WrapRow(rows.0),
1534 output_rows: BlockRow(rows.0),
1535 longest_row: BlockRow(wrap_summary.longest_row),
1536 longest_row_chars: wrap_summary.longest_row_chars,
1537 };
1538 let mut merged = false;
1539 tree.update_last(
1540 |last_transform| {
1541 if last_transform.block.is_none() {
1542 last_transform.summary.add_summary(&summary);
1543 merged = true;
1544 }
1545 },
1546 (),
1547 );
1548 if !merged {
1549 tree.push(
1550 Transform {
1551 summary,
1552 block: None,
1553 },
1554 (),
1555 );
1556 }
1557}
1558
1559impl BlockPoint {
1560 pub fn new(row: BlockRow, column: u32) -> Self {
1561 Self(Point::new(row.0, column))
1562 }
1563}
1564
1565impl Deref for BlockPoint {
1566 type Target = Point;
1567
1568 fn deref(&self) -> &Self::Target {
1569 &self.0
1570 }
1571}
1572
1573impl std::ops::DerefMut for BlockPoint {
1574 fn deref_mut(&mut self) -> &mut Self::Target {
1575 &mut self.0
1576 }
1577}
1578
1579impl Deref for BlockMapReader<'_> {
1580 type Target = BlockSnapshot;
1581
1582 fn deref(&self) -> &Self::Target {
1583 &self.snapshot
1584 }
1585}
1586
1587impl DerefMut for BlockMapReader<'_> {
1588 fn deref_mut(&mut self) -> &mut Self::Target {
1589 &mut self.snapshot
1590 }
1591}
1592
1593impl BlockMapReader<'_> {
1594 #[ztracing::instrument(skip_all)]
1595 pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
1596 let block = self.blocks.iter().find(|block| block.id == block_id)?;
1597 let buffer_row = block
1598 .start()
1599 .to_point(self.wrap_snapshot.buffer_snapshot())
1600 .row;
1601 let wrap_row = self
1602 .wrap_snapshot
1603 .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
1604 .row();
1605 let start_wrap_row = self
1606 .wrap_snapshot
1607 .prev_row_boundary(WrapPoint::new(wrap_row, 0));
1608 let end_wrap_row = self
1609 .wrap_snapshot
1610 .next_row_boundary(WrapPoint::new(wrap_row, 0))
1611 .unwrap_or(self.wrap_snapshot.max_point().row() + WrapRow(1));
1612
1613 let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(());
1614 cursor.seek(&start_wrap_row, Bias::Left);
1615 while let Some(transform) = cursor.item() {
1616 if cursor.start().0 > end_wrap_row {
1617 break;
1618 }
1619
1620 if let Some(BlockId::Custom(id)) = transform.block.as_ref().map(|block| block.id())
1621 && id == block_id
1622 {
1623 return Some(cursor.start().1);
1624 }
1625 cursor.next();
1626 }
1627
1628 None
1629 }
1630}
1631
1632impl BlockMapWriter<'_> {
1633 #[ztracing::instrument(skip_all)]
1634 pub fn insert(
1635 &mut self,
1636 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
1637 ) -> Vec<CustomBlockId> {
1638 let blocks = blocks.into_iter();
1639 let mut ids = Vec::with_capacity(blocks.size_hint().1.unwrap_or(0));
1640 let mut edits = Patch::default();
1641 let wrap_snapshot = &*self.block_map.wrap_snapshot.borrow();
1642 let buffer = wrap_snapshot.buffer_snapshot();
1643
1644 let mut previous_wrap_row_range: Option<Range<WrapRow>> = None;
1645 for block in blocks {
1646 if let BlockPlacement::Replace(_) = &block.placement {
1647 debug_assert!(block.height.unwrap() > 0);
1648 }
1649
1650 let id = CustomBlockId(self.block_map.next_block_id.fetch_add(1, SeqCst));
1651 ids.push(id);
1652
1653 let start = block.placement.start().to_point(buffer);
1654 let end = block.placement.end().to_point(buffer);
1655 let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1656 let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1657
1658 let (start_row, end_row) = {
1659 previous_wrap_row_range.take_if(|range| {
1660 !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1661 });
1662 let range = previous_wrap_row_range.get_or_insert_with(|| {
1663 let start_row =
1664 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1665 let end_row = wrap_snapshot
1666 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1667 .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1668 start_row..end_row
1669 });
1670 (range.start, range.end)
1671 };
1672 let block_ix = match self
1673 .block_map
1674 .custom_blocks
1675 .binary_search_by(|probe| probe.placement.cmp(&block.placement, buffer))
1676 {
1677 Ok(ix) | Err(ix) => ix,
1678 };
1679 let new_block = Arc::new(CustomBlock {
1680 id,
1681 placement: block.placement.clone(),
1682 height: block.height,
1683 render: Arc::new(Mutex::new(block.render)),
1684 style: block.style,
1685 priority: block.priority,
1686 });
1687 self.block_map
1688 .custom_blocks
1689 .insert(block_ix, new_block.clone());
1690 self.block_map
1691 .custom_blocks_by_id
1692 .insert(id, new_block.clone());
1693
1694 // Insert a matching custom block in the companion (if any)
1695 if let Some(CompanionViewMut {
1696 entity_id: their_entity_id,
1697 wrap_snapshot: their_snapshot,
1698 block_map: their_block_map,
1699 companion,
1700 ..
1701 }) = self.companion.as_deref_mut()
1702 {
1703 their_block_map.insert_custom_block_into_companion(
1704 *their_entity_id,
1705 their_snapshot,
1706 &new_block,
1707 buffer,
1708 companion,
1709 );
1710 }
1711
1712 edits = edits.compose([Edit {
1713 old: start_row..end_row,
1714 new: start_row..end_row,
1715 }]);
1716 }
1717
1718 let default_patch = Patch::default();
1719 self.block_map.sync(
1720 wrap_snapshot,
1721 edits,
1722 self.companion.as_deref().map(
1723 |CompanionViewMut {
1724 entity_id,
1725 wrap_snapshot,
1726 companion,
1727 ..
1728 }| {
1729 CompanionView::new(*entity_id, wrap_snapshot, &default_patch, companion)
1730 },
1731 ),
1732 );
1733 ids
1734 }
1735
1736 #[ztracing::instrument(skip_all)]
1737 pub fn resize(&mut self, mut heights: HashMap<CustomBlockId, u32>) {
1738 let wrap_snapshot = &*self.block_map.wrap_snapshot.borrow();
1739 let buffer = wrap_snapshot.buffer_snapshot();
1740 let mut edits = Patch::default();
1741 let mut last_block_buffer_row = None;
1742
1743 for block in &mut self.block_map.custom_blocks {
1744 if let Some(new_height) = heights.remove(&block.id) {
1745 if let BlockPlacement::Replace(_) = &block.placement {
1746 debug_assert!(new_height > 0);
1747 }
1748
1749 if block.height != Some(new_height) {
1750 let new_block = CustomBlock {
1751 id: block.id,
1752 placement: block.placement.clone(),
1753 height: Some(new_height),
1754 style: block.style,
1755 render: block.render.clone(),
1756 priority: block.priority,
1757 };
1758 let new_block = Arc::new(new_block);
1759 *block = new_block.clone();
1760 self.block_map
1761 .custom_blocks_by_id
1762 .insert(block.id, new_block);
1763
1764 let start_row = block.placement.start().to_point(buffer).row;
1765 let end_row = block.placement.end().to_point(buffer).row;
1766 if last_block_buffer_row != Some(end_row) {
1767 last_block_buffer_row = Some(end_row);
1768 let start_wrap_row = wrap_snapshot
1769 .make_wrap_point(Point::new(start_row, 0), Bias::Left)
1770 .row();
1771 let end_wrap_row = wrap_snapshot
1772 .make_wrap_point(Point::new(end_row, 0), Bias::Left)
1773 .row();
1774 let start =
1775 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1776 let end = wrap_snapshot
1777 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1778 .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1779 edits.push(Edit {
1780 old: start..end,
1781 new: start..end,
1782 })
1783 }
1784 }
1785 }
1786 }
1787
1788 let default_patch = Patch::default();
1789 self.block_map.sync(
1790 wrap_snapshot,
1791 edits,
1792 self.companion.as_deref().map(
1793 |CompanionViewMut {
1794 entity_id,
1795 wrap_snapshot,
1796 companion,
1797 ..
1798 }| {
1799 CompanionView::new(*entity_id, wrap_snapshot, &default_patch, companion)
1800 },
1801 ),
1802 );
1803 }
1804
1805 #[ztracing::instrument(skip_all)]
1806 pub fn remove(&mut self, block_ids: HashSet<CustomBlockId>) {
1807 let wrap_snapshot = &*self.block_map.wrap_snapshot.borrow();
1808 let buffer = wrap_snapshot.buffer_snapshot();
1809 let mut edits = Patch::default();
1810 let mut last_block_buffer_row = None;
1811 let mut previous_wrap_row_range: Option<Range<WrapRow>> = None;
1812 self.block_map.custom_blocks.retain(|block| {
1813 if block_ids.contains(&block.id) {
1814 let start = block.placement.start().to_point(buffer);
1815 let end = block.placement.end().to_point(buffer);
1816 if last_block_buffer_row != Some(end.row) {
1817 last_block_buffer_row = Some(end.row);
1818 let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1819 let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1820 let (start_row, end_row) = {
1821 previous_wrap_row_range.take_if(|range| {
1822 !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1823 });
1824 let range = previous_wrap_row_range.get_or_insert_with(|| {
1825 let start_row =
1826 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1827 let end_row = wrap_snapshot
1828 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1829 .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1830 start_row..end_row
1831 });
1832 (range.start, range.end)
1833 };
1834
1835 edits.push(Edit {
1836 old: start_row..end_row,
1837 new: start_row..end_row,
1838 })
1839 }
1840 false
1841 } else {
1842 true
1843 }
1844 });
1845 self.block_map
1846 .custom_blocks_by_id
1847 .retain(|id, _| !block_ids.contains(id));
1848
1849 if let Some(CompanionViewMut {
1850 entity_id: their_entity_id,
1851 wrap_snapshot: their_snapshot,
1852 companion,
1853 block_map: their_block_map,
1854 ..
1855 }) = self.companion.as_deref_mut()
1856 {
1857 let their_block_ids: HashSet<_> = block_ids
1858 .iter()
1859 .filter_map(|my_block_id| {
1860 let mapping = companion.companion_custom_block_to_custom_block(*their_entity_id);
1861 let their_block_id =
1862 mapping.get(my_block_id)?;
1863 log::debug!("Removing custom block in the companion with id {their_block_id:?} for mine {my_block_id:?}");
1864 Some(*their_block_id)
1865 })
1866 .collect();
1867 for (lhs_id, rhs_id) in block_ids.iter().zip(their_block_ids.iter()) {
1868 if !companion.is_rhs(*their_entity_id) {
1869 companion.remove_custom_block_mapping(lhs_id, rhs_id);
1870 } else {
1871 companion.remove_custom_block_mapping(rhs_id, lhs_id);
1872 }
1873 }
1874 their_block_map
1875 .write(their_snapshot.clone(), Patch::default(), None)
1876 .remove(their_block_ids);
1877 }
1878
1879 let default_patch = Patch::default();
1880 self.block_map.sync(
1881 wrap_snapshot,
1882 edits,
1883 self.companion.as_deref().map(
1884 |CompanionViewMut {
1885 entity_id,
1886 wrap_snapshot,
1887 companion,
1888 ..
1889 }| {
1890 CompanionView::new(*entity_id, wrap_snapshot, &default_patch, companion)
1891 },
1892 ),
1893 );
1894 }
1895
1896 #[ztracing::instrument(skip_all)]
1897 pub fn remove_intersecting_replace_blocks(
1898 &mut self,
1899 ranges: impl IntoIterator<Item = Range<MultiBufferOffset>>,
1900 inclusive: bool,
1901 ) {
1902 let wrap_snapshot = self.block_map.wrap_snapshot.borrow();
1903 let mut blocks_to_remove = HashSet::default();
1904 for range in ranges {
1905 for block in self.blocks_intersecting_buffer_range(range, inclusive) {
1906 if matches!(block.placement, BlockPlacement::Replace(_)) {
1907 blocks_to_remove.insert(block.id);
1908 }
1909 }
1910 }
1911 drop(wrap_snapshot);
1912 self.remove(blocks_to_remove);
1913 }
1914
1915 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId) {
1916 self.block_map
1917 .buffers_with_disabled_headers
1918 .insert(buffer_id);
1919 }
1920
1921 #[ztracing::instrument(skip_all)]
1922 pub fn fold_buffers(
1923 &mut self,
1924 buffer_ids: impl IntoIterator<Item = BufferId>,
1925 multi_buffer: &MultiBuffer,
1926 cx: &App,
1927 ) {
1928 self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx);
1929 }
1930
1931 #[ztracing::instrument(skip_all)]
1932 pub fn unfold_buffers(
1933 &mut self,
1934 buffer_ids: impl IntoIterator<Item = BufferId>,
1935 multi_buffer: &MultiBuffer,
1936 cx: &App,
1937 ) {
1938 self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx);
1939 }
1940
1941 #[ztracing::instrument(skip_all)]
1942 fn fold_or_unfold_buffers(
1943 &mut self,
1944 fold: bool,
1945 buffer_ids: impl IntoIterator<Item = BufferId>,
1946 multi_buffer: &MultiBuffer,
1947 cx: &App,
1948 ) {
1949 let mut ranges = Vec::new();
1950 for buffer_id in buffer_ids {
1951 if fold {
1952 self.block_map.folded_buffers.insert(buffer_id);
1953 } else {
1954 self.block_map.folded_buffers.remove(&buffer_id);
1955 }
1956 ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
1957 }
1958 ranges.sort_unstable_by_key(|range| range.start);
1959
1960 let mut edits = Patch::default();
1961 let wrap_snapshot = self.block_map.wrap_snapshot.borrow().clone();
1962 for range in ranges {
1963 let last_edit_row = cmp::min(
1964 wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + WrapRow(1),
1965 wrap_snapshot.max_point().row(),
1966 ) + WrapRow(1);
1967 let range = wrap_snapshot.make_wrap_point(range.start, Bias::Left).row()..last_edit_row;
1968 edits.push(Edit {
1969 old: range.clone(),
1970 new: range,
1971 });
1972 }
1973
1974 let default_patch = Patch::default();
1975 self.block_map.sync(
1976 &wrap_snapshot,
1977 edits,
1978 self.companion.as_deref().map(
1979 |CompanionViewMut {
1980 entity_id,
1981 wrap_snapshot,
1982 companion,
1983 ..
1984 }| {
1985 CompanionView::new(*entity_id, wrap_snapshot, &default_patch, companion)
1986 },
1987 ),
1988 );
1989 }
1990
1991 #[ztracing::instrument(skip_all)]
1992 fn blocks_intersecting_buffer_range(
1993 &self,
1994 range: Range<MultiBufferOffset>,
1995 inclusive: bool,
1996 ) -> &[Arc<CustomBlock>] {
1997 if range.is_empty() && !inclusive {
1998 return &[];
1999 }
2000 let wrap_snapshot = self.block_map.wrap_snapshot.borrow();
2001 let buffer = wrap_snapshot.buffer_snapshot();
2002
2003 let start_block_ix = match self.block_map.custom_blocks.binary_search_by(|block| {
2004 let block_end = block.end().to_offset(buffer);
2005 block_end.cmp(&range.start).then(Ordering::Greater)
2006 }) {
2007 Ok(ix) | Err(ix) => ix,
2008 };
2009 let end_block_ix =
2010 match self.block_map.custom_blocks[start_block_ix..].binary_search_by(|block| {
2011 let block_start = block.start().to_offset(buffer);
2012 block_start.cmp(&range.end).then(if inclusive {
2013 Ordering::Less
2014 } else {
2015 Ordering::Greater
2016 })
2017 }) {
2018 Ok(ix) | Err(ix) => ix,
2019 };
2020
2021 &self.block_map.custom_blocks[start_block_ix..][..end_block_ix]
2022 }
2023}
2024
2025impl BlockSnapshot {
2026 #[cfg(test)]
2027 #[ztracing::instrument(skip_all)]
2028 pub fn text(&self) -> String {
2029 self.chunks(
2030 BlockRow(0)..self.transforms.summary().output_rows,
2031 false,
2032 false,
2033 Highlights::default(),
2034 )
2035 .map(|chunk| chunk.text)
2036 .collect()
2037 }
2038
2039 #[ztracing::instrument(skip_all)]
2040 pub(crate) fn chunks<'a>(
2041 &'a self,
2042 rows: Range<BlockRow>,
2043 language_aware: bool,
2044 masked: bool,
2045 highlights: Highlights<'a>,
2046 ) -> BlockChunks<'a> {
2047 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
2048
2049 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2050 cursor.seek(&rows.start, Bias::Right);
2051 let transform_output_start = cursor.start().0;
2052 let transform_input_start = cursor.start().1;
2053
2054 let mut input_start = transform_input_start;
2055 let mut input_end = transform_input_start;
2056 if let Some(transform) = cursor.item()
2057 && transform.block.is_none()
2058 {
2059 input_start += rows.start - transform_output_start;
2060 input_end += cmp::min(
2061 rows.end - transform_output_start,
2062 RowDelta(transform.summary.input_rows.0),
2063 );
2064 }
2065
2066 BlockChunks {
2067 input_chunks: self.wrap_snapshot.chunks(
2068 input_start..input_end,
2069 language_aware,
2070 highlights,
2071 ),
2072 input_chunk: Default::default(),
2073 transforms: cursor,
2074 output_row: rows.start,
2075 line_count_overflow: RowDelta(0),
2076 max_output_row,
2077 masked,
2078 }
2079 }
2080
2081 #[ztracing::instrument(skip_all)]
2082 pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
2083 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2084 cursor.seek(&start_row, Bias::Right);
2085 let Dimensions(output_start, input_start, _) = cursor.start();
2086 let overshoot = if cursor
2087 .item()
2088 .is_some_and(|transform| transform.block.is_none())
2089 {
2090 start_row - *output_start
2091 } else {
2092 RowDelta(0)
2093 };
2094 let input_start_row = *input_start + overshoot;
2095 BlockRows {
2096 transforms: cursor,
2097 input_rows: self.wrap_snapshot.row_infos(input_start_row),
2098 output_row: start_row,
2099 started: false,
2100 }
2101 }
2102
2103 #[ztracing::instrument(skip_all)]
2104 pub fn blocks_in_range(
2105 &self,
2106 rows: Range<BlockRow>,
2107 ) -> impl Iterator<Item = (BlockRow, &Block)> {
2108 let mut cursor = self.transforms.cursor::<BlockRow>(());
2109 cursor.seek(&rows.start, Bias::Left);
2110 while *cursor.start() < rows.start && cursor.end() <= rows.start {
2111 cursor.next();
2112 }
2113
2114 std::iter::from_fn(move || {
2115 while let Some(transform) = cursor.item() {
2116 let start_row = *cursor.start();
2117 if start_row > rows.end
2118 || (start_row == rows.end
2119 && transform
2120 .block
2121 .as_ref()
2122 .is_some_and(|block| block.height() > 0))
2123 {
2124 break;
2125 }
2126 if let Some(block) = &transform.block {
2127 cursor.next();
2128 return Some((start_row, block));
2129 } else {
2130 cursor.next();
2131 }
2132 }
2133 None
2134 })
2135 }
2136
2137 #[ztracing::instrument(skip_all)]
2138 pub(crate) fn sticky_header_excerpt(&self, position: f64) -> Option<StickyHeaderExcerpt<'_>> {
2139 let top_row = position as u32;
2140 let mut cursor = self.transforms.cursor::<BlockRow>(());
2141 cursor.seek(&BlockRow(top_row), Bias::Right);
2142
2143 while let Some(transform) = cursor.item() {
2144 match &transform.block {
2145 Some(
2146 Block::ExcerptBoundary { excerpt, .. } | Block::BufferHeader { excerpt, .. },
2147 ) => {
2148 return Some(StickyHeaderExcerpt { excerpt });
2149 }
2150 Some(block) if block.is_buffer_header() => return None,
2151 _ => {
2152 cursor.prev();
2153 continue;
2154 }
2155 }
2156 }
2157
2158 None
2159 }
2160
2161 #[ztracing::instrument(skip_all)]
2162 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
2163 let buffer = self.wrap_snapshot.buffer_snapshot();
2164 let wrap_point = match block_id {
2165 BlockId::Custom(custom_block_id) => {
2166 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
2167 return Some(Block::Custom(custom_block.clone()));
2168 }
2169 BlockId::ExcerptBoundary(next_excerpt_id) => {
2170 let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
2171 self.wrap_snapshot
2172 .make_wrap_point(excerpt_range.start, Bias::Left)
2173 }
2174 BlockId::FoldedBuffer(excerpt_id) => self
2175 .wrap_snapshot
2176 .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
2177 BlockId::Spacer(_) => return None,
2178 };
2179 let wrap_row = wrap_point.row();
2180
2181 let mut cursor = self.transforms.cursor::<WrapRow>(());
2182 cursor.seek(&wrap_row, Bias::Left);
2183
2184 while let Some(transform) = cursor.item() {
2185 if let Some(block) = transform.block.as_ref() {
2186 if block.id() == block_id {
2187 return Some(block.clone());
2188 }
2189 } else if *cursor.start() > wrap_row {
2190 break;
2191 }
2192
2193 cursor.next();
2194 }
2195
2196 None
2197 }
2198
2199 #[ztracing::instrument(skip_all)]
2200 pub fn max_point(&self) -> BlockPoint {
2201 let row = self
2202 .transforms
2203 .summary()
2204 .output_rows
2205 .saturating_sub(RowDelta(1));
2206 BlockPoint::new(row, self.line_len(row))
2207 }
2208
2209 #[ztracing::instrument(skip_all)]
2210 pub fn longest_row(&self) -> BlockRow {
2211 self.transforms.summary().longest_row
2212 }
2213
2214 #[ztracing::instrument(skip_all)]
2215 pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
2216 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2217 cursor.seek(&range.start, Bias::Right);
2218
2219 let mut longest_row = range.start;
2220 let mut longest_row_chars = 0;
2221 if let Some(transform) = cursor.item() {
2222 if transform.block.is_none() {
2223 let &Dimensions(output_start, input_start, _) = cursor.start();
2224 let overshoot = range.start - output_start;
2225 let wrap_start_row = input_start + WrapRow(overshoot.0);
2226 let wrap_end_row = cmp::min(
2227 input_start + WrapRow((range.end - output_start).0),
2228 cursor.end().1,
2229 );
2230 let summary = self
2231 .wrap_snapshot
2232 .text_summary_for_range(wrap_start_row..wrap_end_row);
2233 longest_row = BlockRow(range.start.0 + summary.longest_row);
2234 longest_row_chars = summary.longest_row_chars;
2235 }
2236 cursor.next();
2237 }
2238
2239 let cursor_start_row = cursor.start().0;
2240 if range.end > cursor_start_row {
2241 let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right);
2242 if summary.longest_row_chars > longest_row_chars {
2243 longest_row = cursor_start_row + summary.longest_row;
2244 longest_row_chars = summary.longest_row_chars;
2245 }
2246
2247 if let Some(transform) = cursor.item()
2248 && transform.block.is_none()
2249 {
2250 let &Dimensions(output_start, input_start, _) = cursor.start();
2251 let overshoot = range.end - output_start;
2252 let wrap_start_row = input_start;
2253 let wrap_end_row = input_start + overshoot;
2254 let summary = self
2255 .wrap_snapshot
2256 .text_summary_for_range(wrap_start_row..wrap_end_row);
2257 if summary.longest_row_chars > longest_row_chars {
2258 longest_row = output_start + RowDelta(summary.longest_row);
2259 }
2260 }
2261 }
2262
2263 longest_row
2264 }
2265
2266 #[ztracing::instrument(skip_all)]
2267 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
2268 let (start, _, item) =
2269 self.transforms
2270 .find::<Dimensions<BlockRow, WrapRow>, _>((), &row, Bias::Right);
2271 if let Some(transform) = item {
2272 let Dimensions(output_start, input_start, _) = start;
2273 let overshoot = row - output_start;
2274 if transform.block.is_some() {
2275 0
2276 } else {
2277 self.wrap_snapshot.line_len(input_start + overshoot)
2278 }
2279 } else if row == BlockRow(0) {
2280 0
2281 } else {
2282 panic!("row out of range");
2283 }
2284 }
2285
2286 #[ztracing::instrument(skip_all)]
2287 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
2288 let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
2289 item.is_some_and(|t| t.block.is_some())
2290 }
2291
2292 #[ztracing::instrument(skip_all)]
2293 pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
2294 let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
2295 let Some(transform) = item else {
2296 return false;
2297 };
2298 matches!(transform.block, Some(Block::FoldedBuffer { .. }))
2299 }
2300
2301 #[ztracing::instrument(skip_all)]
2302 pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
2303 let wrap_point = self
2304 .wrap_snapshot
2305 .make_wrap_point(Point::new(row.0, 0), Bias::Left);
2306 let (_, _, item) = self
2307 .transforms
2308 .find::<WrapRow, _>((), &wrap_point.row(), Bias::Right);
2309 item.is_some_and(|transform| {
2310 transform
2311 .block
2312 .as_ref()
2313 .is_some_and(|block| block.is_replacement())
2314 })
2315 }
2316
2317 #[ztracing::instrument(skip_all)]
2318 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
2319 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2320 cursor.seek(&BlockRow(point.row), Bias::Right);
2321
2322 let max_input_row = self.transforms.summary().input_rows;
2323 let mut search_left = (bias == Bias::Left && cursor.start().1 > WrapRow(0))
2324 || cursor.end().1 == max_input_row;
2325 let mut reversed = false;
2326
2327 loop {
2328 if let Some(transform) = cursor.item() {
2329 let Dimensions(output_start_row, input_start_row, _) = cursor.start();
2330 let Dimensions(output_end_row, input_end_row, _) = cursor.end();
2331 let output_start = Point::new(output_start_row.0, 0);
2332 let input_start = Point::new(input_start_row.0, 0);
2333 let input_end = Point::new(input_end_row.0, 0);
2334
2335 match transform.block.as_ref() {
2336 Some(block) => {
2337 if block.is_replacement()
2338 && (((bias == Bias::Left || search_left) && output_start <= point.0)
2339 || (!search_left && output_start >= point.0))
2340 {
2341 return BlockPoint(output_start);
2342 }
2343 }
2344 None => {
2345 let input_point = if point.row >= output_end_row.0 {
2346 let line_len = self.wrap_snapshot.line_len(input_end_row - RowDelta(1));
2347 self.wrap_snapshot.clip_point(
2348 WrapPoint::new(input_end_row - RowDelta(1), line_len),
2349 bias,
2350 )
2351 } else {
2352 let output_overshoot = point.0.saturating_sub(output_start);
2353 self.wrap_snapshot
2354 .clip_point(WrapPoint(input_start + output_overshoot), bias)
2355 };
2356
2357 if (input_start..input_end).contains(&input_point.0) {
2358 let input_overshoot = input_point.0.saturating_sub(input_start);
2359 return BlockPoint(output_start + input_overshoot);
2360 }
2361 }
2362 }
2363
2364 if search_left {
2365 cursor.prev();
2366 } else {
2367 cursor.next();
2368 }
2369 } else if reversed {
2370 return self.max_point();
2371 } else {
2372 reversed = true;
2373 search_left = !search_left;
2374 cursor.seek(&BlockRow(point.row), Bias::Right);
2375 }
2376 }
2377 }
2378
2379 #[ztracing::instrument(skip_all)]
2380 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
2381 let (start, _, item) = self.transforms.find::<Dimensions<WrapRow, BlockRow>, _>(
2382 (),
2383 &wrap_point.row(),
2384 Bias::Right,
2385 );
2386 if let Some(transform) = item {
2387 if transform.block.is_some() {
2388 BlockPoint::new(start.1, 0)
2389 } else {
2390 let Dimensions(input_start_row, output_start_row, _) = start;
2391 let input_start = Point::new(input_start_row.0, 0);
2392 let output_start = Point::new(output_start_row.0, 0);
2393 let input_overshoot = wrap_point.0 - input_start;
2394 BlockPoint(output_start + input_overshoot)
2395 }
2396 } else {
2397 self.max_point()
2398 }
2399 }
2400
2401 #[ztracing::instrument(skip_all)]
2402 pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
2403 let (start, end, item) = self.transforms.find::<Dimensions<BlockRow, WrapRow>, _>(
2404 (),
2405 &BlockRow(block_point.row),
2406 Bias::Right,
2407 );
2408 if let Some(transform) = item {
2409 match transform.block.as_ref() {
2410 Some(block) => {
2411 if block.place_below() {
2412 let wrap_row = start.1 - RowDelta(1);
2413 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
2414 } else if block.place_above() {
2415 WrapPoint::new(start.1, 0)
2416 } else if bias == Bias::Left {
2417 WrapPoint::new(start.1, 0)
2418 } else {
2419 let wrap_row = end.1 - RowDelta(1);
2420 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
2421 }
2422 }
2423 None => {
2424 let overshoot = block_point.row() - start.0;
2425 let wrap_row = start.1 + RowDelta(overshoot.0);
2426 WrapPoint::new(wrap_row, block_point.column)
2427 }
2428 }
2429 } else {
2430 self.wrap_snapshot.max_point()
2431 }
2432 }
2433}
2434
2435impl BlockChunks<'_> {
2436 /// Go to the next transform
2437 #[ztracing::instrument(skip_all)]
2438 fn advance(&mut self) {
2439 self.input_chunk = Chunk::default();
2440 self.transforms.next();
2441 while let Some(transform) = self.transforms.item() {
2442 if transform
2443 .block
2444 .as_ref()
2445 .is_some_and(|block| block.height() == 0)
2446 {
2447 self.transforms.next();
2448 } else {
2449 break;
2450 }
2451 }
2452
2453 if self
2454 .transforms
2455 .item()
2456 .is_some_and(|transform| transform.block.is_none())
2457 {
2458 let start_input_row = self.transforms.start().1;
2459 let start_output_row = self.transforms.start().0;
2460 if start_output_row < self.max_output_row {
2461 let end_input_row = cmp::min(
2462 self.transforms.end().1,
2463 start_input_row + (self.max_output_row - start_output_row),
2464 );
2465 self.input_chunks.seek(start_input_row..end_input_row);
2466 }
2467 }
2468 }
2469}
2470
2471pub struct StickyHeaderExcerpt<'a> {
2472 pub excerpt: &'a ExcerptInfo,
2473}
2474
2475impl<'a> Iterator for BlockChunks<'a> {
2476 type Item = Chunk<'a>;
2477
2478 #[ztracing::instrument(skip_all)]
2479 fn next(&mut self) -> Option<Self::Item> {
2480 if self.output_row >= self.max_output_row {
2481 return None;
2482 }
2483
2484 if self.line_count_overflow > RowDelta(0) {
2485 let lines = self.line_count_overflow.0.min(u128::BITS);
2486 self.line_count_overflow.0 -= lines;
2487 self.output_row += RowDelta(lines);
2488 return Some(Chunk {
2489 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines as usize]) },
2490 chars: 1u128.unbounded_shl(lines).wrapping_sub(1),
2491 ..Default::default()
2492 });
2493 }
2494
2495 let transform = self.transforms.item()?;
2496 if transform.block.is_some() {
2497 let block_start = self.transforms.start().0;
2498 let mut block_end = self.transforms.end().0;
2499 self.advance();
2500 if self.transforms.item().is_none() {
2501 block_end -= RowDelta(1);
2502 }
2503
2504 let start_in_block = self.output_row - block_start;
2505 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
2506 let line_count = end_in_block - start_in_block;
2507 let lines = RowDelta(line_count.0.min(u128::BITS));
2508 self.line_count_overflow = line_count - lines;
2509 self.output_row += lines;
2510
2511 return Some(Chunk {
2512 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines.0 as usize]) },
2513 chars: 1u128.unbounded_shl(lines.0).wrapping_sub(1),
2514 ..Default::default()
2515 });
2516 }
2517
2518 if self.input_chunk.text.is_empty() {
2519 if let Some(input_chunk) = self.input_chunks.next() {
2520 self.input_chunk = input_chunk;
2521 } else {
2522 if self.output_row < self.max_output_row {
2523 self.output_row.0 += 1;
2524 self.advance();
2525 if self.transforms.item().is_some() {
2526 return Some(Chunk {
2527 text: "\n",
2528 chars: 1,
2529 ..Default::default()
2530 });
2531 }
2532 }
2533 return None;
2534 }
2535 }
2536
2537 let transform_end = self.transforms.end().0;
2538 let (prefix_rows, prefix_bytes) =
2539 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
2540 self.output_row += prefix_rows;
2541
2542 let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
2543 self.input_chunk.text = suffix;
2544 self.input_chunk.tabs >>= prefix_bytes.saturating_sub(1);
2545 self.input_chunk.chars >>= prefix_bytes.saturating_sub(1);
2546
2547 let mut tabs = self.input_chunk.tabs;
2548 let mut chars = self.input_chunk.chars;
2549
2550 if self.masked {
2551 // Not great for multibyte text because to keep cursor math correct we
2552 // need to have the same number of chars in the input as output.
2553 let chars_count = prefix.chars().count();
2554 let bullet_len = chars_count;
2555 prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
2556 chars = 1u128.unbounded_shl(bullet_len as u32).wrapping_sub(1);
2557 tabs = 0;
2558 }
2559
2560 let chunk = Chunk {
2561 text: prefix,
2562 tabs,
2563 chars,
2564 ..self.input_chunk.clone()
2565 };
2566
2567 if self.output_row == transform_end {
2568 self.advance();
2569 }
2570
2571 Some(chunk)
2572 }
2573}
2574
2575impl Iterator for BlockRows<'_> {
2576 type Item = RowInfo;
2577
2578 #[ztracing::instrument(skip_all)]
2579 fn next(&mut self) -> Option<Self::Item> {
2580 if self.started {
2581 self.output_row.0 += 1;
2582 } else {
2583 self.started = true;
2584 }
2585
2586 if self.output_row >= self.transforms.end().0 {
2587 self.transforms.next();
2588 while let Some(transform) = self.transforms.item() {
2589 if transform
2590 .block
2591 .as_ref()
2592 .is_some_and(|block| block.height() == 0)
2593 {
2594 self.transforms.next();
2595 } else {
2596 break;
2597 }
2598 }
2599
2600 let transform = self.transforms.item()?;
2601 if transform
2602 .block
2603 .as_ref()
2604 .is_none_or(|block| block.is_replacement())
2605 {
2606 self.input_rows.seek(self.transforms.start().1);
2607 }
2608 }
2609
2610 let transform = self.transforms.item()?;
2611 if transform.block.as_ref().is_none_or(|block| {
2612 block.is_replacement()
2613 && self.transforms.start().0 == self.output_row
2614 && matches!(block, Block::FoldedBuffer { .. }).not()
2615 }) {
2616 self.input_rows.next()
2617 } else {
2618 Some(RowInfo::default())
2619 }
2620 }
2621}
2622
2623impl sum_tree::Item for Transform {
2624 type Summary = TransformSummary;
2625
2626 fn summary(&self, _cx: ()) -> Self::Summary {
2627 self.summary.clone()
2628 }
2629}
2630
2631impl sum_tree::ContextLessSummary for TransformSummary {
2632 fn zero() -> Self {
2633 Default::default()
2634 }
2635
2636 fn add_summary(&mut self, summary: &Self) {
2637 if summary.longest_row_chars > self.longest_row_chars {
2638 self.longest_row = self.output_rows + summary.longest_row;
2639 self.longest_row_chars = summary.longest_row_chars;
2640 }
2641 self.input_rows += summary.input_rows;
2642 self.output_rows += summary.output_rows;
2643 }
2644}
2645
2646impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
2647 fn zero(_cx: ()) -> Self {
2648 Default::default()
2649 }
2650
2651 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
2652 *self += summary.input_rows;
2653 }
2654}
2655
2656impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
2657 fn zero(_cx: ()) -> Self {
2658 Default::default()
2659 }
2660
2661 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
2662 *self += summary.output_rows;
2663 }
2664}
2665
2666impl Deref for BlockContext<'_, '_> {
2667 type Target = App;
2668
2669 fn deref(&self) -> &Self::Target {
2670 self.app
2671 }
2672}
2673
2674impl DerefMut for BlockContext<'_, '_> {
2675 fn deref_mut(&mut self) -> &mut Self::Target {
2676 self.app
2677 }
2678}
2679
2680impl CustomBlock {
2681 #[ztracing::instrument(skip_all)]
2682 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
2683 self.render.lock()(cx)
2684 }
2685
2686 #[ztracing::instrument(skip_all)]
2687 pub fn start(&self) -> Anchor {
2688 *self.placement.start()
2689 }
2690
2691 #[ztracing::instrument(skip_all)]
2692 pub fn end(&self) -> Anchor {
2693 *self.placement.end()
2694 }
2695
2696 pub fn style(&self) -> BlockStyle {
2697 self.style
2698 }
2699}
2700
2701impl Debug for CustomBlock {
2702 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2703 f.debug_struct("Block")
2704 .field("id", &self.id)
2705 .field("placement", &self.placement)
2706 .field("height", &self.height)
2707 .field("style", &self.style)
2708 .field("priority", &self.priority)
2709 .finish_non_exhaustive()
2710 }
2711}
2712
2713// Count the number of bytes prior to a target point. If the string doesn't contain the target
2714// point, return its total extent. Otherwise return the target point itself.
2715fn offset_for_row(s: &str, target: RowDelta) -> (RowDelta, usize) {
2716 let mut row = 0;
2717 let mut offset = 0;
2718 for (ix, line) in s.split('\n').enumerate() {
2719 if ix > 0 {
2720 row += 1;
2721 offset += 1;
2722 }
2723 if row >= target.0 {
2724 break;
2725 }
2726 offset += line.len();
2727 }
2728 (RowDelta(row), offset)
2729}
2730
2731#[cfg(test)]
2732mod tests {
2733 use super::*;
2734 use crate::{
2735 display_map::{
2736 Companion, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
2737 },
2738 split::{convert_lhs_rows_to_rhs, convert_rhs_rows_to_lhs},
2739 test::test_font,
2740 };
2741 use buffer_diff::BufferDiff;
2742 use gpui::{App, AppContext as _, Element, div, font, px};
2743 use itertools::Itertools;
2744 use language::{Buffer, Capability};
2745 use multi_buffer::{ExcerptRange, MultiBuffer};
2746 use rand::prelude::*;
2747 use settings::SettingsStore;
2748 use std::env;
2749 use util::RandomCharIter;
2750
2751 #[gpui::test]
2752 fn test_offset_for_row() {
2753 assert_eq!(offset_for_row("", RowDelta(0)), (RowDelta(0), 0));
2754 assert_eq!(offset_for_row("", RowDelta(1)), (RowDelta(0), 0));
2755 assert_eq!(offset_for_row("abcd", RowDelta(0)), (RowDelta(0), 0));
2756 assert_eq!(offset_for_row("abcd", RowDelta(1)), (RowDelta(0), 4));
2757 assert_eq!(offset_for_row("\n", RowDelta(0)), (RowDelta(0), 0));
2758 assert_eq!(offset_for_row("\n", RowDelta(1)), (RowDelta(1), 1));
2759 assert_eq!(
2760 offset_for_row("abc\ndef\nghi", RowDelta(0)),
2761 (RowDelta(0), 0)
2762 );
2763 assert_eq!(
2764 offset_for_row("abc\ndef\nghi", RowDelta(1)),
2765 (RowDelta(1), 4)
2766 );
2767 assert_eq!(
2768 offset_for_row("abc\ndef\nghi", RowDelta(2)),
2769 (RowDelta(2), 8)
2770 );
2771 assert_eq!(
2772 offset_for_row("abc\ndef\nghi", RowDelta(3)),
2773 (RowDelta(2), 11)
2774 );
2775 }
2776
2777 #[gpui::test]
2778 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
2779 cx.update(init_test);
2780
2781 let text = "aaa\nbbb\nccc\nddd";
2782
2783 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2784 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2785 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2786 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2787 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2788 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2789 let (wrap_map, wraps_snapshot) =
2790 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2791 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2792
2793 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
2794 let block_ids = writer.insert(vec![
2795 BlockProperties {
2796 style: BlockStyle::Fixed,
2797 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2798 height: Some(1),
2799 render: Arc::new(|_| div().into_any()),
2800 priority: 0,
2801 },
2802 BlockProperties {
2803 style: BlockStyle::Fixed,
2804 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2805 height: Some(2),
2806 render: Arc::new(|_| div().into_any()),
2807 priority: 0,
2808 },
2809 BlockProperties {
2810 style: BlockStyle::Fixed,
2811 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2812 height: Some(3),
2813 render: Arc::new(|_| div().into_any()),
2814 priority: 0,
2815 },
2816 ]);
2817
2818 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
2819 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2820
2821 let blocks = snapshot
2822 .blocks_in_range(BlockRow(0)..BlockRow(8))
2823 .map(|(start_row, block)| {
2824 let block = block.as_custom().unwrap();
2825 (start_row.0..start_row.0 + block.height.unwrap(), block.id)
2826 })
2827 .collect::<Vec<_>>();
2828
2829 // When multiple blocks are on the same line, the newer blocks appear first.
2830 assert_eq!(
2831 blocks,
2832 &[
2833 (1..2, block_ids[0]),
2834 (2..4, block_ids[1]),
2835 (7..10, block_ids[2]),
2836 ]
2837 );
2838
2839 assert_eq!(
2840 snapshot.to_block_point(WrapPoint::new(WrapRow(0), 3)),
2841 BlockPoint::new(BlockRow(0), 3)
2842 );
2843 assert_eq!(
2844 snapshot.to_block_point(WrapPoint::new(WrapRow(1), 0)),
2845 BlockPoint::new(BlockRow(4), 0)
2846 );
2847 assert_eq!(
2848 snapshot.to_block_point(WrapPoint::new(WrapRow(3), 3)),
2849 BlockPoint::new(BlockRow(6), 3)
2850 );
2851
2852 assert_eq!(
2853 snapshot.to_wrap_point(BlockPoint::new(BlockRow(0), 3), Bias::Left),
2854 WrapPoint::new(WrapRow(0), 3)
2855 );
2856 assert_eq!(
2857 snapshot.to_wrap_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2858 WrapPoint::new(WrapRow(1), 0)
2859 );
2860 assert_eq!(
2861 snapshot.to_wrap_point(BlockPoint::new(BlockRow(3), 0), Bias::Left),
2862 WrapPoint::new(WrapRow(1), 0)
2863 );
2864 assert_eq!(
2865 snapshot.to_wrap_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
2866 WrapPoint::new(WrapRow(3), 3)
2867 );
2868
2869 assert_eq!(
2870 snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2871 BlockPoint::new(BlockRow(0), 3)
2872 );
2873 assert_eq!(
2874 snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Right),
2875 BlockPoint::new(BlockRow(4), 0)
2876 );
2877 assert_eq!(
2878 snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Left),
2879 BlockPoint::new(BlockRow(0), 3)
2880 );
2881 assert_eq!(
2882 snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Right),
2883 BlockPoint::new(BlockRow(4), 0)
2884 );
2885 assert_eq!(
2886 snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Left),
2887 BlockPoint::new(BlockRow(4), 0)
2888 );
2889 assert_eq!(
2890 snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Right),
2891 BlockPoint::new(BlockRow(4), 0)
2892 );
2893 assert_eq!(
2894 snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Left),
2895 BlockPoint::new(BlockRow(6), 3)
2896 );
2897 assert_eq!(
2898 snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Right),
2899 BlockPoint::new(BlockRow(6), 3)
2900 );
2901 assert_eq!(
2902 snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
2903 BlockPoint::new(BlockRow(6), 3)
2904 );
2905 assert_eq!(
2906 snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Right),
2907 BlockPoint::new(BlockRow(6), 3)
2908 );
2909
2910 assert_eq!(
2911 snapshot
2912 .row_infos(BlockRow(0))
2913 .map(|row_info| row_info.buffer_row)
2914 .collect::<Vec<_>>(),
2915 &[
2916 Some(0),
2917 None,
2918 None,
2919 None,
2920 Some(1),
2921 Some(2),
2922 Some(3),
2923 None,
2924 None,
2925 None
2926 ]
2927 );
2928
2929 // Insert a line break, separating two block decorations into separate lines.
2930 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2931 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
2932 buffer.snapshot(cx)
2933 });
2934
2935 let (inlay_snapshot, inlay_edits) =
2936 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
2937 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2938 let (tab_snapshot, tab_edits) =
2939 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
2940 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2941 wrap_map.sync(tab_snapshot, tab_edits, cx)
2942 });
2943 let snapshot = block_map.read(wraps_snapshot, wrap_edits, None);
2944 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
2945 }
2946
2947 #[gpui::test]
2948 fn test_multibuffer_headers_and_footers(cx: &mut App) {
2949 init_test(cx);
2950
2951 let buffer1 = cx.new(|cx| Buffer::local("Buffer 1", cx));
2952 let buffer2 = cx.new(|cx| Buffer::local("Buffer 2", cx));
2953 let buffer3 = cx.new(|cx| Buffer::local("Buffer 3", cx));
2954
2955 let mut excerpt_ids = Vec::new();
2956 let multi_buffer = cx.new(|cx| {
2957 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
2958 excerpt_ids.extend(multi_buffer.push_excerpts(
2959 buffer1.clone(),
2960 [ExcerptRange::new(0..buffer1.read(cx).len())],
2961 cx,
2962 ));
2963 excerpt_ids.extend(multi_buffer.push_excerpts(
2964 buffer2.clone(),
2965 [ExcerptRange::new(0..buffer2.read(cx).len())],
2966 cx,
2967 ));
2968 excerpt_ids.extend(multi_buffer.push_excerpts(
2969 buffer3.clone(),
2970 [ExcerptRange::new(0..buffer3.read(cx).len())],
2971 cx,
2972 ));
2973
2974 multi_buffer
2975 });
2976
2977 let font = test_font();
2978 let font_size = px(14.);
2979 let font_id = cx.text_system().resolve_font(&font);
2980 let mut wrap_width = px(0.);
2981 for c in "Buff".chars() {
2982 wrap_width += cx
2983 .text_system()
2984 .advance(font_id, font_size, c)
2985 .unwrap()
2986 .width;
2987 }
2988
2989 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2990 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
2991 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2992 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2993 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
2994
2995 let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2996 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
2997
2998 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
2999 assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
3000
3001 let blocks: Vec<_> = snapshot
3002 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3003 .map(|(row, block)| (row.0..row.0 + block.height(), block.id()))
3004 .collect();
3005 assert_eq!(
3006 blocks,
3007 vec![
3008 (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
3009 (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
3010 (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
3011 ]
3012 );
3013 }
3014
3015 #[gpui::test]
3016 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
3017 cx.update(init_test);
3018
3019 let text = "aaa\nbbb\nccc\nddd";
3020
3021 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3022 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3023 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
3024 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3025 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3026 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
3027 let (_wrap_map, wraps_snapshot) =
3028 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3029 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3030
3031 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3032 let block_ids = writer.insert(vec![
3033 BlockProperties {
3034 style: BlockStyle::Fixed,
3035 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3036 height: Some(1),
3037 render: Arc::new(|_| div().into_any()),
3038 priority: 0,
3039 },
3040 BlockProperties {
3041 style: BlockStyle::Fixed,
3042 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
3043 height: Some(2),
3044 render: Arc::new(|_| div().into_any()),
3045 priority: 0,
3046 },
3047 BlockProperties {
3048 style: BlockStyle::Fixed,
3049 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
3050 height: Some(3),
3051 render: Arc::new(|_| div().into_any()),
3052 priority: 0,
3053 },
3054 ]);
3055
3056 {
3057 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3058 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
3059
3060 let mut block_map_writer =
3061 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3062
3063 let mut new_heights = HashMap::default();
3064 new_heights.insert(block_ids[0], 2);
3065 block_map_writer.resize(new_heights);
3066 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3067 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3068 }
3069
3070 {
3071 let mut block_map_writer =
3072 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3073
3074 let mut new_heights = HashMap::default();
3075 new_heights.insert(block_ids[0], 1);
3076 block_map_writer.resize(new_heights);
3077
3078 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3079 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
3080 }
3081
3082 {
3083 let mut block_map_writer =
3084 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3085
3086 let mut new_heights = HashMap::default();
3087 new_heights.insert(block_ids[0], 0);
3088 block_map_writer.resize(new_heights);
3089
3090 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3091 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
3092 }
3093
3094 {
3095 let mut block_map_writer =
3096 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3097
3098 let mut new_heights = HashMap::default();
3099 new_heights.insert(block_ids[0], 3);
3100 block_map_writer.resize(new_heights);
3101
3102 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3103 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3104 }
3105
3106 {
3107 let mut block_map_writer =
3108 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3109
3110 let mut new_heights = HashMap::default();
3111 new_heights.insert(block_ids[0], 3);
3112 block_map_writer.resize(new_heights);
3113
3114 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3115 // Same height as before, should remain the same
3116 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3117 }
3118 }
3119
3120 #[gpui::test]
3121 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
3122 cx.update(init_test);
3123
3124 let text = "one two three\nfour five six\nseven eight";
3125
3126 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3127 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3128 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3129 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3130 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3131 let (_, wraps_snapshot) = cx.update(|cx| {
3132 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
3133 });
3134 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3135
3136 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3137 writer.insert(vec![
3138 BlockProperties {
3139 style: BlockStyle::Fixed,
3140 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
3141 render: Arc::new(|_| div().into_any()),
3142 height: Some(1),
3143 priority: 0,
3144 },
3145 BlockProperties {
3146 style: BlockStyle::Fixed,
3147 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
3148 render: Arc::new(|_| div().into_any()),
3149 height: Some(1),
3150 priority: 0,
3151 },
3152 ]);
3153
3154 // Blocks with an 'above' disposition go above their corresponding buffer line.
3155 // Blocks with a 'below' disposition go below their corresponding buffer line.
3156 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3157 assert_eq!(
3158 snapshot.text(),
3159 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
3160 );
3161 }
3162
3163 #[gpui::test]
3164 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
3165 cx.update(init_test);
3166
3167 let text = "line1\nline2\nline3\nline4\nline5";
3168
3169 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3170 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
3171 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3172 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3173 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3174 let tab_size = 1.try_into().unwrap();
3175 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
3176 let (wrap_map, wraps_snapshot) =
3177 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3178 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3179
3180 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3181 let replace_block_id = writer.insert(vec![BlockProperties {
3182 style: BlockStyle::Fixed,
3183 placement: BlockPlacement::Replace(
3184 buffer_snapshot.anchor_after(Point::new(1, 3))
3185 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
3186 ),
3187 height: Some(4),
3188 render: Arc::new(|_| div().into_any()),
3189 priority: 0,
3190 }])[0];
3191
3192 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3193 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3194
3195 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3196 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
3197 buffer.snapshot(cx)
3198 });
3199 let (inlay_snapshot, inlay_edits) =
3200 inlay_map.sync(buffer_snapshot, buffer_subscription.consume().into_inner());
3201 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3202 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3203 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3204 wrap_map.sync(tab_snapshot, tab_edits, cx)
3205 });
3206 let blocks_snapshot = block_map.read(wraps_snapshot, wrap_edits, None);
3207 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3208
3209 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3210 buffer.edit(
3211 [(
3212 Point::new(1, 5)..Point::new(1, 5),
3213 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
3214 )],
3215 None,
3216 cx,
3217 );
3218 buffer.snapshot(cx)
3219 });
3220 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
3221 buffer_snapshot.clone(),
3222 buffer_subscription.consume().into_inner(),
3223 );
3224 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3225 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3226 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3227 wrap_map.sync(tab_snapshot, tab_edits, cx)
3228 });
3229 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
3230 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3231
3232 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
3233 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3234 writer.insert(vec![
3235 BlockProperties {
3236 style: BlockStyle::Fixed,
3237 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
3238 height: Some(1),
3239 render: Arc::new(|_| div().into_any()),
3240 priority: 0,
3241 },
3242 BlockProperties {
3243 style: BlockStyle::Fixed,
3244 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
3245 height: Some(1),
3246 render: Arc::new(|_| div().into_any()),
3247 priority: 0,
3248 },
3249 BlockProperties {
3250 style: BlockStyle::Fixed,
3251 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
3252 height: Some(1),
3253 render: Arc::new(|_| div().into_any()),
3254 priority: 0,
3255 },
3256 ]);
3257 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3258 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3259
3260 // Ensure blocks inserted *inside* replaced region are hidden.
3261 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3262 writer.insert(vec![
3263 BlockProperties {
3264 style: BlockStyle::Fixed,
3265 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
3266 height: Some(1),
3267 render: Arc::new(|_| div().into_any()),
3268 priority: 0,
3269 },
3270 BlockProperties {
3271 style: BlockStyle::Fixed,
3272 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
3273 height: Some(1),
3274 render: Arc::new(|_| div().into_any()),
3275 priority: 0,
3276 },
3277 BlockProperties {
3278 style: BlockStyle::Fixed,
3279 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
3280 height: Some(1),
3281 render: Arc::new(|_| div().into_any()),
3282 priority: 0,
3283 },
3284 ]);
3285 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3286 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3287
3288 // Removing the replace block shows all the hidden blocks again.
3289 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3290 writer.remove(HashSet::from_iter([replace_block_id]));
3291 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3292 assert_eq!(
3293 blocks_snapshot.text(),
3294 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
3295 );
3296 }
3297
3298 #[gpui::test]
3299 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
3300 cx.update(init_test);
3301
3302 let text = "111\n222\n333\n444\n555\n666";
3303
3304 let buffer = cx.update(|cx| {
3305 MultiBuffer::build_multi(
3306 [
3307 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
3308 (
3309 text,
3310 vec![
3311 Point::new(1, 0)..Point::new(1, 3),
3312 Point::new(2, 0)..Point::new(2, 3),
3313 Point::new(3, 0)..Point::new(3, 3),
3314 ],
3315 ),
3316 (
3317 text,
3318 vec![
3319 Point::new(4, 0)..Point::new(4, 3),
3320 Point::new(5, 0)..Point::new(5, 3),
3321 ],
3322 ),
3323 ],
3324 cx,
3325 )
3326 });
3327 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3328 let buffer_ids = buffer_snapshot
3329 .excerpts()
3330 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3331 .dedup()
3332 .collect::<Vec<_>>();
3333 assert_eq!(buffer_ids.len(), 3);
3334 let buffer_id_1 = buffer_ids[0];
3335 let buffer_id_2 = buffer_ids[1];
3336 let buffer_id_3 = buffer_ids[2];
3337
3338 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3339 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3340 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3341 let (_, wrap_snapshot) =
3342 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3343 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3344 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3345
3346 assert_eq!(
3347 blocks_snapshot.text(),
3348 "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
3349 );
3350 assert_eq!(
3351 blocks_snapshot
3352 .row_infos(BlockRow(0))
3353 .map(|i| i.buffer_row)
3354 .collect::<Vec<_>>(),
3355 vec![
3356 None,
3357 None,
3358 Some(0),
3359 None,
3360 None,
3361 Some(1),
3362 None,
3363 Some(2),
3364 None,
3365 Some(3),
3366 None,
3367 None,
3368 Some(4),
3369 None,
3370 Some(5),
3371 ]
3372 );
3373
3374 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3375 let excerpt_blocks_2 = writer.insert(vec![
3376 BlockProperties {
3377 style: BlockStyle::Fixed,
3378 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3379 height: Some(1),
3380 render: Arc::new(|_| div().into_any()),
3381 priority: 0,
3382 },
3383 BlockProperties {
3384 style: BlockStyle::Fixed,
3385 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
3386 height: Some(1),
3387 render: Arc::new(|_| div().into_any()),
3388 priority: 0,
3389 },
3390 BlockProperties {
3391 style: BlockStyle::Fixed,
3392 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
3393 height: Some(1),
3394 render: Arc::new(|_| div().into_any()),
3395 priority: 0,
3396 },
3397 ]);
3398 let excerpt_blocks_3 = writer.insert(vec![
3399 BlockProperties {
3400 style: BlockStyle::Fixed,
3401 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
3402 height: Some(1),
3403 render: Arc::new(|_| div().into_any()),
3404 priority: 0,
3405 },
3406 BlockProperties {
3407 style: BlockStyle::Fixed,
3408 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
3409 height: Some(1),
3410 render: Arc::new(|_| div().into_any()),
3411 priority: 0,
3412 },
3413 ]);
3414
3415 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3416 assert_eq!(
3417 blocks_snapshot.text(),
3418 "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3419 );
3420 assert_eq!(
3421 blocks_snapshot
3422 .row_infos(BlockRow(0))
3423 .map(|i| i.buffer_row)
3424 .collect::<Vec<_>>(),
3425 vec![
3426 None,
3427 None,
3428 Some(0),
3429 None,
3430 None,
3431 None,
3432 Some(1),
3433 None,
3434 None,
3435 Some(2),
3436 None,
3437 Some(3),
3438 None,
3439 None,
3440 None,
3441 None,
3442 Some(4),
3443 None,
3444 Some(5),
3445 None,
3446 ]
3447 );
3448
3449 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3450 buffer.read_with(cx, |buffer, cx| {
3451 writer.fold_buffers([buffer_id_1], buffer, cx);
3452 });
3453 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
3454 style: BlockStyle::Fixed,
3455 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
3456 height: Some(1),
3457 render: Arc::new(|_| div().into_any()),
3458 priority: 0,
3459 }]);
3460 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3461 let blocks = blocks_snapshot
3462 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3463 .collect::<Vec<_>>();
3464 for (_, block) in &blocks {
3465 if let BlockId::Custom(custom_block_id) = block.id() {
3466 assert!(
3467 !excerpt_blocks_1.contains(&custom_block_id),
3468 "Should have no blocks from the folded buffer"
3469 );
3470 assert!(
3471 excerpt_blocks_2.contains(&custom_block_id)
3472 || excerpt_blocks_3.contains(&custom_block_id),
3473 "Should have only blocks from unfolded buffers"
3474 );
3475 }
3476 }
3477 assert_eq!(
3478 1,
3479 blocks
3480 .iter()
3481 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3482 .count(),
3483 "Should have one folded block, producing a header of the second buffer"
3484 );
3485 assert_eq!(
3486 blocks_snapshot.text(),
3487 "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3488 );
3489 assert_eq!(
3490 blocks_snapshot
3491 .row_infos(BlockRow(0))
3492 .map(|i| i.buffer_row)
3493 .collect::<Vec<_>>(),
3494 vec![
3495 None,
3496 None,
3497 None,
3498 None,
3499 None,
3500 Some(1),
3501 None,
3502 None,
3503 Some(2),
3504 None,
3505 Some(3),
3506 None,
3507 None,
3508 None,
3509 None,
3510 Some(4),
3511 None,
3512 Some(5),
3513 None,
3514 ]
3515 );
3516
3517 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3518 buffer.read_with(cx, |buffer, cx| {
3519 writer.fold_buffers([buffer_id_2], buffer, cx);
3520 });
3521 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3522 let blocks = blocks_snapshot
3523 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3524 .collect::<Vec<_>>();
3525 for (_, block) in &blocks {
3526 if let BlockId::Custom(custom_block_id) = block.id() {
3527 assert!(
3528 !excerpt_blocks_1.contains(&custom_block_id),
3529 "Should have no blocks from the folded buffer_1"
3530 );
3531 assert!(
3532 !excerpt_blocks_2.contains(&custom_block_id),
3533 "Should have no blocks from the folded buffer_2"
3534 );
3535 assert!(
3536 excerpt_blocks_3.contains(&custom_block_id),
3537 "Should have only blocks from unfolded buffers"
3538 );
3539 }
3540 }
3541 assert_eq!(
3542 2,
3543 blocks
3544 .iter()
3545 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3546 .count(),
3547 "Should have two folded blocks, producing headers"
3548 );
3549 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
3550 assert_eq!(
3551 blocks_snapshot
3552 .row_infos(BlockRow(0))
3553 .map(|i| i.buffer_row)
3554 .collect::<Vec<_>>(),
3555 vec![
3556 None,
3557 None,
3558 None,
3559 None,
3560 None,
3561 None,
3562 None,
3563 Some(4),
3564 None,
3565 Some(5),
3566 None,
3567 ]
3568 );
3569
3570 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3571 buffer.read_with(cx, |buffer, cx| {
3572 writer.unfold_buffers([buffer_id_1], buffer, cx);
3573 });
3574 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3575 let blocks = blocks_snapshot
3576 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3577 .collect::<Vec<_>>();
3578 for (_, block) in &blocks {
3579 if let BlockId::Custom(custom_block_id) = block.id() {
3580 assert!(
3581 !excerpt_blocks_2.contains(&custom_block_id),
3582 "Should have no blocks from the folded buffer_2"
3583 );
3584 assert!(
3585 excerpt_blocks_1.contains(&custom_block_id)
3586 || excerpt_blocks_3.contains(&custom_block_id),
3587 "Should have only blocks from unfolded buffers"
3588 );
3589 }
3590 }
3591 assert_eq!(
3592 1,
3593 blocks
3594 .iter()
3595 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3596 .count(),
3597 "Should be back to a single folded buffer, producing a header for buffer_2"
3598 );
3599 assert_eq!(
3600 blocks_snapshot.text(),
3601 "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
3602 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
3603 );
3604 assert_eq!(
3605 blocks_snapshot
3606 .row_infos(BlockRow(0))
3607 .map(|i| i.buffer_row)
3608 .collect::<Vec<_>>(),
3609 vec![
3610 None,
3611 None,
3612 None,
3613 Some(0),
3614 None,
3615 None,
3616 None,
3617 None,
3618 None,
3619 Some(4),
3620 None,
3621 Some(5),
3622 None,
3623 ]
3624 );
3625
3626 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3627 buffer.read_with(cx, |buffer, cx| {
3628 writer.fold_buffers([buffer_id_3], buffer, cx);
3629 });
3630 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3631 let blocks = blocks_snapshot
3632 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3633 .collect::<Vec<_>>();
3634 for (_, block) in &blocks {
3635 if let BlockId::Custom(custom_block_id) = block.id() {
3636 assert!(
3637 excerpt_blocks_1.contains(&custom_block_id),
3638 "Should have no blocks from the folded buffer_1"
3639 );
3640 assert!(
3641 !excerpt_blocks_2.contains(&custom_block_id),
3642 "Should have only blocks from unfolded buffers"
3643 );
3644 assert!(
3645 !excerpt_blocks_3.contains(&custom_block_id),
3646 "Should have only blocks from unfolded buffers"
3647 );
3648 }
3649 }
3650
3651 assert_eq!(
3652 blocks_snapshot.text(),
3653 "\n\n\n111\n\n\n\n",
3654 "Should have a single, first buffer left after folding"
3655 );
3656 assert_eq!(
3657 blocks_snapshot
3658 .row_infos(BlockRow(0))
3659 .map(|i| i.buffer_row)
3660 .collect::<Vec<_>>(),
3661 vec![None, None, None, Some(0), None, None, None, None,]
3662 );
3663 }
3664
3665 #[gpui::test]
3666 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
3667 cx.update(init_test);
3668
3669 let text = "111";
3670
3671 let buffer = cx.update(|cx| {
3672 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
3673 });
3674 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3675 let buffer_ids = buffer_snapshot
3676 .excerpts()
3677 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3678 .dedup()
3679 .collect::<Vec<_>>();
3680 assert_eq!(buffer_ids.len(), 1);
3681 let buffer_id = buffer_ids[0];
3682
3683 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
3684 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3685 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3686 let (_, wrap_snapshot) =
3687 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3688 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3689 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3690
3691 assert_eq!(blocks_snapshot.text(), "\n\n111");
3692
3693 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3694 buffer.read_with(cx, |buffer, cx| {
3695 writer.fold_buffers([buffer_id], buffer, cx);
3696 });
3697 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3698 let blocks = blocks_snapshot
3699 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3700 .collect::<Vec<_>>();
3701 assert_eq!(
3702 1,
3703 blocks
3704 .iter()
3705 .filter(|(_, block)| { matches!(block, Block::FoldedBuffer { .. }) })
3706 .count(),
3707 "Should have one folded block, producing a header of the second buffer"
3708 );
3709 assert_eq!(blocks_snapshot.text(), "\n");
3710 assert_eq!(
3711 blocks_snapshot
3712 .row_infos(BlockRow(0))
3713 .map(|i| i.buffer_row)
3714 .collect::<Vec<_>>(),
3715 vec![None, None],
3716 "When fully folded, should be no buffer rows"
3717 );
3718 }
3719
3720 #[gpui::test(iterations = 60)]
3721 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
3722 cx.update(init_test);
3723
3724 let operations = env::var("OPERATIONS")
3725 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3726 .unwrap_or(10);
3727
3728 let wrap_width = if rng.random_bool(0.2) {
3729 None
3730 } else {
3731 Some(px(rng.random_range(0.0..=100.0)))
3732 };
3733 let tab_size = 1.try_into().unwrap();
3734 let font_size = px(14.0);
3735 let buffer_start_header_height = rng.random_range(1..=5);
3736 let excerpt_header_height = rng.random_range(1..=5);
3737
3738 log::info!("Wrap width: {:?}", wrap_width);
3739 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
3740 let is_singleton = rng.random();
3741 let buffer = if is_singleton {
3742 let len = rng.random_range(0..10);
3743 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
3744 log::info!("initial singleton buffer text: {:?}", text);
3745 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
3746 } else {
3747 cx.update(|cx| {
3748 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
3749 log::info!(
3750 "initial multi-buffer text: {:?}",
3751 multibuffer.read(cx).read(cx).text()
3752 );
3753 multibuffer
3754 })
3755 };
3756
3757 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3758 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3759 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3760 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3761 let font = test_font();
3762 let (wrap_map, wraps_snapshot) =
3763 cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
3764 let mut block_map = BlockMap::new(
3765 wraps_snapshot,
3766 buffer_start_header_height,
3767 excerpt_header_height,
3768 );
3769
3770 for _ in 0..operations {
3771 let mut buffer_edits = Vec::new();
3772 match rng.random_range(0..=100) {
3773 0..=19 => {
3774 let wrap_width = if rng.random_bool(0.2) {
3775 None
3776 } else {
3777 Some(px(rng.random_range(0.0..=100.0)))
3778 };
3779 log::info!("Setting wrap width to {:?}", wrap_width);
3780 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
3781 }
3782 20..=39 => {
3783 let block_count = rng.random_range(1..=5);
3784 let block_properties = (0..block_count)
3785 .map(|_| {
3786 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
3787 let offset = buffer.clip_offset(
3788 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
3789 Bias::Left,
3790 );
3791 let mut min_height = 0;
3792 let placement = match rng.random_range(0..3) {
3793 0 => {
3794 min_height = 1;
3795 let start = buffer.anchor_after(offset);
3796 let end = buffer.anchor_after(buffer.clip_offset(
3797 rng.random_range(offset..=buffer.len()),
3798 Bias::Left,
3799 ));
3800 BlockPlacement::Replace(start..=end)
3801 }
3802 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3803 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3804 };
3805
3806 let height = rng.random_range(min_height..512);
3807 BlockProperties {
3808 style: BlockStyle::Fixed,
3809 placement,
3810 height: Some(height),
3811 render: Arc::new(|_| div().into_any()),
3812 priority: 0,
3813 }
3814 })
3815 .collect::<Vec<_>>();
3816
3817 let (inlay_snapshot, inlay_edits) =
3818 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3819 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3820 let (tab_snapshot, tab_edits) =
3821 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3822 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3823 wrap_map.sync(tab_snapshot, tab_edits, cx)
3824 });
3825 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3826 let block_ids =
3827 block_map.insert(block_properties.iter().map(|props| BlockProperties {
3828 placement: props.placement.clone(),
3829 height: props.height,
3830 style: props.style,
3831 render: Arc::new(|_| div().into_any()),
3832 priority: 0,
3833 }));
3834
3835 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3836 log::info!(
3837 "inserted block {:?} with height {:?} and id {:?}",
3838 block_properties
3839 .placement
3840 .as_ref()
3841 .map(|p| p.to_point(&buffer_snapshot)),
3842 block_properties.height,
3843 block_id
3844 );
3845 }
3846 }
3847 40..=59 if !block_map.custom_blocks.is_empty() => {
3848 let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3849 let block_ids_to_remove = block_map
3850 .custom_blocks
3851 .choose_multiple(&mut rng, block_count)
3852 .map(|block| block.id)
3853 .collect::<HashSet<_>>();
3854
3855 let (inlay_snapshot, inlay_edits) =
3856 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3857 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3858 let (tab_snapshot, tab_edits) =
3859 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3860 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3861 wrap_map.sync(tab_snapshot, tab_edits, cx)
3862 });
3863 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3864 log::info!(
3865 "removing {} blocks: {:?}",
3866 block_ids_to_remove.len(),
3867 block_ids_to_remove
3868 );
3869 block_map.remove(block_ids_to_remove);
3870 }
3871 60..=79 => {
3872 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3873 log::info!("Noop fold/unfold operation on a singleton buffer");
3874 continue;
3875 }
3876 let (inlay_snapshot, inlay_edits) =
3877 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3878 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3879 let (tab_snapshot, tab_edits) =
3880 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3881 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3882 wrap_map.sync(tab_snapshot, tab_edits, cx)
3883 });
3884 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3885 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3886 let folded_buffers: Vec<_> =
3887 block_map.block_map.folded_buffers.iter().cloned().collect();
3888 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3889 unfolded_buffers.dedup();
3890 log::debug!("All buffers {unfolded_buffers:?}");
3891 log::debug!("Folded buffers {folded_buffers:?}");
3892 unfolded_buffers.retain(|buffer_id| {
3893 !block_map.block_map.folded_buffers.contains(buffer_id)
3894 });
3895 (unfolded_buffers, folded_buffers)
3896 });
3897 let mut folded_count = folded_buffers.len();
3898 let mut unfolded_count = unfolded_buffers.len();
3899
3900 let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
3901 let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
3902 if !fold && !unfold {
3903 log::info!(
3904 "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3905 );
3906 continue;
3907 }
3908
3909 buffer.update(cx, |buffer, cx| {
3910 if fold {
3911 let buffer_to_fold =
3912 unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
3913 log::info!("Folding {buffer_to_fold:?}");
3914 let related_excerpts = buffer_snapshot
3915 .excerpts()
3916 .filter_map(|(excerpt_id, buffer, range)| {
3917 if buffer.remote_id() == buffer_to_fold {
3918 Some((
3919 excerpt_id,
3920 buffer
3921 .text_for_range(range.context)
3922 .collect::<String>(),
3923 ))
3924 } else {
3925 None
3926 }
3927 })
3928 .collect::<Vec<_>>();
3929 log::info!(
3930 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
3931 );
3932 folded_count += 1;
3933 unfolded_count -= 1;
3934 block_map.fold_buffers([buffer_to_fold], buffer, cx);
3935 }
3936 if unfold {
3937 let buffer_to_unfold =
3938 folded_buffers[rng.random_range(0..folded_buffers.len())];
3939 log::info!("Unfolding {buffer_to_unfold:?}");
3940 unfolded_count += 1;
3941 folded_count -= 1;
3942 block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
3943 }
3944 log::info!(
3945 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3946 );
3947 });
3948 }
3949 _ => {
3950 buffer.update(cx, |buffer, cx| {
3951 let mutation_count = rng.random_range(1..=5);
3952 let subscription = buffer.subscribe();
3953 buffer.randomly_mutate(&mut rng, mutation_count, cx);
3954 buffer_snapshot = buffer.snapshot(cx);
3955 buffer_edits.extend(subscription.consume());
3956 log::info!("buffer text: {:?}", buffer_snapshot.text());
3957 });
3958 }
3959 }
3960
3961 let (inlay_snapshot, inlay_edits) =
3962 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
3963 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3964 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3965 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3966 wrap_map.sync(tab_snapshot, tab_edits, cx)
3967 });
3968 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
3969 assert_eq!(
3970 blocks_snapshot.transforms.summary().input_rows,
3971 wraps_snapshot.max_point().row() + RowDelta(1)
3972 );
3973 log::info!("wrapped text: {:?}", wraps_snapshot.text());
3974 log::info!("blocks text: {:?}", blocks_snapshot.text());
3975
3976 let mut expected_blocks = Vec::new();
3977 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
3978 Some((
3979 block.placement.to_wrap_row(&wraps_snapshot)?,
3980 Block::Custom(block.clone()),
3981 ))
3982 }));
3983
3984 let mut inlay_point_cursor = wraps_snapshot.inlay_point_cursor();
3985 let mut tab_point_cursor = wraps_snapshot.tab_point_cursor();
3986 let mut fold_point_cursor = wraps_snapshot.fold_point_cursor();
3987 let mut wrap_point_cursor = wraps_snapshot.wrap_point_cursor();
3988
3989 // Note that this needs to be synced with the related section in BlockMap::sync
3990 expected_blocks.extend(block_map.header_and_footer_blocks(
3991 &buffer_snapshot,
3992 MultiBufferOffset(0)..,
3993 |point, bias| {
3994 wrap_point_cursor
3995 .map(
3996 tab_point_cursor
3997 .map(fold_point_cursor.map(inlay_point_cursor.map(point), bias)),
3998 )
3999 .row()
4000 },
4001 ));
4002
4003 BlockMap::sort_blocks(&mut expected_blocks);
4004
4005 for (placement, block) in &expected_blocks {
4006 log::info!(
4007 "Block {:?} placement: {:?} Height: {:?}",
4008 block.id(),
4009 placement,
4010 block.height()
4011 );
4012 }
4013
4014 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
4015
4016 let input_buffer_rows = buffer_snapshot
4017 .row_infos(MultiBufferRow(0))
4018 .map(|row| row.buffer_row)
4019 .collect::<Vec<_>>();
4020 let mut expected_buffer_rows = Vec::new();
4021 let mut expected_text = String::new();
4022 let mut expected_block_positions = Vec::new();
4023 let mut expected_replaced_buffer_rows = HashSet::default();
4024 let input_text = wraps_snapshot.text();
4025
4026 // Loop over the input lines, creating (N - 1) empty lines for
4027 // blocks of height N.
4028 //
4029 // It's important to note that output *starts* as one empty line,
4030 // so we special case row 0 to assume a leading '\n'.
4031 //
4032 // Linehood is the birthright of strings.
4033 let input_text_lines = input_text.split('\n').enumerate().peekable();
4034 let mut block_row = 0;
4035 for (wrap_row, input_line) in input_text_lines {
4036 let wrap_row = WrapRow(wrap_row as u32);
4037 let multibuffer_row = wraps_snapshot
4038 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
4039 .row;
4040
4041 // Create empty lines for the above block
4042 while let Some((placement, block)) = sorted_blocks_iter.peek() {
4043 if *placement.start() == wrap_row && block.place_above() {
4044 let (_, block) = sorted_blocks_iter.next().unwrap();
4045 expected_block_positions.push((block_row, block.id()));
4046 if block.height() > 0 {
4047 let text = "\n".repeat((block.height() - 1) as usize);
4048 if block_row > 0 {
4049 expected_text.push('\n')
4050 }
4051 expected_text.push_str(&text);
4052 for _ in 0..block.height() {
4053 expected_buffer_rows.push(None);
4054 }
4055 block_row += block.height();
4056 }
4057 } else {
4058 break;
4059 }
4060 }
4061
4062 // Skip lines within replace blocks, then create empty lines for the replace block's height
4063 let mut is_in_replace_block = false;
4064 if let Some((BlockPlacement::Replace(replace_range), block)) =
4065 sorted_blocks_iter.peek()
4066 && wrap_row >= *replace_range.start()
4067 {
4068 is_in_replace_block = true;
4069
4070 if wrap_row == *replace_range.start() {
4071 if matches!(block, Block::FoldedBuffer { .. }) {
4072 expected_buffer_rows.push(None);
4073 } else {
4074 expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
4075 }
4076 }
4077
4078 if wrap_row == *replace_range.end() {
4079 expected_block_positions.push((block_row, block.id()));
4080 let text = "\n".repeat((block.height() - 1) as usize);
4081 if block_row > 0 {
4082 expected_text.push('\n');
4083 }
4084 expected_text.push_str(&text);
4085
4086 for _ in 1..block.height() {
4087 expected_buffer_rows.push(None);
4088 }
4089 block_row += block.height();
4090
4091 sorted_blocks_iter.next();
4092 }
4093 }
4094
4095 if is_in_replace_block {
4096 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
4097 } else {
4098 let buffer_row = input_buffer_rows[multibuffer_row as usize];
4099 let soft_wrapped = wraps_snapshot
4100 .to_tab_point(WrapPoint::new(wrap_row, 0))
4101 .column()
4102 > 0;
4103 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
4104 if block_row > 0 {
4105 expected_text.push('\n');
4106 }
4107 expected_text.push_str(input_line);
4108 block_row += 1;
4109 }
4110
4111 while let Some((placement, block)) = sorted_blocks_iter.peek() {
4112 if *placement.end() == wrap_row && block.place_below() {
4113 let (_, block) = sorted_blocks_iter.next().unwrap();
4114 expected_block_positions.push((block_row, block.id()));
4115 if block.height() > 0 {
4116 let text = "\n".repeat((block.height() - 1) as usize);
4117 if block_row > 0 {
4118 expected_text.push('\n')
4119 }
4120 expected_text.push_str(&text);
4121 for _ in 0..block.height() {
4122 expected_buffer_rows.push(None);
4123 }
4124 block_row += block.height();
4125 }
4126 } else {
4127 break;
4128 }
4129 }
4130 }
4131
4132 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
4133 let expected_row_count = expected_lines.len();
4134 log::info!("expected text: {expected_text:?}");
4135
4136 assert_eq!(
4137 blocks_snapshot.max_point().row + 1,
4138 expected_row_count as u32,
4139 "actual row count != expected row count",
4140 );
4141 assert_eq!(
4142 blocks_snapshot.text(),
4143 expected_text,
4144 "actual text != expected text",
4145 );
4146
4147 for start_row in 0..expected_row_count {
4148 let end_row = rng.random_range(start_row + 1..=expected_row_count);
4149 let mut expected_text = expected_lines[start_row..end_row].join("\n");
4150 if end_row < expected_row_count {
4151 expected_text.push('\n');
4152 }
4153
4154 let actual_text = blocks_snapshot
4155 .chunks(
4156 BlockRow(start_row as u32)..BlockRow(end_row as u32),
4157 false,
4158 false,
4159 Highlights::default(),
4160 )
4161 .map(|chunk| chunk.text)
4162 .collect::<String>();
4163 assert_eq!(
4164 actual_text,
4165 expected_text,
4166 "incorrect text starting row row range {:?}",
4167 start_row..end_row
4168 );
4169 assert_eq!(
4170 blocks_snapshot
4171 .row_infos(BlockRow(start_row as u32))
4172 .map(|row_info| row_info.buffer_row)
4173 .collect::<Vec<_>>(),
4174 &expected_buffer_rows[start_row..],
4175 "incorrect buffer_rows starting at row {:?}",
4176 start_row
4177 );
4178 }
4179
4180 assert_eq!(
4181 blocks_snapshot
4182 .blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4183 .map(|(row, block)| (row.0, block.id()))
4184 .collect::<Vec<_>>(),
4185 expected_block_positions,
4186 "invalid blocks_in_range({:?})",
4187 0..expected_row_count
4188 );
4189
4190 for (_, expected_block) in
4191 blocks_snapshot.blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4192 {
4193 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
4194 assert_eq!(
4195 actual_block.map(|block| block.id()),
4196 Some(expected_block.id())
4197 );
4198 }
4199
4200 for (block_row, block_id) in expected_block_positions {
4201 if let BlockId::Custom(block_id) = block_id {
4202 assert_eq!(
4203 blocks_snapshot.row_for_block(block_id),
4204 Some(BlockRow(block_row))
4205 );
4206 }
4207 }
4208
4209 let mut expected_longest_rows = Vec::new();
4210 let mut longest_line_len = -1_isize;
4211 for (row, line) in expected_lines.iter().enumerate() {
4212 let row = row as u32;
4213
4214 assert_eq!(
4215 blocks_snapshot.line_len(BlockRow(row)),
4216 line.len() as u32,
4217 "invalid line len for row {}",
4218 row
4219 );
4220
4221 let line_char_count = line.chars().count() as isize;
4222 match line_char_count.cmp(&longest_line_len) {
4223 Ordering::Less => {}
4224 Ordering::Equal => expected_longest_rows.push(row),
4225 Ordering::Greater => {
4226 longest_line_len = line_char_count;
4227 expected_longest_rows.clear();
4228 expected_longest_rows.push(row);
4229 }
4230 }
4231 }
4232
4233 let longest_row = blocks_snapshot.longest_row();
4234 assert!(
4235 expected_longest_rows.contains(&longest_row.0),
4236 "incorrect longest row {}. expected {:?} with length {}",
4237 longest_row.0,
4238 expected_longest_rows,
4239 longest_line_len,
4240 );
4241
4242 for _ in 0..10 {
4243 let end_row = rng.random_range(1..=expected_lines.len());
4244 let start_row = rng.random_range(0..end_row);
4245
4246 let mut expected_longest_rows_in_range = vec![];
4247 let mut longest_line_len_in_range = 0;
4248
4249 let mut row = start_row as u32;
4250 for line in &expected_lines[start_row..end_row] {
4251 let line_char_count = line.chars().count() as isize;
4252 match line_char_count.cmp(&longest_line_len_in_range) {
4253 Ordering::Less => {}
4254 Ordering::Equal => expected_longest_rows_in_range.push(row),
4255 Ordering::Greater => {
4256 longest_line_len_in_range = line_char_count;
4257 expected_longest_rows_in_range.clear();
4258 expected_longest_rows_in_range.push(row);
4259 }
4260 }
4261 row += 1;
4262 }
4263
4264 let longest_row_in_range = blocks_snapshot
4265 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
4266 assert!(
4267 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
4268 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
4269 longest_row.0,
4270 start_row..end_row,
4271 expected_longest_rows_in_range,
4272 longest_line_len_in_range,
4273 );
4274 }
4275
4276 // Ensure that conversion between block points and wrap points is stable.
4277 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row().0 {
4278 let wrap_point = WrapPoint::new(WrapRow(row), 0);
4279 let block_point = blocks_snapshot.to_block_point(wrap_point);
4280 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
4281 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
4282 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
4283 assert_eq!(
4284 blocks_snapshot.to_block_point(right_wrap_point),
4285 block_point
4286 );
4287 }
4288
4289 let mut block_point = BlockPoint::new(BlockRow(0), 0);
4290 for c in expected_text.chars() {
4291 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
4292 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
4293 assert_eq!(
4294 blocks_snapshot
4295 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
4296 left_point,
4297 "block point: {:?}, wrap point: {:?}",
4298 block_point,
4299 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
4300 );
4301 assert_eq!(
4302 left_buffer_point,
4303 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
4304 "{:?} is not valid in buffer coordinates",
4305 left_point
4306 );
4307
4308 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
4309 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
4310 assert_eq!(
4311 blocks_snapshot
4312 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
4313 right_point,
4314 "block point: {:?}, wrap point: {:?}",
4315 block_point,
4316 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
4317 );
4318 assert_eq!(
4319 right_buffer_point,
4320 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
4321 "{:?} is not valid in buffer coordinates",
4322 right_point
4323 );
4324
4325 if c == '\n' {
4326 block_point.0 += Point::new(1, 0);
4327 } else {
4328 block_point.column += c.len_utf8() as u32;
4329 }
4330 }
4331
4332 for buffer_row in 0..=buffer_snapshot.max_point().row {
4333 let buffer_row = MultiBufferRow(buffer_row);
4334 assert_eq!(
4335 blocks_snapshot.is_line_replaced(buffer_row),
4336 expected_replaced_buffer_rows.contains(&buffer_row),
4337 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
4338 );
4339 }
4340 }
4341 }
4342
4343 #[gpui::test]
4344 fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
4345 cx.update(init_test);
4346
4347 let text = "abc\ndef\nghi\njkl\nmno";
4348 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
4349 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4350 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4351 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
4352 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4353 let (_wrap_map, wraps_snapshot) =
4354 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4355 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
4356
4357 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4358 let _block_id = writer.insert(vec![BlockProperties {
4359 style: BlockStyle::Fixed,
4360 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
4361 height: Some(1),
4362 render: Arc::new(|_| div().into_any()),
4363 priority: 0,
4364 }])[0];
4365
4366 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
4367 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4368
4369 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4370 writer.remove_intersecting_replace_blocks(
4371 [buffer_snapshot
4372 .anchor_after(Point::new(1, 0))
4373 .to_offset(&buffer_snapshot)
4374 ..buffer_snapshot
4375 .anchor_after(Point::new(1, 0))
4376 .to_offset(&buffer_snapshot)],
4377 false,
4378 );
4379 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
4380 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4381 }
4382
4383 #[gpui::test]
4384 fn test_folded_buffer_with_near_blocks(cx: &mut gpui::TestAppContext) {
4385 cx.update(init_test);
4386
4387 let text = "line 1\nline 2\nline 3";
4388 let buffer = cx.update(|cx| {
4389 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(2, 6)])], cx)
4390 });
4391 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4392 let buffer_ids = buffer_snapshot
4393 .excerpts()
4394 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
4395 .dedup()
4396 .collect::<Vec<_>>();
4397 assert_eq!(buffer_ids.len(), 1);
4398 let buffer_id = buffer_ids[0];
4399
4400 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4401 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4402 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4403 let (_, wrap_snapshot) =
4404 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4405 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4406
4407 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4408 writer.insert(vec![BlockProperties {
4409 style: BlockStyle::Fixed,
4410 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(0, 0))),
4411 height: Some(1),
4412 render: Arc::new(|_| div().into_any()),
4413 priority: 0,
4414 }]);
4415
4416 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4417 assert_eq!(blocks_snapshot.text(), "\nline 1\n\nline 2\nline 3");
4418
4419 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4420 buffer.read_with(cx, |buffer, cx| {
4421 writer.fold_buffers([buffer_id], buffer, cx);
4422 });
4423
4424 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4425 assert_eq!(blocks_snapshot.text(), "");
4426 }
4427
4428 #[gpui::test]
4429 fn test_folded_buffer_with_near_blocks_on_last_line(cx: &mut gpui::TestAppContext) {
4430 cx.update(init_test);
4431
4432 let text = "line 1\nline 2\nline 3\nline 4";
4433 let buffer = cx.update(|cx| {
4434 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(3, 6)])], cx)
4435 });
4436 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4437 let buffer_ids = buffer_snapshot
4438 .excerpts()
4439 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
4440 .dedup()
4441 .collect::<Vec<_>>();
4442 assert_eq!(buffer_ids.len(), 1);
4443 let buffer_id = buffer_ids[0];
4444
4445 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4446 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4447 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4448 let (_, wrap_snapshot) =
4449 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4450 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4451
4452 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4453 writer.insert(vec![BlockProperties {
4454 style: BlockStyle::Fixed,
4455 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(3, 6))),
4456 height: Some(1),
4457 render: Arc::new(|_| div().into_any()),
4458 priority: 0,
4459 }]);
4460
4461 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4462 assert_eq!(blocks_snapshot.text(), "\nline 1\nline 2\nline 3\nline 4\n");
4463
4464 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4465 buffer.read_with(cx, |buffer, cx| {
4466 writer.fold_buffers([buffer_id], buffer, cx);
4467 });
4468
4469 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4470 assert_eq!(blocks_snapshot.text(), "");
4471 }
4472
4473 #[gpui::test]
4474 fn test_companion_spacer_blocks(cx: &mut gpui::TestAppContext) {
4475 cx.update(init_test);
4476
4477 let base_text = "aaa\nbbb\nccc\nddd\nddd\nddd\neee\n";
4478 let main_text = "aaa\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n";
4479
4480 let rhs_buffer = cx.new(|cx| Buffer::local(main_text, cx));
4481 let diff = cx.new(|cx| {
4482 BufferDiff::new_with_base_text(base_text, &rhs_buffer.read(cx).text_snapshot(), cx)
4483 });
4484 let lhs_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer());
4485
4486 let lhs_multibuffer = cx.new(|cx| {
4487 let mut mb = MultiBuffer::new(Capability::ReadWrite);
4488 mb.push_excerpts(
4489 lhs_buffer.clone(),
4490 [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
4491 cx,
4492 );
4493 mb.add_inverted_diff(diff.clone(), cx);
4494 mb
4495 });
4496 let rhs_multibuffer = cx.new(|cx| {
4497 let mut mb = MultiBuffer::new(Capability::ReadWrite);
4498 mb.push_excerpts(
4499 rhs_buffer.clone(),
4500 [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
4501 cx,
4502 );
4503 mb.add_diff(diff.clone(), cx);
4504 mb
4505 });
4506 let subscription =
4507 rhs_multibuffer.update(cx, |rhs_multibuffer, _| rhs_multibuffer.subscribe());
4508
4509 let lhs_excerpt_id =
4510 lhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
4511 let rhs_excerpt_id =
4512 rhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
4513
4514 let lhs_buffer_snapshot = cx.update(|cx| lhs_multibuffer.read(cx).snapshot(cx));
4515 let (mut _lhs_inlay_map, lhs_inlay_snapshot) = InlayMap::new(lhs_buffer_snapshot);
4516 let (mut _lhs_fold_map, lhs_fold_snapshot) = FoldMap::new(lhs_inlay_snapshot);
4517 let (mut _lhs_tab_map, lhs_tab_snapshot) =
4518 TabMap::new(lhs_fold_snapshot, 4.try_into().unwrap());
4519 let (_lhs_wrap_map, lhs_wrap_snapshot) =
4520 cx.update(|cx| WrapMap::new(lhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4521 let lhs_block_map = BlockMap::new(lhs_wrap_snapshot.clone(), 0, 0);
4522
4523 let rhs_buffer_snapshot = cx.update(|cx| rhs_multibuffer.read(cx).snapshot(cx));
4524 let (mut rhs_inlay_map, rhs_inlay_snapshot) = InlayMap::new(rhs_buffer_snapshot);
4525 let (mut rhs_fold_map, rhs_fold_snapshot) = FoldMap::new(rhs_inlay_snapshot);
4526 let (mut rhs_tab_map, rhs_tab_snapshot) =
4527 TabMap::new(rhs_fold_snapshot, 4.try_into().unwrap());
4528 let (_rhs_wrap_map, rhs_wrap_snapshot) =
4529 cx.update(|cx| WrapMap::new(rhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4530 let rhs_block_map = BlockMap::new(rhs_wrap_snapshot.clone(), 0, 0);
4531
4532 let rhs_entity_id = rhs_multibuffer.entity_id();
4533
4534 let companion = cx.new(|_| {
4535 let mut c = Companion::new(
4536 rhs_entity_id,
4537 Default::default(),
4538 convert_rhs_rows_to_lhs,
4539 convert_lhs_rows_to_rhs,
4540 );
4541 c.add_excerpt_mapping(lhs_excerpt_id, rhs_excerpt_id);
4542 c
4543 });
4544
4545 let rhs_edits = Patch::new(vec![text::Edit {
4546 old: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4547 new: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4548 }]);
4549 let lhs_edits = Patch::new(vec![text::Edit {
4550 old: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4551 new: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4552 }]);
4553
4554 let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4555 rhs_block_map.read(
4556 rhs_wrap_snapshot.clone(),
4557 rhs_edits.clone(),
4558 Some(CompanionView::new(
4559 rhs_entity_id,
4560 &lhs_wrap_snapshot,
4561 &lhs_edits,
4562 companion,
4563 )),
4564 )
4565 });
4566
4567 let lhs_entity_id = lhs_multibuffer.entity_id();
4568 let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4569 lhs_block_map.read(
4570 lhs_wrap_snapshot.clone(),
4571 lhs_edits.clone(),
4572 Some(CompanionView::new(
4573 lhs_entity_id,
4574 &rhs_wrap_snapshot,
4575 &rhs_edits,
4576 companion,
4577 )),
4578 )
4579 });
4580
4581 // LHS:
4582 // aaa
4583 // - bbb
4584 // - ccc
4585 // ddd
4586 // ddd
4587 // ddd
4588 // <extra line>
4589 // <extra line>
4590 // <extra line>
4591 // *eee
4592 //
4593 // RHS:
4594 // aaa
4595 // <extra line>
4596 // <extra line>
4597 // ddd
4598 // ddd
4599 // ddd
4600 // + XXX
4601 // + YYY
4602 // + ZZZ
4603 // eee
4604
4605 assert_eq!(
4606 rhs_snapshot.snapshot.text(),
4607 "aaa\n\n\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4608 "RHS should have 2 spacer lines after 'aaa' to align with LHS's deleted lines"
4609 );
4610
4611 assert_eq!(
4612 lhs_snapshot.snapshot.text(),
4613 "aaa\nbbb\nccc\nddd\nddd\nddd\n\n\n\neee\n",
4614 "LHS should have 3 spacer lines in place of RHS's inserted lines"
4615 );
4616
4617 // LHS:
4618 // aaa
4619 // - bbb
4620 // - ccc
4621 // ddd
4622 // ddd
4623 // ddd
4624 // <extra line>
4625 // <extra line>
4626 // <extra line>
4627 // eee
4628 //
4629 // RHS:
4630 // aaa
4631 // <extra line>
4632 // <extra line>
4633 // ddd
4634 // foo
4635 // foo
4636 // foo
4637 // ddd
4638 // ddd
4639 // + XXX
4640 // + YYY
4641 // + ZZZ
4642 // eee
4643
4644 let rhs_buffer_snapshot = rhs_multibuffer.update(cx, |multibuffer, cx| {
4645 multibuffer.edit(
4646 [(Point::new(2, 0)..Point::new(2, 0), "foo\nfoo\nfoo\n")],
4647 None,
4648 cx,
4649 );
4650 multibuffer.snapshot(cx)
4651 });
4652
4653 let (rhs_inlay_snapshot, rhs_inlay_edits) =
4654 rhs_inlay_map.sync(rhs_buffer_snapshot, subscription.consume().into_inner());
4655 let (rhs_fold_snapshot, rhs_fold_edits) =
4656 rhs_fold_map.read(rhs_inlay_snapshot, rhs_inlay_edits);
4657 let (rhs_tab_snapshot, rhs_tab_edits) =
4658 rhs_tab_map.sync(rhs_fold_snapshot, rhs_fold_edits, 4.try_into().unwrap());
4659 let (rhs_wrap_snapshot, rhs_wrap_edits) = _rhs_wrap_map.update(cx, |wrap_map, cx| {
4660 wrap_map.sync(rhs_tab_snapshot, rhs_tab_edits, cx)
4661 });
4662
4663 let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4664 rhs_block_map.read(
4665 rhs_wrap_snapshot.clone(),
4666 rhs_wrap_edits.clone(),
4667 Some(CompanionView::new(
4668 rhs_entity_id,
4669 &lhs_wrap_snapshot,
4670 &Default::default(),
4671 companion,
4672 )),
4673 )
4674 });
4675
4676 let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4677 lhs_block_map.read(
4678 lhs_wrap_snapshot.clone(),
4679 Default::default(),
4680 Some(CompanionView::new(
4681 lhs_entity_id,
4682 &rhs_wrap_snapshot,
4683 &rhs_wrap_edits,
4684 companion,
4685 )),
4686 )
4687 });
4688
4689 assert_eq!(
4690 rhs_snapshot.snapshot.text(),
4691 "aaa\n\n\nddd\nfoo\nfoo\nfoo\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4692 "RHS should have the insertion"
4693 );
4694
4695 assert_eq!(
4696 lhs_snapshot.snapshot.text(),
4697 "aaa\nbbb\nccc\nddd\n\n\n\nddd\nddd\n\n\n\neee\n",
4698 "LHS should have 3 more spacer lines to balance the insertion"
4699 );
4700 }
4701
4702 fn init_test(cx: &mut gpui::App) {
4703 let settings = SettingsStore::test(cx);
4704 cx.set_global(settings);
4705 theme::init(theme::LoadThemes::JustBase, cx);
4706 assets::Assets.load_test_fonts(cx);
4707 }
4708
4709 impl Block {
4710 fn as_custom(&self) -> Option<&CustomBlock> {
4711 match self {
4712 Block::Custom(block) => Some(block),
4713 _ => None,
4714 }
4715 }
4716 }
4717
4718 impl BlockSnapshot {
4719 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
4720 self.wrap_snapshot
4721 .to_point(self.to_wrap_point(point, bias), bias)
4722 }
4723 }
4724}