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