From ee693a8d2b7d55e2454eedffc256dee50a9c7ab4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Dec 2021 17:52:34 -0700 Subject: [PATCH] Get all tests passing with new blocks API Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map/block_map.rs | 754 ++++++++++----------- 1 file changed, 371 insertions(+), 383 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 9b973d2457f5bedf47f05599350636bc23b14b54..5aa88a88c4af182242e57f2fd5b9834911db9ab8 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -704,12 +704,17 @@ impl<'a> Iterator for Chunks<'a> { let transform = self.transforms.item()?; if transform.block.is_some() { let block_start = self.transforms.start().0 .0; - let block_end = self.transforms.end(&()).0 .0; + let mut block_end = self.transforms.end(&()).0 .0; + self.transforms.next(&()); + if self.transforms.item().is_none() { + block_end -= 1; + } + let start_in_block = self.output_row - block_start; let end_in_block = cmp::min(self.max_output_row, block_end) - block_start; let line_count = end_in_block - start_in_block; self.output_row += line_count; - self.transforms.next(&()); + return Some(Chunk { text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) }, highlight_style: None, @@ -904,7 +909,7 @@ mod tests { let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone()); let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx); - let block_ids = writer.insert( + writer.insert( vec![ BlockProperties { position: Point::new(1, 0), @@ -929,7 +934,7 @@ mod tests { ); let mut snapshot = block_map.read(wraps_snapshot, vec![], cx); - assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n\n"); + assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n"); let blocks = snapshot .blocks_in_range(0..8) @@ -960,11 +965,11 @@ mod tests { ); assert_eq!( snapshot.to_block_point(WrapPoint::new(1, 0)), - BlockPoint::new(3, 0) + BlockPoint::new(4, 0) ); assert_eq!( snapshot.to_block_point(WrapPoint::new(3, 3)), - BlockPoint::new(5, 3) + BlockPoint::new(6, 3) ); assert_eq!( @@ -980,7 +985,7 @@ mod tests { WrapPoint::new(1, 0) ); assert_eq!( - snapshot.to_wrap_point(BlockPoint::new(6, 0)), + snapshot.to_wrap_point(BlockPoint::new(7, 0)), WrapPoint::new(3, 3) ); @@ -990,7 +995,7 @@ mod tests { ); assert_eq!( snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right), - BlockPoint::new(3, 0) + BlockPoint::new(4, 0) ); assert_eq!( snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left), @@ -998,36 +1003,47 @@ mod tests { ); assert_eq!( snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right), - BlockPoint::new(3, 0) + BlockPoint::new(4, 0) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(3, 0), Bias::Left), - BlockPoint::new(3, 0) + snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left), + BlockPoint::new(4, 0) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(3, 0), Bias::Right), - BlockPoint::new(3, 0) + snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right), + BlockPoint::new(4, 0) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(5, 3), Bias::Left), - BlockPoint::new(5, 3) + snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left), + BlockPoint::new(6, 3) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(5, 3), Bias::Right), - BlockPoint::new(5, 3) + snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right), + BlockPoint::new(6, 3) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(6, 0), Bias::Left), - BlockPoint::new(5, 3) + snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left), + BlockPoint::new(6, 3) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(6, 0), Bias::Right), - BlockPoint::new(5, 3) + snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right), + BlockPoint::new(6, 3) ); assert_eq!( snapshot.buffer_rows(0).collect::>(), - &[Some(0), None, None, Some(1), Some(2), Some(3), None] + &[ + Some(0), + None, + None, + None, + Some(1), + Some(2), + Some(3), + None, + None, + None + ] ); // Insert a line break, separating two block decorations into separate @@ -1047,365 +1063,337 @@ mod tests { assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n"); } - // #[gpui::test] - // fn test_blocks_on_wrapped_lines(cx: &mut gpui::MutableAppContext) { - // let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); - // let font_id = cx - // .font_cache() - // .select_font(family_id, &Default::default()) - // .unwrap(); - - // let text = "one two three\nfour five six\nseven eight"; - - // let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); - // let (_, folds_snapshot) = FoldMap::new(buffer.read(cx).snapshot()); - // let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1); - // let (_, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, Some(60.), cx); - // let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone()); - - // let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx); - // writer.insert( - // vec![ - // BlockProperties { - // position: Point::new(1, 12), - // text: "BLOCK 2", - // disposition: BlockDisposition::Below, - // build_runs: None, - // build_style: None, - // }, - // ], - // cx, - // ); - - // // Blocks with an 'above' disposition go above their corresponding buffer line. - // // Blocks with a 'below' disposition go below their corresponding buffer line. - // let mut snapshot = block_map.read(wraps_snapshot, vec![], cx); - // assert_eq!( - // snapshot.text(), - // "one two \nthree\n BLOCK 2\nseven \neight" - // ); - // } - - // #[gpui::test(iterations = 100)] - // fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { - // let operations = env::var("OPERATIONS") - // .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - // .unwrap_or(10); - - // let wrap_width = if rng.gen_bool(0.2) { - // None - // } else { - // Some(rng.gen_range(0.0..=100.0)) - // }; - // let tab_size = 1; - // let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); - // let font_id = cx - // .font_cache() - // .select_font(family_id, &Default::default()) - // .unwrap(); - // let font_size = 14.0; - - // log::info!("Wrap width: {:?}", wrap_width); - - // let buffer = cx.add_model(|cx| { - // let len = rng.gen_range(0..10); - // let text = RandomCharIter::new(&mut rng).take(len).collect::(); - // log::info!("initial buffer text: {:?}", text); - // Buffer::new(0, text, cx) - // }); - // let mut buffer_snapshot = buffer.read(cx).snapshot(); - // let (fold_map, folds_snapshot) = FoldMap::new(buffer_snapshot.clone()); - // let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); - // let (wrap_map, wraps_snapshot) = - // WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx); - // let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot); - // let mut expected_blocks = Vec::new(); - - // for _ in 0..operations { - // let mut buffer_edits = Vec::new(); - // match rng.gen_range(0..=100) { - // 0..=19 => { - // let wrap_width = if rng.gen_bool(0.2) { - // None - // } else { - // Some(rng.gen_range(0.0..=100.0)) - // }; - // log::info!("Setting wrap width to {:?}", wrap_width); - // wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); - // } - // 20..=39 => { - // let block_count = rng.gen_range(1..=1); - // let block_properties = (0..block_count) - // .map(|_| { - // let buffer = buffer.read(cx); - // let position = buffer.anchor_after( - // buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left), - // ); - - // let len = rng.gen_range(0..10); - // let mut text = Rope::from( - // RandomCharIter::new(&mut rng) - // .take(len) - // .collect::() - // .to_uppercase() - // .as_str(), - // ); - // let disposition = if rng.gen() { - // text.push_front("<"); - // BlockDisposition::Above - // } else { - // text.push_front(">"); - // BlockDisposition::Below - // }; - // log::info!( - // "inserting block {:?} {:?} with text {:?}", - // disposition, - // position.to_point(buffer), - // text.to_string() - // ); - // BlockProperties { - // position, - // text, - // disposition, - // build_runs: None, - // build_style: None, - // } - // }) - // .collect::>(); - - // let (folds_snapshot, fold_edits) = - // fold_map.read(buffer_snapshot.clone(), vec![]); - // let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); - // let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { - // wrap_map.sync(tabs_snapshot, tab_edits, cx) - // }); - // let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); - // let block_ids = block_map.insert(block_properties.clone(), cx); - // for (block_id, props) in block_ids.into_iter().zip(block_properties) { - // expected_blocks.push((block_id, props)); - // } - // } - // 40..=59 if !expected_blocks.is_empty() => { - // let block_count = rng.gen_range(1..=4.min(expected_blocks.len())); - // let block_ids_to_remove = (0..block_count) - // .map(|_| { - // expected_blocks - // .remove(rng.gen_range(0..expected_blocks.len())) - // .0 - // }) - // .collect(); - - // let (folds_snapshot, fold_edits) = - // fold_map.read(buffer_snapshot.clone(), vec![]); - // let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); - // let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { - // wrap_map.sync(tabs_snapshot, tab_edits, cx) - // }); - // let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); - // block_map.remove(block_ids_to_remove, cx); - // } - // _ => { - // buffer.update(cx, |buffer, cx| { - // let v0 = buffer.version(); - // let edit_count = rng.gen_range(1..=5); - // buffer.randomly_edit(&mut rng, edit_count, cx); - // log::info!("buffer text: {:?}", buffer.text()); - // buffer_edits.extend(buffer.edits_since(&v0)); - // buffer_snapshot = buffer.snapshot(); - // }); - // } - // } - - // let (folds_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); - // let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); - // let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { - // wrap_map.sync(tabs_snapshot, tab_edits, cx) - // }); - // let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, cx); - // assert_eq!( - // blocks_snapshot.transforms.summary().input_rows, - // wraps_snapshot.max_point().row() + 1 - // ); - // log::info!("blocks text: {:?}", blocks_snapshot.text()); - - // let buffer = buffer.read(cx); - // let mut sorted_blocks = expected_blocks - // .iter() - // .cloned() - // .map(|(id, block)| { - // let mut position = block.position.to_point(buffer); - // let column = wraps_snapshot.from_point(position, Bias::Left).column(); - // match block.disposition { - // BlockDisposition::Above => { - // position.column = 0; - // } - // BlockDisposition::Below => { - // position.column = buffer.line_len(position.row); - // } - // }; - // let row = wraps_snapshot.from_point(position, Bias::Left).row(); - // ( - // id, - // BlockProperties { - // position: BlockPoint::new(row, column), - // text: block.text, - // build_runs: block.build_runs.clone(), - // build_style: None, - // disposition: block.disposition, - // }, - // ) - // }) - // .collect::>(); - // sorted_blocks - // .sort_unstable_by_key(|(id, block)| (block.position.row, block.disposition, *id)); - // let mut sorted_blocks = sorted_blocks.into_iter().peekable(); - - // let mut expected_buffer_rows = Vec::new(); - // let mut expected_text = String::new(); - // let input_text = wraps_snapshot.text(); - // for (row, input_line) in input_text.split('\n').enumerate() { - // let row = row as u32; - // if row > 0 { - // expected_text.push('\n'); - // } - - // let buffer_row = wraps_snapshot - // .to_point(WrapPoint::new(row, 0), Bias::Left) - // .row; - - // while let Some((block_id, block)) = sorted_blocks.peek() { - // if block.position.row == row && block.disposition == BlockDisposition::Above { - // let text = block.text.to_string(); - // let padding = " ".repeat(block.position.column as usize); - // for line in text.split('\n') { - // if !line.is_empty() { - // expected_text.push_str(&padding); - // expected_text.push_str(line); - // } - // expected_text.push('\n'); - // expected_buffer_rows.push(DisplayRow::Block(*block_id, None)); - // } - // sorted_blocks.next(); - // } else { - // break; - // } - // } - - // let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0; - // expected_buffer_rows.push(if soft_wrapped { - // DisplayRow::Wrap - // } else { - // DisplayRow::Buffer(buffer_row) - // }); - // expected_text.push_str(input_line); - - // while let Some((block_id, block)) = sorted_blocks.peek() { - // if block.position.row == row && block.disposition == BlockDisposition::Below { - // let text = block.text.to_string(); - // let padding = " ".repeat(block.position.column as usize); - // for line in text.split('\n') { - // expected_text.push('\n'); - // if !line.is_empty() { - // expected_text.push_str(&padding); - // expected_text.push_str(line); - // } - // expected_buffer_rows.push(DisplayRow::Block(*block_id, None)); - // } - // sorted_blocks.next(); - // } else { - // break; - // } - // } - // } - - // let expected_lines = expected_text.split('\n').collect::>(); - // let expected_row_count = expected_lines.len(); - // for start_row in 0..expected_row_count { - // let expected_text = expected_lines[start_row..].join("\n"); - // let actual_text = blocks_snapshot - // .chunks(start_row as u32..expected_row_count as u32, None, None) - // .map(|chunk| chunk.text) - // .collect::(); - // assert_eq!( - // actual_text, expected_text, - // "incorrect text starting from row {}", - // start_row - // ); - // assert_eq!( - // blocks_snapshot - // .buffer_rows(start_row as u32, None) - // .collect::>(), - // &expected_buffer_rows[start_row..] - // ); - // } - - // let mut expected_longest_rows = Vec::new(); - // let mut longest_line_len = -1_isize; - // for (row, line) in expected_lines.iter().enumerate() { - // let row = row as u32; - - // assert_eq!( - // blocks_snapshot.line_len(row), - // line.len() as u32, - // "invalid line len for row {}", - // row - // ); - - // let line_char_count = line.chars().count() as isize; - // match line_char_count.cmp(&longest_line_len) { - // Ordering::Less => {} - // Ordering::Equal => expected_longest_rows.push(row), - // Ordering::Greater => { - // longest_line_len = line_char_count; - // expected_longest_rows.clear(); - // expected_longest_rows.push(row); - // } - // } - // } - - // log::info!("getting longest row >>>>>>>>>>>>>>>>>>>>>>>>"); - // let longest_row = blocks_snapshot.longest_row(); - // assert!( - // expected_longest_rows.contains(&longest_row), - // "incorrect longest row {}. expected {:?} with length {}", - // longest_row, - // expected_longest_rows, - // longest_line_len, - // ); - - // for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { - // let wrap_point = WrapPoint::new(row, 0); - // let block_point = blocks_snapshot.to_block_point(wrap_point); - // assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point); - // } - - // let mut block_point = BlockPoint::new(0, 0); - // for c in expected_text.chars() { - // let left_point = blocks_snapshot.clip_point(block_point, Bias::Left); - // let right_point = blocks_snapshot.clip_point(block_point, Bias::Right); - - // assert_eq!( - // blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)), - // left_point - // ); - // assert_eq!( - // blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)), - // right_point - // ); - - // if c == '\n' { - // block_point.0 += Point::new(1, 0); - // } else { - // block_point.column += c.len_utf8() as u32; - // } - // } - // } - // } + #[gpui::test] + fn test_blocks_on_wrapped_lines(cx: &mut gpui::MutableAppContext) { + let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); + let font_id = cx + .font_cache() + .select_font(family_id, &Default::default()) + .unwrap(); + + let text = "one two three\nfour five six\nseven eight"; + + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); + let (_, folds_snapshot) = FoldMap::new(buffer.read(cx).snapshot()); + let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1); + let (_, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, Some(60.), cx); + let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone()); + + let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx); + writer.insert( + vec![ + BlockProperties { + position: Point::new(1, 12), + disposition: BlockDisposition::Above, + render: Arc::new(|_| Empty::new().named("block 1")), + height: 1, + }, + BlockProperties { + position: Point::new(1, 1), + disposition: BlockDisposition::Below, + render: Arc::new(|_| Empty::new().named("block 2")), + height: 1, + }, + ], + cx, + ); + + // Blocks with an 'above' disposition go above their corresponding buffer line. + // Blocks with a 'below' disposition go below their corresponding buffer line. + let mut snapshot = block_map.read(wraps_snapshot, vec![], cx); + assert_eq!( + snapshot.text(), + "one two \nthree\n\nfour five \nsix\n\nseven \neight" + ); + } + + #[gpui::test(iterations = 100)] + fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let wrap_width = if rng.gen_bool(0.2) { + None + } else { + Some(rng.gen_range(0.0..=100.0)) + }; + let tab_size = 1; + let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); + let font_id = cx + .font_cache() + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 14.0; + + log::info!("Wrap width: {:?}", wrap_width); + + let buffer = cx.add_model(|cx| { + let len = rng.gen_range(0..10); + let text = RandomCharIter::new(&mut rng).take(len).collect::(); + log::info!("initial buffer text: {:?}", text); + Buffer::new(0, text, cx) + }); + let mut buffer_snapshot = buffer.read(cx).snapshot(); + let (fold_map, folds_snapshot) = FoldMap::new(buffer_snapshot.clone()); + let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); + let (wrap_map, wraps_snapshot) = + WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx); + let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot); + let mut expected_blocks = Vec::new(); + + for _ in 0..operations { + let mut buffer_edits = Vec::new(); + match rng.gen_range(0..=100) { + 0..=19 => { + let wrap_width = if rng.gen_bool(0.2) { + None + } else { + Some(rng.gen_range(0.0..=100.0)) + }; + log::info!("Setting wrap width to {:?}", wrap_width); + wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); + } + 20..=39 => { + let block_count = rng.gen_range(1..=1); + let block_properties = (0..block_count) + .map(|_| { + let buffer = buffer.read(cx); + let position = buffer.anchor_after( + buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left), + ); + + let disposition = if rng.gen() { + BlockDisposition::Above + } else { + BlockDisposition::Below + }; + let height = rng.gen_range(1..5); + log::info!( + "inserting block {:?} {:?} with height {}", + disposition, + position.to_point(buffer), + height + ); + BlockProperties { + position, + height, + disposition, + render: Arc::new(|_| Empty::new().boxed()), + } + }) + .collect::>(); + + let (folds_snapshot, fold_edits) = + fold_map.read(buffer_snapshot.clone(), vec![]); + let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); + let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { + wrap_map.sync(tabs_snapshot, tab_edits, cx) + }); + let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); + let block_ids = block_map.insert(block_properties.clone(), cx); + for (block_id, props) in block_ids.into_iter().zip(block_properties) { + expected_blocks.push((block_id, props)); + } + } + 40..=59 if !expected_blocks.is_empty() => { + let block_count = rng.gen_range(1..=4.min(expected_blocks.len())); + let block_ids_to_remove = (0..block_count) + .map(|_| { + expected_blocks + .remove(rng.gen_range(0..expected_blocks.len())) + .0 + }) + .collect(); + + let (folds_snapshot, fold_edits) = + fold_map.read(buffer_snapshot.clone(), vec![]); + let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); + let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { + wrap_map.sync(tabs_snapshot, tab_edits, cx) + }); + let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); + block_map.remove(block_ids_to_remove, cx); + } + _ => { + buffer.update(cx, |buffer, cx| { + let v0 = buffer.version(); + let edit_count = rng.gen_range(1..=5); + buffer.randomly_edit(&mut rng, edit_count, cx); + log::info!("buffer text: {:?}", buffer.text()); + buffer_edits.extend(buffer.edits_since(&v0)); + buffer_snapshot = buffer.snapshot(); + }); + } + } + + let (folds_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); + let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); + let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { + wrap_map.sync(tabs_snapshot, tab_edits, cx) + }); + let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, cx); + assert_eq!( + blocks_snapshot.transforms.summary().input_rows, + wraps_snapshot.max_point().row() + 1 + ); + log::info!("blocks text: {:?}", blocks_snapshot.text()); + + let buffer = buffer.read(cx); + let mut sorted_blocks = expected_blocks + .iter() + .cloned() + .map(|(id, block)| { + let mut position = block.position.to_point(buffer); + let column = wraps_snapshot.from_point(position, Bias::Left).column(); + match block.disposition { + BlockDisposition::Above => { + position.column = 0; + } + BlockDisposition::Below => { + position.column = buffer.line_len(position.row); + } + }; + let row = wraps_snapshot.from_point(position, Bias::Left).row(); + ( + id, + BlockProperties { + position: BlockPoint::new(row, column), + height: block.height, + disposition: block.disposition, + render: block.render.clone(), + }, + ) + }) + .collect::>(); + sorted_blocks + .sort_unstable_by_key(|(id, block)| (block.position.row, block.disposition, *id)); + let mut sorted_blocks = sorted_blocks.into_iter().peekable(); + + let mut expected_buffer_rows = Vec::new(); + let mut expected_text = String::new(); + let input_text = wraps_snapshot.text(); + for (row, input_line) in input_text.split('\n').enumerate() { + let row = row as u32; + if row > 0 { + expected_text.push('\n'); + } + + let buffer_row = wraps_snapshot + .to_point(WrapPoint::new(row, 0), Bias::Left) + .row; + + while let Some((_, block)) = sorted_blocks.peek() { + if block.position.row == row && block.disposition == BlockDisposition::Above { + let text = "\n".repeat(block.height as usize); + expected_text.push_str(&text); + for _ in 0..block.height { + expected_buffer_rows.push(None); + } + sorted_blocks.next(); + } else { + break; + } + } + + let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0; + expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) }); + expected_text.push_str(input_line); + + while let Some((_, block)) = sorted_blocks.peek() { + if block.position.row == row && block.disposition == BlockDisposition::Below { + let text = "\n".repeat(block.height as usize); + expected_text.push_str(&text); + for _ in 0..block.height { + expected_buffer_rows.push(None); + } + sorted_blocks.next(); + } else { + break; + } + } + } + + let expected_lines = expected_text.split('\n').collect::>(); + let expected_row_count = expected_lines.len(); + for start_row in 0..expected_row_count { + let expected_text = expected_lines[start_row..].join("\n"); + let actual_text = blocks_snapshot + .chunks(start_row as u32..expected_row_count as u32, None) + .map(|chunk| chunk.text) + .collect::(); + assert_eq!( + actual_text, expected_text, + "incorrect text starting from row {}", + start_row + ); + assert_eq!( + blocks_snapshot + .buffer_rows(start_row as u32) + .collect::>(), + &expected_buffer_rows[start_row..] + ); + } + + let mut expected_longest_rows = Vec::new(); + let mut longest_line_len = -1_isize; + for (row, line) in expected_lines.iter().enumerate() { + let row = row as u32; + + assert_eq!( + blocks_snapshot.line_len(row), + line.len() as u32, + "invalid line len for row {}", + row + ); + + let line_char_count = line.chars().count() as isize; + match line_char_count.cmp(&longest_line_len) { + Ordering::Less => {} + Ordering::Equal => expected_longest_rows.push(row), + Ordering::Greater => { + longest_line_len = line_char_count; + expected_longest_rows.clear(); + expected_longest_rows.push(row); + } + } + } + + let longest_row = blocks_snapshot.longest_row(); + assert!( + expected_longest_rows.contains(&longest_row), + "incorrect longest row {}. expected {:?} with length {}", + longest_row, + expected_longest_rows, + longest_line_len, + ); + + for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { + let wrap_point = WrapPoint::new(row, 0); + let block_point = blocks_snapshot.to_block_point(wrap_point); + assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point); + } + + let mut block_point = BlockPoint::new(0, 0); + for c in expected_text.chars() { + let left_point = blocks_snapshot.clip_point(block_point, Bias::Left); + let right_point = blocks_snapshot.clip_point(block_point, Bias::Right); + + assert_eq!( + blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)), + left_point + ); + assert_eq!( + blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)), + right_point + ); + + if c == '\n' { + block_point.0 += Point::new(1, 0); + } else { + block_point.column += c.len_utf8() as u32; + } + } + } + } }