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