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