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