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