1use super::{
2 wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
3 Highlights,
4};
5use crate::{EditorStyle, GutterDimensions};
6use collections::{Bound, HashMap, HashSet};
7use gpui::{AnyElement, EntityId, Pixels, WindowContext};
8use language::{BufferSnapshot, Chunk, Patch, Point};
9use multi_buffer::{Anchor, ExcerptId, ExcerptRange, MultiBufferRow, ToPoint as _};
10use parking_lot::Mutex;
11use std::{
12 cell::RefCell,
13 cmp::{self, Ordering},
14 fmt::Debug,
15 ops::{Deref, DerefMut, Range, RangeBounds},
16 sync::{
17 atomic::{AtomicUsize, Ordering::SeqCst},
18 Arc,
19 },
20};
21use sum_tree::{Bias, SumTree, TreeMap};
22use text::Edit;
23use ui::ElementId;
24
25const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
26const BULLETS: &str = "********************************************************************************************************************************";
27
28/// Tracks custom blocks such as diagnostics that should be displayed within buffer.
29///
30/// See the [`display_map` module documentation](crate::display_map) for more information.
31pub struct BlockMap {
32 next_block_id: AtomicUsize,
33 wrap_snapshot: RefCell<WrapSnapshot>,
34 custom_blocks: Vec<Arc<CustomBlock>>,
35 custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
36 transforms: RefCell<SumTree<Transform>>,
37 show_excerpt_controls: bool,
38 buffer_header_height: u32,
39 excerpt_header_height: u32,
40 excerpt_footer_height: u32,
41}
42
43pub struct BlockMapReader<'a> {
44 blocks: &'a Vec<Arc<CustomBlock>>,
45 pub snapshot: BlockSnapshot,
46}
47
48pub struct BlockMapWriter<'a>(&'a mut BlockMap);
49
50#[derive(Clone)]
51pub struct BlockSnapshot {
52 wrap_snapshot: WrapSnapshot,
53 transforms: SumTree<Transform>,
54 custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
55 pub(super) buffer_header_height: u32,
56 pub(super) excerpt_header_height: u32,
57 pub(super) excerpt_footer_height: u32,
58}
59
60#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
61pub struct CustomBlockId(usize);
62
63impl Into<ElementId> for CustomBlockId {
64 fn into(self) -> ElementId {
65 ElementId::Integer(self.0)
66 }
67}
68
69#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
70pub struct BlockPoint(pub Point);
71
72#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
73pub struct BlockRow(pub(super) u32);
74
75#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
76struct WrapRow(u32);
77
78pub type RenderBlock = Box<dyn Send + FnMut(&mut BlockContext) -> AnyElement>;
79
80pub struct CustomBlock {
81 id: CustomBlockId,
82 position: Anchor,
83 height: u32,
84 style: BlockStyle,
85 render: Arc<Mutex<RenderBlock>>,
86 disposition: BlockDisposition,
87}
88
89pub struct BlockProperties<P> {
90 pub position: P,
91 pub height: u32,
92 pub style: BlockStyle,
93 pub render: RenderBlock,
94 pub disposition: BlockDisposition,
95}
96
97impl<P: Debug> Debug for BlockProperties<P> {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 f.debug_struct("BlockProperties")
100 .field("position", &self.position)
101 .field("height", &self.height)
102 .field("style", &self.style)
103 .field("disposition", &self.disposition)
104 .finish()
105 }
106}
107
108#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
109pub enum BlockStyle {
110 Fixed,
111 Flex,
112 Sticky,
113}
114
115pub struct BlockContext<'a, 'b> {
116 pub context: &'b mut WindowContext<'a>,
117 pub anchor_x: Pixels,
118 pub max_width: Pixels,
119 pub gutter_dimensions: &'b GutterDimensions,
120 pub em_width: Pixels,
121 pub line_height: Pixels,
122 pub block_id: BlockId,
123 pub editor_style: &'b EditorStyle,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
127pub enum BlockId {
128 Custom(CustomBlockId),
129 ExcerptHeader(ExcerptId),
130 ExcerptFooter(ExcerptId),
131}
132
133impl From<BlockId> for EntityId {
134 fn from(value: BlockId) -> Self {
135 match value {
136 BlockId::Custom(CustomBlockId(id)) => EntityId::from(id as u64),
137 BlockId::ExcerptHeader(id) => id.into(),
138 BlockId::ExcerptFooter(id) => id.into(),
139 }
140 }
141}
142
143impl From<BlockId> for ElementId {
144 fn from(value: BlockId) -> Self {
145 match value {
146 BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
147 BlockId::ExcerptHeader(id) => ("ExcerptHeader", EntityId::from(id)).into(),
148 BlockId::ExcerptFooter(id) => ("ExcerptFooter", EntityId::from(id)).into(),
149 }
150 }
151}
152
153impl std::fmt::Display for BlockId {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 match self {
156 Self::Custom(id) => write!(f, "Block({id:?})"),
157 Self::ExcerptHeader(id) => write!(f, "ExcerptHeader({id:?})"),
158 Self::ExcerptFooter(id) => write!(f, "ExcerptFooter({id:?})"),
159 }
160 }
161}
162
163/// Whether the block should be considered above or below the anchor line
164#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
165pub enum BlockDisposition {
166 Above,
167 Below,
168}
169
170#[derive(Clone, Debug)]
171struct Transform {
172 summary: TransformSummary,
173 block: Option<Block>,
174}
175
176pub(crate) enum BlockType {
177 Custom(CustomBlockId),
178 Header,
179 Footer,
180}
181
182pub(crate) trait BlockLike {
183 fn block_type(&self) -> BlockType;
184 fn disposition(&self) -> BlockDisposition;
185}
186
187#[allow(clippy::large_enum_variant)]
188#[derive(Clone)]
189pub enum Block {
190 Custom(Arc<CustomBlock>),
191 ExcerptHeader {
192 id: ExcerptId,
193 buffer: BufferSnapshot,
194 range: ExcerptRange<text::Anchor>,
195 height: u32,
196 starts_new_buffer: bool,
197 show_excerpt_controls: bool,
198 },
199 ExcerptFooter {
200 id: ExcerptId,
201 disposition: BlockDisposition,
202 height: u32,
203 },
204}
205
206impl BlockLike for Block {
207 fn block_type(&self) -> BlockType {
208 match self {
209 Block::Custom(block) => BlockType::Custom(block.id),
210 Block::ExcerptHeader { .. } => BlockType::Header,
211 Block::ExcerptFooter { .. } => BlockType::Footer,
212 }
213 }
214
215 fn disposition(&self) -> BlockDisposition {
216 self.disposition()
217 }
218}
219
220impl Block {
221 pub fn id(&self) -> BlockId {
222 match self {
223 Block::Custom(block) => BlockId::Custom(block.id),
224 Block::ExcerptHeader { id, .. } => BlockId::ExcerptHeader(*id),
225 Block::ExcerptFooter { id, .. } => BlockId::ExcerptFooter(*id),
226 }
227 }
228
229 fn disposition(&self) -> BlockDisposition {
230 match self {
231 Block::Custom(block) => block.disposition,
232 Block::ExcerptHeader { .. } => BlockDisposition::Above,
233 Block::ExcerptFooter { disposition, .. } => *disposition,
234 }
235 }
236
237 pub fn height(&self) -> u32 {
238 match self {
239 Block::Custom(block) => block.height,
240 Block::ExcerptHeader { height, .. } => *height,
241 Block::ExcerptFooter { height, .. } => *height,
242 }
243 }
244
245 pub fn style(&self) -> BlockStyle {
246 match self {
247 Block::Custom(block) => block.style,
248 Block::ExcerptHeader { .. } => BlockStyle::Sticky,
249 Block::ExcerptFooter { .. } => BlockStyle::Sticky,
250 }
251 }
252}
253
254impl Debug for Block {
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 match self {
257 Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
258 Self::ExcerptHeader {
259 buffer,
260 starts_new_buffer,
261 id,
262 ..
263 } => f
264 .debug_struct("ExcerptHeader")
265 .field("id", &id)
266 .field("path", &buffer.file().map(|f| f.path()))
267 .field("starts_new_buffer", &starts_new_buffer)
268 .finish(),
269 Block::ExcerptFooter {
270 id, disposition, ..
271 } => f
272 .debug_struct("ExcerptFooter")
273 .field("id", &id)
274 .field("disposition", &disposition)
275 .finish(),
276 }
277 }
278}
279
280#[derive(Clone, Debug, Default)]
281struct TransformSummary {
282 input_rows: u32,
283 output_rows: u32,
284}
285
286pub struct BlockChunks<'a> {
287 transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
288 input_chunks: wrap_map::WrapChunks<'a>,
289 input_chunk: Chunk<'a>,
290 output_row: u32,
291 max_output_row: u32,
292 masked: bool,
293}
294
295#[derive(Clone)]
296pub struct BlockBufferRows<'a> {
297 transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
298 input_buffer_rows: wrap_map::WrapBufferRows<'a>,
299 output_row: BlockRow,
300 started: bool,
301}
302
303impl BlockMap {
304 pub fn new(
305 wrap_snapshot: WrapSnapshot,
306 show_excerpt_controls: bool,
307 buffer_header_height: u32,
308 excerpt_header_height: u32,
309 excerpt_footer_height: u32,
310 ) -> Self {
311 let row_count = wrap_snapshot.max_point().row() + 1;
312 let map = Self {
313 next_block_id: AtomicUsize::new(0),
314 custom_blocks: Vec::new(),
315 custom_blocks_by_id: TreeMap::default(),
316 transforms: RefCell::new(SumTree::from_item(Transform::isomorphic(row_count), &())),
317 wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
318 show_excerpt_controls,
319 buffer_header_height,
320 excerpt_header_height,
321 excerpt_footer_height,
322 };
323 map.sync(
324 &wrap_snapshot,
325 Patch::new(vec![Edit {
326 old: 0..row_count,
327 new: 0..row_count,
328 }]),
329 );
330 map
331 }
332
333 pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapReader {
334 self.sync(&wrap_snapshot, edits);
335 *self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
336 BlockMapReader {
337 blocks: &self.custom_blocks,
338 snapshot: BlockSnapshot {
339 wrap_snapshot,
340 transforms: self.transforms.borrow().clone(),
341 custom_blocks_by_id: self.custom_blocks_by_id.clone(),
342 buffer_header_height: self.buffer_header_height,
343 excerpt_header_height: self.excerpt_header_height,
344 excerpt_footer_height: self.excerpt_footer_height,
345 },
346 }
347 }
348
349 pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapWriter {
350 self.sync(&wrap_snapshot, edits);
351 *self.wrap_snapshot.borrow_mut() = wrap_snapshot;
352 BlockMapWriter(self)
353 }
354
355 fn sync(&self, wrap_snapshot: &WrapSnapshot, mut edits: Patch<u32>) {
356 let buffer = wrap_snapshot.buffer_snapshot();
357
358 // Handle changing the last excerpt if it is empty.
359 if buffer.trailing_excerpt_update_count()
360 != self
361 .wrap_snapshot
362 .borrow()
363 .buffer_snapshot()
364 .trailing_excerpt_update_count()
365 {
366 let max_point = wrap_snapshot.max_point();
367 let edit_start = wrap_snapshot.prev_row_boundary(max_point);
368 let edit_end = max_point.row() + 1;
369 edits = edits.compose([WrapEdit {
370 old: edit_start..edit_end,
371 new: edit_start..edit_end,
372 }]);
373 }
374
375 let edits = edits.into_inner();
376 if edits.is_empty() {
377 return;
378 }
379
380 let mut transforms = self.transforms.borrow_mut();
381 let mut new_transforms = SumTree::new();
382 let old_row_count = transforms.summary().input_rows;
383 let new_row_count = wrap_snapshot.max_point().row() + 1;
384 let mut cursor = transforms.cursor::<WrapRow>();
385 let mut last_block_ix = 0;
386 let mut blocks_in_edit = Vec::new();
387 let mut edits = edits.into_iter().peekable();
388
389 while let Some(edit) = edits.next() {
390 // Preserve any old transforms that precede this edit.
391 let old_start = WrapRow(edit.old.start);
392 let new_start = WrapRow(edit.new.start);
393 new_transforms.append(cursor.slice(&old_start, Bias::Left, &()), &());
394 if let Some(transform) = cursor.item() {
395 if transform.is_isomorphic() && old_start == cursor.end(&()) {
396 new_transforms.push(transform.clone(), &());
397 cursor.next(&());
398 while let Some(transform) = cursor.item() {
399 if transform
400 .block
401 .as_ref()
402 .map_or(false, |b| b.disposition().is_below())
403 {
404 new_transforms.push(transform.clone(), &());
405 cursor.next(&());
406 } else {
407 break;
408 }
409 }
410 }
411 }
412
413 // Preserve any portion of an old transform that precedes this edit.
414 let extent_before_edit = old_start.0 - cursor.start().0;
415 push_isomorphic(&mut new_transforms, extent_before_edit);
416
417 // Skip over any old transforms that intersect this edit.
418 let mut old_end = WrapRow(edit.old.end);
419 let mut new_end = WrapRow(edit.new.end);
420 cursor.seek(&old_end, Bias::Left, &());
421 cursor.next(&());
422 if old_end == *cursor.start() {
423 while let Some(transform) = cursor.item() {
424 if transform
425 .block
426 .as_ref()
427 .map_or(false, |b| b.disposition().is_below())
428 {
429 cursor.next(&());
430 } else {
431 break;
432 }
433 }
434 }
435
436 // Combine this edit with any subsequent edits that intersect the same transform.
437 while let Some(next_edit) = edits.peek() {
438 if next_edit.old.start <= cursor.start().0 {
439 old_end = WrapRow(next_edit.old.end);
440 new_end = WrapRow(next_edit.new.end);
441 cursor.seek(&old_end, Bias::Left, &());
442 cursor.next(&());
443 if old_end == *cursor.start() {
444 while let Some(transform) = cursor.item() {
445 if transform
446 .block
447 .as_ref()
448 .map_or(false, |b| b.disposition().is_below())
449 {
450 cursor.next(&());
451 } else {
452 break;
453 }
454 }
455 }
456 edits.next();
457 } else {
458 break;
459 }
460 }
461
462 // Find the blocks within this edited region.
463 let new_buffer_start =
464 wrap_snapshot.to_point(WrapPoint::new(new_start.0, 0), Bias::Left);
465 let start_bound = Bound::Included(new_buffer_start);
466 let start_block_ix =
467 match self.custom_blocks[last_block_ix..].binary_search_by(|probe| {
468 probe
469 .position
470 .to_point(buffer)
471 .cmp(&new_buffer_start)
472 .then(Ordering::Greater)
473 }) {
474 Ok(ix) | Err(ix) => last_block_ix + ix,
475 };
476
477 let end_bound;
478 let end_block_ix = if new_end.0 > wrap_snapshot.max_point().row() {
479 end_bound = Bound::Unbounded;
480 self.custom_blocks.len()
481 } else {
482 let new_buffer_end =
483 wrap_snapshot.to_point(WrapPoint::new(new_end.0, 0), Bias::Left);
484 end_bound = Bound::Excluded(new_buffer_end);
485 match self.custom_blocks[start_block_ix..].binary_search_by(|probe| {
486 probe
487 .position
488 .to_point(buffer)
489 .cmp(&new_buffer_end)
490 .then(Ordering::Greater)
491 }) {
492 Ok(ix) | Err(ix) => start_block_ix + ix,
493 }
494 };
495 last_block_ix = end_block_ix;
496
497 debug_assert!(blocks_in_edit.is_empty());
498 blocks_in_edit.extend(self.custom_blocks[start_block_ix..end_block_ix].iter().map(
499 |block| {
500 let mut position = block.position.to_point(buffer);
501 match block.disposition {
502 BlockDisposition::Above => position.column = 0,
503 BlockDisposition::Below => {
504 position.column = buffer.line_len(MultiBufferRow(position.row))
505 }
506 }
507 let position = wrap_snapshot.make_wrap_point(position, Bias::Left);
508 (position.row(), Block::Custom(block.clone()))
509 },
510 ));
511
512 if buffer.show_headers() {
513 blocks_in_edit.extend(BlockMap::header_and_footer_blocks(
514 self.show_excerpt_controls,
515 self.excerpt_footer_height,
516 self.buffer_header_height,
517 self.excerpt_header_height,
518 buffer,
519 (start_bound, end_bound),
520 wrap_snapshot,
521 ));
522 }
523
524 BlockMap::sort_blocks(&mut blocks_in_edit);
525
526 // For each of these blocks, insert a new isomorphic transform preceding the block,
527 // and then insert the block itself.
528 for (block_row, block) in blocks_in_edit.drain(..) {
529 let insertion_row = match block.disposition() {
530 BlockDisposition::Above => block_row,
531 BlockDisposition::Below => block_row + 1,
532 };
533 let extent_before_block = insertion_row - new_transforms.summary().input_rows;
534 push_isomorphic(&mut new_transforms, extent_before_block);
535 new_transforms.push(Transform::block(block), &());
536 }
537
538 old_end = WrapRow(old_end.0.min(old_row_count));
539 new_end = WrapRow(new_end.0.min(new_row_count));
540
541 // Insert an isomorphic transform after the final block.
542 let extent_after_last_block = new_end.0 - new_transforms.summary().input_rows;
543 push_isomorphic(&mut new_transforms, extent_after_last_block);
544
545 // Preserve any portion of the old transform after this edit.
546 let extent_after_edit = cursor.start().0 - old_end.0;
547 push_isomorphic(&mut new_transforms, extent_after_edit);
548 }
549
550 new_transforms.append(cursor.suffix(&()), &());
551 debug_assert_eq!(
552 new_transforms.summary().input_rows,
553 wrap_snapshot.max_point().row() + 1
554 );
555
556 drop(cursor);
557 *transforms = new_transforms;
558 }
559
560 pub fn replace_blocks(&mut self, mut renderers: HashMap<CustomBlockId, RenderBlock>) {
561 for block in &mut self.custom_blocks {
562 if let Some(render) = renderers.remove(&block.id) {
563 *block.render.lock() = render;
564 }
565 }
566 }
567
568 pub fn show_excerpt_controls(&self) -> bool {
569 self.show_excerpt_controls
570 }
571
572 pub fn header_and_footer_blocks<'a, 'b: 'a, 'c: 'a + 'b, R, T>(
573 show_excerpt_controls: bool,
574 excerpt_footer_height: u32,
575 buffer_header_height: u32,
576 excerpt_header_height: u32,
577 buffer: &'b multi_buffer::MultiBufferSnapshot,
578 range: R,
579 wrap_snapshot: &'c WrapSnapshot,
580 ) -> impl Iterator<Item = (u32, Block)> + 'b
581 where
582 R: RangeBounds<T>,
583 T: multi_buffer::ToOffset,
584 {
585 buffer
586 .excerpt_boundaries_in_range(range)
587 .flat_map(move |excerpt_boundary| {
588 let mut wrap_row = wrap_snapshot
589 .make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
590 .row();
591
592 [
593 show_excerpt_controls
594 .then(|| {
595 let disposition;
596 if excerpt_boundary.next.is_some() {
597 disposition = BlockDisposition::Above;
598 } else {
599 wrap_row = wrap_snapshot
600 .make_wrap_point(
601 Point::new(
602 excerpt_boundary.row.0,
603 buffer.line_len(excerpt_boundary.row),
604 ),
605 Bias::Left,
606 )
607 .row();
608 disposition = BlockDisposition::Below;
609 }
610
611 excerpt_boundary.prev.as_ref().map(|prev| {
612 (
613 wrap_row,
614 Block::ExcerptFooter {
615 id: prev.id,
616 height: excerpt_footer_height,
617 disposition,
618 },
619 )
620 })
621 })
622 .flatten(),
623 excerpt_boundary.next.map(|next| {
624 let starts_new_buffer = excerpt_boundary
625 .prev
626 .map_or(true, |prev| prev.buffer_id != next.buffer_id);
627
628 (
629 wrap_row,
630 Block::ExcerptHeader {
631 id: next.id,
632 buffer: next.buffer,
633 range: next.range,
634 height: if starts_new_buffer {
635 buffer_header_height
636 } else {
637 excerpt_header_height
638 },
639 starts_new_buffer,
640 show_excerpt_controls,
641 },
642 )
643 }),
644 ]
645 })
646 .flatten()
647 }
648
649 pub(crate) fn sort_blocks<B: BlockLike>(blocks: &mut Vec<(u32, B)>) {
650 // Place excerpt headers and footers above custom blocks on the same row
651 blocks.sort_unstable_by(|(row_a, block_a), (row_b, block_b)| {
652 row_a.cmp(row_b).then_with(|| {
653 block_a
654 .disposition()
655 .cmp(&block_b.disposition())
656 .then_with(|| match ((block_a.block_type()), (block_b.block_type())) {
657 (BlockType::Footer, BlockType::Footer) => Ordering::Equal,
658 (BlockType::Footer, _) => Ordering::Less,
659 (_, BlockType::Footer) => Ordering::Greater,
660 (BlockType::Header, BlockType::Header) => Ordering::Equal,
661 (BlockType::Header, _) => Ordering::Less,
662 (_, BlockType::Header) => Ordering::Greater,
663 (BlockType::Custom(a_id), BlockType::Custom(b_id)) => a_id.cmp(&b_id),
664 })
665 })
666 });
667 }
668}
669
670fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32) {
671 if rows == 0 {
672 return;
673 }
674
675 let mut extent = Some(rows);
676 tree.update_last(
677 |last_transform| {
678 if last_transform.is_isomorphic() {
679 let extent = extent.take().unwrap();
680 last_transform.summary.input_rows += extent;
681 last_transform.summary.output_rows += extent;
682 }
683 },
684 &(),
685 );
686 if let Some(extent) = extent {
687 tree.push(Transform::isomorphic(extent), &());
688 }
689}
690
691impl BlockPoint {
692 pub fn new(row: u32, column: u32) -> Self {
693 Self(Point::new(row, column))
694 }
695}
696
697impl Deref for BlockPoint {
698 type Target = Point;
699
700 fn deref(&self) -> &Self::Target {
701 &self.0
702 }
703}
704
705impl std::ops::DerefMut for BlockPoint {
706 fn deref_mut(&mut self) -> &mut Self::Target {
707 &mut self.0
708 }
709}
710
711impl<'a> Deref for BlockMapReader<'a> {
712 type Target = BlockSnapshot;
713
714 fn deref(&self) -> &Self::Target {
715 &self.snapshot
716 }
717}
718
719impl<'a> DerefMut for BlockMapReader<'a> {
720 fn deref_mut(&mut self) -> &mut Self::Target {
721 &mut self.snapshot
722 }
723}
724
725impl<'a> BlockMapReader<'a> {
726 pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
727 let block = self.blocks.iter().find(|block| block.id == block_id)?;
728 let buffer_row = block
729 .position
730 .to_point(self.wrap_snapshot.buffer_snapshot())
731 .row;
732 let wrap_row = self
733 .wrap_snapshot
734 .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
735 .row();
736 let start_wrap_row = WrapRow(
737 self.wrap_snapshot
738 .prev_row_boundary(WrapPoint::new(wrap_row, 0)),
739 );
740 let end_wrap_row = WrapRow(
741 self.wrap_snapshot
742 .next_row_boundary(WrapPoint::new(wrap_row, 0))
743 .unwrap_or(self.wrap_snapshot.max_point().row() + 1),
744 );
745
746 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
747 cursor.seek(&start_wrap_row, Bias::Left, &());
748 while let Some(transform) = cursor.item() {
749 if cursor.start().0 > end_wrap_row {
750 break;
751 }
752
753 if let Some(BlockType::Custom(id)) =
754 transform.block.as_ref().map(|block| block.block_type())
755 {
756 if id == block_id {
757 return Some(cursor.start().1);
758 }
759 }
760 cursor.next(&());
761 }
762
763 None
764 }
765}
766
767impl<'a> BlockMapWriter<'a> {
768 pub fn insert(
769 &mut self,
770 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
771 ) -> Vec<CustomBlockId> {
772 let mut ids = Vec::new();
773 let mut edits = Patch::default();
774 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
775 let buffer = wrap_snapshot.buffer_snapshot();
776
777 for block in blocks {
778 let id = CustomBlockId(self.0.next_block_id.fetch_add(1, SeqCst));
779 ids.push(id);
780
781 let position = block.position;
782 let point = position.to_point(buffer);
783 let wrap_row = wrap_snapshot
784 .make_wrap_point(Point::new(point.row, 0), Bias::Left)
785 .row();
786 let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
787 let end_row = wrap_snapshot
788 .next_row_boundary(WrapPoint::new(wrap_row, 0))
789 .unwrap_or(wrap_snapshot.max_point().row() + 1);
790
791 let block_ix = match self
792 .0
793 .custom_blocks
794 .binary_search_by(|probe| probe.position.cmp(&position, buffer))
795 {
796 Ok(ix) | Err(ix) => ix,
797 };
798 let new_block = Arc::new(CustomBlock {
799 id,
800 position,
801 height: block.height,
802 render: Arc::new(Mutex::new(block.render)),
803 disposition: block.disposition,
804 style: block.style,
805 });
806 self.0.custom_blocks.insert(block_ix, new_block.clone());
807 self.0.custom_blocks_by_id.insert(id, new_block);
808
809 edits = edits.compose([Edit {
810 old: start_row..end_row,
811 new: start_row..end_row,
812 }]);
813 }
814
815 self.0.sync(wrap_snapshot, edits);
816 ids
817 }
818
819 pub fn resize(&mut self, mut heights: HashMap<CustomBlockId, u32>) {
820 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
821 let buffer = wrap_snapshot.buffer_snapshot();
822 let mut edits = Patch::default();
823 let mut last_block_buffer_row = None;
824
825 for block in &mut self.0.custom_blocks {
826 if let Some(new_height) = heights.remove(&block.id) {
827 if block.height != new_height {
828 let new_block = CustomBlock {
829 id: block.id,
830 position: block.position,
831 height: new_height,
832 style: block.style,
833 render: block.render.clone(),
834 disposition: block.disposition,
835 };
836 let new_block = Arc::new(new_block);
837 *block = new_block.clone();
838 self.0.custom_blocks_by_id.insert(block.id, new_block);
839
840 let buffer_row = block.position.to_point(buffer).row;
841 if last_block_buffer_row != Some(buffer_row) {
842 last_block_buffer_row = Some(buffer_row);
843 let wrap_row = wrap_snapshot
844 .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
845 .row();
846 let start_row =
847 wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
848 let end_row = wrap_snapshot
849 .next_row_boundary(WrapPoint::new(wrap_row, 0))
850 .unwrap_or(wrap_snapshot.max_point().row() + 1);
851 edits.push(Edit {
852 old: start_row..end_row,
853 new: start_row..end_row,
854 })
855 }
856 }
857 }
858 }
859
860 self.0.sync(wrap_snapshot, edits);
861 }
862
863 pub fn remove(&mut self, block_ids: HashSet<CustomBlockId>) {
864 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
865 let buffer = wrap_snapshot.buffer_snapshot();
866 let mut edits = Patch::default();
867 let mut last_block_buffer_row = None;
868 self.0.custom_blocks.retain(|block| {
869 if block_ids.contains(&block.id) {
870 let buffer_row = block.position.to_point(buffer).row;
871 if last_block_buffer_row != Some(buffer_row) {
872 last_block_buffer_row = Some(buffer_row);
873 let wrap_row = wrap_snapshot
874 .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
875 .row();
876 let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
877 let end_row = wrap_snapshot
878 .next_row_boundary(WrapPoint::new(wrap_row, 0))
879 .unwrap_or(wrap_snapshot.max_point().row() + 1);
880 edits.push(Edit {
881 old: start_row..end_row,
882 new: start_row..end_row,
883 })
884 }
885 self.0.custom_blocks_by_id.remove(&block.id);
886 false
887 } else {
888 true
889 }
890 });
891 self.0.sync(wrap_snapshot, edits);
892 }
893}
894
895impl BlockSnapshot {
896 #[cfg(test)]
897 pub fn text(&self) -> String {
898 self.chunks(
899 0..self.transforms.summary().output_rows,
900 false,
901 false,
902 Highlights::default(),
903 )
904 .map(|chunk| chunk.text)
905 .collect()
906 }
907
908 pub(crate) fn chunks<'a>(
909 &'a self,
910 rows: Range<u32>,
911 language_aware: bool,
912 masked: bool,
913 highlights: Highlights<'a>,
914 ) -> BlockChunks<'a> {
915 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
916 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
917 let input_end = {
918 cursor.seek(&BlockRow(rows.end), Bias::Right, &());
919 let overshoot = if cursor
920 .item()
921 .map_or(false, |transform| transform.is_isomorphic())
922 {
923 rows.end - cursor.start().0 .0
924 } else {
925 0
926 };
927 cursor.start().1 .0 + overshoot
928 };
929 let input_start = {
930 cursor.seek(&BlockRow(rows.start), Bias::Right, &());
931 let overshoot = if cursor
932 .item()
933 .map_or(false, |transform| transform.is_isomorphic())
934 {
935 rows.start - cursor.start().0 .0
936 } else {
937 0
938 };
939 cursor.start().1 .0 + overshoot
940 };
941 BlockChunks {
942 input_chunks: self.wrap_snapshot.chunks(
943 input_start..input_end,
944 language_aware,
945 highlights,
946 ),
947 input_chunk: Default::default(),
948 transforms: cursor,
949 output_row: rows.start,
950 max_output_row,
951 masked,
952 }
953 }
954
955 pub(super) fn buffer_rows(&self, start_row: BlockRow) -> BlockBufferRows {
956 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
957 cursor.seek(&start_row, Bias::Right, &());
958 let (output_start, input_start) = cursor.start();
959 let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) {
960 start_row.0 - output_start.0
961 } else {
962 0
963 };
964 let input_start_row = input_start.0 + overshoot;
965 BlockBufferRows {
966 transforms: cursor,
967 input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
968 output_row: start_row,
969 started: false,
970 }
971 }
972
973 pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
974 let mut cursor = self.transforms.cursor::<BlockRow>();
975 cursor.seek(&BlockRow(rows.start), Bias::Right, &());
976 std::iter::from_fn(move || {
977 while let Some(transform) = cursor.item() {
978 let start_row = cursor.start().0;
979 if start_row >= rows.end {
980 break;
981 }
982 if let Some(block) = &transform.block {
983 cursor.next(&());
984 return Some((start_row, block));
985 } else {
986 cursor.next(&());
987 }
988 }
989 None
990 })
991 }
992
993 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
994 let buffer = self.wrap_snapshot.buffer_snapshot();
995
996 match block_id {
997 BlockId::Custom(custom_block_id) => {
998 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
999 Some(Block::Custom(custom_block.clone()))
1000 }
1001 BlockId::ExcerptHeader(excerpt_id) => {
1002 let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
1003 let wrap_point = self
1004 .wrap_snapshot
1005 .make_wrap_point(excerpt_range.start, Bias::Left);
1006 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
1007 cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
1008 while let Some(transform) = cursor.item() {
1009 if let Some(block) = transform.block.as_ref() {
1010 if block.id() == block_id {
1011 return Some(block.clone());
1012 }
1013 } else if cursor.start().0 > WrapRow(wrap_point.row()) {
1014 break;
1015 }
1016
1017 cursor.next(&());
1018 }
1019
1020 None
1021 }
1022 BlockId::ExcerptFooter(excerpt_id) => {
1023 let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
1024 let wrap_point = self
1025 .wrap_snapshot
1026 .make_wrap_point(excerpt_range.end, Bias::Left);
1027
1028 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
1029 cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
1030 while let Some(transform) = cursor.item() {
1031 if let Some(block) = transform.block.as_ref() {
1032 if block.id() == block_id {
1033 return Some(block.clone());
1034 }
1035 } else if cursor.start().0 > WrapRow(wrap_point.row()) {
1036 break;
1037 }
1038
1039 cursor.next(&());
1040 }
1041
1042 None
1043 }
1044 }
1045 }
1046
1047 pub fn max_point(&self) -> BlockPoint {
1048 let row = self.transforms.summary().output_rows - 1;
1049 BlockPoint::new(row, self.line_len(BlockRow(row)))
1050 }
1051
1052 pub fn longest_row(&self) -> u32 {
1053 let input_row = self.wrap_snapshot.longest_row();
1054 self.to_block_point(WrapPoint::new(input_row, 0)).row
1055 }
1056
1057 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
1058 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
1059 cursor.seek(&BlockRow(row.0), Bias::Right, &());
1060 if let Some(transform) = cursor.item() {
1061 let (output_start, input_start) = cursor.start();
1062 let overshoot = row.0 - output_start.0;
1063 if transform.block.is_some() {
1064 0
1065 } else {
1066 self.wrap_snapshot.line_len(input_start.0 + overshoot)
1067 }
1068 } else {
1069 panic!("row out of range");
1070 }
1071 }
1072
1073 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
1074 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
1075 cursor.seek(&row, Bias::Right, &());
1076 cursor.item().map_or(false, |t| t.block.is_some())
1077 }
1078
1079 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
1080 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
1081 cursor.seek(&BlockRow(point.row), Bias::Right, &());
1082
1083 let max_input_row = WrapRow(self.transforms.summary().input_rows);
1084 let mut search_left =
1085 (bias == Bias::Left && cursor.start().1 .0 > 0) || cursor.end(&()).1 == max_input_row;
1086 let mut reversed = false;
1087
1088 loop {
1089 if let Some(transform) = cursor.item() {
1090 if transform.is_isomorphic() {
1091 let (output_start_row, input_start_row) = cursor.start();
1092 let (output_end_row, input_end_row) = cursor.end(&());
1093 let output_start = Point::new(output_start_row.0, 0);
1094 let input_start = Point::new(input_start_row.0, 0);
1095 let input_end = Point::new(input_end_row.0, 0);
1096 let input_point = if point.row >= output_end_row.0 {
1097 let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
1098 self.wrap_snapshot
1099 .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
1100 } else {
1101 let output_overshoot = point.0.saturating_sub(output_start);
1102 self.wrap_snapshot
1103 .clip_point(WrapPoint(input_start + output_overshoot), bias)
1104 };
1105
1106 if (input_start..input_end).contains(&input_point.0) {
1107 let input_overshoot = input_point.0.saturating_sub(input_start);
1108 return BlockPoint(output_start + input_overshoot);
1109 }
1110 }
1111
1112 if search_left {
1113 cursor.prev(&());
1114 } else {
1115 cursor.next(&());
1116 }
1117 } else if reversed {
1118 return self.max_point();
1119 } else {
1120 reversed = true;
1121 search_left = !search_left;
1122 cursor.seek(&BlockRow(point.row), Bias::Right, &());
1123 }
1124 }
1125 }
1126
1127 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
1128 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
1129 cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
1130 if let Some(transform) = cursor.item() {
1131 debug_assert!(transform.is_isomorphic());
1132 } else {
1133 return self.max_point();
1134 }
1135
1136 let (input_start_row, output_start_row) = cursor.start();
1137 let input_start = Point::new(input_start_row.0, 0);
1138 let output_start = Point::new(output_start_row.0, 0);
1139 let input_overshoot = wrap_point.0 - input_start;
1140 BlockPoint(output_start + input_overshoot)
1141 }
1142
1143 pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
1144 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
1145 cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
1146 if let Some(transform) = cursor.item() {
1147 match transform.block.as_ref().map(|b| b.disposition()) {
1148 Some(BlockDisposition::Above) => WrapPoint::new(cursor.start().1 .0, 0),
1149 Some(BlockDisposition::Below) => {
1150 let wrap_row = cursor.start().1 .0 - 1;
1151 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1152 }
1153 None => {
1154 let overshoot = block_point.row - cursor.start().0 .0;
1155 let wrap_row = cursor.start().1 .0 + overshoot;
1156 WrapPoint::new(wrap_row, block_point.column)
1157 }
1158 }
1159 } else {
1160 self.wrap_snapshot.max_point()
1161 }
1162 }
1163}
1164
1165impl Transform {
1166 fn isomorphic(rows: u32) -> Self {
1167 Self {
1168 summary: TransformSummary {
1169 input_rows: rows,
1170 output_rows: rows,
1171 },
1172 block: None,
1173 }
1174 }
1175
1176 fn block(block: Block) -> Self {
1177 Self {
1178 summary: TransformSummary {
1179 input_rows: 0,
1180 output_rows: block.height(),
1181 },
1182 block: Some(block),
1183 }
1184 }
1185
1186 fn is_isomorphic(&self) -> bool {
1187 self.block.is_none()
1188 }
1189}
1190
1191impl<'a> Iterator for BlockChunks<'a> {
1192 type Item = Chunk<'a>;
1193
1194 fn next(&mut self) -> Option<Self::Item> {
1195 if self.output_row >= self.max_output_row {
1196 return None;
1197 }
1198
1199 let transform = self.transforms.item()?;
1200 if transform.block.is_some() {
1201 let block_start = self.transforms.start().0 .0;
1202 let mut block_end = self.transforms.end(&()).0 .0;
1203 self.transforms.next(&());
1204 if self.transforms.item().is_none() {
1205 block_end -= 1;
1206 }
1207
1208 let start_in_block = self.output_row - block_start;
1209 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
1210 let line_count = end_in_block - start_in_block;
1211 self.output_row += line_count;
1212
1213 return Some(Chunk {
1214 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
1215 ..Default::default()
1216 });
1217 }
1218
1219 if self.input_chunk.text.is_empty() {
1220 if let Some(input_chunk) = self.input_chunks.next() {
1221 self.input_chunk = input_chunk;
1222 } else {
1223 self.output_row += 1;
1224 if self.output_row < self.max_output_row {
1225 self.transforms.next(&());
1226 return Some(Chunk {
1227 text: "\n",
1228 ..Default::default()
1229 });
1230 } else {
1231 return None;
1232 }
1233 }
1234 }
1235
1236 let transform_end = self.transforms.end(&()).0 .0;
1237 let (prefix_rows, prefix_bytes) =
1238 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
1239 self.output_row += prefix_rows;
1240 let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
1241 self.input_chunk.text = suffix;
1242 if self.output_row == transform_end {
1243 self.transforms.next(&());
1244 }
1245
1246 if self.masked {
1247 // Not great for multibyte text because to keep cursor math correct we
1248 // need to have the same number of bytes in the input as output.
1249 let chars = prefix.chars().count();
1250 let bullet_len = chars;
1251 prefix = &BULLETS[..bullet_len];
1252 }
1253
1254 Some(Chunk {
1255 text: prefix,
1256 ..self.input_chunk.clone()
1257 })
1258 }
1259}
1260
1261impl<'a> Iterator for BlockBufferRows<'a> {
1262 type Item = Option<BlockRow>;
1263
1264 fn next(&mut self) -> Option<Self::Item> {
1265 if self.started {
1266 self.output_row.0 += 1;
1267 } else {
1268 self.started = true;
1269 }
1270
1271 if self.output_row.0 >= self.transforms.end(&()).0 .0 {
1272 self.transforms.next(&());
1273 }
1274
1275 let transform = self.transforms.item()?;
1276 if transform.block.is_some() {
1277 Some(None)
1278 } else {
1279 Some(self.input_buffer_rows.next().unwrap().map(BlockRow))
1280 }
1281 }
1282}
1283
1284impl sum_tree::Item for Transform {
1285 type Summary = TransformSummary;
1286
1287 fn summary(&self) -> Self::Summary {
1288 self.summary.clone()
1289 }
1290}
1291
1292impl sum_tree::Summary for TransformSummary {
1293 type Context = ();
1294
1295 fn add_summary(&mut self, summary: &Self, _: &()) {
1296 self.input_rows += summary.input_rows;
1297 self.output_rows += summary.output_rows;
1298 }
1299}
1300
1301impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
1302 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1303 self.0 += summary.input_rows;
1304 }
1305}
1306
1307impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
1308 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1309 self.0 += summary.output_rows;
1310 }
1311}
1312
1313impl BlockDisposition {
1314 fn is_below(&self) -> bool {
1315 matches!(self, BlockDisposition::Below)
1316 }
1317}
1318
1319impl<'a> Deref for BlockContext<'a, '_> {
1320 type Target = WindowContext<'a>;
1321
1322 fn deref(&self) -> &Self::Target {
1323 self.context
1324 }
1325}
1326
1327impl DerefMut for BlockContext<'_, '_> {
1328 fn deref_mut(&mut self) -> &mut Self::Target {
1329 self.context
1330 }
1331}
1332
1333impl CustomBlock {
1334 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
1335 self.render.lock()(cx)
1336 }
1337
1338 pub fn position(&self) -> &Anchor {
1339 &self.position
1340 }
1341
1342 pub fn style(&self) -> BlockStyle {
1343 self.style
1344 }
1345}
1346
1347impl Debug for CustomBlock {
1348 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1349 f.debug_struct("Block")
1350 .field("id", &self.id)
1351 .field("position", &self.position)
1352 .field("disposition", &self.disposition)
1353 .finish()
1354 }
1355}
1356
1357// Count the number of bytes prior to a target point. If the string doesn't contain the target
1358// point, return its total extent. Otherwise return the target point itself.
1359fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
1360 let mut row = 0;
1361 let mut offset = 0;
1362 for (ix, line) in s.split('\n').enumerate() {
1363 if ix > 0 {
1364 row += 1;
1365 offset += 1;
1366 }
1367 if row >= target {
1368 break;
1369 }
1370 offset += line.len();
1371 }
1372 (row, offset)
1373}
1374
1375#[cfg(test)]
1376mod tests {
1377 use super::*;
1378 use crate::display_map::{
1379 fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
1380 };
1381 use gpui::{div, font, px, AppContext, Context as _, Element};
1382 use language::{Buffer, Capability};
1383 use multi_buffer::MultiBuffer;
1384 use rand::prelude::*;
1385 use settings::SettingsStore;
1386 use std::env;
1387 use util::RandomCharIter;
1388
1389 #[gpui::test]
1390 fn test_offset_for_row() {
1391 assert_eq!(offset_for_row("", 0), (0, 0));
1392 assert_eq!(offset_for_row("", 1), (0, 0));
1393 assert_eq!(offset_for_row("abcd", 0), (0, 0));
1394 assert_eq!(offset_for_row("abcd", 1), (0, 4));
1395 assert_eq!(offset_for_row("\n", 0), (0, 0));
1396 assert_eq!(offset_for_row("\n", 1), (1, 1));
1397 assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
1398 assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
1399 assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
1400 assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
1401 }
1402
1403 #[gpui::test]
1404 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
1405 cx.update(|cx| init_test(cx));
1406
1407 let text = "aaa\nbbb\nccc\nddd";
1408
1409 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1410 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1411 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1412 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1413 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1414 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
1415 let (wrap_map, wraps_snapshot) =
1416 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
1417 let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
1418
1419 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
1420 let block_ids = writer.insert(vec![
1421 BlockProperties {
1422 style: BlockStyle::Fixed,
1423 position: buffer_snapshot.anchor_after(Point::new(1, 0)),
1424 height: 1,
1425 disposition: BlockDisposition::Above,
1426 render: Box::new(|_| div().into_any()),
1427 },
1428 BlockProperties {
1429 style: BlockStyle::Fixed,
1430 position: buffer_snapshot.anchor_after(Point::new(1, 2)),
1431 height: 2,
1432 disposition: BlockDisposition::Above,
1433 render: Box::new(|_| div().into_any()),
1434 },
1435 BlockProperties {
1436 style: BlockStyle::Fixed,
1437 position: buffer_snapshot.anchor_after(Point::new(3, 3)),
1438 height: 3,
1439 disposition: BlockDisposition::Below,
1440 render: Box::new(|_| div().into_any()),
1441 },
1442 ]);
1443
1444 let snapshot = block_map.read(wraps_snapshot, Default::default());
1445 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
1446
1447 let blocks = snapshot
1448 .blocks_in_range(0..8)
1449 .map(|(start_row, block)| {
1450 let block = block.as_custom().unwrap();
1451 (start_row..start_row + block.height, block.id)
1452 })
1453 .collect::<Vec<_>>();
1454
1455 // When multiple blocks are on the same line, the newer blocks appear first.
1456 assert_eq!(
1457 blocks,
1458 &[
1459 (1..2, block_ids[0]),
1460 (2..4, block_ids[1]),
1461 (7..10, block_ids[2]),
1462 ]
1463 );
1464
1465 assert_eq!(
1466 snapshot.to_block_point(WrapPoint::new(0, 3)),
1467 BlockPoint::new(0, 3)
1468 );
1469 assert_eq!(
1470 snapshot.to_block_point(WrapPoint::new(1, 0)),
1471 BlockPoint::new(4, 0)
1472 );
1473 assert_eq!(
1474 snapshot.to_block_point(WrapPoint::new(3, 3)),
1475 BlockPoint::new(6, 3)
1476 );
1477
1478 assert_eq!(
1479 snapshot.to_wrap_point(BlockPoint::new(0, 3)),
1480 WrapPoint::new(0, 3)
1481 );
1482 assert_eq!(
1483 snapshot.to_wrap_point(BlockPoint::new(1, 0)),
1484 WrapPoint::new(1, 0)
1485 );
1486 assert_eq!(
1487 snapshot.to_wrap_point(BlockPoint::new(3, 0)),
1488 WrapPoint::new(1, 0)
1489 );
1490 assert_eq!(
1491 snapshot.to_wrap_point(BlockPoint::new(7, 0)),
1492 WrapPoint::new(3, 3)
1493 );
1494
1495 assert_eq!(
1496 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
1497 BlockPoint::new(0, 3)
1498 );
1499 assert_eq!(
1500 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
1501 BlockPoint::new(4, 0)
1502 );
1503 assert_eq!(
1504 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
1505 BlockPoint::new(0, 3)
1506 );
1507 assert_eq!(
1508 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
1509 BlockPoint::new(4, 0)
1510 );
1511 assert_eq!(
1512 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
1513 BlockPoint::new(4, 0)
1514 );
1515 assert_eq!(
1516 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
1517 BlockPoint::new(4, 0)
1518 );
1519 assert_eq!(
1520 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
1521 BlockPoint::new(6, 3)
1522 );
1523 assert_eq!(
1524 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
1525 BlockPoint::new(6, 3)
1526 );
1527 assert_eq!(
1528 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
1529 BlockPoint::new(6, 3)
1530 );
1531 assert_eq!(
1532 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
1533 BlockPoint::new(6, 3)
1534 );
1535
1536 assert_eq!(
1537 snapshot
1538 .buffer_rows(BlockRow(0))
1539 .map(|row| row.map(|r| r.0))
1540 .collect::<Vec<_>>(),
1541 &[
1542 Some(0),
1543 None,
1544 None,
1545 None,
1546 Some(1),
1547 Some(2),
1548 Some(3),
1549 None,
1550 None,
1551 None
1552 ]
1553 );
1554
1555 // Insert a line break, separating two block decorations into separate lines.
1556 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1557 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
1558 buffer.snapshot(cx)
1559 });
1560
1561 let (inlay_snapshot, inlay_edits) =
1562 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1563 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1564 let (tab_snapshot, tab_edits) =
1565 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
1566 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1567 wrap_map.sync(tab_snapshot, tab_edits, cx)
1568 });
1569 let snapshot = block_map.read(wraps_snapshot, wrap_edits);
1570 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
1571 }
1572
1573 #[gpui::test]
1574 fn test_multibuffer_headers_and_footers(cx: &mut AppContext) {
1575 init_test(cx);
1576
1577 let buffer1 = cx.new_model(|cx| Buffer::local("Buffer 1", cx));
1578 let buffer2 = cx.new_model(|cx| Buffer::local("Buffer 2", cx));
1579 let buffer3 = cx.new_model(|cx| Buffer::local("Buffer 3", cx));
1580
1581 let mut excerpt_ids = Vec::new();
1582 let multi_buffer = cx.new_model(|cx| {
1583 let mut multi_buffer = MultiBuffer::new(0, Capability::ReadWrite);
1584 excerpt_ids.extend(multi_buffer.push_excerpts(
1585 buffer1.clone(),
1586 [ExcerptRange {
1587 context: 0..buffer1.read(cx).len(),
1588 primary: None,
1589 }],
1590 cx,
1591 ));
1592 excerpt_ids.extend(multi_buffer.push_excerpts(
1593 buffer2.clone(),
1594 [ExcerptRange {
1595 context: 0..buffer2.read(cx).len(),
1596 primary: None,
1597 }],
1598 cx,
1599 ));
1600 excerpt_ids.extend(multi_buffer.push_excerpts(
1601 buffer3.clone(),
1602 [ExcerptRange {
1603 context: 0..buffer3.read(cx).len(),
1604 primary: None,
1605 }],
1606 cx,
1607 ));
1608
1609 multi_buffer
1610 });
1611
1612 let font = font("Helvetica");
1613 let font_size = px(14.);
1614 let font_id = cx.text_system().resolve_font(&font);
1615 let mut wrap_width = px(0.);
1616 for c in "Buff".chars() {
1617 wrap_width += cx
1618 .text_system()
1619 .advance(font_id, font_size, c)
1620 .unwrap()
1621 .width;
1622 }
1623
1624 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
1625 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone());
1626 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
1627 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
1628 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
1629
1630 let block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
1631 let snapshot = block_map.read(wraps_snapshot, Default::default());
1632
1633 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
1634 assert_eq!(
1635 snapshot.text(),
1636 "\nBuff\ner 1\n\n\nBuff\ner 2\n\n\nBuff\ner 3\n"
1637 );
1638
1639 let blocks: Vec<_> = snapshot
1640 .blocks_in_range(0..u32::MAX)
1641 .map(|(row, block)| (row, block.id()))
1642 .collect();
1643 assert_eq!(
1644 blocks,
1645 vec![
1646 (0, BlockId::ExcerptHeader(excerpt_ids[0])),
1647 (3, BlockId::ExcerptFooter(excerpt_ids[0])),
1648 (4, BlockId::ExcerptHeader(excerpt_ids[1])),
1649 (7, BlockId::ExcerptFooter(excerpt_ids[1])),
1650 (8, BlockId::ExcerptHeader(excerpt_ids[2])),
1651 (11, BlockId::ExcerptFooter(excerpt_ids[2]))
1652 ]
1653 );
1654 }
1655
1656 #[gpui::test]
1657 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
1658 let _update = cx.update(|cx| init_test(cx));
1659
1660 let text = "aaa\nbbb\nccc\nddd";
1661
1662 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1663 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1664 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1665 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1666 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1667 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
1668 let (_wrap_map, wraps_snapshot) =
1669 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
1670 let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
1671
1672 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
1673 let block_ids = writer.insert(vec![
1674 BlockProperties {
1675 style: BlockStyle::Fixed,
1676 position: buffer_snapshot.anchor_after(Point::new(1, 0)),
1677 height: 1,
1678 disposition: BlockDisposition::Above,
1679 render: Box::new(|_| div().into_any()),
1680 },
1681 BlockProperties {
1682 style: BlockStyle::Fixed,
1683 position: buffer_snapshot.anchor_after(Point::new(1, 2)),
1684 height: 2,
1685 disposition: BlockDisposition::Above,
1686 render: Box::new(|_| div().into_any()),
1687 },
1688 BlockProperties {
1689 style: BlockStyle::Fixed,
1690 position: buffer_snapshot.anchor_after(Point::new(3, 3)),
1691 height: 3,
1692 disposition: BlockDisposition::Below,
1693 render: Box::new(|_| div().into_any()),
1694 },
1695 ]);
1696
1697 {
1698 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1699 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
1700
1701 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1702
1703 let mut new_heights = HashMap::default();
1704 new_heights.insert(block_ids[0], 2);
1705 block_map_writer.resize(new_heights);
1706 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1707 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
1708 }
1709
1710 {
1711 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1712
1713 let mut new_heights = HashMap::default();
1714 new_heights.insert(block_ids[0], 1);
1715 block_map_writer.resize(new_heights);
1716
1717 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1718 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
1719 }
1720
1721 {
1722 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1723
1724 let mut new_heights = HashMap::default();
1725 new_heights.insert(block_ids[0], 0);
1726 block_map_writer.resize(new_heights);
1727
1728 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1729 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
1730 }
1731
1732 {
1733 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1734
1735 let mut new_heights = HashMap::default();
1736 new_heights.insert(block_ids[0], 3);
1737 block_map_writer.resize(new_heights);
1738
1739 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1740 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
1741 }
1742
1743 {
1744 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1745
1746 let mut new_heights = HashMap::default();
1747 new_heights.insert(block_ids[0], 3);
1748 block_map_writer.resize(new_heights);
1749
1750 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1751 // Same height as before, should remain the same
1752 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
1753 }
1754 }
1755
1756 #[cfg(target_os = "macos")]
1757 #[gpui::test]
1758 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
1759 cx.update(|cx| init_test(cx));
1760
1761 let _font_id = cx.text_system().font_id(&font("Helvetica")).unwrap();
1762
1763 let text = "one two three\nfour five six\nseven eight";
1764
1765 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1766 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1767 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1768 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
1769 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
1770 let (_, wraps_snapshot) = cx.update(|cx| {
1771 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx)
1772 });
1773 let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 0);
1774
1775 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
1776 writer.insert(vec![
1777 BlockProperties {
1778 style: BlockStyle::Fixed,
1779 position: buffer_snapshot.anchor_after(Point::new(1, 12)),
1780 disposition: BlockDisposition::Above,
1781 render: Box::new(|_| div().into_any()),
1782 height: 1,
1783 },
1784 BlockProperties {
1785 style: BlockStyle::Fixed,
1786 position: buffer_snapshot.anchor_after(Point::new(1, 1)),
1787 disposition: BlockDisposition::Below,
1788 render: Box::new(|_| div().into_any()),
1789 height: 1,
1790 },
1791 ]);
1792
1793 // Blocks with an 'above' disposition go above their corresponding buffer line.
1794 // Blocks with a 'below' disposition go below their corresponding buffer line.
1795 let snapshot = block_map.read(wraps_snapshot, Default::default());
1796 assert_eq!(
1797 snapshot.text(),
1798 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
1799 );
1800 }
1801
1802 #[gpui::test(iterations = 100)]
1803 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
1804 cx.update(|cx| init_test(cx));
1805
1806 let operations = env::var("OPERATIONS")
1807 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1808 .unwrap_or(10);
1809
1810 let wrap_width = if rng.gen_bool(0.2) {
1811 None
1812 } else {
1813 Some(px(rng.gen_range(0.0..=100.0)))
1814 };
1815 let tab_size = 1.try_into().unwrap();
1816 let font_size = px(14.0);
1817 let buffer_start_header_height = rng.gen_range(1..=5);
1818 let excerpt_header_height = rng.gen_range(1..=5);
1819 let excerpt_footer_height = rng.gen_range(1..=5);
1820
1821 log::info!("Wrap width: {:?}", wrap_width);
1822 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
1823 log::info!("Excerpt Footer Height: {:?}", excerpt_footer_height);
1824
1825 let buffer = if rng.gen() {
1826 let len = rng.gen_range(0..10);
1827 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
1828 log::info!("initial buffer text: {:?}", text);
1829 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
1830 } else {
1831 cx.update(|cx| MultiBuffer::build_random(&mut rng, cx))
1832 };
1833
1834 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1835 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1836 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1837 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
1838 let (wrap_map, wraps_snapshot) = cx
1839 .update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), font_size, wrap_width, cx));
1840 let mut block_map = BlockMap::new(
1841 wraps_snapshot,
1842 true,
1843 buffer_start_header_height,
1844 excerpt_header_height,
1845 excerpt_footer_height,
1846 );
1847 let mut custom_blocks = Vec::new();
1848
1849 for _ in 0..operations {
1850 let mut buffer_edits = Vec::new();
1851 match rng.gen_range(0..=100) {
1852 0..=19 => {
1853 let wrap_width = if rng.gen_bool(0.2) {
1854 None
1855 } else {
1856 Some(px(rng.gen_range(0.0..=100.0)))
1857 };
1858 log::info!("Setting wrap width to {:?}", wrap_width);
1859 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1860 }
1861 20..=39 => {
1862 let block_count = rng.gen_range(1..=5);
1863 let block_properties = (0..block_count)
1864 .map(|_| {
1865 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
1866 let position = buffer.anchor_after(
1867 buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left),
1868 );
1869
1870 let disposition = if rng.gen() {
1871 BlockDisposition::Above
1872 } else {
1873 BlockDisposition::Below
1874 };
1875 let height = rng.gen_range(1..5);
1876 log::info!(
1877 "inserting block {:?} {:?} with height {}",
1878 disposition,
1879 position.to_point(&buffer),
1880 height
1881 );
1882 BlockProperties {
1883 style: BlockStyle::Fixed,
1884 position,
1885 height,
1886 disposition,
1887 render: Box::new(|_| div().into_any()),
1888 }
1889 })
1890 .collect::<Vec<_>>();
1891
1892 let (inlay_snapshot, inlay_edits) =
1893 inlay_map.sync(buffer_snapshot.clone(), vec![]);
1894 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1895 let (tab_snapshot, tab_edits) =
1896 tab_map.sync(fold_snapshot, fold_edits, tab_size);
1897 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1898 wrap_map.sync(tab_snapshot, tab_edits, cx)
1899 });
1900 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
1901 let block_ids =
1902 block_map.insert(block_properties.iter().map(|props| BlockProperties {
1903 position: props.position,
1904 height: props.height,
1905 style: props.style,
1906 render: Box::new(|_| div().into_any()),
1907 disposition: props.disposition,
1908 }));
1909 for (block_id, props) in block_ids.into_iter().zip(block_properties) {
1910 custom_blocks.push((block_id, props));
1911 }
1912 }
1913 40..=59 if !custom_blocks.is_empty() => {
1914 let block_count = rng.gen_range(1..=4.min(custom_blocks.len()));
1915 let block_ids_to_remove = (0..block_count)
1916 .map(|_| {
1917 custom_blocks
1918 .remove(rng.gen_range(0..custom_blocks.len()))
1919 .0
1920 })
1921 .collect();
1922
1923 let (inlay_snapshot, inlay_edits) =
1924 inlay_map.sync(buffer_snapshot.clone(), vec![]);
1925 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1926 let (tab_snapshot, tab_edits) =
1927 tab_map.sync(fold_snapshot, fold_edits, tab_size);
1928 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1929 wrap_map.sync(tab_snapshot, tab_edits, cx)
1930 });
1931 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
1932 block_map.remove(block_ids_to_remove);
1933 }
1934 _ => {
1935 buffer.update(cx, |buffer, cx| {
1936 let mutation_count = rng.gen_range(1..=5);
1937 let subscription = buffer.subscribe();
1938 buffer.randomly_mutate(&mut rng, mutation_count, cx);
1939 buffer_snapshot = buffer.snapshot(cx);
1940 buffer_edits.extend(subscription.consume());
1941 log::info!("buffer text: {:?}", buffer_snapshot.text());
1942 });
1943 }
1944 }
1945
1946 let (inlay_snapshot, inlay_edits) =
1947 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1948 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1949 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
1950 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1951 wrap_map.sync(tab_snapshot, tab_edits, cx)
1952 });
1953 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
1954 assert_eq!(
1955 blocks_snapshot.transforms.summary().input_rows,
1956 wraps_snapshot.max_point().row() + 1
1957 );
1958 log::info!("blocks text: {:?}", blocks_snapshot.text());
1959
1960 let mut expected_blocks = Vec::new();
1961 expected_blocks.extend(custom_blocks.iter().map(|(id, block)| {
1962 let mut position = block.position.to_point(&buffer_snapshot);
1963 match block.disposition {
1964 BlockDisposition::Above => {
1965 position.column = 0;
1966 }
1967 BlockDisposition::Below => {
1968 position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
1969 }
1970 };
1971 let row = wraps_snapshot.make_wrap_point(position, Bias::Left).row();
1972 (
1973 row,
1974 ExpectedBlock::Custom {
1975 disposition: block.disposition,
1976 id: *id,
1977 height: block.height,
1978 },
1979 )
1980 }));
1981
1982 // Note that this needs to be synced with the related section in BlockMap::sync
1983 expected_blocks.extend(
1984 BlockMap::header_and_footer_blocks(
1985 true,
1986 excerpt_footer_height,
1987 buffer_start_header_height,
1988 excerpt_header_height,
1989 &buffer_snapshot,
1990 0..,
1991 &wraps_snapshot,
1992 )
1993 .map(|(row, block)| (row, block.into())),
1994 );
1995
1996 BlockMap::sort_blocks(&mut expected_blocks);
1997
1998 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
1999
2000 let input_buffer_rows = buffer_snapshot
2001 .buffer_rows(MultiBufferRow(0))
2002 .collect::<Vec<_>>();
2003 let mut expected_buffer_rows = Vec::new();
2004 let mut expected_text = String::new();
2005 let mut expected_block_positions = Vec::new();
2006 let input_text = wraps_snapshot.text();
2007 for (row, input_line) in input_text.split('\n').enumerate() {
2008 let row = row as u32;
2009 if row > 0 {
2010 expected_text.push('\n');
2011 }
2012
2013 let buffer_row = input_buffer_rows[wraps_snapshot
2014 .to_point(WrapPoint::new(row, 0), Bias::Left)
2015 .row as usize];
2016
2017 while let Some((block_row, block)) = sorted_blocks_iter.peek() {
2018 if *block_row == row && block.disposition() == BlockDisposition::Above {
2019 let (_, block) = sorted_blocks_iter.next().unwrap();
2020 let height = block.height() as usize;
2021 expected_block_positions
2022 .push((expected_text.matches('\n').count() as u32, block));
2023 let text = "\n".repeat(height);
2024 expected_text.push_str(&text);
2025 for _ in 0..height {
2026 expected_buffer_rows.push(None);
2027 }
2028 } else {
2029 break;
2030 }
2031 }
2032
2033 let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
2034 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
2035 expected_text.push_str(input_line);
2036
2037 while let Some((block_row, block)) = sorted_blocks_iter.peek() {
2038 if *block_row == row && block.disposition() == BlockDisposition::Below {
2039 let (_, block) = sorted_blocks_iter.next().unwrap();
2040 let height = block.height() as usize;
2041 expected_block_positions
2042 .push((expected_text.matches('\n').count() as u32 + 1, block));
2043 let text = "\n".repeat(height);
2044 expected_text.push_str(&text);
2045 for _ in 0..height {
2046 expected_buffer_rows.push(None);
2047 }
2048 } else {
2049 break;
2050 }
2051 }
2052 }
2053
2054 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
2055 let expected_row_count = expected_lines.len();
2056 for start_row in 0..expected_row_count {
2057 let expected_text = expected_lines[start_row..].join("\n");
2058 let actual_text = blocks_snapshot
2059 .chunks(
2060 start_row as u32..blocks_snapshot.max_point().row + 1,
2061 false,
2062 false,
2063 Highlights::default(),
2064 )
2065 .map(|chunk| chunk.text)
2066 .collect::<String>();
2067 assert_eq!(
2068 actual_text, expected_text,
2069 "incorrect text starting from row {}",
2070 start_row
2071 );
2072 assert_eq!(
2073 blocks_snapshot
2074 .buffer_rows(BlockRow(start_row as u32))
2075 .map(|row| row.map(|r| r.0))
2076 .collect::<Vec<_>>(),
2077 &expected_buffer_rows[start_row..]
2078 );
2079 }
2080
2081 assert_eq!(
2082 blocks_snapshot
2083 .blocks_in_range(0..(expected_row_count as u32))
2084 .map(|(row, block)| (row, block.clone().into()))
2085 .collect::<Vec<_>>(),
2086 expected_block_positions
2087 );
2088
2089 for (_, expected_block) in
2090 blocks_snapshot.blocks_in_range(0..(expected_row_count as u32))
2091 {
2092 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
2093 assert_eq!(
2094 actual_block.map(|block| block.id()),
2095 Some(expected_block.id())
2096 );
2097 }
2098
2099 for (block_row, block) in expected_block_positions {
2100 if let BlockType::Custom(block_id) = block.block_type() {
2101 assert_eq!(
2102 blocks_snapshot.row_for_block(block_id),
2103 Some(BlockRow(block_row))
2104 );
2105 }
2106 }
2107
2108 let mut expected_longest_rows = Vec::new();
2109 let mut longest_line_len = -1_isize;
2110 for (row, line) in expected_lines.iter().enumerate() {
2111 let row = row as u32;
2112
2113 assert_eq!(
2114 blocks_snapshot.line_len(BlockRow(row)),
2115 line.len() as u32,
2116 "invalid line len for row {}",
2117 row
2118 );
2119
2120 let line_char_count = line.chars().count() as isize;
2121 match line_char_count.cmp(&longest_line_len) {
2122 Ordering::Less => {}
2123 Ordering::Equal => expected_longest_rows.push(row),
2124 Ordering::Greater => {
2125 longest_line_len = line_char_count;
2126 expected_longest_rows.clear();
2127 expected_longest_rows.push(row);
2128 }
2129 }
2130 }
2131
2132 let longest_row = blocks_snapshot.longest_row();
2133 assert!(
2134 expected_longest_rows.contains(&longest_row),
2135 "incorrect longest row {}. expected {:?} with length {}",
2136 longest_row,
2137 expected_longest_rows,
2138 longest_line_len,
2139 );
2140
2141 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
2142 let wrap_point = WrapPoint::new(row, 0);
2143 let block_point = blocks_snapshot.to_block_point(wrap_point);
2144 assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point);
2145 }
2146
2147 let mut block_point = BlockPoint::new(0, 0);
2148 for c in expected_text.chars() {
2149 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
2150 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
2151 assert_eq!(
2152 blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)),
2153 left_point
2154 );
2155 assert_eq!(
2156 left_buffer_point,
2157 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
2158 "{:?} is not valid in buffer coordinates",
2159 left_point
2160 );
2161
2162 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
2163 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
2164 assert_eq!(
2165 blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)),
2166 right_point
2167 );
2168 assert_eq!(
2169 right_buffer_point,
2170 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
2171 "{:?} is not valid in buffer coordinates",
2172 right_point
2173 );
2174
2175 if c == '\n' {
2176 block_point.0 += Point::new(1, 0);
2177 } else {
2178 block_point.column += c.len_utf8() as u32;
2179 }
2180 }
2181 }
2182
2183 #[derive(Debug, Eq, PartialEq)]
2184 enum ExpectedBlock {
2185 ExcerptHeader {
2186 height: u32,
2187 starts_new_buffer: bool,
2188 },
2189 ExcerptFooter {
2190 height: u32,
2191 disposition: BlockDisposition,
2192 },
2193 Custom {
2194 disposition: BlockDisposition,
2195 id: CustomBlockId,
2196 height: u32,
2197 },
2198 }
2199
2200 impl BlockLike for ExpectedBlock {
2201 fn block_type(&self) -> BlockType {
2202 match self {
2203 ExpectedBlock::Custom { id, .. } => BlockType::Custom(*id),
2204 ExpectedBlock::ExcerptHeader { .. } => BlockType::Header,
2205 ExpectedBlock::ExcerptFooter { .. } => BlockType::Footer,
2206 }
2207 }
2208
2209 fn disposition(&self) -> BlockDisposition {
2210 self.disposition()
2211 }
2212 }
2213
2214 impl ExpectedBlock {
2215 fn height(&self) -> u32 {
2216 match self {
2217 ExpectedBlock::ExcerptHeader { height, .. } => *height,
2218 ExpectedBlock::Custom { height, .. } => *height,
2219 ExpectedBlock::ExcerptFooter { height, .. } => *height,
2220 }
2221 }
2222
2223 fn disposition(&self) -> BlockDisposition {
2224 match self {
2225 ExpectedBlock::ExcerptHeader { .. } => BlockDisposition::Above,
2226 ExpectedBlock::Custom { disposition, .. } => *disposition,
2227 ExpectedBlock::ExcerptFooter { disposition, .. } => *disposition,
2228 }
2229 }
2230 }
2231
2232 impl From<Block> for ExpectedBlock {
2233 fn from(block: Block) -> Self {
2234 match block {
2235 Block::Custom(block) => ExpectedBlock::Custom {
2236 id: block.id,
2237 disposition: block.disposition,
2238 height: block.height,
2239 },
2240 Block::ExcerptHeader {
2241 height,
2242 starts_new_buffer,
2243 ..
2244 } => ExpectedBlock::ExcerptHeader {
2245 height,
2246 starts_new_buffer,
2247 },
2248 Block::ExcerptFooter {
2249 height,
2250 disposition,
2251 ..
2252 } => ExpectedBlock::ExcerptFooter {
2253 height,
2254 disposition,
2255 },
2256 }
2257 }
2258 }
2259 }
2260
2261 fn init_test(cx: &mut gpui::AppContext) {
2262 let settings = SettingsStore::test(cx);
2263 cx.set_global(settings);
2264 theme::init(theme::LoadThemes::JustBase, cx);
2265 assets::Assets.load_test_fonts(cx);
2266 }
2267
2268 impl Block {
2269 fn as_custom(&self) -> Option<&CustomBlock> {
2270 match self {
2271 Block::Custom(block) => Some(block),
2272 Block::ExcerptHeader { .. } => None,
2273 Block::ExcerptFooter { .. } => None,
2274 }
2275 }
2276 }
2277
2278 impl BlockSnapshot {
2279 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
2280 self.wrap_snapshot.to_point(self.to_wrap_point(point), bias)
2281 }
2282 }
2283}