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