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(
1197 &mut self,
1198 ranges: impl IntoIterator<Item = Range<usize>>,
1199 inclusive: bool,
1200 ) {
1201 let wrap_snapshot = self.0.wrap_snapshot.borrow();
1202 let mut blocks_to_remove = HashSet::default();
1203 for range in ranges {
1204 for block in self.blocks_intersecting_buffer_range(range, inclusive) {
1205 if matches!(block.placement, BlockPlacement::Replace(_)) {
1206 blocks_to_remove.insert(block.id);
1207 }
1208 }
1209 }
1210 drop(wrap_snapshot);
1211 self.remove(blocks_to_remove);
1212 }
1213
1214 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId) {
1215 self.0.buffers_with_disabled_headers.insert(buffer_id);
1216 }
1217
1218 pub fn fold_buffers(
1219 &mut self,
1220 buffer_ids: impl IntoIterator<Item = BufferId>,
1221 multi_buffer: &MultiBuffer,
1222 cx: &App,
1223 ) {
1224 self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx);
1225 }
1226
1227 pub fn unfold_buffers(
1228 &mut self,
1229 buffer_ids: impl IntoIterator<Item = BufferId>,
1230 multi_buffer: &MultiBuffer,
1231 cx: &App,
1232 ) {
1233 self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx);
1234 }
1235
1236 fn fold_or_unfold_buffers(
1237 &mut self,
1238 fold: bool,
1239 buffer_ids: impl IntoIterator<Item = BufferId>,
1240 multi_buffer: &MultiBuffer,
1241 cx: &App,
1242 ) {
1243 let mut ranges = Vec::new();
1244 for buffer_id in buffer_ids {
1245 if fold {
1246 self.0.folded_buffers.insert(buffer_id);
1247 } else {
1248 self.0.folded_buffers.remove(&buffer_id);
1249 }
1250 ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
1251 }
1252 ranges.sort_unstable_by_key(|range| range.start);
1253
1254 let mut edits = Patch::default();
1255 let wrap_snapshot = self.0.wrap_snapshot.borrow().clone();
1256 for range in ranges {
1257 let last_edit_row = cmp::min(
1258 wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1,
1259 wrap_snapshot.max_point().row(),
1260 ) + 1;
1261 let range = wrap_snapshot.make_wrap_point(range.start, Bias::Left).row()..last_edit_row;
1262 edits.push(Edit {
1263 old: range.clone(),
1264 new: range,
1265 });
1266 }
1267
1268 self.0.sync(&wrap_snapshot, edits);
1269 }
1270
1271 fn blocks_intersecting_buffer_range(
1272 &self,
1273 range: Range<usize>,
1274 inclusive: bool,
1275 ) -> &[Arc<CustomBlock>] {
1276 if range.is_empty() && !inclusive {
1277 return &[];
1278 }
1279 let wrap_snapshot = self.0.wrap_snapshot.borrow();
1280 let buffer = wrap_snapshot.buffer_snapshot();
1281
1282 let start_block_ix = match self.0.custom_blocks.binary_search_by(|block| {
1283 let block_end = block.end().to_offset(buffer);
1284 block_end.cmp(&range.start).then(Ordering::Greater)
1285 }) {
1286 Ok(ix) | Err(ix) => ix,
1287 };
1288 let end_block_ix = match self.0.custom_blocks[start_block_ix..].binary_search_by(|block| {
1289 let block_start = block.start().to_offset(buffer);
1290 block_start.cmp(&range.end).then(if inclusive {
1291 Ordering::Less
1292 } else {
1293 Ordering::Greater
1294 })
1295 }) {
1296 Ok(ix) | Err(ix) => ix,
1297 };
1298
1299 &self.0.custom_blocks[start_block_ix..][..end_block_ix]
1300 }
1301}
1302
1303impl BlockSnapshot {
1304 #[cfg(test)]
1305 pub fn text(&self) -> String {
1306 self.chunks(
1307 0..self.transforms.summary().output_rows,
1308 false,
1309 false,
1310 Highlights::default(),
1311 )
1312 .map(|chunk| chunk.text)
1313 .collect()
1314 }
1315
1316 pub(crate) fn chunks<'a>(
1317 &'a self,
1318 rows: Range<u32>,
1319 language_aware: bool,
1320 masked: bool,
1321 highlights: Highlights<'a>,
1322 ) -> BlockChunks<'a> {
1323 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
1324
1325 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1326 cursor.seek(&BlockRow(rows.start), Bias::Right);
1327 let transform_output_start = cursor.start().0.0;
1328 let transform_input_start = cursor.start().1.0;
1329
1330 let mut input_start = transform_input_start;
1331 let mut input_end = transform_input_start;
1332 if let Some(transform) = cursor.item()
1333 && transform.block.is_none()
1334 {
1335 input_start += rows.start - transform_output_start;
1336 input_end += cmp::min(
1337 rows.end - transform_output_start,
1338 transform.summary.input_rows,
1339 );
1340 }
1341
1342 BlockChunks {
1343 input_chunks: self.wrap_snapshot.chunks(
1344 input_start..input_end,
1345 language_aware,
1346 highlights,
1347 ),
1348 input_chunk: Default::default(),
1349 transforms: cursor,
1350 output_row: rows.start,
1351 max_output_row,
1352 masked,
1353 }
1354 }
1355
1356 pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
1357 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1358 cursor.seek(&start_row, Bias::Right);
1359 let Dimensions(output_start, input_start, _) = cursor.start();
1360 let overshoot = if cursor
1361 .item()
1362 .is_some_and(|transform| transform.block.is_none())
1363 {
1364 start_row.0 - output_start.0
1365 } else {
1366 0
1367 };
1368 let input_start_row = input_start.0 + overshoot;
1369 BlockRows {
1370 transforms: cursor,
1371 input_rows: self.wrap_snapshot.row_infos(input_start_row),
1372 output_row: start_row,
1373 started: false,
1374 }
1375 }
1376
1377 pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
1378 let mut cursor = self.transforms.cursor::<BlockRow>(());
1379 cursor.seek(&BlockRow(rows.start), Bias::Left);
1380 while cursor.start().0 < rows.start && cursor.end().0 <= rows.start {
1381 cursor.next();
1382 }
1383
1384 std::iter::from_fn(move || {
1385 while let Some(transform) = cursor.item() {
1386 let start_row = cursor.start().0;
1387 if start_row > rows.end
1388 || (start_row == rows.end
1389 && transform
1390 .block
1391 .as_ref()
1392 .is_some_and(|block| block.height() > 0))
1393 {
1394 break;
1395 }
1396 if let Some(block) = &transform.block {
1397 cursor.next();
1398 return Some((start_row, block));
1399 } else {
1400 cursor.next();
1401 }
1402 }
1403 None
1404 })
1405 }
1406
1407 pub(crate) fn sticky_header_excerpt(&self, position: f64) -> Option<StickyHeaderExcerpt<'_>> {
1408 let top_row = position as u32;
1409 let mut cursor = self.transforms.cursor::<BlockRow>(());
1410 cursor.seek(&BlockRow(top_row), Bias::Right);
1411
1412 while let Some(transform) = cursor.item() {
1413 match &transform.block {
1414 Some(
1415 Block::ExcerptBoundary { excerpt, .. } | Block::BufferHeader { excerpt, .. },
1416 ) => {
1417 return Some(StickyHeaderExcerpt { excerpt });
1418 }
1419 Some(block) if block.is_buffer_header() => return None,
1420 _ => {
1421 cursor.prev();
1422 continue;
1423 }
1424 }
1425 }
1426
1427 None
1428 }
1429
1430 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
1431 let buffer = self.wrap_snapshot.buffer_snapshot();
1432 let wrap_point = match block_id {
1433 BlockId::Custom(custom_block_id) => {
1434 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
1435 return Some(Block::Custom(custom_block.clone()));
1436 }
1437 BlockId::ExcerptBoundary(next_excerpt_id) => {
1438 let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
1439 self.wrap_snapshot
1440 .make_wrap_point(excerpt_range.start, Bias::Left)
1441 }
1442 BlockId::FoldedBuffer(excerpt_id) => self
1443 .wrap_snapshot
1444 .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
1445 };
1446 let wrap_row = WrapRow(wrap_point.row());
1447
1448 let mut cursor = self.transforms.cursor::<WrapRow>(());
1449 cursor.seek(&wrap_row, Bias::Left);
1450
1451 while let Some(transform) = cursor.item() {
1452 if let Some(block) = transform.block.as_ref() {
1453 if block.id() == block_id {
1454 return Some(block.clone());
1455 }
1456 } else if *cursor.start() > wrap_row {
1457 break;
1458 }
1459
1460 cursor.next();
1461 }
1462
1463 None
1464 }
1465
1466 pub fn max_point(&self) -> BlockPoint {
1467 let row = self.transforms.summary().output_rows.saturating_sub(1);
1468 BlockPoint::new(row, self.line_len(BlockRow(row)))
1469 }
1470
1471 pub fn longest_row(&self) -> u32 {
1472 self.transforms.summary().longest_row
1473 }
1474
1475 pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
1476 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1477 cursor.seek(&range.start, Bias::Right);
1478
1479 let mut longest_row = range.start;
1480 let mut longest_row_chars = 0;
1481 if let Some(transform) = cursor.item() {
1482 if transform.block.is_none() {
1483 let Dimensions(output_start, input_start, _) = cursor.start();
1484 let overshoot = range.start.0 - output_start.0;
1485 let wrap_start_row = input_start.0 + overshoot;
1486 let wrap_end_row = cmp::min(
1487 input_start.0 + (range.end.0 - output_start.0),
1488 cursor.end().1.0,
1489 );
1490 let summary = self
1491 .wrap_snapshot
1492 .text_summary_for_range(wrap_start_row..wrap_end_row);
1493 longest_row = BlockRow(range.start.0 + summary.longest_row);
1494 longest_row_chars = summary.longest_row_chars;
1495 }
1496 cursor.next();
1497 }
1498
1499 let cursor_start_row = cursor.start().0;
1500 if range.end > cursor_start_row {
1501 let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right);
1502 if summary.longest_row_chars > longest_row_chars {
1503 longest_row = BlockRow(cursor_start_row.0 + summary.longest_row);
1504 longest_row_chars = summary.longest_row_chars;
1505 }
1506
1507 if let Some(transform) = cursor.item()
1508 && transform.block.is_none()
1509 {
1510 let Dimensions(output_start, input_start, _) = cursor.start();
1511 let overshoot = range.end.0 - output_start.0;
1512 let wrap_start_row = input_start.0;
1513 let wrap_end_row = input_start.0 + overshoot;
1514 let summary = self
1515 .wrap_snapshot
1516 .text_summary_for_range(wrap_start_row..wrap_end_row);
1517 if summary.longest_row_chars > longest_row_chars {
1518 longest_row = BlockRow(output_start.0 + summary.longest_row);
1519 }
1520 }
1521 }
1522
1523 longest_row
1524 }
1525
1526 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
1527 let (start, _, item) =
1528 self.transforms
1529 .find::<Dimensions<BlockRow, WrapRow>, _>((), &row, Bias::Right);
1530 if let Some(transform) = item {
1531 let Dimensions(output_start, input_start, _) = start;
1532 let overshoot = row.0 - output_start.0;
1533 if transform.block.is_some() {
1534 0
1535 } else {
1536 self.wrap_snapshot.line_len(input_start.0 + overshoot)
1537 }
1538 } else if row.0 == 0 {
1539 0
1540 } else {
1541 panic!("row out of range");
1542 }
1543 }
1544
1545 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
1546 let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
1547 item.is_some_and(|t| t.block.is_some())
1548 }
1549
1550 pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
1551 let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
1552 let Some(transform) = item else {
1553 return false;
1554 };
1555 matches!(transform.block, Some(Block::FoldedBuffer { .. }))
1556 }
1557
1558 pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
1559 let wrap_point = self
1560 .wrap_snapshot
1561 .make_wrap_point(Point::new(row.0, 0), Bias::Left);
1562 let (_, _, item) =
1563 self.transforms
1564 .find::<WrapRow, _>((), &WrapRow(wrap_point.row()), Bias::Right);
1565 item.is_some_and(|transform| {
1566 transform
1567 .block
1568 .as_ref()
1569 .is_some_and(|block| block.is_replacement())
1570 })
1571 }
1572
1573 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
1574 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1575 cursor.seek(&BlockRow(point.row), Bias::Right);
1576
1577 let max_input_row = WrapRow(self.transforms.summary().input_rows);
1578 let mut search_left =
1579 (bias == Bias::Left && cursor.start().1.0 > 0) || cursor.end().1 == max_input_row;
1580 let mut reversed = false;
1581
1582 loop {
1583 if let Some(transform) = cursor.item() {
1584 let Dimensions(output_start_row, input_start_row, _) = cursor.start();
1585 let Dimensions(output_end_row, input_end_row, _) = cursor.end();
1586 let output_start = Point::new(output_start_row.0, 0);
1587 let input_start = Point::new(input_start_row.0, 0);
1588 let input_end = Point::new(input_end_row.0, 0);
1589
1590 match transform.block.as_ref() {
1591 Some(block) => {
1592 if block.is_replacement()
1593 && (((bias == Bias::Left || search_left) && output_start <= point.0)
1594 || (!search_left && output_start >= point.0))
1595 {
1596 return BlockPoint(output_start);
1597 }
1598 }
1599 None => {
1600 let input_point = if point.row >= output_end_row.0 {
1601 let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
1602 self.wrap_snapshot
1603 .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
1604 } else {
1605 let output_overshoot = point.0.saturating_sub(output_start);
1606 self.wrap_snapshot
1607 .clip_point(WrapPoint(input_start + output_overshoot), bias)
1608 };
1609
1610 if (input_start..input_end).contains(&input_point.0) {
1611 let input_overshoot = input_point.0.saturating_sub(input_start);
1612 return BlockPoint(output_start + input_overshoot);
1613 }
1614 }
1615 }
1616
1617 if search_left {
1618 cursor.prev();
1619 } else {
1620 cursor.next();
1621 }
1622 } else if reversed {
1623 return self.max_point();
1624 } else {
1625 reversed = true;
1626 search_left = !search_left;
1627 cursor.seek(&BlockRow(point.row), Bias::Right);
1628 }
1629 }
1630 }
1631
1632 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
1633 let (start, _, item) = self.transforms.find::<Dimensions<WrapRow, BlockRow>, _>(
1634 (),
1635 &WrapRow(wrap_point.row()),
1636 Bias::Right,
1637 );
1638 if let Some(transform) = item {
1639 if transform.block.is_some() {
1640 BlockPoint::new(start.1.0, 0)
1641 } else {
1642 let Dimensions(input_start_row, output_start_row, _) = start;
1643 let input_start = Point::new(input_start_row.0, 0);
1644 let output_start = Point::new(output_start_row.0, 0);
1645 let input_overshoot = wrap_point.0 - input_start;
1646 BlockPoint(output_start + input_overshoot)
1647 }
1648 } else {
1649 self.max_point()
1650 }
1651 }
1652
1653 pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
1654 let (start, end, item) = self.transforms.find::<Dimensions<BlockRow, WrapRow>, _>(
1655 (),
1656 &BlockRow(block_point.row),
1657 Bias::Right,
1658 );
1659 if let Some(transform) = item {
1660 match transform.block.as_ref() {
1661 Some(block) => {
1662 if block.place_below() {
1663 let wrap_row = start.1.0 - 1;
1664 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1665 } else if block.place_above() {
1666 WrapPoint::new(start.1.0, 0)
1667 } else if bias == Bias::Left {
1668 WrapPoint::new(start.1.0, 0)
1669 } else {
1670 let wrap_row = end.1.0 - 1;
1671 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1672 }
1673 }
1674 None => {
1675 let overshoot = block_point.row - start.0.0;
1676 let wrap_row = start.1.0 + overshoot;
1677 WrapPoint::new(wrap_row, block_point.column)
1678 }
1679 }
1680 } else {
1681 self.wrap_snapshot.max_point()
1682 }
1683 }
1684}
1685
1686impl BlockChunks<'_> {
1687 /// Go to the next transform
1688 fn advance(&mut self) {
1689 self.input_chunk = Chunk::default();
1690 self.transforms.next();
1691 while let Some(transform) = self.transforms.item() {
1692 if transform
1693 .block
1694 .as_ref()
1695 .is_some_and(|block| block.height() == 0)
1696 {
1697 self.transforms.next();
1698 } else {
1699 break;
1700 }
1701 }
1702
1703 if self
1704 .transforms
1705 .item()
1706 .is_some_and(|transform| transform.block.is_none())
1707 {
1708 let start_input_row = self.transforms.start().1.0;
1709 let start_output_row = self.transforms.start().0.0;
1710 if start_output_row < self.max_output_row {
1711 let end_input_row = cmp::min(
1712 self.transforms.end().1.0,
1713 start_input_row + (self.max_output_row - start_output_row),
1714 );
1715 self.input_chunks.seek(start_input_row..end_input_row);
1716 }
1717 }
1718 }
1719}
1720
1721pub struct StickyHeaderExcerpt<'a> {
1722 pub excerpt: &'a ExcerptInfo,
1723}
1724
1725impl<'a> Iterator for BlockChunks<'a> {
1726 type Item = Chunk<'a>;
1727
1728 fn next(&mut self) -> Option<Self::Item> {
1729 if self.output_row >= self.max_output_row {
1730 return None;
1731 }
1732
1733 let transform = self.transforms.item()?;
1734 if transform.block.is_some() {
1735 let block_start = self.transforms.start().0.0;
1736 let mut block_end = self.transforms.end().0.0;
1737 self.advance();
1738 if self.transforms.item().is_none() {
1739 block_end -= 1;
1740 }
1741
1742 let start_in_block = self.output_row - block_start;
1743 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
1744 // todo: We need to split the chunk here?
1745 let line_count = cmp::min(end_in_block - start_in_block, u128::BITS);
1746 self.output_row += line_count;
1747
1748 return Some(Chunk {
1749 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
1750 chars: 1u128.unbounded_shl(line_count) - 1,
1751 ..Default::default()
1752 });
1753 }
1754
1755 if self.input_chunk.text.is_empty() {
1756 if let Some(input_chunk) = self.input_chunks.next() {
1757 self.input_chunk = input_chunk;
1758 } else {
1759 if self.output_row < self.max_output_row {
1760 self.output_row += 1;
1761 self.advance();
1762 if self.transforms.item().is_some() {
1763 return Some(Chunk {
1764 text: "\n",
1765 chars: 1,
1766 ..Default::default()
1767 });
1768 }
1769 }
1770 return None;
1771 }
1772 }
1773
1774 let transform_end = self.transforms.end().0.0;
1775 let (prefix_rows, prefix_bytes) =
1776 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
1777 self.output_row += prefix_rows;
1778
1779 let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
1780 self.input_chunk.text = suffix;
1781 self.input_chunk.tabs >>= prefix_bytes.saturating_sub(1);
1782 self.input_chunk.chars >>= prefix_bytes.saturating_sub(1);
1783
1784 let mut tabs = self.input_chunk.tabs;
1785 let mut chars = self.input_chunk.chars;
1786
1787 if self.masked {
1788 // Not great for multibyte text because to keep cursor math correct we
1789 // need to have the same number of chars in the input as output.
1790 let chars_count = prefix.chars().count();
1791 let bullet_len = chars_count;
1792 prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
1793 chars = 1u128.unbounded_shl(bullet_len as u32).wrapping_sub(1);
1794 tabs = 0;
1795 }
1796
1797 let chunk = Chunk {
1798 text: prefix,
1799 tabs,
1800 chars,
1801 ..self.input_chunk.clone()
1802 };
1803
1804 if self.output_row == transform_end {
1805 self.advance();
1806 }
1807
1808 Some(chunk)
1809 }
1810}
1811
1812impl Iterator for BlockRows<'_> {
1813 type Item = RowInfo;
1814
1815 fn next(&mut self) -> Option<Self::Item> {
1816 if self.started {
1817 self.output_row.0 += 1;
1818 } else {
1819 self.started = true;
1820 }
1821
1822 if self.output_row.0 >= self.transforms.end().0.0 {
1823 self.transforms.next();
1824 while let Some(transform) = self.transforms.item() {
1825 if transform
1826 .block
1827 .as_ref()
1828 .is_some_and(|block| block.height() == 0)
1829 {
1830 self.transforms.next();
1831 } else {
1832 break;
1833 }
1834 }
1835
1836 let transform = self.transforms.item()?;
1837 if transform
1838 .block
1839 .as_ref()
1840 .is_none_or(|block| block.is_replacement())
1841 {
1842 self.input_rows.seek(self.transforms.start().1.0);
1843 }
1844 }
1845
1846 let transform = self.transforms.item()?;
1847 if let Some(block) = transform.block.as_ref() {
1848 if block.is_replacement() && self.transforms.start().0 == self.output_row {
1849 if matches!(block, Block::FoldedBuffer { .. }) {
1850 Some(RowInfo::default())
1851 } else {
1852 Some(self.input_rows.next().unwrap())
1853 }
1854 } else {
1855 Some(RowInfo::default())
1856 }
1857 } else {
1858 Some(self.input_rows.next().unwrap())
1859 }
1860 }
1861}
1862
1863impl sum_tree::Item for Transform {
1864 type Summary = TransformSummary;
1865
1866 fn summary(&self, _cx: ()) -> Self::Summary {
1867 self.summary.clone()
1868 }
1869}
1870
1871impl sum_tree::ContextLessSummary for TransformSummary {
1872 fn zero() -> Self {
1873 Default::default()
1874 }
1875
1876 fn add_summary(&mut self, summary: &Self) {
1877 if summary.longest_row_chars > self.longest_row_chars {
1878 self.longest_row = self.output_rows + summary.longest_row;
1879 self.longest_row_chars = summary.longest_row_chars;
1880 }
1881 self.input_rows += summary.input_rows;
1882 self.output_rows += summary.output_rows;
1883 }
1884}
1885
1886impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
1887 fn zero(_cx: ()) -> Self {
1888 Default::default()
1889 }
1890
1891 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1892 self.0 += summary.input_rows;
1893 }
1894}
1895
1896impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
1897 fn zero(_cx: ()) -> Self {
1898 Default::default()
1899 }
1900
1901 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1902 self.0 += summary.output_rows;
1903 }
1904}
1905
1906impl Deref for BlockContext<'_, '_> {
1907 type Target = App;
1908
1909 fn deref(&self) -> &Self::Target {
1910 self.app
1911 }
1912}
1913
1914impl DerefMut for BlockContext<'_, '_> {
1915 fn deref_mut(&mut self) -> &mut Self::Target {
1916 self.app
1917 }
1918}
1919
1920impl CustomBlock {
1921 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
1922 self.render.lock()(cx)
1923 }
1924
1925 pub fn start(&self) -> Anchor {
1926 *self.placement.start()
1927 }
1928
1929 pub fn end(&self) -> Anchor {
1930 *self.placement.end()
1931 }
1932
1933 pub fn style(&self) -> BlockStyle {
1934 self.style
1935 }
1936}
1937
1938impl Debug for CustomBlock {
1939 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1940 f.debug_struct("Block")
1941 .field("id", &self.id)
1942 .field("placement", &self.placement)
1943 .field("height", &self.height)
1944 .field("style", &self.style)
1945 .field("priority", &self.priority)
1946 .finish_non_exhaustive()
1947 }
1948}
1949
1950// Count the number of bytes prior to a target point. If the string doesn't contain the target
1951// point, return its total extent. Otherwise return the target point itself.
1952fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
1953 let mut row = 0;
1954 let mut offset = 0;
1955 for (ix, line) in s.split('\n').enumerate() {
1956 if ix > 0 {
1957 row += 1;
1958 offset += 1;
1959 }
1960 if row >= target {
1961 break;
1962 }
1963 offset += line.len();
1964 }
1965 (row, offset)
1966}
1967
1968#[cfg(test)]
1969mod tests {
1970 use super::*;
1971 use crate::{
1972 display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap},
1973 test::test_font,
1974 };
1975 use gpui::{App, AppContext as _, Element, div, font, px};
1976 use itertools::Itertools;
1977 use language::{Buffer, Capability};
1978 use multi_buffer::{ExcerptRange, MultiBuffer};
1979 use rand::prelude::*;
1980 use settings::SettingsStore;
1981 use std::env;
1982 use util::RandomCharIter;
1983
1984 #[gpui::test]
1985 fn test_offset_for_row() {
1986 assert_eq!(offset_for_row("", 0), (0, 0));
1987 assert_eq!(offset_for_row("", 1), (0, 0));
1988 assert_eq!(offset_for_row("abcd", 0), (0, 0));
1989 assert_eq!(offset_for_row("abcd", 1), (0, 4));
1990 assert_eq!(offset_for_row("\n", 0), (0, 0));
1991 assert_eq!(offset_for_row("\n", 1), (1, 1));
1992 assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
1993 assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
1994 assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
1995 assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
1996 }
1997
1998 #[gpui::test]
1999 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
2000 cx.update(init_test);
2001
2002 let text = "aaa\nbbb\nccc\nddd";
2003
2004 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2005 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2006 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2007 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2008 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2009 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2010 let (wrap_map, wraps_snapshot) =
2011 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2012 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2013
2014 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2015 let block_ids = writer.insert(vec![
2016 BlockProperties {
2017 style: BlockStyle::Fixed,
2018 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2019 height: Some(1),
2020 render: Arc::new(|_| div().into_any()),
2021 priority: 0,
2022 },
2023 BlockProperties {
2024 style: BlockStyle::Fixed,
2025 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2026 height: Some(2),
2027 render: Arc::new(|_| div().into_any()),
2028 priority: 0,
2029 },
2030 BlockProperties {
2031 style: BlockStyle::Fixed,
2032 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2033 height: Some(3),
2034 render: Arc::new(|_| div().into_any()),
2035 priority: 0,
2036 },
2037 ]);
2038
2039 let snapshot = block_map.read(wraps_snapshot, Default::default());
2040 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2041
2042 let blocks = snapshot
2043 .blocks_in_range(0..8)
2044 .map(|(start_row, block)| {
2045 let block = block.as_custom().unwrap();
2046 (start_row..start_row + block.height.unwrap(), block.id)
2047 })
2048 .collect::<Vec<_>>();
2049
2050 // When multiple blocks are on the same line, the newer blocks appear first.
2051 assert_eq!(
2052 blocks,
2053 &[
2054 (1..2, block_ids[0]),
2055 (2..4, block_ids[1]),
2056 (7..10, block_ids[2]),
2057 ]
2058 );
2059
2060 assert_eq!(
2061 snapshot.to_block_point(WrapPoint::new(0, 3)),
2062 BlockPoint::new(0, 3)
2063 );
2064 assert_eq!(
2065 snapshot.to_block_point(WrapPoint::new(1, 0)),
2066 BlockPoint::new(4, 0)
2067 );
2068 assert_eq!(
2069 snapshot.to_block_point(WrapPoint::new(3, 3)),
2070 BlockPoint::new(6, 3)
2071 );
2072
2073 assert_eq!(
2074 snapshot.to_wrap_point(BlockPoint::new(0, 3), Bias::Left),
2075 WrapPoint::new(0, 3)
2076 );
2077 assert_eq!(
2078 snapshot.to_wrap_point(BlockPoint::new(1, 0), Bias::Left),
2079 WrapPoint::new(1, 0)
2080 );
2081 assert_eq!(
2082 snapshot.to_wrap_point(BlockPoint::new(3, 0), Bias::Left),
2083 WrapPoint::new(1, 0)
2084 );
2085 assert_eq!(
2086 snapshot.to_wrap_point(BlockPoint::new(7, 0), Bias::Left),
2087 WrapPoint::new(3, 3)
2088 );
2089
2090 assert_eq!(
2091 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
2092 BlockPoint::new(0, 3)
2093 );
2094 assert_eq!(
2095 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
2096 BlockPoint::new(4, 0)
2097 );
2098 assert_eq!(
2099 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
2100 BlockPoint::new(0, 3)
2101 );
2102 assert_eq!(
2103 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
2104 BlockPoint::new(4, 0)
2105 );
2106 assert_eq!(
2107 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
2108 BlockPoint::new(4, 0)
2109 );
2110 assert_eq!(
2111 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
2112 BlockPoint::new(4, 0)
2113 );
2114 assert_eq!(
2115 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
2116 BlockPoint::new(6, 3)
2117 );
2118 assert_eq!(
2119 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
2120 BlockPoint::new(6, 3)
2121 );
2122 assert_eq!(
2123 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
2124 BlockPoint::new(6, 3)
2125 );
2126 assert_eq!(
2127 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
2128 BlockPoint::new(6, 3)
2129 );
2130
2131 assert_eq!(
2132 snapshot
2133 .row_infos(BlockRow(0))
2134 .map(|row_info| row_info.buffer_row)
2135 .collect::<Vec<_>>(),
2136 &[
2137 Some(0),
2138 None,
2139 None,
2140 None,
2141 Some(1),
2142 Some(2),
2143 Some(3),
2144 None,
2145 None,
2146 None
2147 ]
2148 );
2149
2150 // Insert a line break, separating two block decorations into separate lines.
2151 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2152 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
2153 buffer.snapshot(cx)
2154 });
2155
2156 let (inlay_snapshot, inlay_edits) =
2157 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
2158 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2159 let (tab_snapshot, tab_edits) =
2160 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
2161 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2162 wrap_map.sync(tab_snapshot, tab_edits, cx)
2163 });
2164 let snapshot = block_map.read(wraps_snapshot, wrap_edits);
2165 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
2166 }
2167
2168 #[gpui::test]
2169 fn test_multibuffer_headers_and_footers(cx: &mut App) {
2170 init_test(cx);
2171
2172 let buffer1 = cx.new(|cx| Buffer::local("Buffer 1", cx));
2173 let buffer2 = cx.new(|cx| Buffer::local("Buffer 2", cx));
2174 let buffer3 = cx.new(|cx| Buffer::local("Buffer 3", cx));
2175
2176 let mut excerpt_ids = Vec::new();
2177 let multi_buffer = cx.new(|cx| {
2178 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
2179 excerpt_ids.extend(multi_buffer.push_excerpts(
2180 buffer1.clone(),
2181 [ExcerptRange::new(0..buffer1.read(cx).len())],
2182 cx,
2183 ));
2184 excerpt_ids.extend(multi_buffer.push_excerpts(
2185 buffer2.clone(),
2186 [ExcerptRange::new(0..buffer2.read(cx).len())],
2187 cx,
2188 ));
2189 excerpt_ids.extend(multi_buffer.push_excerpts(
2190 buffer3.clone(),
2191 [ExcerptRange::new(0..buffer3.read(cx).len())],
2192 cx,
2193 ));
2194
2195 multi_buffer
2196 });
2197
2198 let font = test_font();
2199 let font_size = px(14.);
2200 let font_id = cx.text_system().resolve_font(&font);
2201 let mut wrap_width = px(0.);
2202 for c in "Buff".chars() {
2203 wrap_width += cx
2204 .text_system()
2205 .advance(font_id, font_size, c)
2206 .unwrap()
2207 .width;
2208 }
2209
2210 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2211 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
2212 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2213 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2214 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
2215
2216 let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2217 let snapshot = block_map.read(wraps_snapshot, Default::default());
2218
2219 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
2220 assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
2221
2222 let blocks: Vec<_> = snapshot
2223 .blocks_in_range(0..u32::MAX)
2224 .map(|(row, block)| (row..row + block.height(), block.id()))
2225 .collect();
2226 assert_eq!(
2227 blocks,
2228 vec![
2229 (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
2230 (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
2231 (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
2232 ]
2233 );
2234 }
2235
2236 #[gpui::test]
2237 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
2238 cx.update(init_test);
2239
2240 let text = "aaa\nbbb\nccc\nddd";
2241
2242 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2243 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2244 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2245 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2246 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2247 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2248 let (_wrap_map, wraps_snapshot) =
2249 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2250 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2251
2252 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2253 let block_ids = writer.insert(vec![
2254 BlockProperties {
2255 style: BlockStyle::Fixed,
2256 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2257 height: Some(1),
2258 render: Arc::new(|_| div().into_any()),
2259 priority: 0,
2260 },
2261 BlockProperties {
2262 style: BlockStyle::Fixed,
2263 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2264 height: Some(2),
2265 render: Arc::new(|_| div().into_any()),
2266 priority: 0,
2267 },
2268 BlockProperties {
2269 style: BlockStyle::Fixed,
2270 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2271 height: Some(3),
2272 render: Arc::new(|_| div().into_any()),
2273 priority: 0,
2274 },
2275 ]);
2276
2277 {
2278 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2279 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2280
2281 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2282
2283 let mut new_heights = HashMap::default();
2284 new_heights.insert(block_ids[0], 2);
2285 block_map_writer.resize(new_heights);
2286 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2287 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2288 }
2289
2290 {
2291 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2292
2293 let mut new_heights = HashMap::default();
2294 new_heights.insert(block_ids[0], 1);
2295 block_map_writer.resize(new_heights);
2296
2297 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2298 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2299 }
2300
2301 {
2302 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2303
2304 let mut new_heights = HashMap::default();
2305 new_heights.insert(block_ids[0], 0);
2306 block_map_writer.resize(new_heights);
2307
2308 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2309 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
2310 }
2311
2312 {
2313 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2314
2315 let mut new_heights = HashMap::default();
2316 new_heights.insert(block_ids[0], 3);
2317 block_map_writer.resize(new_heights);
2318
2319 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2320 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2321 }
2322
2323 {
2324 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2325
2326 let mut new_heights = HashMap::default();
2327 new_heights.insert(block_ids[0], 3);
2328 block_map_writer.resize(new_heights);
2329
2330 let snapshot = block_map.read(wraps_snapshot, Default::default());
2331 // Same height as before, should remain the same
2332 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2333 }
2334 }
2335
2336 #[gpui::test]
2337 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
2338 cx.update(init_test);
2339
2340 let text = "one two three\nfour five six\nseven eight";
2341
2342 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2343 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2344 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2345 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2346 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2347 let (_, wraps_snapshot) = cx.update(|cx| {
2348 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
2349 });
2350 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2351
2352 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2353 writer.insert(vec![
2354 BlockProperties {
2355 style: BlockStyle::Fixed,
2356 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
2357 render: Arc::new(|_| div().into_any()),
2358 height: Some(1),
2359 priority: 0,
2360 },
2361 BlockProperties {
2362 style: BlockStyle::Fixed,
2363 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
2364 render: Arc::new(|_| div().into_any()),
2365 height: Some(1),
2366 priority: 0,
2367 },
2368 ]);
2369
2370 // Blocks with an 'above' disposition go above their corresponding buffer line.
2371 // Blocks with a 'below' disposition go below their corresponding buffer line.
2372 let snapshot = block_map.read(wraps_snapshot, Default::default());
2373 assert_eq!(
2374 snapshot.text(),
2375 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
2376 );
2377 }
2378
2379 #[gpui::test]
2380 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
2381 cx.update(init_test);
2382
2383 let text = "line1\nline2\nline3\nline4\nline5";
2384
2385 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2386 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
2387 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2388 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2389 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2390 let tab_size = 1.try_into().unwrap();
2391 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
2392 let (wrap_map, wraps_snapshot) =
2393 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2394 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2395
2396 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2397 let replace_block_id = writer.insert(vec![BlockProperties {
2398 style: BlockStyle::Fixed,
2399 placement: BlockPlacement::Replace(
2400 buffer_snapshot.anchor_after(Point::new(1, 3))
2401 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
2402 ),
2403 height: Some(4),
2404 render: Arc::new(|_| div().into_any()),
2405 priority: 0,
2406 }])[0];
2407
2408 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2409 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2410
2411 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2412 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
2413 buffer.snapshot(cx)
2414 });
2415 let (inlay_snapshot, inlay_edits) =
2416 inlay_map.sync(buffer_snapshot, buffer_subscription.consume().into_inner());
2417 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2418 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2419 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2420 wrap_map.sync(tab_snapshot, tab_edits, cx)
2421 });
2422 let blocks_snapshot = block_map.read(wraps_snapshot, wrap_edits);
2423 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2424
2425 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2426 buffer.edit(
2427 [(
2428 Point::new(1, 5)..Point::new(1, 5),
2429 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
2430 )],
2431 None,
2432 cx,
2433 );
2434 buffer.snapshot(cx)
2435 });
2436 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2437 buffer_snapshot.clone(),
2438 buffer_subscription.consume().into_inner(),
2439 );
2440 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2441 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2442 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2443 wrap_map.sync(tab_snapshot, tab_edits, cx)
2444 });
2445 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2446 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2447
2448 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
2449 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2450 writer.insert(vec![
2451 BlockProperties {
2452 style: BlockStyle::Fixed,
2453 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
2454 height: Some(1),
2455 render: Arc::new(|_| div().into_any()),
2456 priority: 0,
2457 },
2458 BlockProperties {
2459 style: BlockStyle::Fixed,
2460 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
2461 height: Some(1),
2462 render: Arc::new(|_| div().into_any()),
2463 priority: 0,
2464 },
2465 BlockProperties {
2466 style: BlockStyle::Fixed,
2467 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
2468 height: Some(1),
2469 render: Arc::new(|_| div().into_any()),
2470 priority: 0,
2471 },
2472 ]);
2473 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2474 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2475
2476 // Ensure blocks inserted *inside* replaced region are hidden.
2477 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2478 writer.insert(vec![
2479 BlockProperties {
2480 style: BlockStyle::Fixed,
2481 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
2482 height: Some(1),
2483 render: Arc::new(|_| div().into_any()),
2484 priority: 0,
2485 },
2486 BlockProperties {
2487 style: BlockStyle::Fixed,
2488 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
2489 height: Some(1),
2490 render: Arc::new(|_| div().into_any()),
2491 priority: 0,
2492 },
2493 BlockProperties {
2494 style: BlockStyle::Fixed,
2495 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
2496 height: Some(1),
2497 render: Arc::new(|_| div().into_any()),
2498 priority: 0,
2499 },
2500 ]);
2501 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2502 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2503
2504 // Removing the replace block shows all the hidden blocks again.
2505 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2506 writer.remove(HashSet::from_iter([replace_block_id]));
2507 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2508 assert_eq!(
2509 blocks_snapshot.text(),
2510 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
2511 );
2512 }
2513
2514 #[gpui::test]
2515 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
2516 cx.update(init_test);
2517
2518 let text = "111\n222\n333\n444\n555\n666";
2519
2520 let buffer = cx.update(|cx| {
2521 MultiBuffer::build_multi(
2522 [
2523 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
2524 (
2525 text,
2526 vec![
2527 Point::new(1, 0)..Point::new(1, 3),
2528 Point::new(2, 0)..Point::new(2, 3),
2529 Point::new(3, 0)..Point::new(3, 3),
2530 ],
2531 ),
2532 (
2533 text,
2534 vec![
2535 Point::new(4, 0)..Point::new(4, 3),
2536 Point::new(5, 0)..Point::new(5, 3),
2537 ],
2538 ),
2539 ],
2540 cx,
2541 )
2542 });
2543 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2544 let buffer_ids = buffer_snapshot
2545 .excerpts()
2546 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2547 .dedup()
2548 .collect::<Vec<_>>();
2549 assert_eq!(buffer_ids.len(), 3);
2550 let buffer_id_1 = buffer_ids[0];
2551 let buffer_id_2 = buffer_ids[1];
2552 let buffer_id_3 = buffer_ids[2];
2553
2554 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2555 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2556 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2557 let (_, wrap_snapshot) =
2558 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2559 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2560 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2561
2562 assert_eq!(
2563 blocks_snapshot.text(),
2564 "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
2565 );
2566 assert_eq!(
2567 blocks_snapshot
2568 .row_infos(BlockRow(0))
2569 .map(|i| i.buffer_row)
2570 .collect::<Vec<_>>(),
2571 vec![
2572 None,
2573 None,
2574 Some(0),
2575 None,
2576 None,
2577 Some(1),
2578 None,
2579 Some(2),
2580 None,
2581 Some(3),
2582 None,
2583 None,
2584 Some(4),
2585 None,
2586 Some(5),
2587 ]
2588 );
2589
2590 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2591 let excerpt_blocks_2 = writer.insert(vec![
2592 BlockProperties {
2593 style: BlockStyle::Fixed,
2594 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2595 height: Some(1),
2596 render: Arc::new(|_| div().into_any()),
2597 priority: 0,
2598 },
2599 BlockProperties {
2600 style: BlockStyle::Fixed,
2601 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
2602 height: Some(1),
2603 render: Arc::new(|_| div().into_any()),
2604 priority: 0,
2605 },
2606 BlockProperties {
2607 style: BlockStyle::Fixed,
2608 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
2609 height: Some(1),
2610 render: Arc::new(|_| div().into_any()),
2611 priority: 0,
2612 },
2613 ]);
2614 let excerpt_blocks_3 = writer.insert(vec![
2615 BlockProperties {
2616 style: BlockStyle::Fixed,
2617 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
2618 height: Some(1),
2619 render: Arc::new(|_| div().into_any()),
2620 priority: 0,
2621 },
2622 BlockProperties {
2623 style: BlockStyle::Fixed,
2624 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
2625 height: Some(1),
2626 render: Arc::new(|_| div().into_any()),
2627 priority: 0,
2628 },
2629 ]);
2630
2631 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2632 assert_eq!(
2633 blocks_snapshot.text(),
2634 "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2635 );
2636 assert_eq!(
2637 blocks_snapshot
2638 .row_infos(BlockRow(0))
2639 .map(|i| i.buffer_row)
2640 .collect::<Vec<_>>(),
2641 vec![
2642 None,
2643 None,
2644 Some(0),
2645 None,
2646 None,
2647 None,
2648 Some(1),
2649 None,
2650 None,
2651 Some(2),
2652 None,
2653 Some(3),
2654 None,
2655 None,
2656 None,
2657 None,
2658 Some(4),
2659 None,
2660 Some(5),
2661 None,
2662 ]
2663 );
2664
2665 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2666 buffer.read_with(cx, |buffer, cx| {
2667 writer.fold_buffers([buffer_id_1], buffer, cx);
2668 });
2669 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
2670 style: BlockStyle::Fixed,
2671 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
2672 height: Some(1),
2673 render: Arc::new(|_| div().into_any()),
2674 priority: 0,
2675 }]);
2676 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2677 let blocks = blocks_snapshot
2678 .blocks_in_range(0..u32::MAX)
2679 .collect::<Vec<_>>();
2680 for (_, block) in &blocks {
2681 if let BlockId::Custom(custom_block_id) = block.id() {
2682 assert!(
2683 !excerpt_blocks_1.contains(&custom_block_id),
2684 "Should have no blocks from the folded buffer"
2685 );
2686 assert!(
2687 excerpt_blocks_2.contains(&custom_block_id)
2688 || excerpt_blocks_3.contains(&custom_block_id),
2689 "Should have only blocks from unfolded buffers"
2690 );
2691 }
2692 }
2693 assert_eq!(
2694 1,
2695 blocks
2696 .iter()
2697 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2698 .count(),
2699 "Should have one folded block, producing a header of the second buffer"
2700 );
2701 assert_eq!(
2702 blocks_snapshot.text(),
2703 "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2704 );
2705 assert_eq!(
2706 blocks_snapshot
2707 .row_infos(BlockRow(0))
2708 .map(|i| i.buffer_row)
2709 .collect::<Vec<_>>(),
2710 vec![
2711 None,
2712 None,
2713 None,
2714 None,
2715 None,
2716 Some(1),
2717 None,
2718 None,
2719 Some(2),
2720 None,
2721 Some(3),
2722 None,
2723 None,
2724 None,
2725 None,
2726 Some(4),
2727 None,
2728 Some(5),
2729 None,
2730 ]
2731 );
2732
2733 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2734 buffer.read_with(cx, |buffer, cx| {
2735 writer.fold_buffers([buffer_id_2], buffer, cx);
2736 });
2737 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2738 let blocks = blocks_snapshot
2739 .blocks_in_range(0..u32::MAX)
2740 .collect::<Vec<_>>();
2741 for (_, block) in &blocks {
2742 if let BlockId::Custom(custom_block_id) = block.id() {
2743 assert!(
2744 !excerpt_blocks_1.contains(&custom_block_id),
2745 "Should have no blocks from the folded buffer_1"
2746 );
2747 assert!(
2748 !excerpt_blocks_2.contains(&custom_block_id),
2749 "Should have no blocks from the folded buffer_2"
2750 );
2751 assert!(
2752 excerpt_blocks_3.contains(&custom_block_id),
2753 "Should have only blocks from unfolded buffers"
2754 );
2755 }
2756 }
2757 assert_eq!(
2758 2,
2759 blocks
2760 .iter()
2761 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2762 .count(),
2763 "Should have two folded blocks, producing headers"
2764 );
2765 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
2766 assert_eq!(
2767 blocks_snapshot
2768 .row_infos(BlockRow(0))
2769 .map(|i| i.buffer_row)
2770 .collect::<Vec<_>>(),
2771 vec![
2772 None,
2773 None,
2774 None,
2775 None,
2776 None,
2777 None,
2778 None,
2779 Some(4),
2780 None,
2781 Some(5),
2782 None,
2783 ]
2784 );
2785
2786 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2787 buffer.read_with(cx, |buffer, cx| {
2788 writer.unfold_buffers([buffer_id_1], buffer, cx);
2789 });
2790 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2791 let blocks = blocks_snapshot
2792 .blocks_in_range(0..u32::MAX)
2793 .collect::<Vec<_>>();
2794 for (_, block) in &blocks {
2795 if let BlockId::Custom(custom_block_id) = block.id() {
2796 assert!(
2797 !excerpt_blocks_2.contains(&custom_block_id),
2798 "Should have no blocks from the folded buffer_2"
2799 );
2800 assert!(
2801 excerpt_blocks_1.contains(&custom_block_id)
2802 || excerpt_blocks_3.contains(&custom_block_id),
2803 "Should have only blocks from unfolded buffers"
2804 );
2805 }
2806 }
2807 assert_eq!(
2808 1,
2809 blocks
2810 .iter()
2811 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2812 .count(),
2813 "Should be back to a single folded buffer, producing a header for buffer_2"
2814 );
2815 assert_eq!(
2816 blocks_snapshot.text(),
2817 "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
2818 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
2819 );
2820 assert_eq!(
2821 blocks_snapshot
2822 .row_infos(BlockRow(0))
2823 .map(|i| i.buffer_row)
2824 .collect::<Vec<_>>(),
2825 vec![
2826 None,
2827 None,
2828 None,
2829 Some(0),
2830 None,
2831 None,
2832 None,
2833 None,
2834 None,
2835 Some(4),
2836 None,
2837 Some(5),
2838 None,
2839 ]
2840 );
2841
2842 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2843 buffer.read_with(cx, |buffer, cx| {
2844 writer.fold_buffers([buffer_id_3], buffer, cx);
2845 });
2846 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
2847 let blocks = blocks_snapshot
2848 .blocks_in_range(0..u32::MAX)
2849 .collect::<Vec<_>>();
2850 for (_, block) in &blocks {
2851 if let BlockId::Custom(custom_block_id) = block.id() {
2852 assert!(
2853 excerpt_blocks_1.contains(&custom_block_id),
2854 "Should have no blocks from the folded buffer_1"
2855 );
2856 assert!(
2857 !excerpt_blocks_2.contains(&custom_block_id),
2858 "Should have only blocks from unfolded buffers"
2859 );
2860 assert!(
2861 !excerpt_blocks_3.contains(&custom_block_id),
2862 "Should have only blocks from unfolded buffers"
2863 );
2864 }
2865 }
2866
2867 assert_eq!(
2868 blocks_snapshot.text(),
2869 "\n\n\n111\n\n\n\n",
2870 "Should have a single, first buffer left after folding"
2871 );
2872 assert_eq!(
2873 blocks_snapshot
2874 .row_infos(BlockRow(0))
2875 .map(|i| i.buffer_row)
2876 .collect::<Vec<_>>(),
2877 vec![None, None, None, Some(0), None, None, None, None,]
2878 );
2879 }
2880
2881 #[gpui::test]
2882 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
2883 cx.update(init_test);
2884
2885 let text = "111";
2886
2887 let buffer = cx.update(|cx| {
2888 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
2889 });
2890 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2891 let buffer_ids = buffer_snapshot
2892 .excerpts()
2893 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2894 .dedup()
2895 .collect::<Vec<_>>();
2896 assert_eq!(buffer_ids.len(), 1);
2897 let buffer_id = buffer_ids[0];
2898
2899 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
2900 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2901 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2902 let (_, wrap_snapshot) =
2903 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2904 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2905 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2906
2907 assert_eq!(blocks_snapshot.text(), "\n\n111");
2908
2909 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2910 buffer.read_with(cx, |buffer, cx| {
2911 writer.fold_buffers([buffer_id], buffer, cx);
2912 });
2913 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
2914 let blocks = blocks_snapshot
2915 .blocks_in_range(0..u32::MAX)
2916 .collect::<Vec<_>>();
2917 assert_eq!(
2918 1,
2919 blocks
2920 .iter()
2921 .filter(|(_, block)| { matches!(block, Block::FoldedBuffer { .. }) })
2922 .count(),
2923 "Should have one folded block, producing a header of the second buffer"
2924 );
2925 assert_eq!(blocks_snapshot.text(), "\n");
2926 assert_eq!(
2927 blocks_snapshot
2928 .row_infos(BlockRow(0))
2929 .map(|i| i.buffer_row)
2930 .collect::<Vec<_>>(),
2931 vec![None, None],
2932 "When fully folded, should be no buffer rows"
2933 );
2934 }
2935
2936 #[gpui::test(iterations = 100)]
2937 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
2938 cx.update(init_test);
2939
2940 let operations = env::var("OPERATIONS")
2941 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2942 .unwrap_or(10);
2943
2944 let wrap_width = if rng.random_bool(0.2) {
2945 None
2946 } else {
2947 Some(px(rng.random_range(0.0..=100.0)))
2948 };
2949 let tab_size = 1.try_into().unwrap();
2950 let font_size = px(14.0);
2951 let buffer_start_header_height = rng.random_range(1..=5);
2952 let excerpt_header_height = rng.random_range(1..=5);
2953
2954 log::info!("Wrap width: {:?}", wrap_width);
2955 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
2956 let is_singleton = rng.random();
2957 let buffer = if is_singleton {
2958 let len = rng.random_range(0..10);
2959 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
2960 log::info!("initial singleton buffer text: {:?}", text);
2961 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
2962 } else {
2963 cx.update(|cx| {
2964 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
2965 log::info!(
2966 "initial multi-buffer text: {:?}",
2967 multibuffer.read(cx).read(cx).text()
2968 );
2969 multibuffer
2970 })
2971 };
2972
2973 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2974 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2975 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2976 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2977 let font = test_font();
2978 let (wrap_map, wraps_snapshot) =
2979 cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
2980 let mut block_map = BlockMap::new(
2981 wraps_snapshot,
2982 buffer_start_header_height,
2983 excerpt_header_height,
2984 );
2985
2986 for _ in 0..operations {
2987 let mut buffer_edits = Vec::new();
2988 match rng.random_range(0..=100) {
2989 0..=19 => {
2990 let wrap_width = if rng.random_bool(0.2) {
2991 None
2992 } else {
2993 Some(px(rng.random_range(0.0..=100.0)))
2994 };
2995 log::info!("Setting wrap width to {:?}", wrap_width);
2996 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
2997 }
2998 20..=39 => {
2999 let block_count = rng.random_range(1..=5);
3000 let block_properties = (0..block_count)
3001 .map(|_| {
3002 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
3003 let offset =
3004 buffer.clip_offset(rng.random_range(0..=buffer.len()), Bias::Left);
3005 let mut min_height = 0;
3006 let placement = match rng.random_range(0..3) {
3007 0 => {
3008 min_height = 1;
3009 let start = buffer.anchor_after(offset);
3010 let end = buffer.anchor_after(buffer.clip_offset(
3011 rng.random_range(offset..=buffer.len()),
3012 Bias::Left,
3013 ));
3014 BlockPlacement::Replace(start..=end)
3015 }
3016 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3017 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3018 };
3019
3020 let height = rng.random_range(min_height..5);
3021 BlockProperties {
3022 style: BlockStyle::Fixed,
3023 placement,
3024 height: Some(height),
3025 render: Arc::new(|_| div().into_any()),
3026 priority: 0,
3027 }
3028 })
3029 .collect::<Vec<_>>();
3030
3031 let (inlay_snapshot, inlay_edits) =
3032 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3033 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3034 let (tab_snapshot, tab_edits) =
3035 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3036 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3037 wrap_map.sync(tab_snapshot, tab_edits, cx)
3038 });
3039 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3040 let block_ids =
3041 block_map.insert(block_properties.iter().map(|props| BlockProperties {
3042 placement: props.placement.clone(),
3043 height: props.height,
3044 style: props.style,
3045 render: Arc::new(|_| div().into_any()),
3046 priority: 0,
3047 }));
3048
3049 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3050 log::info!(
3051 "inserted block {:?} with height {:?} and id {:?}",
3052 block_properties
3053 .placement
3054 .as_ref()
3055 .map(|p| p.to_point(&buffer_snapshot)),
3056 block_properties.height,
3057 block_id
3058 );
3059 }
3060 }
3061 40..=59 if !block_map.custom_blocks.is_empty() => {
3062 let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3063 let block_ids_to_remove = block_map
3064 .custom_blocks
3065 .choose_multiple(&mut rng, block_count)
3066 .map(|block| block.id)
3067 .collect::<HashSet<_>>();
3068
3069 let (inlay_snapshot, inlay_edits) =
3070 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3071 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3072 let (tab_snapshot, tab_edits) =
3073 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3074 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3075 wrap_map.sync(tab_snapshot, tab_edits, cx)
3076 });
3077 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3078 log::info!(
3079 "removing {} blocks: {:?}",
3080 block_ids_to_remove.len(),
3081 block_ids_to_remove
3082 );
3083 block_map.remove(block_ids_to_remove);
3084 }
3085 60..=79 => {
3086 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3087 log::info!("Noop fold/unfold operation on a singleton buffer");
3088 continue;
3089 }
3090 let (inlay_snapshot, inlay_edits) =
3091 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3092 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3093 let (tab_snapshot, tab_edits) =
3094 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3095 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3096 wrap_map.sync(tab_snapshot, tab_edits, cx)
3097 });
3098 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3099 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3100 let folded_buffers = block_map
3101 .0
3102 .folded_buffers
3103 .iter()
3104 .cloned()
3105 .collect::<Vec<_>>();
3106 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3107 unfolded_buffers.dedup();
3108 log::debug!("All buffers {unfolded_buffers:?}");
3109 log::debug!("Folded buffers {folded_buffers:?}");
3110 unfolded_buffers
3111 .retain(|buffer_id| !block_map.0.folded_buffers.contains(buffer_id));
3112 (unfolded_buffers, folded_buffers)
3113 });
3114 let mut folded_count = folded_buffers.len();
3115 let mut unfolded_count = unfolded_buffers.len();
3116
3117 let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
3118 let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
3119 if !fold && !unfold {
3120 log::info!(
3121 "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3122 );
3123 continue;
3124 }
3125
3126 buffer.update(cx, |buffer, cx| {
3127 if fold {
3128 let buffer_to_fold =
3129 unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
3130 log::info!("Folding {buffer_to_fold:?}");
3131 let related_excerpts = buffer_snapshot
3132 .excerpts()
3133 .filter_map(|(excerpt_id, buffer, range)| {
3134 if buffer.remote_id() == buffer_to_fold {
3135 Some((
3136 excerpt_id,
3137 buffer
3138 .text_for_range(range.context)
3139 .collect::<String>(),
3140 ))
3141 } else {
3142 None
3143 }
3144 })
3145 .collect::<Vec<_>>();
3146 log::info!(
3147 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
3148 );
3149 folded_count += 1;
3150 unfolded_count -= 1;
3151 block_map.fold_buffers([buffer_to_fold], buffer, cx);
3152 }
3153 if unfold {
3154 let buffer_to_unfold =
3155 folded_buffers[rng.random_range(0..folded_buffers.len())];
3156 log::info!("Unfolding {buffer_to_unfold:?}");
3157 unfolded_count += 1;
3158 folded_count -= 1;
3159 block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
3160 }
3161 log::info!(
3162 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3163 );
3164 });
3165 }
3166 _ => {
3167 buffer.update(cx, |buffer, cx| {
3168 let mutation_count = rng.random_range(1..=5);
3169 let subscription = buffer.subscribe();
3170 buffer.randomly_mutate(&mut rng, mutation_count, cx);
3171 buffer_snapshot = buffer.snapshot(cx);
3172 buffer_edits.extend(subscription.consume());
3173 log::info!("buffer text: {:?}", buffer_snapshot.text());
3174 });
3175 }
3176 }
3177
3178 let (inlay_snapshot, inlay_edits) =
3179 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
3180 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3181 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3182 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3183 wrap_map.sync(tab_snapshot, tab_edits, cx)
3184 });
3185 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
3186 assert_eq!(
3187 blocks_snapshot.transforms.summary().input_rows,
3188 wraps_snapshot.max_point().row() + 1
3189 );
3190 log::info!("wrapped text: {:?}", wraps_snapshot.text());
3191 log::info!("blocks text: {:?}", blocks_snapshot.text());
3192
3193 let mut expected_blocks = Vec::new();
3194 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
3195 Some((
3196 block.placement.to_wrap_row(&wraps_snapshot)?,
3197 Block::Custom(block.clone()),
3198 ))
3199 }));
3200
3201 // Note that this needs to be synced with the related section in BlockMap::sync
3202 expected_blocks.extend(block_map.header_and_footer_blocks(
3203 &buffer_snapshot,
3204 0..,
3205 &wraps_snapshot,
3206 ));
3207
3208 BlockMap::sort_blocks(&mut expected_blocks);
3209
3210 for (placement, block) in &expected_blocks {
3211 log::info!(
3212 "Block {:?} placement: {:?} Height: {:?}",
3213 block.id(),
3214 placement,
3215 block.height()
3216 );
3217 }
3218
3219 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
3220
3221 let input_buffer_rows = buffer_snapshot
3222 .row_infos(MultiBufferRow(0))
3223 .map(|row| row.buffer_row)
3224 .collect::<Vec<_>>();
3225 let mut expected_buffer_rows = Vec::new();
3226 let mut expected_text = String::new();
3227 let mut expected_block_positions = Vec::new();
3228 let mut expected_replaced_buffer_rows = HashSet::default();
3229 let input_text = wraps_snapshot.text();
3230
3231 // Loop over the input lines, creating (N - 1) empty lines for
3232 // blocks of height N.
3233 //
3234 // It's important to note that output *starts* as one empty line,
3235 // so we special case row 0 to assume a leading '\n'.
3236 //
3237 // Linehood is the birthright of strings.
3238 let input_text_lines = input_text.split('\n').enumerate().peekable();
3239 let mut block_row = 0;
3240 for (wrap_row, input_line) in input_text_lines {
3241 let wrap_row = wrap_row as u32;
3242 let multibuffer_row = wraps_snapshot
3243 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
3244 .row;
3245
3246 // Create empty lines for the above block
3247 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3248 if placement.start().0 == wrap_row && block.place_above() {
3249 let (_, block) = sorted_blocks_iter.next().unwrap();
3250 expected_block_positions.push((block_row, block.id()));
3251 if block.height() > 0 {
3252 let text = "\n".repeat((block.height() - 1) as usize);
3253 if block_row > 0 {
3254 expected_text.push('\n')
3255 }
3256 expected_text.push_str(&text);
3257 for _ in 0..block.height() {
3258 expected_buffer_rows.push(None);
3259 }
3260 block_row += block.height();
3261 }
3262 } else {
3263 break;
3264 }
3265 }
3266
3267 // Skip lines within replace blocks, then create empty lines for the replace block's height
3268 let mut is_in_replace_block = false;
3269 if let Some((BlockPlacement::Replace(replace_range), block)) =
3270 sorted_blocks_iter.peek()
3271 && wrap_row >= replace_range.start().0
3272 {
3273 is_in_replace_block = true;
3274
3275 if wrap_row == replace_range.start().0 {
3276 if matches!(block, Block::FoldedBuffer { .. }) {
3277 expected_buffer_rows.push(None);
3278 } else {
3279 expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
3280 }
3281 }
3282
3283 if wrap_row == replace_range.end().0 {
3284 expected_block_positions.push((block_row, block.id()));
3285 let text = "\n".repeat((block.height() - 1) as usize);
3286 if block_row > 0 {
3287 expected_text.push('\n');
3288 }
3289 expected_text.push_str(&text);
3290
3291 for _ in 1..block.height() {
3292 expected_buffer_rows.push(None);
3293 }
3294 block_row += block.height();
3295
3296 sorted_blocks_iter.next();
3297 }
3298 }
3299
3300 if is_in_replace_block {
3301 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
3302 } else {
3303 let buffer_row = input_buffer_rows[multibuffer_row as usize];
3304 let soft_wrapped = wraps_snapshot
3305 .to_tab_point(WrapPoint::new(wrap_row, 0))
3306 .column()
3307 > 0;
3308 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
3309 if block_row > 0 {
3310 expected_text.push('\n');
3311 }
3312 expected_text.push_str(input_line);
3313 block_row += 1;
3314 }
3315
3316 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3317 if placement.end().0 == wrap_row && block.place_below() {
3318 let (_, block) = sorted_blocks_iter.next().unwrap();
3319 expected_block_positions.push((block_row, block.id()));
3320 if block.height() > 0 {
3321 let text = "\n".repeat((block.height() - 1) as usize);
3322 if block_row > 0 {
3323 expected_text.push('\n')
3324 }
3325 expected_text.push_str(&text);
3326 for _ in 0..block.height() {
3327 expected_buffer_rows.push(None);
3328 }
3329 block_row += block.height();
3330 }
3331 } else {
3332 break;
3333 }
3334 }
3335 }
3336
3337 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
3338 let expected_row_count = expected_lines.len();
3339 log::info!("expected text: {expected_text:?}");
3340
3341 assert_eq!(
3342 blocks_snapshot.max_point().row + 1,
3343 expected_row_count as u32,
3344 "actual row count != expected row count",
3345 );
3346 assert_eq!(
3347 blocks_snapshot.text(),
3348 expected_text,
3349 "actual text != expected text",
3350 );
3351
3352 for start_row in 0..expected_row_count {
3353 let end_row = rng.random_range(start_row + 1..=expected_row_count);
3354 let mut expected_text = expected_lines[start_row..end_row].join("\n");
3355 if end_row < expected_row_count {
3356 expected_text.push('\n');
3357 }
3358
3359 let actual_text = blocks_snapshot
3360 .chunks(
3361 start_row as u32..end_row as u32,
3362 false,
3363 false,
3364 Highlights::default(),
3365 )
3366 .map(|chunk| chunk.text)
3367 .collect::<String>();
3368 assert_eq!(
3369 actual_text,
3370 expected_text,
3371 "incorrect text starting row row range {:?}",
3372 start_row..end_row
3373 );
3374 assert_eq!(
3375 blocks_snapshot
3376 .row_infos(BlockRow(start_row as u32))
3377 .map(|row_info| row_info.buffer_row)
3378 .collect::<Vec<_>>(),
3379 &expected_buffer_rows[start_row..],
3380 "incorrect buffer_rows starting at row {:?}",
3381 start_row
3382 );
3383 }
3384
3385 assert_eq!(
3386 blocks_snapshot
3387 .blocks_in_range(0..(expected_row_count as u32))
3388 .map(|(row, block)| (row, block.id()))
3389 .collect::<Vec<_>>(),
3390 expected_block_positions,
3391 "invalid blocks_in_range({:?})",
3392 0..expected_row_count
3393 );
3394
3395 for (_, expected_block) in
3396 blocks_snapshot.blocks_in_range(0..(expected_row_count as u32))
3397 {
3398 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
3399 assert_eq!(
3400 actual_block.map(|block| block.id()),
3401 Some(expected_block.id())
3402 );
3403 }
3404
3405 for (block_row, block_id) in expected_block_positions {
3406 if let BlockId::Custom(block_id) = block_id {
3407 assert_eq!(
3408 blocks_snapshot.row_for_block(block_id),
3409 Some(BlockRow(block_row))
3410 );
3411 }
3412 }
3413
3414 let mut expected_longest_rows = Vec::new();
3415 let mut longest_line_len = -1_isize;
3416 for (row, line) in expected_lines.iter().enumerate() {
3417 let row = row as u32;
3418
3419 assert_eq!(
3420 blocks_snapshot.line_len(BlockRow(row)),
3421 line.len() as u32,
3422 "invalid line len for row {}",
3423 row
3424 );
3425
3426 let line_char_count = line.chars().count() as isize;
3427 match line_char_count.cmp(&longest_line_len) {
3428 Ordering::Less => {}
3429 Ordering::Equal => expected_longest_rows.push(row),
3430 Ordering::Greater => {
3431 longest_line_len = line_char_count;
3432 expected_longest_rows.clear();
3433 expected_longest_rows.push(row);
3434 }
3435 }
3436 }
3437
3438 let longest_row = blocks_snapshot.longest_row();
3439 assert!(
3440 expected_longest_rows.contains(&longest_row),
3441 "incorrect longest row {}. expected {:?} with length {}",
3442 longest_row,
3443 expected_longest_rows,
3444 longest_line_len,
3445 );
3446
3447 for _ in 0..10 {
3448 let end_row = rng.random_range(1..=expected_lines.len());
3449 let start_row = rng.random_range(0..end_row);
3450
3451 let mut expected_longest_rows_in_range = vec![];
3452 let mut longest_line_len_in_range = 0;
3453
3454 let mut row = start_row as u32;
3455 for line in &expected_lines[start_row..end_row] {
3456 let line_char_count = line.chars().count() as isize;
3457 match line_char_count.cmp(&longest_line_len_in_range) {
3458 Ordering::Less => {}
3459 Ordering::Equal => expected_longest_rows_in_range.push(row),
3460 Ordering::Greater => {
3461 longest_line_len_in_range = line_char_count;
3462 expected_longest_rows_in_range.clear();
3463 expected_longest_rows_in_range.push(row);
3464 }
3465 }
3466 row += 1;
3467 }
3468
3469 let longest_row_in_range = blocks_snapshot
3470 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
3471 assert!(
3472 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
3473 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
3474 longest_row,
3475 start_row..end_row,
3476 expected_longest_rows_in_range,
3477 longest_line_len_in_range,
3478 );
3479 }
3480
3481 // Ensure that conversion between block points and wrap points is stable.
3482 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
3483 let wrap_point = WrapPoint::new(row, 0);
3484 let block_point = blocks_snapshot.to_block_point(wrap_point);
3485 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
3486 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
3487 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
3488 assert_eq!(
3489 blocks_snapshot.to_block_point(right_wrap_point),
3490 block_point
3491 );
3492 }
3493
3494 let mut block_point = BlockPoint::new(0, 0);
3495 for c in expected_text.chars() {
3496 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
3497 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
3498 assert_eq!(
3499 blocks_snapshot
3500 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
3501 left_point,
3502 "block point: {:?}, wrap point: {:?}",
3503 block_point,
3504 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
3505 );
3506 assert_eq!(
3507 left_buffer_point,
3508 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
3509 "{:?} is not valid in buffer coordinates",
3510 left_point
3511 );
3512
3513 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
3514 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
3515 assert_eq!(
3516 blocks_snapshot
3517 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
3518 right_point,
3519 "block point: {:?}, wrap point: {:?}",
3520 block_point,
3521 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
3522 );
3523 assert_eq!(
3524 right_buffer_point,
3525 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
3526 "{:?} is not valid in buffer coordinates",
3527 right_point
3528 );
3529
3530 if c == '\n' {
3531 block_point.0 += Point::new(1, 0);
3532 } else {
3533 block_point.column += c.len_utf8() as u32;
3534 }
3535 }
3536
3537 for buffer_row in 0..=buffer_snapshot.max_point().row {
3538 let buffer_row = MultiBufferRow(buffer_row);
3539 assert_eq!(
3540 blocks_snapshot.is_line_replaced(buffer_row),
3541 expected_replaced_buffer_rows.contains(&buffer_row),
3542 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
3543 );
3544 }
3545 }
3546 }
3547
3548 #[gpui::test]
3549 fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
3550 cx.update(init_test);
3551
3552 let text = "abc\ndef\nghi\njkl\nmno";
3553 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3554 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3555 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3556 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3557 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3558 let (_wrap_map, wraps_snapshot) =
3559 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3560 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3561
3562 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3563 let _block_id = writer.insert(vec![BlockProperties {
3564 style: BlockStyle::Fixed,
3565 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3566 height: Some(1),
3567 render: Arc::new(|_| div().into_any()),
3568 priority: 0,
3569 }])[0];
3570
3571 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
3572 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3573
3574 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3575 writer.remove_intersecting_replace_blocks(
3576 [buffer_snapshot
3577 .anchor_after(Point::new(1, 0))
3578 .to_offset(&buffer_snapshot)
3579 ..buffer_snapshot
3580 .anchor_after(Point::new(1, 0))
3581 .to_offset(&buffer_snapshot)],
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}