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