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