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