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