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::Left, &());
976 while cursor.start().0 < rows.start && cursor.end(&()).0 <= rows.start {
977 cursor.next(&());
978 }
979
980 std::iter::from_fn(move || {
981 while let Some(transform) = cursor.item() {
982 let start_row = cursor.start().0;
983 if start_row > rows.end
984 || (start_row == rows.end
985 && transform
986 .block
987 .as_ref()
988 .map_or(false, |block| block.height() > 0))
989 {
990 break;
991 }
992 if let Some(block) = &transform.block {
993 cursor.next(&());
994 return Some((start_row, block));
995 } else {
996 cursor.next(&());
997 }
998 }
999 None
1000 })
1001 }
1002
1003 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
1004 let buffer = self.wrap_snapshot.buffer_snapshot();
1005
1006 match block_id {
1007 BlockId::Custom(custom_block_id) => {
1008 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
1009 Some(Block::Custom(custom_block.clone()))
1010 }
1011 BlockId::ExcerptHeader(excerpt_id) => {
1012 let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
1013 let wrap_point = self
1014 .wrap_snapshot
1015 .make_wrap_point(excerpt_range.start, Bias::Left);
1016 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
1017 cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
1018 while let Some(transform) = cursor.item() {
1019 if let Some(block) = transform.block.as_ref() {
1020 if block.id() == block_id {
1021 return Some(block.clone());
1022 }
1023 } else if cursor.start().0 > WrapRow(wrap_point.row()) {
1024 break;
1025 }
1026
1027 cursor.next(&());
1028 }
1029
1030 None
1031 }
1032 BlockId::ExcerptFooter(excerpt_id) => {
1033 let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
1034 let wrap_point = self
1035 .wrap_snapshot
1036 .make_wrap_point(excerpt_range.end, Bias::Left);
1037
1038 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
1039 cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
1040 while let Some(transform) = cursor.item() {
1041 if let Some(block) = transform.block.as_ref() {
1042 if block.id() == block_id {
1043 return Some(block.clone());
1044 }
1045 } else if cursor.start().0 > WrapRow(wrap_point.row()) {
1046 break;
1047 }
1048
1049 cursor.next(&());
1050 }
1051
1052 None
1053 }
1054 }
1055 }
1056
1057 pub fn max_point(&self) -> BlockPoint {
1058 let row = self.transforms.summary().output_rows - 1;
1059 BlockPoint::new(row, self.line_len(BlockRow(row)))
1060 }
1061
1062 pub fn longest_row(&self) -> u32 {
1063 let input_row = self.wrap_snapshot.longest_row();
1064 self.to_block_point(WrapPoint::new(input_row, 0)).row
1065 }
1066
1067 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
1068 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
1069 cursor.seek(&BlockRow(row.0), Bias::Right, &());
1070 if let Some(transform) = cursor.item() {
1071 let (output_start, input_start) = cursor.start();
1072 let overshoot = row.0 - output_start.0;
1073 if transform.block.is_some() {
1074 0
1075 } else {
1076 self.wrap_snapshot.line_len(input_start.0 + overshoot)
1077 }
1078 } else {
1079 panic!("row out of range");
1080 }
1081 }
1082
1083 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
1084 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
1085 cursor.seek(&row, Bias::Right, &());
1086 cursor.item().map_or(false, |t| t.block.is_some())
1087 }
1088
1089 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
1090 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
1091 cursor.seek(&BlockRow(point.row), Bias::Right, &());
1092
1093 let max_input_row = WrapRow(self.transforms.summary().input_rows);
1094 let mut search_left =
1095 (bias == Bias::Left && cursor.start().1 .0 > 0) || cursor.end(&()).1 == max_input_row;
1096 let mut reversed = false;
1097
1098 loop {
1099 if let Some(transform) = cursor.item() {
1100 if transform.is_isomorphic() {
1101 let (output_start_row, input_start_row) = cursor.start();
1102 let (output_end_row, input_end_row) = cursor.end(&());
1103 let output_start = Point::new(output_start_row.0, 0);
1104 let input_start = Point::new(input_start_row.0, 0);
1105 let input_end = Point::new(input_end_row.0, 0);
1106 let input_point = if point.row >= output_end_row.0 {
1107 let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
1108 self.wrap_snapshot
1109 .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
1110 } else {
1111 let output_overshoot = point.0.saturating_sub(output_start);
1112 self.wrap_snapshot
1113 .clip_point(WrapPoint(input_start + output_overshoot), bias)
1114 };
1115
1116 if (input_start..input_end).contains(&input_point.0) {
1117 let input_overshoot = input_point.0.saturating_sub(input_start);
1118 return BlockPoint(output_start + input_overshoot);
1119 }
1120 }
1121
1122 if search_left {
1123 cursor.prev(&());
1124 } else {
1125 cursor.next(&());
1126 }
1127 } else if reversed {
1128 return self.max_point();
1129 } else {
1130 reversed = true;
1131 search_left = !search_left;
1132 cursor.seek(&BlockRow(point.row), Bias::Right, &());
1133 }
1134 }
1135 }
1136
1137 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
1138 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
1139 cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
1140 if let Some(transform) = cursor.item() {
1141 debug_assert!(transform.is_isomorphic());
1142 } else {
1143 return self.max_point();
1144 }
1145
1146 let (input_start_row, output_start_row) = cursor.start();
1147 let input_start = Point::new(input_start_row.0, 0);
1148 let output_start = Point::new(output_start_row.0, 0);
1149 let input_overshoot = wrap_point.0 - input_start;
1150 BlockPoint(output_start + input_overshoot)
1151 }
1152
1153 pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
1154 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
1155 cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
1156 if let Some(transform) = cursor.item() {
1157 match transform.block.as_ref().map(|b| b.disposition()) {
1158 Some(BlockDisposition::Above) => WrapPoint::new(cursor.start().1 .0, 0),
1159 Some(BlockDisposition::Below) => {
1160 let wrap_row = cursor.start().1 .0 - 1;
1161 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1162 }
1163 None => {
1164 let overshoot = block_point.row - cursor.start().0 .0;
1165 let wrap_row = cursor.start().1 .0 + overshoot;
1166 WrapPoint::new(wrap_row, block_point.column)
1167 }
1168 }
1169 } else {
1170 self.wrap_snapshot.max_point()
1171 }
1172 }
1173}
1174
1175impl Transform {
1176 fn isomorphic(rows: u32) -> Self {
1177 Self {
1178 summary: TransformSummary {
1179 input_rows: rows,
1180 output_rows: rows,
1181 },
1182 block: None,
1183 }
1184 }
1185
1186 fn block(block: Block) -> Self {
1187 Self {
1188 summary: TransformSummary {
1189 input_rows: 0,
1190 output_rows: block.height(),
1191 },
1192 block: Some(block),
1193 }
1194 }
1195
1196 fn is_isomorphic(&self) -> bool {
1197 self.block.is_none()
1198 }
1199}
1200
1201impl<'a> BlockChunks<'a> {
1202 fn advance(&mut self) {
1203 self.transforms.next(&());
1204 while let Some(transform) = self.transforms.item() {
1205 if transform
1206 .block
1207 .as_ref()
1208 .map_or(false, |block| block.height() == 0)
1209 {
1210 self.transforms.next(&());
1211 } else {
1212 break;
1213 }
1214 }
1215 }
1216}
1217
1218impl<'a> Iterator for BlockChunks<'a> {
1219 type Item = Chunk<'a>;
1220
1221 fn next(&mut self) -> Option<Self::Item> {
1222 if self.output_row >= self.max_output_row {
1223 return None;
1224 }
1225
1226 let transform = self.transforms.item()?;
1227 if transform.block.is_some() {
1228 let block_start = self.transforms.start().0 .0;
1229 let mut block_end = self.transforms.end(&()).0 .0;
1230 self.advance();
1231 if self.transforms.item().is_none() {
1232 block_end -= 1;
1233 }
1234
1235 let start_in_block = self.output_row - block_start;
1236 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
1237 let line_count = end_in_block - start_in_block;
1238 self.output_row += line_count;
1239
1240 return Some(Chunk {
1241 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
1242 ..Default::default()
1243 });
1244 }
1245
1246 if self.input_chunk.text.is_empty() {
1247 if let Some(input_chunk) = self.input_chunks.next() {
1248 self.input_chunk = input_chunk;
1249 } else {
1250 self.output_row += 1;
1251 if self.output_row < self.max_output_row {
1252 self.advance();
1253 return Some(Chunk {
1254 text: "\n",
1255 ..Default::default()
1256 });
1257 } else {
1258 return None;
1259 }
1260 }
1261 }
1262
1263 let transform_end = self.transforms.end(&()).0 .0;
1264 let (prefix_rows, prefix_bytes) =
1265 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
1266 self.output_row += prefix_rows;
1267 let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
1268 self.input_chunk.text = suffix;
1269 if self.output_row == transform_end {
1270 self.advance();
1271 }
1272
1273 if self.masked {
1274 // Not great for multibyte text because to keep cursor math correct we
1275 // need to have the same number of bytes in the input as output.
1276 let chars = prefix.chars().count();
1277 let bullet_len = chars;
1278 prefix = &BULLETS[..bullet_len];
1279 }
1280
1281 Some(Chunk {
1282 text: prefix,
1283 ..self.input_chunk.clone()
1284 })
1285 }
1286}
1287
1288impl<'a> Iterator for BlockBufferRows<'a> {
1289 type Item = Option<BlockRow>;
1290
1291 fn next(&mut self) -> Option<Self::Item> {
1292 if self.started {
1293 self.output_row.0 += 1;
1294 } else {
1295 self.started = true;
1296 }
1297
1298 if self.output_row.0 >= self.transforms.end(&()).0 .0 {
1299 self.transforms.next(&());
1300 }
1301
1302 while let Some(transform) = self.transforms.item() {
1303 if transform
1304 .block
1305 .as_ref()
1306 .map_or(false, |block| block.height() == 0)
1307 {
1308 self.transforms.next(&());
1309 } else {
1310 break;
1311 }
1312 }
1313
1314 let transform = self.transforms.item()?;
1315 if transform.block.is_some() {
1316 Some(None)
1317 } else {
1318 Some(self.input_buffer_rows.next().unwrap().map(BlockRow))
1319 }
1320 }
1321}
1322
1323impl sum_tree::Item for Transform {
1324 type Summary = TransformSummary;
1325
1326 fn summary(&self) -> Self::Summary {
1327 self.summary.clone()
1328 }
1329}
1330
1331impl sum_tree::Summary for TransformSummary {
1332 type Context = ();
1333
1334 fn add_summary(&mut self, summary: &Self, _: &()) {
1335 self.input_rows += summary.input_rows;
1336 self.output_rows += summary.output_rows;
1337 }
1338}
1339
1340impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
1341 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1342 self.0 += summary.input_rows;
1343 }
1344}
1345
1346impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
1347 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1348 self.0 += summary.output_rows;
1349 }
1350}
1351
1352impl BlockDisposition {
1353 fn is_below(&self) -> bool {
1354 matches!(self, BlockDisposition::Below)
1355 }
1356}
1357
1358impl<'a> Deref for BlockContext<'a, '_> {
1359 type Target = WindowContext<'a>;
1360
1361 fn deref(&self) -> &Self::Target {
1362 self.context
1363 }
1364}
1365
1366impl DerefMut for BlockContext<'_, '_> {
1367 fn deref_mut(&mut self) -> &mut Self::Target {
1368 self.context
1369 }
1370}
1371
1372impl CustomBlock {
1373 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
1374 self.render.lock()(cx)
1375 }
1376
1377 pub fn position(&self) -> &Anchor {
1378 &self.position
1379 }
1380
1381 pub fn style(&self) -> BlockStyle {
1382 self.style
1383 }
1384}
1385
1386impl Debug for CustomBlock {
1387 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1388 f.debug_struct("Block")
1389 .field("id", &self.id)
1390 .field("position", &self.position)
1391 .field("disposition", &self.disposition)
1392 .finish()
1393 }
1394}
1395
1396// Count the number of bytes prior to a target point. If the string doesn't contain the target
1397// point, return its total extent. Otherwise return the target point itself.
1398fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
1399 let mut row = 0;
1400 let mut offset = 0;
1401 for (ix, line) in s.split('\n').enumerate() {
1402 if ix > 0 {
1403 row += 1;
1404 offset += 1;
1405 }
1406 if row >= target {
1407 break;
1408 }
1409 offset += line.len();
1410 }
1411 (row, offset)
1412}
1413
1414#[cfg(test)]
1415mod tests {
1416 use super::*;
1417 use crate::display_map::{
1418 fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
1419 };
1420 use gpui::{div, font, px, AppContext, Context as _, Element};
1421 use language::{Buffer, Capability};
1422 use multi_buffer::MultiBuffer;
1423 use rand::prelude::*;
1424 use settings::SettingsStore;
1425 use std::env;
1426 use util::RandomCharIter;
1427
1428 #[gpui::test]
1429 fn test_offset_for_row() {
1430 assert_eq!(offset_for_row("", 0), (0, 0));
1431 assert_eq!(offset_for_row("", 1), (0, 0));
1432 assert_eq!(offset_for_row("abcd", 0), (0, 0));
1433 assert_eq!(offset_for_row("abcd", 1), (0, 4));
1434 assert_eq!(offset_for_row("\n", 0), (0, 0));
1435 assert_eq!(offset_for_row("\n", 1), (1, 1));
1436 assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
1437 assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
1438 assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
1439 assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
1440 }
1441
1442 #[gpui::test]
1443 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
1444 cx.update(|cx| init_test(cx));
1445
1446 let text = "aaa\nbbb\nccc\nddd";
1447
1448 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1449 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1450 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1451 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1452 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1453 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
1454 let (wrap_map, wraps_snapshot) =
1455 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
1456 let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
1457
1458 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
1459 let block_ids = writer.insert(vec![
1460 BlockProperties {
1461 style: BlockStyle::Fixed,
1462 position: buffer_snapshot.anchor_after(Point::new(1, 0)),
1463 height: 1,
1464 disposition: BlockDisposition::Above,
1465 render: Box::new(|_| div().into_any()),
1466 },
1467 BlockProperties {
1468 style: BlockStyle::Fixed,
1469 position: buffer_snapshot.anchor_after(Point::new(1, 2)),
1470 height: 2,
1471 disposition: BlockDisposition::Above,
1472 render: Box::new(|_| div().into_any()),
1473 },
1474 BlockProperties {
1475 style: BlockStyle::Fixed,
1476 position: buffer_snapshot.anchor_after(Point::new(3, 3)),
1477 height: 3,
1478 disposition: BlockDisposition::Below,
1479 render: Box::new(|_| div().into_any()),
1480 },
1481 ]);
1482
1483 let snapshot = block_map.read(wraps_snapshot, Default::default());
1484 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
1485
1486 let blocks = snapshot
1487 .blocks_in_range(0..8)
1488 .map(|(start_row, block)| {
1489 let block = block.as_custom().unwrap();
1490 (start_row..start_row + block.height, block.id)
1491 })
1492 .collect::<Vec<_>>();
1493
1494 // When multiple blocks are on the same line, the newer blocks appear first.
1495 assert_eq!(
1496 blocks,
1497 &[
1498 (1..2, block_ids[0]),
1499 (2..4, block_ids[1]),
1500 (7..10, block_ids[2]),
1501 ]
1502 );
1503
1504 assert_eq!(
1505 snapshot.to_block_point(WrapPoint::new(0, 3)),
1506 BlockPoint::new(0, 3)
1507 );
1508 assert_eq!(
1509 snapshot.to_block_point(WrapPoint::new(1, 0)),
1510 BlockPoint::new(4, 0)
1511 );
1512 assert_eq!(
1513 snapshot.to_block_point(WrapPoint::new(3, 3)),
1514 BlockPoint::new(6, 3)
1515 );
1516
1517 assert_eq!(
1518 snapshot.to_wrap_point(BlockPoint::new(0, 3)),
1519 WrapPoint::new(0, 3)
1520 );
1521 assert_eq!(
1522 snapshot.to_wrap_point(BlockPoint::new(1, 0)),
1523 WrapPoint::new(1, 0)
1524 );
1525 assert_eq!(
1526 snapshot.to_wrap_point(BlockPoint::new(3, 0)),
1527 WrapPoint::new(1, 0)
1528 );
1529 assert_eq!(
1530 snapshot.to_wrap_point(BlockPoint::new(7, 0)),
1531 WrapPoint::new(3, 3)
1532 );
1533
1534 assert_eq!(
1535 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
1536 BlockPoint::new(0, 3)
1537 );
1538 assert_eq!(
1539 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
1540 BlockPoint::new(4, 0)
1541 );
1542 assert_eq!(
1543 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
1544 BlockPoint::new(0, 3)
1545 );
1546 assert_eq!(
1547 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
1548 BlockPoint::new(4, 0)
1549 );
1550 assert_eq!(
1551 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
1552 BlockPoint::new(4, 0)
1553 );
1554 assert_eq!(
1555 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
1556 BlockPoint::new(4, 0)
1557 );
1558 assert_eq!(
1559 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
1560 BlockPoint::new(6, 3)
1561 );
1562 assert_eq!(
1563 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
1564 BlockPoint::new(6, 3)
1565 );
1566 assert_eq!(
1567 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
1568 BlockPoint::new(6, 3)
1569 );
1570 assert_eq!(
1571 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
1572 BlockPoint::new(6, 3)
1573 );
1574
1575 assert_eq!(
1576 snapshot
1577 .buffer_rows(BlockRow(0))
1578 .map(|row| row.map(|r| r.0))
1579 .collect::<Vec<_>>(),
1580 &[
1581 Some(0),
1582 None,
1583 None,
1584 None,
1585 Some(1),
1586 Some(2),
1587 Some(3),
1588 None,
1589 None,
1590 None
1591 ]
1592 );
1593
1594 // Insert a line break, separating two block decorations into separate lines.
1595 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1596 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
1597 buffer.snapshot(cx)
1598 });
1599
1600 let (inlay_snapshot, inlay_edits) =
1601 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1602 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1603 let (tab_snapshot, tab_edits) =
1604 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
1605 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1606 wrap_map.sync(tab_snapshot, tab_edits, cx)
1607 });
1608 let snapshot = block_map.read(wraps_snapshot, wrap_edits);
1609 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
1610 }
1611
1612 #[gpui::test]
1613 fn test_multibuffer_headers_and_footers(cx: &mut AppContext) {
1614 init_test(cx);
1615
1616 let buffer1 = cx.new_model(|cx| Buffer::local("Buffer 1", cx));
1617 let buffer2 = cx.new_model(|cx| Buffer::local("Buffer 2", cx));
1618 let buffer3 = cx.new_model(|cx| Buffer::local("Buffer 3", cx));
1619
1620 let mut excerpt_ids = Vec::new();
1621 let multi_buffer = cx.new_model(|cx| {
1622 let mut multi_buffer = MultiBuffer::new(0, Capability::ReadWrite);
1623 excerpt_ids.extend(multi_buffer.push_excerpts(
1624 buffer1.clone(),
1625 [ExcerptRange {
1626 context: 0..buffer1.read(cx).len(),
1627 primary: None,
1628 }],
1629 cx,
1630 ));
1631 excerpt_ids.extend(multi_buffer.push_excerpts(
1632 buffer2.clone(),
1633 [ExcerptRange {
1634 context: 0..buffer2.read(cx).len(),
1635 primary: None,
1636 }],
1637 cx,
1638 ));
1639 excerpt_ids.extend(multi_buffer.push_excerpts(
1640 buffer3.clone(),
1641 [ExcerptRange {
1642 context: 0..buffer3.read(cx).len(),
1643 primary: None,
1644 }],
1645 cx,
1646 ));
1647
1648 multi_buffer
1649 });
1650
1651 let font = font("Helvetica");
1652 let font_size = px(14.);
1653 let font_id = cx.text_system().resolve_font(&font);
1654 let mut wrap_width = px(0.);
1655 for c in "Buff".chars() {
1656 wrap_width += cx
1657 .text_system()
1658 .advance(font_id, font_size, c)
1659 .unwrap()
1660 .width;
1661 }
1662
1663 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
1664 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone());
1665 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
1666 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
1667 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
1668
1669 let block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
1670 let snapshot = block_map.read(wraps_snapshot, Default::default());
1671
1672 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
1673 assert_eq!(
1674 snapshot.text(),
1675 "\nBuff\ner 1\n\n\nBuff\ner 2\n\n\nBuff\ner 3\n"
1676 );
1677
1678 let blocks: Vec<_> = snapshot
1679 .blocks_in_range(0..u32::MAX)
1680 .map(|(row, block)| (row, block.id()))
1681 .collect();
1682 assert_eq!(
1683 blocks,
1684 vec![
1685 (0, BlockId::ExcerptHeader(excerpt_ids[0])),
1686 (3, BlockId::ExcerptFooter(excerpt_ids[0])),
1687 (4, BlockId::ExcerptHeader(excerpt_ids[1])),
1688 (7, BlockId::ExcerptFooter(excerpt_ids[1])),
1689 (8, BlockId::ExcerptHeader(excerpt_ids[2])),
1690 (11, BlockId::ExcerptFooter(excerpt_ids[2]))
1691 ]
1692 );
1693 }
1694
1695 #[gpui::test]
1696 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
1697 let _update = cx.update(|cx| init_test(cx));
1698
1699 let text = "aaa\nbbb\nccc\nddd";
1700
1701 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1702 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1703 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1704 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1705 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1706 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
1707 let (_wrap_map, wraps_snapshot) =
1708 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
1709 let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
1710
1711 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
1712 let block_ids = writer.insert(vec![
1713 BlockProperties {
1714 style: BlockStyle::Fixed,
1715 position: buffer_snapshot.anchor_after(Point::new(1, 0)),
1716 height: 1,
1717 disposition: BlockDisposition::Above,
1718 render: Box::new(|_| div().into_any()),
1719 },
1720 BlockProperties {
1721 style: BlockStyle::Fixed,
1722 position: buffer_snapshot.anchor_after(Point::new(1, 2)),
1723 height: 2,
1724 disposition: BlockDisposition::Above,
1725 render: Box::new(|_| div().into_any()),
1726 },
1727 BlockProperties {
1728 style: BlockStyle::Fixed,
1729 position: buffer_snapshot.anchor_after(Point::new(3, 3)),
1730 height: 3,
1731 disposition: BlockDisposition::Below,
1732 render: Box::new(|_| div().into_any()),
1733 },
1734 ]);
1735
1736 {
1737 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1738 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
1739
1740 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1741
1742 let mut new_heights = HashMap::default();
1743 new_heights.insert(block_ids[0], 2);
1744 block_map_writer.resize(new_heights);
1745 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1746 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
1747 }
1748
1749 {
1750 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1751
1752 let mut new_heights = HashMap::default();
1753 new_heights.insert(block_ids[0], 1);
1754 block_map_writer.resize(new_heights);
1755
1756 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1757 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
1758 }
1759
1760 {
1761 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1762
1763 let mut new_heights = HashMap::default();
1764 new_heights.insert(block_ids[0], 0);
1765 block_map_writer.resize(new_heights);
1766
1767 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1768 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
1769 }
1770
1771 {
1772 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1773
1774 let mut new_heights = HashMap::default();
1775 new_heights.insert(block_ids[0], 3);
1776 block_map_writer.resize(new_heights);
1777
1778 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1779 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
1780 }
1781
1782 {
1783 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1784
1785 let mut new_heights = HashMap::default();
1786 new_heights.insert(block_ids[0], 3);
1787 block_map_writer.resize(new_heights);
1788
1789 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1790 // Same height as before, should remain the same
1791 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
1792 }
1793 }
1794
1795 #[cfg(target_os = "macos")]
1796 #[gpui::test]
1797 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
1798 cx.update(|cx| init_test(cx));
1799
1800 let _font_id = cx.text_system().font_id(&font("Helvetica")).unwrap();
1801
1802 let text = "one two three\nfour five six\nseven eight";
1803
1804 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1805 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1806 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1807 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
1808 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
1809 let (_, wraps_snapshot) = cx.update(|cx| {
1810 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx)
1811 });
1812 let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 0);
1813
1814 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
1815 writer.insert(vec![
1816 BlockProperties {
1817 style: BlockStyle::Fixed,
1818 position: buffer_snapshot.anchor_after(Point::new(1, 12)),
1819 disposition: BlockDisposition::Above,
1820 render: Box::new(|_| div().into_any()),
1821 height: 1,
1822 },
1823 BlockProperties {
1824 style: BlockStyle::Fixed,
1825 position: buffer_snapshot.anchor_after(Point::new(1, 1)),
1826 disposition: BlockDisposition::Below,
1827 render: Box::new(|_| div().into_any()),
1828 height: 1,
1829 },
1830 ]);
1831
1832 // Blocks with an 'above' disposition go above their corresponding buffer line.
1833 // Blocks with a 'below' disposition go below their corresponding buffer line.
1834 let snapshot = block_map.read(wraps_snapshot, Default::default());
1835 assert_eq!(
1836 snapshot.text(),
1837 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
1838 );
1839 }
1840
1841 #[gpui::test(iterations = 100)]
1842 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
1843 cx.update(|cx| init_test(cx));
1844
1845 let operations = env::var("OPERATIONS")
1846 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1847 .unwrap_or(10);
1848
1849 let wrap_width = if rng.gen_bool(0.2) {
1850 None
1851 } else {
1852 Some(px(rng.gen_range(0.0..=100.0)))
1853 };
1854 let tab_size = 1.try_into().unwrap();
1855 let font_size = px(14.0);
1856 let buffer_start_header_height = rng.gen_range(1..=5);
1857 let excerpt_header_height = rng.gen_range(1..=5);
1858 let excerpt_footer_height = rng.gen_range(1..=5);
1859
1860 log::info!("Wrap width: {:?}", wrap_width);
1861 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
1862 log::info!("Excerpt Footer Height: {:?}", excerpt_footer_height);
1863
1864 let buffer = if rng.gen() {
1865 let len = rng.gen_range(0..10);
1866 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
1867 log::info!("initial buffer text: {:?}", text);
1868 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
1869 } else {
1870 cx.update(|cx| MultiBuffer::build_random(&mut rng, cx))
1871 };
1872
1873 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1874 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1875 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1876 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
1877 let (wrap_map, wraps_snapshot) = cx
1878 .update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), font_size, wrap_width, cx));
1879 let mut block_map = BlockMap::new(
1880 wraps_snapshot,
1881 true,
1882 buffer_start_header_height,
1883 excerpt_header_height,
1884 excerpt_footer_height,
1885 );
1886 let mut custom_blocks = Vec::new();
1887
1888 for _ in 0..operations {
1889 let mut buffer_edits = Vec::new();
1890 match rng.gen_range(0..=100) {
1891 0..=19 => {
1892 let wrap_width = if rng.gen_bool(0.2) {
1893 None
1894 } else {
1895 Some(px(rng.gen_range(0.0..=100.0)))
1896 };
1897 log::info!("Setting wrap width to {:?}", wrap_width);
1898 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1899 }
1900 20..=39 => {
1901 let block_count = rng.gen_range(1..=5);
1902 let block_properties = (0..block_count)
1903 .map(|_| {
1904 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
1905 let position = buffer.anchor_after(
1906 buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left),
1907 );
1908
1909 let disposition = if rng.gen() {
1910 BlockDisposition::Above
1911 } else {
1912 BlockDisposition::Below
1913 };
1914 let height = rng.gen_range(0..5);
1915 log::info!(
1916 "inserting block {:?} {:?} with height {}",
1917 disposition,
1918 position.to_point(&buffer),
1919 height
1920 );
1921 BlockProperties {
1922 style: BlockStyle::Fixed,
1923 position,
1924 height,
1925 disposition,
1926 render: Box::new(|_| div().into_any()),
1927 }
1928 })
1929 .collect::<Vec<_>>();
1930
1931 let (inlay_snapshot, inlay_edits) =
1932 inlay_map.sync(buffer_snapshot.clone(), vec![]);
1933 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1934 let (tab_snapshot, tab_edits) =
1935 tab_map.sync(fold_snapshot, fold_edits, tab_size);
1936 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1937 wrap_map.sync(tab_snapshot, tab_edits, cx)
1938 });
1939 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
1940 let block_ids =
1941 block_map.insert(block_properties.iter().map(|props| BlockProperties {
1942 position: props.position,
1943 height: props.height,
1944 style: props.style,
1945 render: Box::new(|_| div().into_any()),
1946 disposition: props.disposition,
1947 }));
1948 for (block_id, props) in block_ids.into_iter().zip(block_properties) {
1949 custom_blocks.push((block_id, props));
1950 }
1951 }
1952 40..=59 if !custom_blocks.is_empty() => {
1953 let block_count = rng.gen_range(1..=4.min(custom_blocks.len()));
1954 let block_ids_to_remove = (0..block_count)
1955 .map(|_| {
1956 custom_blocks
1957 .remove(rng.gen_range(0..custom_blocks.len()))
1958 .0
1959 })
1960 .collect();
1961
1962 let (inlay_snapshot, inlay_edits) =
1963 inlay_map.sync(buffer_snapshot.clone(), vec![]);
1964 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1965 let (tab_snapshot, tab_edits) =
1966 tab_map.sync(fold_snapshot, fold_edits, tab_size);
1967 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1968 wrap_map.sync(tab_snapshot, tab_edits, cx)
1969 });
1970 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
1971 block_map.remove(block_ids_to_remove);
1972 }
1973 _ => {
1974 buffer.update(cx, |buffer, cx| {
1975 let mutation_count = rng.gen_range(1..=5);
1976 let subscription = buffer.subscribe();
1977 buffer.randomly_mutate(&mut rng, mutation_count, cx);
1978 buffer_snapshot = buffer.snapshot(cx);
1979 buffer_edits.extend(subscription.consume());
1980 log::info!("buffer text: {:?}", buffer_snapshot.text());
1981 });
1982 }
1983 }
1984
1985 let (inlay_snapshot, inlay_edits) =
1986 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1987 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1988 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
1989 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1990 wrap_map.sync(tab_snapshot, tab_edits, cx)
1991 });
1992 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
1993 assert_eq!(
1994 blocks_snapshot.transforms.summary().input_rows,
1995 wraps_snapshot.max_point().row() + 1
1996 );
1997 log::info!("blocks text: {:?}", blocks_snapshot.text());
1998
1999 let mut expected_blocks = Vec::new();
2000 expected_blocks.extend(custom_blocks.iter().map(|(id, block)| {
2001 let mut position = block.position.to_point(&buffer_snapshot);
2002 match block.disposition {
2003 BlockDisposition::Above => {
2004 position.column = 0;
2005 }
2006 BlockDisposition::Below => {
2007 position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
2008 }
2009 };
2010 let row = wraps_snapshot.make_wrap_point(position, Bias::Left).row();
2011 (
2012 row,
2013 ExpectedBlock::Custom {
2014 disposition: block.disposition,
2015 id: *id,
2016 height: block.height,
2017 },
2018 )
2019 }));
2020
2021 // Note that this needs to be synced with the related section in BlockMap::sync
2022 expected_blocks.extend(
2023 BlockMap::header_and_footer_blocks(
2024 true,
2025 excerpt_footer_height,
2026 buffer_start_header_height,
2027 excerpt_header_height,
2028 &buffer_snapshot,
2029 0..,
2030 &wraps_snapshot,
2031 )
2032 .map(|(row, block)| (row, block.into())),
2033 );
2034
2035 BlockMap::sort_blocks(&mut expected_blocks);
2036
2037 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
2038
2039 let input_buffer_rows = buffer_snapshot
2040 .buffer_rows(MultiBufferRow(0))
2041 .collect::<Vec<_>>();
2042 let mut expected_buffer_rows = Vec::new();
2043 let mut expected_text = String::new();
2044 let mut expected_block_positions = Vec::new();
2045 let input_text = wraps_snapshot.text();
2046 for (row, input_line) in input_text.split('\n').enumerate() {
2047 let row = row as u32;
2048 if row > 0 {
2049 expected_text.push('\n');
2050 }
2051
2052 let buffer_row = input_buffer_rows[wraps_snapshot
2053 .to_point(WrapPoint::new(row, 0), Bias::Left)
2054 .row as usize];
2055
2056 while let Some((block_row, block)) = sorted_blocks_iter.peek() {
2057 if *block_row == row && block.disposition() == BlockDisposition::Above {
2058 let (_, block) = sorted_blocks_iter.next().unwrap();
2059 let height = block.height() as usize;
2060 expected_block_positions
2061 .push((expected_text.matches('\n').count() as u32, block));
2062 let text = "\n".repeat(height);
2063 expected_text.push_str(&text);
2064 for _ in 0..height {
2065 expected_buffer_rows.push(None);
2066 }
2067 } else {
2068 break;
2069 }
2070 }
2071
2072 let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
2073 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
2074 expected_text.push_str(input_line);
2075
2076 while let Some((block_row, block)) = sorted_blocks_iter.peek() {
2077 if *block_row == row && block.disposition() == BlockDisposition::Below {
2078 let (_, block) = sorted_blocks_iter.next().unwrap();
2079 let height = block.height() as usize;
2080 expected_block_positions
2081 .push((expected_text.matches('\n').count() as u32 + 1, block));
2082 let text = "\n".repeat(height);
2083 expected_text.push_str(&text);
2084 for _ in 0..height {
2085 expected_buffer_rows.push(None);
2086 }
2087 } else {
2088 break;
2089 }
2090 }
2091 }
2092
2093 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
2094 let expected_row_count = expected_lines.len();
2095 for start_row in 0..expected_row_count {
2096 let expected_text = expected_lines[start_row..].join("\n");
2097 let actual_text = blocks_snapshot
2098 .chunks(
2099 start_row as u32..blocks_snapshot.max_point().row + 1,
2100 false,
2101 false,
2102 Highlights::default(),
2103 )
2104 .map(|chunk| chunk.text)
2105 .collect::<String>();
2106 assert_eq!(
2107 actual_text, expected_text,
2108 "incorrect text starting from row {}",
2109 start_row
2110 );
2111 assert_eq!(
2112 blocks_snapshot
2113 .buffer_rows(BlockRow(start_row as u32))
2114 .map(|row| row.map(|r| r.0))
2115 .collect::<Vec<_>>(),
2116 &expected_buffer_rows[start_row..]
2117 );
2118 }
2119
2120 assert_eq!(
2121 blocks_snapshot
2122 .blocks_in_range(0..(expected_row_count as u32))
2123 .map(|(row, block)| (row, block.clone().into()))
2124 .collect::<Vec<_>>(),
2125 expected_block_positions,
2126 "invalid blocks_in_range({:?})",
2127 0..expected_row_count
2128 );
2129
2130 for (_, expected_block) in
2131 blocks_snapshot.blocks_in_range(0..(expected_row_count as u32))
2132 {
2133 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
2134 assert_eq!(
2135 actual_block.map(|block| block.id()),
2136 Some(expected_block.id())
2137 );
2138 }
2139
2140 for (block_row, block) in expected_block_positions {
2141 if let BlockType::Custom(block_id) = block.block_type() {
2142 assert_eq!(
2143 blocks_snapshot.row_for_block(block_id),
2144 Some(BlockRow(block_row))
2145 );
2146 }
2147 }
2148
2149 let mut expected_longest_rows = Vec::new();
2150 let mut longest_line_len = -1_isize;
2151 for (row, line) in expected_lines.iter().enumerate() {
2152 let row = row as u32;
2153
2154 assert_eq!(
2155 blocks_snapshot.line_len(BlockRow(row)),
2156 line.len() as u32,
2157 "invalid line len for row {}",
2158 row
2159 );
2160
2161 let line_char_count = line.chars().count() as isize;
2162 match line_char_count.cmp(&longest_line_len) {
2163 Ordering::Less => {}
2164 Ordering::Equal => expected_longest_rows.push(row),
2165 Ordering::Greater => {
2166 longest_line_len = line_char_count;
2167 expected_longest_rows.clear();
2168 expected_longest_rows.push(row);
2169 }
2170 }
2171 }
2172
2173 let longest_row = blocks_snapshot.longest_row();
2174 assert!(
2175 expected_longest_rows.contains(&longest_row),
2176 "incorrect longest row {}. expected {:?} with length {}",
2177 longest_row,
2178 expected_longest_rows,
2179 longest_line_len,
2180 );
2181
2182 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
2183 let wrap_point = WrapPoint::new(row, 0);
2184 let block_point = blocks_snapshot.to_block_point(wrap_point);
2185 assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point);
2186 }
2187
2188 let mut block_point = BlockPoint::new(0, 0);
2189 for c in expected_text.chars() {
2190 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
2191 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
2192 assert_eq!(
2193 blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)),
2194 left_point
2195 );
2196 assert_eq!(
2197 left_buffer_point,
2198 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
2199 "{:?} is not valid in buffer coordinates",
2200 left_point
2201 );
2202
2203 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
2204 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
2205 assert_eq!(
2206 blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)),
2207 right_point
2208 );
2209 assert_eq!(
2210 right_buffer_point,
2211 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
2212 "{:?} is not valid in buffer coordinates",
2213 right_point
2214 );
2215
2216 if c == '\n' {
2217 block_point.0 += Point::new(1, 0);
2218 } else {
2219 block_point.column += c.len_utf8() as u32;
2220 }
2221 }
2222 }
2223
2224 #[derive(Debug, Eq, PartialEq)]
2225 enum ExpectedBlock {
2226 ExcerptHeader {
2227 height: u32,
2228 starts_new_buffer: bool,
2229 },
2230 ExcerptFooter {
2231 height: u32,
2232 disposition: BlockDisposition,
2233 },
2234 Custom {
2235 disposition: BlockDisposition,
2236 id: CustomBlockId,
2237 height: u32,
2238 },
2239 }
2240
2241 impl BlockLike for ExpectedBlock {
2242 fn block_type(&self) -> BlockType {
2243 match self {
2244 ExpectedBlock::Custom { id, .. } => BlockType::Custom(*id),
2245 ExpectedBlock::ExcerptHeader { .. } => BlockType::Header,
2246 ExpectedBlock::ExcerptFooter { .. } => BlockType::Footer,
2247 }
2248 }
2249
2250 fn disposition(&self) -> BlockDisposition {
2251 self.disposition()
2252 }
2253 }
2254
2255 impl ExpectedBlock {
2256 fn height(&self) -> u32 {
2257 match self {
2258 ExpectedBlock::ExcerptHeader { height, .. } => *height,
2259 ExpectedBlock::Custom { height, .. } => *height,
2260 ExpectedBlock::ExcerptFooter { height, .. } => *height,
2261 }
2262 }
2263
2264 fn disposition(&self) -> BlockDisposition {
2265 match self {
2266 ExpectedBlock::ExcerptHeader { .. } => BlockDisposition::Above,
2267 ExpectedBlock::Custom { disposition, .. } => *disposition,
2268 ExpectedBlock::ExcerptFooter { disposition, .. } => *disposition,
2269 }
2270 }
2271 }
2272
2273 impl From<Block> for ExpectedBlock {
2274 fn from(block: Block) -> Self {
2275 match block {
2276 Block::Custom(block) => ExpectedBlock::Custom {
2277 id: block.id,
2278 disposition: block.disposition,
2279 height: block.height,
2280 },
2281 Block::ExcerptHeader {
2282 height,
2283 starts_new_buffer,
2284 ..
2285 } => ExpectedBlock::ExcerptHeader {
2286 height,
2287 starts_new_buffer,
2288 },
2289 Block::ExcerptFooter {
2290 height,
2291 disposition,
2292 ..
2293 } => ExpectedBlock::ExcerptFooter {
2294 height,
2295 disposition,
2296 },
2297 }
2298 }
2299 }
2300 }
2301
2302 fn init_test(cx: &mut gpui::AppContext) {
2303 let settings = SettingsStore::test(cx);
2304 cx.set_global(settings);
2305 theme::init(theme::LoadThemes::JustBase, cx);
2306 assets::Assets.load_test_fonts(cx);
2307 }
2308
2309 impl Block {
2310 fn as_custom(&self) -> Option<&CustomBlock> {
2311 match self {
2312 Block::Custom(block) => Some(block),
2313 Block::ExcerptHeader { .. } => None,
2314 Block::ExcerptFooter { .. } => None,
2315 }
2316 }
2317 }
2318
2319 impl BlockSnapshot {
2320 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
2321 self.wrap_snapshot.to_point(self.to_wrap_point(point), bias)
2322 }
2323 }
2324}