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