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