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