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