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