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