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