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