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