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