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};
2856 use multi_buffer::{ExcerptRange, MultiBuffer};
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 mut excerpt_ids = Vec::new();
3067 let multi_buffer = cx.new(|cx| {
3068 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
3069 excerpt_ids.extend(multi_buffer.push_excerpts(
3070 buffer1.clone(),
3071 [ExcerptRange::new(0..buffer1.read(cx).len())],
3072 cx,
3073 ));
3074 excerpt_ids.extend(multi_buffer.push_excerpts(
3075 buffer2.clone(),
3076 [ExcerptRange::new(0..buffer2.read(cx).len())],
3077 cx,
3078 ));
3079 excerpt_ids.extend(multi_buffer.push_excerpts(
3080 buffer3.clone(),
3081 [ExcerptRange::new(0..buffer3.read(cx).len())],
3082 cx,
3083 ));
3084
3085 multi_buffer
3086 });
3087
3088 let font = test_font();
3089 let font_size = px(14.);
3090 let font_id = cx.text_system().resolve_font(&font);
3091 let mut wrap_width = px(0.);
3092 for c in "Buff".chars() {
3093 wrap_width += cx
3094 .text_system()
3095 .advance(font_id, font_size, c)
3096 .unwrap()
3097 .width;
3098 }
3099
3100 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
3101 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
3102 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3103 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3104 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
3105
3106 let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3107 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3108
3109 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
3110 assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
3111
3112 let blocks: Vec<_> = snapshot
3113 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3114 .map(|(row, block)| (row.0..row.0 + block.height(), block.id()))
3115 .collect();
3116 assert_eq!(
3117 blocks,
3118 vec![
3119 (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
3120 (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
3121 (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
3122 ]
3123 );
3124 }
3125
3126 #[gpui::test]
3127 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
3128 cx.update(init_test);
3129
3130 let text = "aaa\nbbb\nccc\nddd";
3131
3132 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3133 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3134 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
3135 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3136 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3137 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
3138 let (_wrap_map, wraps_snapshot) =
3139 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3140 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3141
3142 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3143 let block_ids = writer.insert(vec![
3144 BlockProperties {
3145 style: BlockStyle::Fixed,
3146 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3147 height: Some(1),
3148 render: Arc::new(|_| div().into_any()),
3149 priority: 0,
3150 },
3151 BlockProperties {
3152 style: BlockStyle::Fixed,
3153 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
3154 height: Some(2),
3155 render: Arc::new(|_| div().into_any()),
3156 priority: 0,
3157 },
3158 BlockProperties {
3159 style: BlockStyle::Fixed,
3160 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
3161 height: Some(3),
3162 render: Arc::new(|_| div().into_any()),
3163 priority: 0,
3164 },
3165 ]);
3166
3167 {
3168 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3169 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
3170
3171 let mut block_map_writer =
3172 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3173
3174 let mut new_heights = HashMap::default();
3175 new_heights.insert(block_ids[0], 2);
3176 block_map_writer.resize(new_heights);
3177 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3178 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3179 }
3180
3181 {
3182 let mut block_map_writer =
3183 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3184
3185 let mut new_heights = HashMap::default();
3186 new_heights.insert(block_ids[0], 1);
3187 block_map_writer.resize(new_heights);
3188
3189 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3190 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
3191 }
3192
3193 {
3194 let mut block_map_writer =
3195 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3196
3197 let mut new_heights = HashMap::default();
3198 new_heights.insert(block_ids[0], 0);
3199 block_map_writer.resize(new_heights);
3200
3201 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3202 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
3203 }
3204
3205 {
3206 let mut block_map_writer =
3207 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3208
3209 let mut new_heights = HashMap::default();
3210 new_heights.insert(block_ids[0], 3);
3211 block_map_writer.resize(new_heights);
3212
3213 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3214 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3215 }
3216
3217 {
3218 let mut block_map_writer =
3219 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3220
3221 let mut new_heights = HashMap::default();
3222 new_heights.insert(block_ids[0], 3);
3223 block_map_writer.resize(new_heights);
3224
3225 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3226 // Same height as before, should remain the same
3227 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3228 }
3229 }
3230
3231 #[gpui::test]
3232 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
3233 cx.update(init_test);
3234
3235 let text = "one two three\nfour five six\nseven eight";
3236
3237 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3238 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3239 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3240 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3241 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3242 let (_, wraps_snapshot) = cx.update(|cx| {
3243 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
3244 });
3245 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3246
3247 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3248 writer.insert(vec![
3249 BlockProperties {
3250 style: BlockStyle::Fixed,
3251 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
3252 render: Arc::new(|_| div().into_any()),
3253 height: Some(1),
3254 priority: 0,
3255 },
3256 BlockProperties {
3257 style: BlockStyle::Fixed,
3258 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
3259 render: Arc::new(|_| div().into_any()),
3260 height: Some(1),
3261 priority: 0,
3262 },
3263 ]);
3264
3265 // Blocks with an 'above' disposition go above their corresponding buffer line.
3266 // Blocks with a 'below' disposition go below their corresponding buffer line.
3267 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3268 assert_eq!(
3269 snapshot.text(),
3270 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
3271 );
3272 }
3273
3274 #[gpui::test]
3275 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
3276 cx.update(init_test);
3277
3278 let text = "line1\nline2\nline3\nline4\nline5";
3279
3280 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3281 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
3282 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3283 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3284 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3285 let tab_size = 1.try_into().unwrap();
3286 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
3287 let (wrap_map, wraps_snapshot) =
3288 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3289 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3290
3291 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3292 let replace_block_id = writer.insert(vec![BlockProperties {
3293 style: BlockStyle::Fixed,
3294 placement: BlockPlacement::Replace(
3295 buffer_snapshot.anchor_after(Point::new(1, 3))
3296 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
3297 ),
3298 height: Some(4),
3299 render: Arc::new(|_| div().into_any()),
3300 priority: 0,
3301 }])[0];
3302
3303 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3304 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3305
3306 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3307 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
3308 buffer.snapshot(cx)
3309 });
3310 let (inlay_snapshot, inlay_edits) =
3311 inlay_map.sync(buffer_snapshot, buffer_subscription.consume().into_inner());
3312 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3313 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3314 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3315 wrap_map.sync(tab_snapshot, tab_edits, cx)
3316 });
3317 let blocks_snapshot = block_map.read(wraps_snapshot, wrap_edits, None);
3318 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3319
3320 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3321 buffer.edit(
3322 [(
3323 Point::new(1, 5)..Point::new(1, 5),
3324 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
3325 )],
3326 None,
3327 cx,
3328 );
3329 buffer.snapshot(cx)
3330 });
3331 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
3332 buffer_snapshot.clone(),
3333 buffer_subscription.consume().into_inner(),
3334 );
3335 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3336 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3337 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3338 wrap_map.sync(tab_snapshot, tab_edits, cx)
3339 });
3340 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
3341 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3342
3343 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
3344 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3345 writer.insert(vec![
3346 BlockProperties {
3347 style: BlockStyle::Fixed,
3348 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
3349 height: Some(1),
3350 render: Arc::new(|_| div().into_any()),
3351 priority: 0,
3352 },
3353 BlockProperties {
3354 style: BlockStyle::Fixed,
3355 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
3356 height: Some(1),
3357 render: Arc::new(|_| div().into_any()),
3358 priority: 0,
3359 },
3360 BlockProperties {
3361 style: BlockStyle::Fixed,
3362 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
3363 height: Some(1),
3364 render: Arc::new(|_| div().into_any()),
3365 priority: 0,
3366 },
3367 ]);
3368 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3369 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3370
3371 // Ensure blocks inserted *inside* replaced region are hidden.
3372 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3373 writer.insert(vec![
3374 BlockProperties {
3375 style: BlockStyle::Fixed,
3376 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
3377 height: Some(1),
3378 render: Arc::new(|_| div().into_any()),
3379 priority: 0,
3380 },
3381 BlockProperties {
3382 style: BlockStyle::Fixed,
3383 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
3384 height: Some(1),
3385 render: Arc::new(|_| div().into_any()),
3386 priority: 0,
3387 },
3388 BlockProperties {
3389 style: BlockStyle::Fixed,
3390 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
3391 height: Some(1),
3392 render: Arc::new(|_| div().into_any()),
3393 priority: 0,
3394 },
3395 ]);
3396 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3397 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3398
3399 // Removing the replace block shows all the hidden blocks again.
3400 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3401 writer.remove(HashSet::from_iter([replace_block_id]));
3402 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3403 assert_eq!(
3404 blocks_snapshot.text(),
3405 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
3406 );
3407 }
3408
3409 #[gpui::test]
3410 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
3411 cx.update(init_test);
3412
3413 let text = "111\n222\n333\n444\n555\n666";
3414
3415 let buffer = cx.update(|cx| {
3416 MultiBuffer::build_multi(
3417 [
3418 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
3419 (
3420 text,
3421 vec![
3422 Point::new(1, 0)..Point::new(1, 3),
3423 Point::new(2, 0)..Point::new(2, 3),
3424 Point::new(3, 0)..Point::new(3, 3),
3425 ],
3426 ),
3427 (
3428 text,
3429 vec![
3430 Point::new(4, 0)..Point::new(4, 3),
3431 Point::new(5, 0)..Point::new(5, 3),
3432 ],
3433 ),
3434 ],
3435 cx,
3436 )
3437 });
3438 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3439 let buffer_ids = buffer_snapshot
3440 .excerpts()
3441 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3442 .dedup()
3443 .collect::<Vec<_>>();
3444 assert_eq!(buffer_ids.len(), 3);
3445 let buffer_id_1 = buffer_ids[0];
3446 let buffer_id_2 = buffer_ids[1];
3447 let buffer_id_3 = buffer_ids[2];
3448
3449 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3450 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3451 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3452 let (_, wrap_snapshot) =
3453 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3454 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3455 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3456
3457 assert_eq!(
3458 blocks_snapshot.text(),
3459 "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
3460 );
3461 assert_eq!(
3462 blocks_snapshot
3463 .row_infos(BlockRow(0))
3464 .map(|i| i.buffer_row)
3465 .collect::<Vec<_>>(),
3466 vec![
3467 None,
3468 None,
3469 Some(0),
3470 None,
3471 None,
3472 Some(1),
3473 None,
3474 Some(2),
3475 None,
3476 Some(3),
3477 None,
3478 None,
3479 Some(4),
3480 None,
3481 Some(5),
3482 ]
3483 );
3484
3485 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3486 let excerpt_blocks_2 = writer.insert(vec![
3487 BlockProperties {
3488 style: BlockStyle::Fixed,
3489 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3490 height: Some(1),
3491 render: Arc::new(|_| div().into_any()),
3492 priority: 0,
3493 },
3494 BlockProperties {
3495 style: BlockStyle::Fixed,
3496 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 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::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
3504 height: Some(1),
3505 render: Arc::new(|_| div().into_any()),
3506 priority: 0,
3507 },
3508 ]);
3509 let excerpt_blocks_3 = writer.insert(vec![
3510 BlockProperties {
3511 style: BlockStyle::Fixed,
3512 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
3513 height: Some(1),
3514 render: Arc::new(|_| div().into_any()),
3515 priority: 0,
3516 },
3517 BlockProperties {
3518 style: BlockStyle::Fixed,
3519 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
3520 height: Some(1),
3521 render: Arc::new(|_| div().into_any()),
3522 priority: 0,
3523 },
3524 ]);
3525
3526 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3527 assert_eq!(
3528 blocks_snapshot.text(),
3529 "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3530 );
3531 assert_eq!(
3532 blocks_snapshot
3533 .row_infos(BlockRow(0))
3534 .map(|i| i.buffer_row)
3535 .collect::<Vec<_>>(),
3536 vec![
3537 None,
3538 None,
3539 Some(0),
3540 None,
3541 None,
3542 None,
3543 Some(1),
3544 None,
3545 None,
3546 Some(2),
3547 None,
3548 Some(3),
3549 None,
3550 None,
3551 None,
3552 None,
3553 Some(4),
3554 None,
3555 Some(5),
3556 None,
3557 ]
3558 );
3559
3560 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3561 buffer.read_with(cx, |buffer, cx| {
3562 writer.fold_buffers([buffer_id_1], buffer, cx);
3563 });
3564 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
3565 style: BlockStyle::Fixed,
3566 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
3567 height: Some(1),
3568 render: Arc::new(|_| div().into_any()),
3569 priority: 0,
3570 }]);
3571 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3572 let blocks = blocks_snapshot
3573 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3574 .collect::<Vec<_>>();
3575 for (_, block) in &blocks {
3576 if let BlockId::Custom(custom_block_id) = block.id() {
3577 assert!(
3578 !excerpt_blocks_1.contains(&custom_block_id),
3579 "Should have no blocks from the folded buffer"
3580 );
3581 assert!(
3582 excerpt_blocks_2.contains(&custom_block_id)
3583 || excerpt_blocks_3.contains(&custom_block_id),
3584 "Should have only blocks from unfolded buffers"
3585 );
3586 }
3587 }
3588 assert_eq!(
3589 1,
3590 blocks
3591 .iter()
3592 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3593 .count(),
3594 "Should have one folded block, producing a header of the second buffer"
3595 );
3596 assert_eq!(
3597 blocks_snapshot.text(),
3598 "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3599 );
3600 assert_eq!(
3601 blocks_snapshot
3602 .row_infos(BlockRow(0))
3603 .map(|i| i.buffer_row)
3604 .collect::<Vec<_>>(),
3605 vec![
3606 None,
3607 None,
3608 None,
3609 None,
3610 None,
3611 Some(1),
3612 None,
3613 None,
3614 Some(2),
3615 None,
3616 Some(3),
3617 None,
3618 None,
3619 None,
3620 None,
3621 Some(4),
3622 None,
3623 Some(5),
3624 None,
3625 ]
3626 );
3627
3628 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3629 buffer.read_with(cx, |buffer, cx| {
3630 writer.fold_buffers([buffer_id_2], buffer, cx);
3631 });
3632 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3633 let blocks = blocks_snapshot
3634 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3635 .collect::<Vec<_>>();
3636 for (_, block) in &blocks {
3637 if let BlockId::Custom(custom_block_id) = block.id() {
3638 assert!(
3639 !excerpt_blocks_1.contains(&custom_block_id),
3640 "Should have no blocks from the folded buffer_1"
3641 );
3642 assert!(
3643 !excerpt_blocks_2.contains(&custom_block_id),
3644 "Should have no blocks from the folded buffer_2"
3645 );
3646 assert!(
3647 excerpt_blocks_3.contains(&custom_block_id),
3648 "Should have only blocks from unfolded buffers"
3649 );
3650 }
3651 }
3652 assert_eq!(
3653 2,
3654 blocks
3655 .iter()
3656 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3657 .count(),
3658 "Should have two folded blocks, producing headers"
3659 );
3660 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
3661 assert_eq!(
3662 blocks_snapshot
3663 .row_infos(BlockRow(0))
3664 .map(|i| i.buffer_row)
3665 .collect::<Vec<_>>(),
3666 vec![
3667 None,
3668 None,
3669 None,
3670 None,
3671 None,
3672 None,
3673 None,
3674 Some(4),
3675 None,
3676 Some(5),
3677 None,
3678 ]
3679 );
3680
3681 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3682 buffer.read_with(cx, |buffer, cx| {
3683 writer.unfold_buffers([buffer_id_1], buffer, cx);
3684 });
3685 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3686 let blocks = blocks_snapshot
3687 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3688 .collect::<Vec<_>>();
3689 for (_, block) in &blocks {
3690 if let BlockId::Custom(custom_block_id) = block.id() {
3691 assert!(
3692 !excerpt_blocks_2.contains(&custom_block_id),
3693 "Should have no blocks from the folded buffer_2"
3694 );
3695 assert!(
3696 excerpt_blocks_1.contains(&custom_block_id)
3697 || excerpt_blocks_3.contains(&custom_block_id),
3698 "Should have only blocks from unfolded buffers"
3699 );
3700 }
3701 }
3702 assert_eq!(
3703 1,
3704 blocks
3705 .iter()
3706 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3707 .count(),
3708 "Should be back to a single folded buffer, producing a header for buffer_2"
3709 );
3710 assert_eq!(
3711 blocks_snapshot.text(),
3712 "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
3713 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
3714 );
3715 assert_eq!(
3716 blocks_snapshot
3717 .row_infos(BlockRow(0))
3718 .map(|i| i.buffer_row)
3719 .collect::<Vec<_>>(),
3720 vec![
3721 None,
3722 None,
3723 None,
3724 Some(0),
3725 None,
3726 None,
3727 None,
3728 None,
3729 None,
3730 Some(4),
3731 None,
3732 Some(5),
3733 None,
3734 ]
3735 );
3736
3737 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3738 buffer.read_with(cx, |buffer, cx| {
3739 writer.fold_buffers([buffer_id_3], buffer, cx);
3740 });
3741 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3742 let blocks = blocks_snapshot
3743 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3744 .collect::<Vec<_>>();
3745 for (_, block) in &blocks {
3746 if let BlockId::Custom(custom_block_id) = block.id() {
3747 assert!(
3748 excerpt_blocks_1.contains(&custom_block_id),
3749 "Should have no blocks from the folded buffer_1"
3750 );
3751 assert!(
3752 !excerpt_blocks_2.contains(&custom_block_id),
3753 "Should have only blocks from unfolded buffers"
3754 );
3755 assert!(
3756 !excerpt_blocks_3.contains(&custom_block_id),
3757 "Should have only blocks from unfolded buffers"
3758 );
3759 }
3760 }
3761
3762 assert_eq!(
3763 blocks_snapshot.text(),
3764 "\n\n\n111\n\n\n\n",
3765 "Should have a single, first buffer left after folding"
3766 );
3767 assert_eq!(
3768 blocks_snapshot
3769 .row_infos(BlockRow(0))
3770 .map(|i| i.buffer_row)
3771 .collect::<Vec<_>>(),
3772 vec![None, None, None, Some(0), None, None, None, None,]
3773 );
3774 }
3775
3776 #[gpui::test]
3777 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
3778 cx.update(init_test);
3779
3780 let text = "111";
3781
3782 let buffer = cx.update(|cx| {
3783 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
3784 });
3785 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3786 let buffer_ids = buffer_snapshot
3787 .excerpts()
3788 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3789 .dedup()
3790 .collect::<Vec<_>>();
3791 assert_eq!(buffer_ids.len(), 1);
3792 let buffer_id = buffer_ids[0];
3793
3794 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
3795 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3796 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3797 let (_, wrap_snapshot) =
3798 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3799 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3800 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3801
3802 assert_eq!(blocks_snapshot.text(), "\n\n111");
3803
3804 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3805 buffer.read_with(cx, |buffer, cx| {
3806 writer.fold_buffers([buffer_id], buffer, cx);
3807 });
3808 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3809 let blocks = blocks_snapshot
3810 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3811 .collect::<Vec<_>>();
3812 assert_eq!(
3813 1,
3814 blocks
3815 .iter()
3816 .filter(|(_, block)| { matches!(block, Block::FoldedBuffer { .. }) })
3817 .count(),
3818 "Should have one folded block, producing a header of the second buffer"
3819 );
3820 assert_eq!(blocks_snapshot.text(), "\n");
3821 assert_eq!(
3822 blocks_snapshot
3823 .row_infos(BlockRow(0))
3824 .map(|i| i.buffer_row)
3825 .collect::<Vec<_>>(),
3826 vec![None, None],
3827 "When fully folded, should be no buffer rows"
3828 );
3829 }
3830
3831 #[gpui::test(iterations = 60)]
3832 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
3833 cx.update(init_test);
3834
3835 let operations = env::var("OPERATIONS")
3836 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3837 .unwrap_or(10);
3838
3839 let wrap_width = if rng.random_bool(0.2) {
3840 None
3841 } else {
3842 Some(px(rng.random_range(0.0..=100.0)))
3843 };
3844 let tab_size = 1.try_into().unwrap();
3845 let font_size = px(14.0);
3846 let buffer_start_header_height = rng.random_range(1..=5);
3847 let excerpt_header_height = rng.random_range(1..=5);
3848
3849 log::info!("Wrap width: {:?}", wrap_width);
3850 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
3851 let is_singleton = rng.random();
3852 let buffer = if is_singleton {
3853 let len = rng.random_range(0..10);
3854 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
3855 log::info!("initial singleton buffer text: {:?}", text);
3856 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
3857 } else {
3858 cx.update(|cx| {
3859 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
3860 log::info!(
3861 "initial multi-buffer text: {:?}",
3862 multibuffer.read(cx).read(cx).text()
3863 );
3864 multibuffer
3865 })
3866 };
3867
3868 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3869 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3870 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3871 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3872 let font = test_font();
3873 let (wrap_map, wraps_snapshot) =
3874 cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
3875 let mut block_map = BlockMap::new(
3876 wraps_snapshot,
3877 buffer_start_header_height,
3878 excerpt_header_height,
3879 );
3880
3881 for _ in 0..operations {
3882 let mut buffer_edits = Vec::new();
3883 match rng.random_range(0..=100) {
3884 0..=19 => {
3885 let wrap_width = if rng.random_bool(0.2) {
3886 None
3887 } else {
3888 Some(px(rng.random_range(0.0..=100.0)))
3889 };
3890 log::info!("Setting wrap width to {:?}", wrap_width);
3891 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
3892 }
3893 20..=39 => {
3894 let block_count = rng.random_range(1..=5);
3895 let block_properties = (0..block_count)
3896 .map(|_| {
3897 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
3898 let offset = buffer.clip_offset(
3899 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
3900 Bias::Left,
3901 );
3902 let mut min_height = 0;
3903 let placement = match rng.random_range(0..3) {
3904 0 => {
3905 min_height = 1;
3906 let start = buffer.anchor_after(offset);
3907 let end = buffer.anchor_after(buffer.clip_offset(
3908 rng.random_range(offset..=buffer.len()),
3909 Bias::Left,
3910 ));
3911 BlockPlacement::Replace(start..=end)
3912 }
3913 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3914 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3915 };
3916
3917 let height = rng.random_range(min_height..512);
3918 BlockProperties {
3919 style: BlockStyle::Fixed,
3920 placement,
3921 height: Some(height),
3922 render: Arc::new(|_| div().into_any()),
3923 priority: 0,
3924 }
3925 })
3926 .collect::<Vec<_>>();
3927
3928 let (inlay_snapshot, inlay_edits) =
3929 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3930 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3931 let (tab_snapshot, tab_edits) =
3932 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3933 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3934 wrap_map.sync(tab_snapshot, tab_edits, cx)
3935 });
3936 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3937 let block_ids =
3938 block_map.insert(block_properties.iter().map(|props| BlockProperties {
3939 placement: props.placement.clone(),
3940 height: props.height,
3941 style: props.style,
3942 render: Arc::new(|_| div().into_any()),
3943 priority: 0,
3944 }));
3945
3946 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3947 log::info!(
3948 "inserted block {:?} with height {:?} and id {:?}",
3949 block_properties
3950 .placement
3951 .as_ref()
3952 .map(|p| p.to_point(&buffer_snapshot)),
3953 block_properties.height,
3954 block_id
3955 );
3956 }
3957 }
3958 40..=59 if !block_map.custom_blocks.is_empty() => {
3959 let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3960 let block_ids_to_remove = block_map
3961 .custom_blocks
3962 .choose_multiple(&mut rng, block_count)
3963 .map(|block| block.id)
3964 .collect::<HashSet<_>>();
3965
3966 let (inlay_snapshot, inlay_edits) =
3967 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3968 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3969 let (tab_snapshot, tab_edits) =
3970 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3971 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3972 wrap_map.sync(tab_snapshot, tab_edits, cx)
3973 });
3974 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3975 log::info!(
3976 "removing {} blocks: {:?}",
3977 block_ids_to_remove.len(),
3978 block_ids_to_remove
3979 );
3980 block_map.remove(block_ids_to_remove);
3981 }
3982 60..=79 => {
3983 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3984 log::info!("Noop fold/unfold operation on a singleton buffer");
3985 continue;
3986 }
3987 let (inlay_snapshot, inlay_edits) =
3988 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3989 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3990 let (tab_snapshot, tab_edits) =
3991 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3992 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3993 wrap_map.sync(tab_snapshot, tab_edits, cx)
3994 });
3995 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3996 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3997 let folded_buffers: Vec<_> =
3998 block_map.block_map.folded_buffers.iter().cloned().collect();
3999 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
4000 unfolded_buffers.dedup();
4001 log::debug!("All buffers {unfolded_buffers:?}");
4002 log::debug!("Folded buffers {folded_buffers:?}");
4003 unfolded_buffers.retain(|buffer_id| {
4004 !block_map.block_map.folded_buffers.contains(buffer_id)
4005 });
4006 (unfolded_buffers, folded_buffers)
4007 });
4008 let mut folded_count = folded_buffers.len();
4009 let mut unfolded_count = unfolded_buffers.len();
4010
4011 let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
4012 let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
4013 if !fold && !unfold {
4014 log::info!(
4015 "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
4016 );
4017 continue;
4018 }
4019
4020 buffer.update(cx, |buffer, cx| {
4021 if fold {
4022 let buffer_to_fold =
4023 unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
4024 log::info!("Folding {buffer_to_fold:?}");
4025 let related_excerpts = buffer_snapshot
4026 .excerpts()
4027 .filter_map(|(excerpt_id, buffer, range)| {
4028 if buffer.remote_id() == buffer_to_fold {
4029 Some((
4030 excerpt_id,
4031 buffer
4032 .text_for_range(range.context)
4033 .collect::<String>(),
4034 ))
4035 } else {
4036 None
4037 }
4038 })
4039 .collect::<Vec<_>>();
4040 log::info!(
4041 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
4042 );
4043 folded_count += 1;
4044 unfolded_count -= 1;
4045 block_map.fold_buffers([buffer_to_fold], buffer, cx);
4046 }
4047 if unfold {
4048 let buffer_to_unfold =
4049 folded_buffers[rng.random_range(0..folded_buffers.len())];
4050 log::info!("Unfolding {buffer_to_unfold:?}");
4051 unfolded_count += 1;
4052 folded_count -= 1;
4053 block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
4054 }
4055 log::info!(
4056 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
4057 );
4058 });
4059 }
4060 _ => {
4061 buffer.update(cx, |buffer, cx| {
4062 let mutation_count = rng.random_range(1..=5);
4063 let subscription = buffer.subscribe();
4064 buffer.randomly_mutate(&mut rng, mutation_count, cx);
4065 buffer_snapshot = buffer.snapshot(cx);
4066 buffer_edits.extend(subscription.consume());
4067 log::info!("buffer text: {:?}", buffer_snapshot.text());
4068 });
4069 }
4070 }
4071
4072 let (inlay_snapshot, inlay_edits) =
4073 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
4074 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
4075 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
4076 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
4077 wrap_map.sync(tab_snapshot, tab_edits, cx)
4078 });
4079 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
4080 assert_eq!(
4081 blocks_snapshot.transforms.summary().input_rows,
4082 wraps_snapshot.max_point().row() + RowDelta(1)
4083 );
4084 log::info!("wrapped text: {:?}", wraps_snapshot.text());
4085 log::info!("blocks text: {:?}", blocks_snapshot.text());
4086
4087 let mut expected_blocks = Vec::new();
4088 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
4089 Some((
4090 block.placement.to_wrap_row(&wraps_snapshot)?,
4091 Block::Custom(block.clone()),
4092 ))
4093 }));
4094
4095 let mut inlay_point_cursor = wraps_snapshot.inlay_point_cursor();
4096 let mut tab_point_cursor = wraps_snapshot.tab_point_cursor();
4097 let mut fold_point_cursor = wraps_snapshot.fold_point_cursor();
4098 let mut wrap_point_cursor = wraps_snapshot.wrap_point_cursor();
4099
4100 // Note that this needs to be synced with the related section in BlockMap::sync
4101 expected_blocks.extend(block_map.header_and_footer_blocks(
4102 &buffer_snapshot,
4103 MultiBufferOffset(0)..,
4104 |point, bias| {
4105 wrap_point_cursor
4106 .map(
4107 tab_point_cursor.map(
4108 fold_point_cursor.map(inlay_point_cursor.map(point, bias), bias),
4109 ),
4110 )
4111 .row()
4112 },
4113 ));
4114
4115 BlockMap::sort_blocks(&mut expected_blocks);
4116
4117 for (placement, block) in &expected_blocks {
4118 log::info!(
4119 "Block {:?} placement: {:?} Height: {:?}",
4120 block.id(),
4121 placement,
4122 block.height()
4123 );
4124 }
4125
4126 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
4127
4128 let input_buffer_rows = buffer_snapshot
4129 .row_infos(MultiBufferRow(0))
4130 .map(|row| row.buffer_row)
4131 .collect::<Vec<_>>();
4132 let mut expected_buffer_rows = Vec::new();
4133 let mut expected_text = String::new();
4134 let mut expected_block_positions = Vec::new();
4135 let mut expected_replaced_buffer_rows = HashSet::default();
4136 let input_text = wraps_snapshot.text();
4137
4138 // Loop over the input lines, creating (N - 1) empty lines for
4139 // blocks of height N.
4140 //
4141 // It's important to note that output *starts* as one empty line,
4142 // so we special case row 0 to assume a leading '\n'.
4143 //
4144 // Linehood is the birthright of strings.
4145 let input_text_lines = input_text.split('\n').enumerate().peekable();
4146 let mut block_row = 0;
4147 for (wrap_row, input_line) in input_text_lines {
4148 let wrap_row = WrapRow(wrap_row as u32);
4149 let multibuffer_row = wraps_snapshot
4150 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
4151 .row;
4152
4153 // Create empty lines for the above block
4154 while let Some((placement, block)) = sorted_blocks_iter.peek() {
4155 if *placement.start() == wrap_row && block.place_above() {
4156 let (_, block) = sorted_blocks_iter.next().unwrap();
4157 expected_block_positions.push((block_row, block.id()));
4158 if block.height() > 0 {
4159 let text = "\n".repeat((block.height() - 1) as usize);
4160 if block_row > 0 {
4161 expected_text.push('\n')
4162 }
4163 expected_text.push_str(&text);
4164 for _ in 0..block.height() {
4165 expected_buffer_rows.push(None);
4166 }
4167 block_row += block.height();
4168 }
4169 } else {
4170 break;
4171 }
4172 }
4173
4174 // Skip lines within replace blocks, then create empty lines for the replace block's height
4175 let mut is_in_replace_block = false;
4176 if let Some((BlockPlacement::Replace(replace_range), block)) =
4177 sorted_blocks_iter.peek()
4178 && wrap_row >= *replace_range.start()
4179 {
4180 is_in_replace_block = true;
4181
4182 if wrap_row == *replace_range.start() {
4183 if matches!(block, Block::FoldedBuffer { .. }) {
4184 expected_buffer_rows.push(None);
4185 } else {
4186 expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
4187 }
4188 }
4189
4190 if wrap_row == *replace_range.end() {
4191 expected_block_positions.push((block_row, block.id()));
4192 let text = "\n".repeat((block.height() - 1) as usize);
4193 if block_row > 0 {
4194 expected_text.push('\n');
4195 }
4196 expected_text.push_str(&text);
4197
4198 for _ in 1..block.height() {
4199 expected_buffer_rows.push(None);
4200 }
4201 block_row += block.height();
4202
4203 sorted_blocks_iter.next();
4204 }
4205 }
4206
4207 if is_in_replace_block {
4208 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
4209 } else {
4210 let buffer_row = input_buffer_rows[multibuffer_row as usize];
4211 let soft_wrapped = wraps_snapshot
4212 .to_tab_point(WrapPoint::new(wrap_row, 0))
4213 .column()
4214 > 0;
4215 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
4216 if block_row > 0 {
4217 expected_text.push('\n');
4218 }
4219 expected_text.push_str(input_line);
4220 block_row += 1;
4221 }
4222
4223 while let Some((placement, block)) = sorted_blocks_iter.peek() {
4224 if *placement.end() == wrap_row && block.place_below() {
4225 let (_, block) = sorted_blocks_iter.next().unwrap();
4226 expected_block_positions.push((block_row, block.id()));
4227 if block.height() > 0 {
4228 let text = "\n".repeat((block.height() - 1) as usize);
4229 if block_row > 0 {
4230 expected_text.push('\n')
4231 }
4232 expected_text.push_str(&text);
4233 for _ in 0..block.height() {
4234 expected_buffer_rows.push(None);
4235 }
4236 block_row += block.height();
4237 }
4238 } else {
4239 break;
4240 }
4241 }
4242 }
4243
4244 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
4245 let expected_row_count = expected_lines.len();
4246 log::info!("expected text: {expected_text:?}");
4247
4248 assert_eq!(
4249 blocks_snapshot.max_point().row + 1,
4250 expected_row_count as u32,
4251 "actual row count != expected row count",
4252 );
4253 assert_eq!(
4254 blocks_snapshot.text(),
4255 expected_text,
4256 "actual text != expected text",
4257 );
4258
4259 for start_row in 0..expected_row_count {
4260 let end_row = rng.random_range(start_row + 1..=expected_row_count);
4261 let mut expected_text = expected_lines[start_row..end_row].join("\n");
4262 if end_row < expected_row_count {
4263 expected_text.push('\n');
4264 }
4265
4266 let actual_text = blocks_snapshot
4267 .chunks(
4268 BlockRow(start_row as u32)..BlockRow(end_row as u32),
4269 false,
4270 false,
4271 Highlights::default(),
4272 )
4273 .map(|chunk| chunk.text)
4274 .collect::<String>();
4275 assert_eq!(
4276 actual_text,
4277 expected_text,
4278 "incorrect text starting row row range {:?}",
4279 start_row..end_row
4280 );
4281 assert_eq!(
4282 blocks_snapshot
4283 .row_infos(BlockRow(start_row as u32))
4284 .map(|row_info| row_info.buffer_row)
4285 .collect::<Vec<_>>(),
4286 &expected_buffer_rows[start_row..],
4287 "incorrect buffer_rows starting at row {:?}",
4288 start_row
4289 );
4290 }
4291
4292 assert_eq!(
4293 blocks_snapshot
4294 .blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4295 .map(|(row, block)| (row.0, block.id()))
4296 .collect::<Vec<_>>(),
4297 expected_block_positions,
4298 "invalid blocks_in_range({:?})",
4299 0..expected_row_count
4300 );
4301
4302 for (_, expected_block) in
4303 blocks_snapshot.blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4304 {
4305 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
4306 assert_eq!(
4307 actual_block.map(|block| block.id()),
4308 Some(expected_block.id())
4309 );
4310 }
4311
4312 for (block_row, block_id) in expected_block_positions {
4313 if let BlockId::Custom(block_id) = block_id {
4314 assert_eq!(
4315 blocks_snapshot.row_for_block(block_id),
4316 Some(BlockRow(block_row))
4317 );
4318 }
4319 }
4320
4321 let mut expected_longest_rows = Vec::new();
4322 let mut longest_line_len = -1_isize;
4323 for (row, line) in expected_lines.iter().enumerate() {
4324 let row = row as u32;
4325
4326 assert_eq!(
4327 blocks_snapshot.line_len(BlockRow(row)),
4328 line.len() as u32,
4329 "invalid line len for row {}",
4330 row
4331 );
4332
4333 let line_char_count = line.chars().count() as isize;
4334 match line_char_count.cmp(&longest_line_len) {
4335 Ordering::Less => {}
4336 Ordering::Equal => expected_longest_rows.push(row),
4337 Ordering::Greater => {
4338 longest_line_len = line_char_count;
4339 expected_longest_rows.clear();
4340 expected_longest_rows.push(row);
4341 }
4342 }
4343 }
4344
4345 let longest_row = blocks_snapshot.longest_row();
4346 assert!(
4347 expected_longest_rows.contains(&longest_row.0),
4348 "incorrect longest row {}. expected {:?} with length {}",
4349 longest_row.0,
4350 expected_longest_rows,
4351 longest_line_len,
4352 );
4353
4354 for _ in 0..10 {
4355 let end_row = rng.random_range(1..=expected_lines.len());
4356 let start_row = rng.random_range(0..end_row);
4357
4358 let mut expected_longest_rows_in_range = vec![];
4359 let mut longest_line_len_in_range = 0;
4360
4361 let mut row = start_row as u32;
4362 for line in &expected_lines[start_row..end_row] {
4363 let line_char_count = line.chars().count() as isize;
4364 match line_char_count.cmp(&longest_line_len_in_range) {
4365 Ordering::Less => {}
4366 Ordering::Equal => expected_longest_rows_in_range.push(row),
4367 Ordering::Greater => {
4368 longest_line_len_in_range = line_char_count;
4369 expected_longest_rows_in_range.clear();
4370 expected_longest_rows_in_range.push(row);
4371 }
4372 }
4373 row += 1;
4374 }
4375
4376 let longest_row_in_range = blocks_snapshot
4377 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
4378 assert!(
4379 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
4380 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
4381 longest_row.0,
4382 start_row..end_row,
4383 expected_longest_rows_in_range,
4384 longest_line_len_in_range,
4385 );
4386 }
4387
4388 // Ensure that conversion between block points and wrap points is stable.
4389 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row().0 {
4390 let wrap_point = WrapPoint::new(WrapRow(row), 0);
4391 let block_point = blocks_snapshot.to_block_point(wrap_point);
4392 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
4393 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
4394 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
4395 assert_eq!(
4396 blocks_snapshot.to_block_point(right_wrap_point),
4397 block_point
4398 );
4399 }
4400
4401 let mut block_point = BlockPoint::new(BlockRow(0), 0);
4402 for c in expected_text.chars() {
4403 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
4404 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
4405 assert_eq!(
4406 blocks_snapshot
4407 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
4408 left_point,
4409 "block point: {:?}, wrap point: {:?}",
4410 block_point,
4411 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
4412 );
4413 assert_eq!(
4414 left_buffer_point,
4415 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
4416 "{:?} is not valid in buffer coordinates",
4417 left_point
4418 );
4419
4420 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
4421 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
4422 assert_eq!(
4423 blocks_snapshot
4424 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
4425 right_point,
4426 "block point: {:?}, wrap point: {:?}",
4427 block_point,
4428 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
4429 );
4430 assert_eq!(
4431 right_buffer_point,
4432 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
4433 "{:?} is not valid in buffer coordinates",
4434 right_point
4435 );
4436
4437 if c == '\n' {
4438 block_point.0 += Point::new(1, 0);
4439 } else {
4440 block_point.column += c.len_utf8() as u32;
4441 }
4442 }
4443
4444 for buffer_row in 0..=buffer_snapshot.max_point().row {
4445 let buffer_row = MultiBufferRow(buffer_row);
4446 assert_eq!(
4447 blocks_snapshot.is_line_replaced(buffer_row),
4448 expected_replaced_buffer_rows.contains(&buffer_row),
4449 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
4450 );
4451 }
4452 }
4453 }
4454
4455 #[gpui::test]
4456 fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
4457 cx.update(init_test);
4458
4459 let text = "abc\ndef\nghi\njkl\nmno";
4460 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
4461 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4462 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4463 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
4464 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4465 let (_wrap_map, wraps_snapshot) =
4466 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4467 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
4468
4469 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4470 let _block_id = writer.insert(vec![BlockProperties {
4471 style: BlockStyle::Fixed,
4472 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
4473 height: Some(1),
4474 render: Arc::new(|_| div().into_any()),
4475 priority: 0,
4476 }])[0];
4477
4478 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
4479 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4480
4481 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4482 writer.remove_intersecting_replace_blocks(
4483 [buffer_snapshot
4484 .anchor_after(Point::new(1, 0))
4485 .to_offset(&buffer_snapshot)
4486 ..buffer_snapshot
4487 .anchor_after(Point::new(1, 0))
4488 .to_offset(&buffer_snapshot)],
4489 false,
4490 );
4491 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
4492 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4493 }
4494
4495 #[gpui::test]
4496 fn test_folded_buffer_with_near_blocks(cx: &mut gpui::TestAppContext) {
4497 cx.update(init_test);
4498
4499 let text = "line 1\nline 2\nline 3";
4500 let buffer = cx.update(|cx| {
4501 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(2, 6)])], cx)
4502 });
4503 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4504 let buffer_ids = buffer_snapshot
4505 .excerpts()
4506 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
4507 .dedup()
4508 .collect::<Vec<_>>();
4509 assert_eq!(buffer_ids.len(), 1);
4510 let buffer_id = buffer_ids[0];
4511
4512 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4513 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4514 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4515 let (_, wrap_snapshot) =
4516 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4517 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4518
4519 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4520 writer.insert(vec![BlockProperties {
4521 style: BlockStyle::Fixed,
4522 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(0, 0))),
4523 height: Some(1),
4524 render: Arc::new(|_| div().into_any()),
4525 priority: 0,
4526 }]);
4527
4528 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4529 assert_eq!(blocks_snapshot.text(), "\nline 1\n\nline 2\nline 3");
4530
4531 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4532 buffer.read_with(cx, |buffer, cx| {
4533 writer.fold_buffers([buffer_id], buffer, cx);
4534 });
4535
4536 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4537 assert_eq!(blocks_snapshot.text(), "");
4538 }
4539
4540 #[gpui::test]
4541 fn test_folded_buffer_with_near_blocks_on_last_line(cx: &mut gpui::TestAppContext) {
4542 cx.update(init_test);
4543
4544 let text = "line 1\nline 2\nline 3\nline 4";
4545 let buffer = cx.update(|cx| {
4546 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(3, 6)])], cx)
4547 });
4548 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4549 let buffer_ids = buffer_snapshot
4550 .excerpts()
4551 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
4552 .dedup()
4553 .collect::<Vec<_>>();
4554 assert_eq!(buffer_ids.len(), 1);
4555 let buffer_id = buffer_ids[0];
4556
4557 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4558 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4559 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4560 let (_, wrap_snapshot) =
4561 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4562 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4563
4564 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4565 writer.insert(vec![BlockProperties {
4566 style: BlockStyle::Fixed,
4567 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(3, 6))),
4568 height: Some(1),
4569 render: Arc::new(|_| div().into_any()),
4570 priority: 0,
4571 }]);
4572
4573 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4574 assert_eq!(blocks_snapshot.text(), "\nline 1\nline 2\nline 3\nline 4\n");
4575
4576 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4577 buffer.read_with(cx, |buffer, cx| {
4578 writer.fold_buffers([buffer_id], buffer, cx);
4579 });
4580
4581 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4582 assert_eq!(blocks_snapshot.text(), "");
4583 }
4584
4585 #[gpui::test]
4586 fn test_companion_spacer_blocks(cx: &mut gpui::TestAppContext) {
4587 cx.update(init_test);
4588
4589 let base_text = "aaa\nbbb\nccc\nddd\nddd\nddd\neee\n";
4590 let main_text = "aaa\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n";
4591
4592 let rhs_buffer = cx.new(|cx| Buffer::local(main_text, cx));
4593 let diff = cx.new(|cx| {
4594 BufferDiff::new_with_base_text(base_text, &rhs_buffer.read(cx).text_snapshot(), cx)
4595 });
4596 let lhs_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4597
4598 let lhs_multibuffer = cx.new(|cx| {
4599 let mut mb = MultiBuffer::new(Capability::ReadWrite);
4600 mb.push_excerpts(
4601 lhs_buffer.clone(),
4602 [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
4603 cx,
4604 );
4605 mb.add_inverted_diff(diff.clone(), rhs_buffer.clone(), cx);
4606 mb
4607 });
4608 let rhs_multibuffer = cx.new(|cx| {
4609 let mut mb = MultiBuffer::new(Capability::ReadWrite);
4610 mb.push_excerpts(
4611 rhs_buffer.clone(),
4612 [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
4613 cx,
4614 );
4615 mb.add_diff(diff.clone(), cx);
4616 mb
4617 });
4618 let subscription =
4619 rhs_multibuffer.update(cx, |rhs_multibuffer, _| rhs_multibuffer.subscribe());
4620
4621 let lhs_excerpt_id =
4622 lhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
4623 let rhs_excerpt_id =
4624 rhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
4625
4626 let lhs_buffer_snapshot = cx.update(|cx| lhs_multibuffer.read(cx).snapshot(cx));
4627 let (mut _lhs_inlay_map, lhs_inlay_snapshot) = InlayMap::new(lhs_buffer_snapshot);
4628 let (mut _lhs_fold_map, lhs_fold_snapshot) = FoldMap::new(lhs_inlay_snapshot);
4629 let (mut _lhs_tab_map, lhs_tab_snapshot) =
4630 TabMap::new(lhs_fold_snapshot, 4.try_into().unwrap());
4631 let (_lhs_wrap_map, lhs_wrap_snapshot) =
4632 cx.update(|cx| WrapMap::new(lhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4633 let lhs_block_map = BlockMap::new(lhs_wrap_snapshot.clone(), 0, 0);
4634
4635 let rhs_buffer_snapshot = cx.update(|cx| rhs_multibuffer.read(cx).snapshot(cx));
4636 let (mut rhs_inlay_map, rhs_inlay_snapshot) = InlayMap::new(rhs_buffer_snapshot);
4637 let (mut rhs_fold_map, rhs_fold_snapshot) = FoldMap::new(rhs_inlay_snapshot);
4638 let (mut rhs_tab_map, rhs_tab_snapshot) =
4639 TabMap::new(rhs_fold_snapshot, 4.try_into().unwrap());
4640 let (_rhs_wrap_map, rhs_wrap_snapshot) =
4641 cx.update(|cx| WrapMap::new(rhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4642 let rhs_block_map = BlockMap::new(rhs_wrap_snapshot.clone(), 0, 0);
4643
4644 let rhs_entity_id = rhs_multibuffer.entity_id();
4645
4646 let companion = cx.new(|_| {
4647 let mut c = Companion::new(
4648 rhs_entity_id,
4649 convert_rhs_rows_to_lhs,
4650 convert_lhs_rows_to_rhs,
4651 );
4652 c.add_excerpt_mapping(lhs_excerpt_id, rhs_excerpt_id);
4653 c
4654 });
4655
4656 let rhs_edits = Patch::new(vec![text::Edit {
4657 old: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4658 new: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4659 }]);
4660 let lhs_edits = Patch::new(vec![text::Edit {
4661 old: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4662 new: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4663 }]);
4664
4665 let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4666 rhs_block_map.read(
4667 rhs_wrap_snapshot.clone(),
4668 rhs_edits.clone(),
4669 Some(CompanionView::new(
4670 rhs_entity_id,
4671 &lhs_wrap_snapshot,
4672 &lhs_edits,
4673 companion,
4674 )),
4675 )
4676 });
4677
4678 let lhs_entity_id = lhs_multibuffer.entity_id();
4679 let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4680 lhs_block_map.read(
4681 lhs_wrap_snapshot.clone(),
4682 lhs_edits.clone(),
4683 Some(CompanionView::new(
4684 lhs_entity_id,
4685 &rhs_wrap_snapshot,
4686 &rhs_edits,
4687 companion,
4688 )),
4689 )
4690 });
4691
4692 // LHS:
4693 // aaa
4694 // - bbb
4695 // - ccc
4696 // ddd
4697 // ddd
4698 // ddd
4699 // <extra line>
4700 // <extra line>
4701 // <extra line>
4702 // *eee
4703 //
4704 // RHS:
4705 // aaa
4706 // <extra line>
4707 // <extra line>
4708 // ddd
4709 // ddd
4710 // ddd
4711 // + XXX
4712 // + YYY
4713 // + ZZZ
4714 // eee
4715
4716 assert_eq!(
4717 rhs_snapshot.snapshot.text(),
4718 "aaa\n\n\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4719 "RHS should have 2 spacer lines after 'aaa' to align with LHS's deleted lines"
4720 );
4721
4722 assert_eq!(
4723 lhs_snapshot.snapshot.text(),
4724 "aaa\nbbb\nccc\nddd\nddd\nddd\n\n\n\neee\n",
4725 "LHS should have 3 spacer lines in place of RHS's inserted lines"
4726 );
4727
4728 // LHS:
4729 // aaa
4730 // - bbb
4731 // - ccc
4732 // ddd
4733 // ddd
4734 // ddd
4735 // <extra line>
4736 // <extra line>
4737 // <extra line>
4738 // eee
4739 //
4740 // RHS:
4741 // aaa
4742 // <extra line>
4743 // <extra line>
4744 // ddd
4745 // foo
4746 // foo
4747 // foo
4748 // ddd
4749 // ddd
4750 // + XXX
4751 // + YYY
4752 // + ZZZ
4753 // eee
4754
4755 let rhs_buffer_snapshot = rhs_multibuffer.update(cx, |multibuffer, cx| {
4756 multibuffer.edit(
4757 [(Point::new(2, 0)..Point::new(2, 0), "foo\nfoo\nfoo\n")],
4758 None,
4759 cx,
4760 );
4761 multibuffer.snapshot(cx)
4762 });
4763
4764 let (rhs_inlay_snapshot, rhs_inlay_edits) =
4765 rhs_inlay_map.sync(rhs_buffer_snapshot, subscription.consume().into_inner());
4766 let (rhs_fold_snapshot, rhs_fold_edits) =
4767 rhs_fold_map.read(rhs_inlay_snapshot, rhs_inlay_edits);
4768 let (rhs_tab_snapshot, rhs_tab_edits) =
4769 rhs_tab_map.sync(rhs_fold_snapshot, rhs_fold_edits, 4.try_into().unwrap());
4770 let (rhs_wrap_snapshot, rhs_wrap_edits) = _rhs_wrap_map.update(cx, |wrap_map, cx| {
4771 wrap_map.sync(rhs_tab_snapshot, rhs_tab_edits, cx)
4772 });
4773
4774 let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4775 rhs_block_map.read(
4776 rhs_wrap_snapshot.clone(),
4777 rhs_wrap_edits.clone(),
4778 Some(CompanionView::new(
4779 rhs_entity_id,
4780 &lhs_wrap_snapshot,
4781 &Default::default(),
4782 companion,
4783 )),
4784 )
4785 });
4786
4787 let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4788 lhs_block_map.read(
4789 lhs_wrap_snapshot.clone(),
4790 Default::default(),
4791 Some(CompanionView::new(
4792 lhs_entity_id,
4793 &rhs_wrap_snapshot,
4794 &rhs_wrap_edits,
4795 companion,
4796 )),
4797 )
4798 });
4799
4800 assert_eq!(
4801 rhs_snapshot.snapshot.text(),
4802 "aaa\n\n\nddd\nfoo\nfoo\nfoo\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4803 "RHS should have the insertion"
4804 );
4805
4806 assert_eq!(
4807 lhs_snapshot.snapshot.text(),
4808 "aaa\nbbb\nccc\nddd\n\n\n\nddd\nddd\n\n\n\neee\n",
4809 "LHS should have 3 more spacer lines to balance the insertion"
4810 );
4811 }
4812
4813 fn init_test(cx: &mut gpui::App) {
4814 let settings = SettingsStore::test(cx);
4815 cx.set_global(settings);
4816 theme::init(theme::LoadThemes::JustBase, cx);
4817 assets::Assets.load_test_fonts(cx);
4818 }
4819
4820 impl Block {
4821 fn as_custom(&self) -> Option<&CustomBlock> {
4822 match self {
4823 Block::Custom(block) => Some(block),
4824 _ => None,
4825 }
4826 }
4827 }
4828
4829 impl BlockSnapshot {
4830 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
4831 self.wrap_snapshot
4832 .to_point(self.to_wrap_point(point, bias), bias)
4833 }
4834 }
4835}