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