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