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