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