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::new(0..buffer1.read(cx).len())],
2129 cx,
2130 ));
2131 excerpt_ids.extend(multi_buffer.push_excerpts(
2132 buffer2.clone(),
2133 [ExcerptRange::new(0..buffer2.read(cx).len())],
2134 cx,
2135 ));
2136 excerpt_ids.extend(multi_buffer.push_excerpts(
2137 buffer3.clone(),
2138 [ExcerptRange::new(0..buffer3.read(cx).len())],
2139 cx,
2140 ));
2141
2142 multi_buffer
2143 });
2144
2145 let font = test_font();
2146 let font_size = px(14.);
2147 let font_id = cx.text_system().resolve_font(&font);
2148 let mut wrap_width = px(0.);
2149 for c in "Buff".chars() {
2150 wrap_width += cx
2151 .text_system()
2152 .advance(font_id, font_size, c)
2153 .unwrap()
2154 .width;
2155 }
2156
2157 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2158 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone());
2159 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2160 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2161 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
2162
2163 let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2164 let snapshot = block_map.read(wraps_snapshot, Default::default());
2165
2166 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
2167 assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
2168
2169 let blocks: Vec<_> = snapshot
2170 .blocks_in_range(0..u32::MAX)
2171 .map(|(row, block)| (row..row + block.height(), block.id()))
2172 .collect();
2173 assert_eq!(
2174 blocks,
2175 vec![
2176 (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
2177 (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
2178 (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
2179 ]
2180 );
2181 }
2182
2183 #[gpui::test]
2184 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
2185 cx.update(init_test);
2186
2187 let text = "aaa\nbbb\nccc\nddd";
2188
2189 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2190 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2191 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2192 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2193 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2194 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2195 let (_wrap_map, wraps_snapshot) =
2196 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2197 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2198
2199 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2200 let block_ids = writer.insert(vec![
2201 BlockProperties {
2202 style: BlockStyle::Fixed,
2203 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2204 height: 1,
2205 render: Arc::new(|_| div().into_any()),
2206 priority: 0,
2207 },
2208 BlockProperties {
2209 style: BlockStyle::Fixed,
2210 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2211 height: 2,
2212 render: Arc::new(|_| div().into_any()),
2213 priority: 0,
2214 },
2215 BlockProperties {
2216 style: BlockStyle::Fixed,
2217 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2218 height: 3,
2219 render: Arc::new(|_| div().into_any()),
2220 priority: 0,
2221 },
2222 ]);
2223
2224 {
2225 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2226 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2227
2228 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2229
2230 let mut new_heights = HashMap::default();
2231 new_heights.insert(block_ids[0], 2);
2232 block_map_writer.resize(new_heights);
2233 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2234 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2235 }
2236
2237 {
2238 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2239
2240 let mut new_heights = HashMap::default();
2241 new_heights.insert(block_ids[0], 1);
2242 block_map_writer.resize(new_heights);
2243
2244 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2245 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2246 }
2247
2248 {
2249 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2250
2251 let mut new_heights = HashMap::default();
2252 new_heights.insert(block_ids[0], 0);
2253 block_map_writer.resize(new_heights);
2254
2255 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2256 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
2257 }
2258
2259 {
2260 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2261
2262 let mut new_heights = HashMap::default();
2263 new_heights.insert(block_ids[0], 3);
2264 block_map_writer.resize(new_heights);
2265
2266 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2267 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2268 }
2269
2270 {
2271 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2272
2273 let mut new_heights = HashMap::default();
2274 new_heights.insert(block_ids[0], 3);
2275 block_map_writer.resize(new_heights);
2276
2277 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2278 // Same height as before, should remain the same
2279 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2280 }
2281 }
2282
2283 #[cfg(target_os = "macos")]
2284 #[gpui::test]
2285 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
2286 cx.update(init_test);
2287
2288 let _font_id = cx.text_system().font_id(&font("Helvetica")).unwrap();
2289
2290 let text = "one two three\nfour five six\nseven eight";
2291
2292 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2293 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2294 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2295 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2296 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2297 let (_, wraps_snapshot) = cx.update(|cx| {
2298 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx)
2299 });
2300 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2301
2302 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2303 writer.insert(vec![
2304 BlockProperties {
2305 style: BlockStyle::Fixed,
2306 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
2307 render: Arc::new(|_| div().into_any()),
2308 height: 1,
2309 priority: 0,
2310 },
2311 BlockProperties {
2312 style: BlockStyle::Fixed,
2313 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
2314 render: Arc::new(|_| div().into_any()),
2315 height: 1,
2316 priority: 0,
2317 },
2318 ]);
2319
2320 // Blocks with an 'above' disposition go above their corresponding buffer line.
2321 // Blocks with a 'below' disposition go below their corresponding buffer line.
2322 let snapshot = block_map.read(wraps_snapshot, Default::default());
2323 assert_eq!(
2324 snapshot.text(),
2325 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
2326 );
2327 }
2328
2329 #[gpui::test]
2330 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
2331 cx.update(init_test);
2332
2333 let text = "line1\nline2\nline3\nline4\nline5";
2334
2335 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2336 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
2337 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2338 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2339 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2340 let tab_size = 1.try_into().unwrap();
2341 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
2342 let (wrap_map, wraps_snapshot) =
2343 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2344 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2345
2346 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2347 let replace_block_id = writer.insert(vec![BlockProperties {
2348 style: BlockStyle::Fixed,
2349 placement: BlockPlacement::Replace(
2350 buffer_snapshot.anchor_after(Point::new(1, 3))
2351 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
2352 ),
2353 height: 4,
2354 render: Arc::new(|_| div().into_any()),
2355 priority: 0,
2356 }])[0];
2357
2358 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2359 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2360
2361 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2362 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
2363 buffer.snapshot(cx)
2364 });
2365 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2366 buffer_snapshot.clone(),
2367 buffer_subscription.consume().into_inner(),
2368 );
2369 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2370 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2371 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2372 wrap_map.sync(tab_snapshot, tab_edits, cx)
2373 });
2374 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2375 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2376
2377 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2378 buffer.edit(
2379 [(
2380 Point::new(1, 5)..Point::new(1, 5),
2381 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
2382 )],
2383 None,
2384 cx,
2385 );
2386 buffer.snapshot(cx)
2387 });
2388 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2389 buffer_snapshot.clone(),
2390 buffer_subscription.consume().into_inner(),
2391 );
2392 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2393 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2394 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2395 wrap_map.sync(tab_snapshot, tab_edits, cx)
2396 });
2397 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2398 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2399
2400 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
2401 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2402 writer.insert(vec![
2403 BlockProperties {
2404 style: BlockStyle::Fixed,
2405 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
2406 height: 1,
2407 render: Arc::new(|_| div().into_any()),
2408 priority: 0,
2409 },
2410 BlockProperties {
2411 style: BlockStyle::Fixed,
2412 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
2413 height: 1,
2414 render: Arc::new(|_| div().into_any()),
2415 priority: 0,
2416 },
2417 BlockProperties {
2418 style: BlockStyle::Fixed,
2419 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
2420 height: 1,
2421 render: Arc::new(|_| div().into_any()),
2422 priority: 0,
2423 },
2424 ]);
2425 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2426 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2427
2428 // Ensure blocks inserted *inside* replaced region are hidden.
2429 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2430 writer.insert(vec![
2431 BlockProperties {
2432 style: BlockStyle::Fixed,
2433 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
2434 height: 1,
2435 render: Arc::new(|_| div().into_any()),
2436 priority: 0,
2437 },
2438 BlockProperties {
2439 style: BlockStyle::Fixed,
2440 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
2441 height: 1,
2442 render: Arc::new(|_| div().into_any()),
2443 priority: 0,
2444 },
2445 BlockProperties {
2446 style: BlockStyle::Fixed,
2447 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
2448 height: 1,
2449 render: Arc::new(|_| div().into_any()),
2450 priority: 0,
2451 },
2452 ]);
2453 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2454 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2455
2456 // Removing the replace block shows all the hidden blocks again.
2457 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2458 writer.remove(HashSet::from_iter([replace_block_id]));
2459 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2460 assert_eq!(
2461 blocks_snapshot.text(),
2462 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
2463 );
2464 }
2465
2466 #[gpui::test]
2467 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
2468 cx.update(init_test);
2469
2470 let text = "111\n222\n333\n444\n555\n666";
2471
2472 let buffer = cx.update(|cx| {
2473 MultiBuffer::build_multi(
2474 [
2475 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
2476 (
2477 text,
2478 vec![
2479 Point::new(1, 0)..Point::new(1, 3),
2480 Point::new(2, 0)..Point::new(2, 3),
2481 Point::new(3, 0)..Point::new(3, 3),
2482 ],
2483 ),
2484 (
2485 text,
2486 vec![
2487 Point::new(4, 0)..Point::new(4, 3),
2488 Point::new(5, 0)..Point::new(5, 3),
2489 ],
2490 ),
2491 ],
2492 cx,
2493 )
2494 });
2495 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2496 let buffer_ids = buffer_snapshot
2497 .excerpts()
2498 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2499 .dedup()
2500 .collect::<Vec<_>>();
2501 assert_eq!(buffer_ids.len(), 3);
2502 let buffer_id_1 = buffer_ids[0];
2503 let buffer_id_2 = buffer_ids[1];
2504 let buffer_id_3 = buffer_ids[2];
2505
2506 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2507 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2508 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2509 let (_, wrap_snapshot) =
2510 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2511 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2512 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2513
2514 assert_eq!(
2515 blocks_snapshot.text(),
2516 "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
2517 );
2518 assert_eq!(
2519 blocks_snapshot
2520 .row_infos(BlockRow(0))
2521 .map(|i| i.buffer_row)
2522 .collect::<Vec<_>>(),
2523 vec![
2524 None,
2525 None,
2526 Some(0),
2527 None,
2528 None,
2529 Some(1),
2530 None,
2531 Some(2),
2532 None,
2533 Some(3),
2534 None,
2535 None,
2536 Some(4),
2537 None,
2538 Some(5),
2539 ]
2540 );
2541
2542 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2543 let excerpt_blocks_2 = writer.insert(vec![
2544 BlockProperties {
2545 style: BlockStyle::Fixed,
2546 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2547 height: 1,
2548 render: Arc::new(|_| div().into_any()),
2549 priority: 0,
2550 },
2551 BlockProperties {
2552 style: BlockStyle::Fixed,
2553 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
2554 height: 1,
2555 render: Arc::new(|_| div().into_any()),
2556 priority: 0,
2557 },
2558 BlockProperties {
2559 style: BlockStyle::Fixed,
2560 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
2561 height: 1,
2562 render: Arc::new(|_| div().into_any()),
2563 priority: 0,
2564 },
2565 ]);
2566 let excerpt_blocks_3 = writer.insert(vec![
2567 BlockProperties {
2568 style: BlockStyle::Fixed,
2569 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
2570 height: 1,
2571 render: Arc::new(|_| div().into_any()),
2572 priority: 0,
2573 },
2574 BlockProperties {
2575 style: BlockStyle::Fixed,
2576 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
2577 height: 1,
2578 render: Arc::new(|_| div().into_any()),
2579 priority: 0,
2580 },
2581 ]);
2582
2583 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2584 assert_eq!(
2585 blocks_snapshot.text(),
2586 "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2587 );
2588 assert_eq!(
2589 blocks_snapshot
2590 .row_infos(BlockRow(0))
2591 .map(|i| i.buffer_row)
2592 .collect::<Vec<_>>(),
2593 vec![
2594 None,
2595 None,
2596 Some(0),
2597 None,
2598 None,
2599 None,
2600 Some(1),
2601 None,
2602 None,
2603 Some(2),
2604 None,
2605 Some(3),
2606 None,
2607 None,
2608 None,
2609 None,
2610 Some(4),
2611 None,
2612 Some(5),
2613 None,
2614 ]
2615 );
2616
2617 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2618 buffer.read_with(cx, |buffer, cx| {
2619 writer.fold_buffers([buffer_id_1], buffer, cx);
2620 });
2621 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
2622 style: BlockStyle::Fixed,
2623 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
2624 height: 1,
2625 render: Arc::new(|_| div().into_any()),
2626 priority: 0,
2627 }]);
2628 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2629 let blocks = blocks_snapshot
2630 .blocks_in_range(0..u32::MAX)
2631 .collect::<Vec<_>>();
2632 for (_, block) in &blocks {
2633 if let BlockId::Custom(custom_block_id) = block.id() {
2634 assert!(
2635 !excerpt_blocks_1.contains(&custom_block_id),
2636 "Should have no blocks from the folded buffer"
2637 );
2638 assert!(
2639 excerpt_blocks_2.contains(&custom_block_id)
2640 || excerpt_blocks_3.contains(&custom_block_id),
2641 "Should have only blocks from unfolded buffers"
2642 );
2643 }
2644 }
2645 assert_eq!(
2646 1,
2647 blocks
2648 .iter()
2649 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2650 .count(),
2651 "Should have one folded block, producing a header of the second buffer"
2652 );
2653 assert_eq!(
2654 blocks_snapshot.text(),
2655 "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2656 );
2657 assert_eq!(
2658 blocks_snapshot
2659 .row_infos(BlockRow(0))
2660 .map(|i| i.buffer_row)
2661 .collect::<Vec<_>>(),
2662 vec![
2663 None,
2664 None,
2665 None,
2666 None,
2667 None,
2668 Some(1),
2669 None,
2670 None,
2671 Some(2),
2672 None,
2673 Some(3),
2674 None,
2675 None,
2676 None,
2677 None,
2678 Some(4),
2679 None,
2680 Some(5),
2681 None,
2682 ]
2683 );
2684
2685 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2686 buffer.read_with(cx, |buffer, cx| {
2687 writer.fold_buffers([buffer_id_2], buffer, cx);
2688 });
2689 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2690 let blocks = blocks_snapshot
2691 .blocks_in_range(0..u32::MAX)
2692 .collect::<Vec<_>>();
2693 for (_, block) in &blocks {
2694 if let BlockId::Custom(custom_block_id) = block.id() {
2695 assert!(
2696 !excerpt_blocks_1.contains(&custom_block_id),
2697 "Should have no blocks from the folded buffer_1"
2698 );
2699 assert!(
2700 !excerpt_blocks_2.contains(&custom_block_id),
2701 "Should have no blocks from the folded buffer_2"
2702 );
2703 assert!(
2704 excerpt_blocks_3.contains(&custom_block_id),
2705 "Should have only blocks from unfolded buffers"
2706 );
2707 }
2708 }
2709 assert_eq!(
2710 2,
2711 blocks
2712 .iter()
2713 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2714 .count(),
2715 "Should have two folded blocks, producing headers"
2716 );
2717 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
2718 assert_eq!(
2719 blocks_snapshot
2720 .row_infos(BlockRow(0))
2721 .map(|i| i.buffer_row)
2722 .collect::<Vec<_>>(),
2723 vec![
2724 None,
2725 None,
2726 None,
2727 None,
2728 None,
2729 None,
2730 None,
2731 Some(4),
2732 None,
2733 Some(5),
2734 None,
2735 ]
2736 );
2737
2738 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2739 buffer.read_with(cx, |buffer, cx| {
2740 writer.unfold_buffers([buffer_id_1], buffer, cx);
2741 });
2742 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2743 let blocks = blocks_snapshot
2744 .blocks_in_range(0..u32::MAX)
2745 .collect::<Vec<_>>();
2746 for (_, block) in &blocks {
2747 if let BlockId::Custom(custom_block_id) = block.id() {
2748 assert!(
2749 !excerpt_blocks_2.contains(&custom_block_id),
2750 "Should have no blocks from the folded buffer_2"
2751 );
2752 assert!(
2753 excerpt_blocks_1.contains(&custom_block_id)
2754 || excerpt_blocks_3.contains(&custom_block_id),
2755 "Should have only blocks from unfolded buffers"
2756 );
2757 }
2758 }
2759 assert_eq!(
2760 1,
2761 blocks
2762 .iter()
2763 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2764 .count(),
2765 "Should be back to a single folded buffer, producing a header for buffer_2"
2766 );
2767 assert_eq!(
2768 blocks_snapshot.text(),
2769 "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
2770 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
2771 );
2772 assert_eq!(
2773 blocks_snapshot
2774 .row_infos(BlockRow(0))
2775 .map(|i| i.buffer_row)
2776 .collect::<Vec<_>>(),
2777 vec![
2778 None,
2779 None,
2780 None,
2781 Some(0),
2782 None,
2783 None,
2784 None,
2785 None,
2786 None,
2787 Some(4),
2788 None,
2789 Some(5),
2790 None,
2791 ]
2792 );
2793
2794 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2795 buffer.read_with(cx, |buffer, cx| {
2796 writer.fold_buffers([buffer_id_3], buffer, cx);
2797 });
2798 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2799 let blocks = blocks_snapshot
2800 .blocks_in_range(0..u32::MAX)
2801 .collect::<Vec<_>>();
2802 for (_, block) in &blocks {
2803 if let BlockId::Custom(custom_block_id) = block.id() {
2804 assert!(
2805 excerpt_blocks_1.contains(&custom_block_id),
2806 "Should have no blocks from the folded buffer_1"
2807 );
2808 assert!(
2809 !excerpt_blocks_2.contains(&custom_block_id),
2810 "Should have only blocks from unfolded buffers"
2811 );
2812 assert!(
2813 !excerpt_blocks_3.contains(&custom_block_id),
2814 "Should have only blocks from unfolded buffers"
2815 );
2816 }
2817 }
2818
2819 assert_eq!(
2820 blocks_snapshot.text(),
2821 "\n\n\n111\n\n\n\n",
2822 "Should have a single, first buffer left after folding"
2823 );
2824 assert_eq!(
2825 blocks_snapshot
2826 .row_infos(BlockRow(0))
2827 .map(|i| i.buffer_row)
2828 .collect::<Vec<_>>(),
2829 vec![None, None, None, Some(0), None, None, None, None,]
2830 );
2831 }
2832
2833 #[gpui::test]
2834 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
2835 cx.update(init_test);
2836
2837 let text = "111";
2838
2839 let buffer = cx.update(|cx| {
2840 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
2841 });
2842 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2843 let buffer_ids = buffer_snapshot
2844 .excerpts()
2845 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2846 .dedup()
2847 .collect::<Vec<_>>();
2848 assert_eq!(buffer_ids.len(), 1);
2849 let buffer_id = buffer_ids[0];
2850
2851 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2852 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2853 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2854 let (_, wrap_snapshot) =
2855 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2856 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2857 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2858
2859 assert_eq!(blocks_snapshot.text(), "\n\n111");
2860
2861 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2862 buffer.read_with(cx, |buffer, cx| {
2863 writer.fold_buffers([buffer_id], buffer, cx);
2864 });
2865 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2866 let blocks = blocks_snapshot
2867 .blocks_in_range(0..u32::MAX)
2868 .collect::<Vec<_>>();
2869 assert_eq!(
2870 1,
2871 blocks
2872 .iter()
2873 .filter(|(_, block)| {
2874 match block {
2875 Block::FoldedBuffer { .. } => true,
2876 _ => false,
2877 }
2878 })
2879 .count(),
2880 "Should have one folded block, producing a header of the second buffer"
2881 );
2882 assert_eq!(blocks_snapshot.text(), "\n");
2883 assert_eq!(
2884 blocks_snapshot
2885 .row_infos(BlockRow(0))
2886 .map(|i| i.buffer_row)
2887 .collect::<Vec<_>>(),
2888 vec![None, None],
2889 "When fully folded, should be no buffer rows"
2890 );
2891 }
2892
2893 #[gpui::test(iterations = 100)]
2894 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
2895 cx.update(init_test);
2896
2897 let operations = env::var("OPERATIONS")
2898 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2899 .unwrap_or(10);
2900
2901 let wrap_width = if rng.gen_bool(0.2) {
2902 None
2903 } else {
2904 Some(px(rng.gen_range(0.0..=100.0)))
2905 };
2906 let tab_size = 1.try_into().unwrap();
2907 let font_size = px(14.0);
2908 let buffer_start_header_height = rng.gen_range(1..=5);
2909 let excerpt_header_height = rng.gen_range(1..=5);
2910
2911 log::info!("Wrap width: {:?}", wrap_width);
2912 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
2913 let is_singleton = rng.r#gen();
2914 let buffer = if is_singleton {
2915 let len = rng.gen_range(0..10);
2916 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
2917 log::info!("initial singleton buffer text: {:?}", text);
2918 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
2919 } else {
2920 cx.update(|cx| {
2921 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
2922 log::info!(
2923 "initial multi-buffer text: {:?}",
2924 multibuffer.read(cx).read(cx).text()
2925 );
2926 multibuffer
2927 })
2928 };
2929
2930 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2931 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2932 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2933 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2934 let font = test_font();
2935 let (wrap_map, wraps_snapshot) =
2936 cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
2937 let mut block_map = BlockMap::new(
2938 wraps_snapshot,
2939 buffer_start_header_height,
2940 excerpt_header_height,
2941 );
2942
2943 for _ in 0..operations {
2944 let mut buffer_edits = Vec::new();
2945 match rng.gen_range(0..=100) {
2946 0..=19 => {
2947 let wrap_width = if rng.gen_bool(0.2) {
2948 None
2949 } else {
2950 Some(px(rng.gen_range(0.0..=100.0)))
2951 };
2952 log::info!("Setting wrap width to {:?}", wrap_width);
2953 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
2954 }
2955 20..=39 => {
2956 let block_count = rng.gen_range(1..=5);
2957 let block_properties = (0..block_count)
2958 .map(|_| {
2959 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
2960 let offset =
2961 buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left);
2962 let mut min_height = 0;
2963 let placement = match rng.gen_range(0..3) {
2964 0 => {
2965 min_height = 1;
2966 let start = buffer.anchor_after(offset);
2967 let end = buffer.anchor_after(buffer.clip_offset(
2968 rng.gen_range(offset..=buffer.len()),
2969 Bias::Left,
2970 ));
2971 BlockPlacement::Replace(start..=end)
2972 }
2973 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
2974 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
2975 };
2976
2977 let height = rng.gen_range(min_height..5);
2978 BlockProperties {
2979 style: BlockStyle::Fixed,
2980 placement,
2981 height,
2982 render: Arc::new(|_| div().into_any()),
2983 priority: 0,
2984 }
2985 })
2986 .collect::<Vec<_>>();
2987
2988 let (inlay_snapshot, inlay_edits) =
2989 inlay_map.sync(buffer_snapshot.clone(), vec![]);
2990 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2991 let (tab_snapshot, tab_edits) =
2992 tab_map.sync(fold_snapshot, fold_edits, tab_size);
2993 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2994 wrap_map.sync(tab_snapshot, tab_edits, cx)
2995 });
2996 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
2997 let block_ids =
2998 block_map.insert(block_properties.iter().map(|props| BlockProperties {
2999 placement: props.placement.clone(),
3000 height: props.height,
3001 style: props.style,
3002 render: Arc::new(|_| div().into_any()),
3003 priority: 0,
3004 }));
3005
3006 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3007 log::info!(
3008 "inserted block {:?} with height {} and id {:?}",
3009 block_properties
3010 .placement
3011 .as_ref()
3012 .map(|p| p.to_point(&buffer_snapshot)),
3013 block_properties.height,
3014 block_id
3015 );
3016 }
3017 }
3018 40..=59 if !block_map.custom_blocks.is_empty() => {
3019 let block_count = rng.gen_range(1..=4.min(block_map.custom_blocks.len()));
3020 let block_ids_to_remove = block_map
3021 .custom_blocks
3022 .choose_multiple(&mut rng, block_count)
3023 .map(|block| block.id)
3024 .collect::<HashSet<_>>();
3025
3026 let (inlay_snapshot, inlay_edits) =
3027 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3028 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3029 let (tab_snapshot, tab_edits) =
3030 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3031 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3032 wrap_map.sync(tab_snapshot, tab_edits, cx)
3033 });
3034 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3035 log::info!(
3036 "removing {} blocks: {:?}",
3037 block_ids_to_remove.len(),
3038 block_ids_to_remove
3039 );
3040 block_map.remove(block_ids_to_remove);
3041 }
3042 60..=79 => {
3043 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3044 log::info!("Noop fold/unfold operation on a singleton buffer");
3045 continue;
3046 }
3047 let (inlay_snapshot, inlay_edits) =
3048 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3049 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3050 let (tab_snapshot, tab_edits) =
3051 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3052 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3053 wrap_map.sync(tab_snapshot, tab_edits, cx)
3054 });
3055 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3056 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3057 let folded_buffers = block_map
3058 .0
3059 .folded_buffers
3060 .iter()
3061 .cloned()
3062 .collect::<Vec<_>>();
3063 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3064 unfolded_buffers.dedup();
3065 log::debug!("All buffers {unfolded_buffers:?}");
3066 log::debug!("Folded buffers {folded_buffers:?}");
3067 unfolded_buffers
3068 .retain(|buffer_id| !block_map.0.folded_buffers.contains(buffer_id));
3069 (unfolded_buffers, folded_buffers)
3070 });
3071 let mut folded_count = folded_buffers.len();
3072 let mut unfolded_count = unfolded_buffers.len();
3073
3074 let fold = !unfolded_buffers.is_empty() && rng.gen_bool(0.5);
3075 let unfold = !folded_buffers.is_empty() && rng.gen_bool(0.5);
3076 if !fold && !unfold {
3077 log::info!(
3078 "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3079 );
3080 continue;
3081 }
3082
3083 buffer.update(cx, |buffer, cx| {
3084 if fold {
3085 let buffer_to_fold =
3086 unfolded_buffers[rng.gen_range(0..unfolded_buffers.len())];
3087 log::info!("Folding {buffer_to_fold:?}");
3088 let related_excerpts = buffer_snapshot
3089 .excerpts()
3090 .filter_map(|(excerpt_id, buffer, range)| {
3091 if buffer.remote_id() == buffer_to_fold {
3092 Some((
3093 excerpt_id,
3094 buffer
3095 .text_for_range(range.context)
3096 .collect::<String>(),
3097 ))
3098 } else {
3099 None
3100 }
3101 })
3102 .collect::<Vec<_>>();
3103 log::info!(
3104 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
3105 );
3106 folded_count += 1;
3107 unfolded_count -= 1;
3108 block_map.fold_buffers([buffer_to_fold], buffer, cx);
3109 }
3110 if unfold {
3111 let buffer_to_unfold =
3112 folded_buffers[rng.gen_range(0..folded_buffers.len())];
3113 log::info!("Unfolding {buffer_to_unfold:?}");
3114 unfolded_count += 1;
3115 folded_count -= 1;
3116 block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
3117 }
3118 log::info!(
3119 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3120 );
3121 });
3122 }
3123 _ => {
3124 buffer.update(cx, |buffer, cx| {
3125 let mutation_count = rng.gen_range(1..=5);
3126 let subscription = buffer.subscribe();
3127 buffer.randomly_mutate(&mut rng, mutation_count, cx);
3128 buffer_snapshot = buffer.snapshot(cx);
3129 buffer_edits.extend(subscription.consume());
3130 log::info!("buffer text: {:?}", buffer_snapshot.text());
3131 });
3132 }
3133 }
3134
3135 let (inlay_snapshot, inlay_edits) =
3136 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
3137 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3138 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3139 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3140 wrap_map.sync(tab_snapshot, tab_edits, cx)
3141 });
3142 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
3143 assert_eq!(
3144 blocks_snapshot.transforms.summary().input_rows,
3145 wraps_snapshot.max_point().row() + 1
3146 );
3147 log::info!("wrapped text: {:?}", wraps_snapshot.text());
3148 log::info!("blocks text: {:?}", blocks_snapshot.text());
3149
3150 let mut expected_blocks = Vec::new();
3151 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
3152 Some((
3153 block.placement.to_wrap_row(&wraps_snapshot)?,
3154 Block::Custom(block.clone()),
3155 ))
3156 }));
3157
3158 // Note that this needs to be synced with the related section in BlockMap::sync
3159 expected_blocks.extend(block_map.header_and_footer_blocks(
3160 &buffer_snapshot,
3161 0..,
3162 &wraps_snapshot,
3163 ));
3164
3165 BlockMap::sort_blocks(&mut expected_blocks);
3166
3167 for (placement, block) in &expected_blocks {
3168 log::info!(
3169 "Block {:?} placement: {:?} Height: {:?}",
3170 block.id(),
3171 placement,
3172 block.height()
3173 );
3174 }
3175
3176 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
3177
3178 let input_buffer_rows = buffer_snapshot
3179 .row_infos(MultiBufferRow(0))
3180 .map(|row| row.buffer_row)
3181 .collect::<Vec<_>>();
3182 let mut expected_buffer_rows = Vec::new();
3183 let mut expected_text = String::new();
3184 let mut expected_block_positions = Vec::new();
3185 let mut expected_replaced_buffer_rows = HashSet::default();
3186 let input_text = wraps_snapshot.text();
3187
3188 // Loop over the input lines, creating (N - 1) empty lines for
3189 // blocks of height N.
3190 //
3191 // It's important to note that output *starts* as one empty line,
3192 // so we special case row 0 to assume a leading '\n'.
3193 //
3194 // Linehood is the birthright of strings.
3195 let mut input_text_lines = input_text.split('\n').enumerate().peekable();
3196 let mut block_row = 0;
3197 while let Some((wrap_row, input_line)) = input_text_lines.next() {
3198 let wrap_row = wrap_row as u32;
3199 let multibuffer_row = wraps_snapshot
3200 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
3201 .row;
3202
3203 // Create empty lines for the above block
3204 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3205 if placement.start().0 == wrap_row && block.place_above() {
3206 let (_, block) = sorted_blocks_iter.next().unwrap();
3207 expected_block_positions.push((block_row, block.id()));
3208 if block.height() > 0 {
3209 let text = "\n".repeat((block.height() - 1) as usize);
3210 if block_row > 0 {
3211 expected_text.push('\n')
3212 }
3213 expected_text.push_str(&text);
3214 for _ in 0..block.height() {
3215 expected_buffer_rows.push(None);
3216 }
3217 block_row += block.height();
3218 }
3219 } else {
3220 break;
3221 }
3222 }
3223
3224 // Skip lines within replace blocks, then create empty lines for the replace block's height
3225 let mut is_in_replace_block = false;
3226 if let Some((BlockPlacement::Replace(replace_range), block)) =
3227 sorted_blocks_iter.peek()
3228 {
3229 if wrap_row >= replace_range.start().0 {
3230 is_in_replace_block = true;
3231
3232 if wrap_row == replace_range.start().0 {
3233 if matches!(block, Block::FoldedBuffer { .. }) {
3234 expected_buffer_rows.push(None);
3235 } else {
3236 expected_buffer_rows
3237 .push(input_buffer_rows[multibuffer_row as usize]);
3238 }
3239 }
3240
3241 if wrap_row == replace_range.end().0 {
3242 expected_block_positions.push((block_row, block.id()));
3243 let text = "\n".repeat((block.height() - 1) as usize);
3244 if block_row > 0 {
3245 expected_text.push('\n');
3246 }
3247 expected_text.push_str(&text);
3248
3249 for _ in 1..block.height() {
3250 expected_buffer_rows.push(None);
3251 }
3252 block_row += block.height();
3253
3254 sorted_blocks_iter.next();
3255 }
3256 }
3257 }
3258
3259 if is_in_replace_block {
3260 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
3261 } else {
3262 let buffer_row = input_buffer_rows[multibuffer_row as usize];
3263 let soft_wrapped = wraps_snapshot
3264 .to_tab_point(WrapPoint::new(wrap_row, 0))
3265 .column()
3266 > 0;
3267 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
3268 if block_row > 0 {
3269 expected_text.push('\n');
3270 }
3271 expected_text.push_str(input_line);
3272 block_row += 1;
3273 }
3274
3275 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3276 if placement.end().0 == wrap_row && block.place_below() {
3277 let (_, block) = sorted_blocks_iter.next().unwrap();
3278 expected_block_positions.push((block_row, block.id()));
3279 if block.height() > 0 {
3280 let text = "\n".repeat((block.height() - 1) as usize);
3281 if block_row > 0 {
3282 expected_text.push('\n')
3283 }
3284 expected_text.push_str(&text);
3285 for _ in 0..block.height() {
3286 expected_buffer_rows.push(None);
3287 }
3288 block_row += block.height();
3289 }
3290 } else {
3291 break;
3292 }
3293 }
3294 }
3295
3296 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
3297 let expected_row_count = expected_lines.len();
3298 log::info!("expected text: {expected_text:?}");
3299
3300 assert_eq!(
3301 blocks_snapshot.max_point().row + 1,
3302 expected_row_count as u32,
3303 "actual row count != expected row count",
3304 );
3305 assert_eq!(
3306 blocks_snapshot.text(),
3307 expected_text,
3308 "actual text != expected text",
3309 );
3310
3311 for start_row in 0..expected_row_count {
3312 let end_row = rng.gen_range(start_row + 1..=expected_row_count);
3313 let mut expected_text = expected_lines[start_row..end_row].join("\n");
3314 if end_row < expected_row_count {
3315 expected_text.push('\n');
3316 }
3317
3318 let actual_text = blocks_snapshot
3319 .chunks(
3320 start_row as u32..end_row as u32,
3321 false,
3322 false,
3323 Highlights::default(),
3324 )
3325 .map(|chunk| chunk.text)
3326 .collect::<String>();
3327 assert_eq!(
3328 actual_text,
3329 expected_text,
3330 "incorrect text starting row row range {:?}",
3331 start_row..end_row
3332 );
3333 assert_eq!(
3334 blocks_snapshot
3335 .row_infos(BlockRow(start_row as u32))
3336 .map(|row_info| row_info.buffer_row)
3337 .collect::<Vec<_>>(),
3338 &expected_buffer_rows[start_row..],
3339 "incorrect buffer_rows starting at row {:?}",
3340 start_row
3341 );
3342 }
3343
3344 assert_eq!(
3345 blocks_snapshot
3346 .blocks_in_range(0..(expected_row_count as u32))
3347 .map(|(row, block)| (row, block.id()))
3348 .collect::<Vec<_>>(),
3349 expected_block_positions,
3350 "invalid blocks_in_range({:?})",
3351 0..expected_row_count
3352 );
3353
3354 for (_, expected_block) in
3355 blocks_snapshot.blocks_in_range(0..(expected_row_count as u32))
3356 {
3357 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
3358 assert_eq!(
3359 actual_block.map(|block| block.id()),
3360 Some(expected_block.id())
3361 );
3362 }
3363
3364 for (block_row, block_id) in expected_block_positions {
3365 if let BlockId::Custom(block_id) = block_id {
3366 assert_eq!(
3367 blocks_snapshot.row_for_block(block_id),
3368 Some(BlockRow(block_row))
3369 );
3370 }
3371 }
3372
3373 let mut expected_longest_rows = Vec::new();
3374 let mut longest_line_len = -1_isize;
3375 for (row, line) in expected_lines.iter().enumerate() {
3376 let row = row as u32;
3377
3378 assert_eq!(
3379 blocks_snapshot.line_len(BlockRow(row)),
3380 line.len() as u32,
3381 "invalid line len for row {}",
3382 row
3383 );
3384
3385 let line_char_count = line.chars().count() as isize;
3386 match line_char_count.cmp(&longest_line_len) {
3387 Ordering::Less => {}
3388 Ordering::Equal => expected_longest_rows.push(row),
3389 Ordering::Greater => {
3390 longest_line_len = line_char_count;
3391 expected_longest_rows.clear();
3392 expected_longest_rows.push(row);
3393 }
3394 }
3395 }
3396
3397 let longest_row = blocks_snapshot.longest_row();
3398 assert!(
3399 expected_longest_rows.contains(&longest_row),
3400 "incorrect longest row {}. expected {:?} with length {}",
3401 longest_row,
3402 expected_longest_rows,
3403 longest_line_len,
3404 );
3405
3406 for _ in 0..10 {
3407 let end_row = rng.gen_range(1..=expected_lines.len());
3408 let start_row = rng.gen_range(0..end_row);
3409
3410 let mut expected_longest_rows_in_range = vec![];
3411 let mut longest_line_len_in_range = 0;
3412
3413 let mut row = start_row as u32;
3414 for line in &expected_lines[start_row..end_row] {
3415 let line_char_count = line.chars().count() as isize;
3416 match line_char_count.cmp(&longest_line_len_in_range) {
3417 Ordering::Less => {}
3418 Ordering::Equal => expected_longest_rows_in_range.push(row),
3419 Ordering::Greater => {
3420 longest_line_len_in_range = line_char_count;
3421 expected_longest_rows_in_range.clear();
3422 expected_longest_rows_in_range.push(row);
3423 }
3424 }
3425 row += 1;
3426 }
3427
3428 let longest_row_in_range = blocks_snapshot
3429 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
3430 assert!(
3431 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
3432 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
3433 longest_row,
3434 start_row..end_row,
3435 expected_longest_rows_in_range,
3436 longest_line_len_in_range,
3437 );
3438 }
3439
3440 // Ensure that conversion between block points and wrap points is stable.
3441 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
3442 let wrap_point = WrapPoint::new(row, 0);
3443 let block_point = blocks_snapshot.to_block_point(wrap_point);
3444 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
3445 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
3446 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
3447 assert_eq!(
3448 blocks_snapshot.to_block_point(right_wrap_point),
3449 block_point
3450 );
3451 }
3452
3453 let mut block_point = BlockPoint::new(0, 0);
3454 for c in expected_text.chars() {
3455 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
3456 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
3457 assert_eq!(
3458 blocks_snapshot
3459 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
3460 left_point,
3461 "block point: {:?}, wrap point: {:?}",
3462 block_point,
3463 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
3464 );
3465 assert_eq!(
3466 left_buffer_point,
3467 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
3468 "{:?} is not valid in buffer coordinates",
3469 left_point
3470 );
3471
3472 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
3473 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
3474 assert_eq!(
3475 blocks_snapshot
3476 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
3477 right_point,
3478 "block point: {:?}, wrap point: {:?}",
3479 block_point,
3480 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
3481 );
3482 assert_eq!(
3483 right_buffer_point,
3484 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
3485 "{:?} is not valid in buffer coordinates",
3486 right_point
3487 );
3488
3489 if c == '\n' {
3490 block_point.0 += Point::new(1, 0);
3491 } else {
3492 block_point.column += c.len_utf8() as u32;
3493 }
3494 }
3495
3496 for buffer_row in 0..=buffer_snapshot.max_point().row {
3497 let buffer_row = MultiBufferRow(buffer_row);
3498 assert_eq!(
3499 blocks_snapshot.is_line_replaced(buffer_row),
3500 expected_replaced_buffer_rows.contains(&buffer_row),
3501 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
3502 );
3503 }
3504 }
3505 }
3506
3507 fn init_test(cx: &mut gpui::App) {
3508 let settings = SettingsStore::test(cx);
3509 cx.set_global(settings);
3510 theme::init(theme::LoadThemes::JustBase, cx);
3511 assets::Assets.load_test_fonts(cx);
3512 }
3513
3514 impl Block {
3515 fn as_custom(&self) -> Option<&CustomBlock> {
3516 match self {
3517 Block::Custom(block) => Some(block),
3518 _ => None,
3519 }
3520 }
3521 }
3522
3523 impl BlockSnapshot {
3524 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
3525 self.wrap_snapshot
3526 .to_point(self.to_wrap_point(point, bias), bias)
3527 }
3528 }
3529}