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