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