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