From 0c714210ffd70728635f54e19a16106ca6aedaf4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 1 Dec 2021 15:25:55 -0800 Subject: [PATCH 1/4] Start work on generalizing the BlockMap to allow arbitrary elements Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 49 +- crates/editor/src/display_map/block_map.rs | 1156 ++++++++------------ crates/editor/src/display_map/wrap_map.rs | 22 +- crates/editor/src/editor.rs | 126 +-- crates/editor/src/element.rs | 165 ++- crates/gpui/src/elements.rs | 4 + crates/theme/src/theme.rs | 10 - 7 files changed, 607 insertions(+), 925 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 8137e46583003e13bd5beb79be6d168769fc4925..fa0f17331822b192f5d7ac6c7868175ae573e912 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -3,13 +3,12 @@ mod fold_map; mod tab_map; mod wrap_map; -pub use block_map::{BlockDisposition, BlockId, BlockProperties, BufferRows, Chunks}; +pub use block_map::{ + AlignedBlock, BlockContext, BlockDisposition, BlockId, BlockProperties, BufferRows, Chunks, +}; use block_map::{BlockMap, BlockPoint}; use fold_map::{FoldMap, ToFoldPoint as _}; -use gpui::{ - fonts::{FontId, HighlightStyle}, - AppContext, Entity, ModelContext, ModelHandle, -}; +use gpui::{fonts::FontId, ElementBox, Entity, ModelContext, ModelHandle}; use language::{Anchor, Buffer, Point, Subscription as BufferSubscription, ToOffset, ToPoint}; use std::{ collections::{HashMap, HashSet}, @@ -17,8 +16,7 @@ use std::{ }; use sum_tree::Bias; use tab_map::TabMap; -use text::Rope; -use theme::{BlockStyle, SyntaxTheme}; +use theme::SyntaxTheme; use wrap_map::WrapMap; pub trait ToDisplayPoint { @@ -124,14 +122,13 @@ impl DisplayMap { self.block_map.read(snapshot, edits, cx); } - pub fn insert_blocks( + pub fn insert_blocks

( &mut self, - blocks: impl IntoIterator>, + blocks: impl IntoIterator>, cx: &mut ModelContext, ) -> Vec where P: ToOffset + Clone, - T: Into + Clone, { let snapshot = self.buffer.read(cx).snapshot(); let edits = self.buffer_subscription.consume().into_inner(); @@ -144,12 +141,11 @@ impl DisplayMap { block_map.insert(blocks, cx) } - pub fn restyle_blocks(&mut self, styles: HashMap, Option)>) + pub fn replace_blocks(&mut self, styles: HashMap) where - F1: 'static + Fn(&AppContext) -> Vec<(usize, HighlightStyle)>, - F2: 'static + Fn(&AppContext) -> BlockStyle, + F: 'static + Fn(&BlockContext) -> ElementBox, { - self.block_map.restyle(styles); + self.block_map.replace(styles); } pub fn remove_blocks(&mut self, ids: HashSet, cx: &mut ModelContext) { @@ -198,8 +194,8 @@ impl DisplayMapSnapshot { self.buffer_snapshot.len() == 0 } - pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> { - self.blocks_snapshot.buffer_rows(start_row, cx) + pub fn buffer_rows<'a>(&'a self, start_row: u32) -> BufferRows<'a> { + self.blocks_snapshot.buffer_rows(start_row) } pub fn buffer_row_count(&self) -> u32 { @@ -256,7 +252,7 @@ impl DisplayMapSnapshot { pub fn text_chunks(&self, display_row: u32) -> impl Iterator { self.blocks_snapshot - .chunks(display_row..self.max_point().row() + 1, None, None) + .chunks(display_row..self.max_point().row() + 1, None) .map(|h| h.text) } @@ -264,9 +260,8 @@ impl DisplayMapSnapshot { &'a self, display_rows: Range, theme: Option<&'a SyntaxTheme>, - cx: &'a AppContext, ) -> block_map::Chunks<'a> { - self.blocks_snapshot.chunks(display_rows, theme, Some(cx)) + self.blocks_snapshot.chunks(display_rows, theme) } pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator + 'a { @@ -322,6 +317,13 @@ impl DisplayMapSnapshot { self.folds_snapshot.folds_in_range(range) } + pub fn blocks_in_range<'a>( + &'a self, + rows: Range, + ) -> impl Iterator { + self.blocks_snapshot.blocks_in_range(rows) + } + pub fn intersects_fold(&self, offset: T) -> bool { self.folds_snapshot.intersects_fold(offset) } @@ -448,13 +450,6 @@ impl ToDisplayPoint for Anchor { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum DisplayRow { - Buffer(u32), - Block(BlockId, Option), - Wrap, -} - #[cfg(test)] mod tests { use super::*; @@ -1065,7 +1060,7 @@ mod tests { ) -> Vec<(String, Option)> { let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); let mut chunks: Vec<(String, Option)> = Vec::new(); - for chunk in snapshot.chunks(rows, Some(theme), cx) { + for chunk in snapshot.chunks(rows, Some(theme)) { let color = chunk.highlight_style.map(|s| s.color); if let Some((last_chunk, last_color)) = chunks.last_mut() { if color == *last_color { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 5ed1ac65fb8919a076251e79e5e7ad2e43961ee3..1cb5d961eb433ded12c26924dceb78c09d2b334d 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1,26 +1,23 @@ -use super::{ - wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint}, - BlockStyle, DisplayRow, -}; -use gpui::{fonts::HighlightStyle, AppContext, ModelHandle}; +use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint}; +use gpui::{AppContext, ElementBox, ModelHandle}; use language::{Buffer, Chunk}; use parking_lot::Mutex; use std::{ cmp::{self, Ordering}, collections::{HashMap, HashSet}, fmt::Debug, - iter, ops::{Deref, Range}, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, - vec, }; use sum_tree::SumTree; -use text::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _}; +use text::{Anchor, Bias, Edit, Point, ToOffset, ToPoint as _}; use theme::SyntaxTheme; +const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize]; + pub struct BlockMap { buffer: ModelHandle, next_block_id: AtomicUsize, @@ -51,25 +48,28 @@ struct WrapRow(u32); pub struct Block { id: BlockId, position: Anchor, - text: Rope, - build_runs: Mutex Vec<(usize, HighlightStyle)>>>>, - build_style: Mutex BlockStyle>>>, + height: u8, + render: Mutex ElementBox>>, disposition: BlockDisposition, } #[derive(Clone)] -pub struct BlockProperties +pub struct BlockProperties

where P: Clone, - T: Clone, { pub position: P, - pub text: T, - pub build_runs: Option Vec<(usize, HighlightStyle)>>>, - pub build_style: Option BlockStyle>>, + pub height: u8, + pub render: Arc ElementBox>, pub disposition: BlockDisposition, } +pub struct BlockContext<'a> { + pub cx: &'a AppContext, + pub gutter_width: f32, + pub anchor_x: f32, +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum BlockDisposition { Above, @@ -83,7 +83,7 @@ struct Transform { } #[derive(Clone, Debug)] -struct AlignedBlock { +pub struct AlignedBlock { block: Arc, column: u32, } @@ -92,35 +92,20 @@ struct AlignedBlock { struct TransformSummary { input_rows: u32, output_rows: u32, - longest_row_in_block: u32, - longest_row_in_block_chars: u32, } pub struct Chunks<'a> { transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>, input_chunks: wrap_map::Chunks<'a>, input_chunk: Chunk<'a>, - block_chunks: Option>, output_row: u32, max_output_row: u32, - cx: Option<&'a AppContext>, -} - -struct BlockChunks<'a> { - chunks: rope::Chunks<'a>, - runs: iter::Peekable>, - chunk: Option<&'a str>, - remaining_padding: u32, - padding_column: u32, - run_start: usize, - offset: usize, } pub struct BufferRows<'a> { transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>, input_buffer_rows: wrap_map::BufferRows<'a>, output_row: u32, - cx: Option<&'a AppContext>, started: bool, } @@ -333,19 +318,13 @@ impl BlockMap { *transforms = new_transforms; } - pub fn restyle(&mut self, mut styles: HashMap, Option)>) + pub fn replace(&mut self, mut element_builders: HashMap) where - F1: 'static + Fn(&AppContext) -> Vec<(usize, HighlightStyle)>, - F2: 'static + Fn(&AppContext) -> BlockStyle, + F: 'static + Fn(&BlockContext) -> ElementBox, { for block in &self.blocks { - if let Some((build_runs, build_style)) = styles.remove(&block.id) { - *block.build_runs.lock() = build_runs.map(|build_runs| { - Arc::new(build_runs) as Arc Vec<(usize, HighlightStyle)>> - }); - *block.build_style.lock() = build_style.map(|build_style| { - Arc::new(build_style) as Arc BlockStyle> - }); + if let Some(build_element) = element_builders.remove(&block.id) { + *block.render.lock() = Arc::new(build_element); } } } @@ -393,14 +372,13 @@ impl std::ops::DerefMut for BlockPoint { } impl<'a> BlockMapWriter<'a> { - pub fn insert( + pub fn insert

( &mut self, - blocks: impl IntoIterator>, + blocks: impl IntoIterator>, cx: &AppContext, ) -> Vec where P: ToOffset + Clone, - T: Into + Clone, { let buffer = self.0.buffer.read(cx); let mut ids = Vec::new(); @@ -436,9 +414,8 @@ impl<'a> BlockMapWriter<'a> { Arc::new(Block { id, position, - text: block.text.into(), - build_runs: Mutex::new(block.build_runs), - build_style: Mutex::new(block.build_style), + height: block.height, + render: Mutex::new(block.render), disposition: block.disposition, }), ); @@ -495,17 +472,12 @@ impl<'a> BlockMapWriter<'a> { impl BlockSnapshot { #[cfg(test)] fn text(&mut self) -> String { - self.chunks(0..self.transforms.summary().output_rows, None, None) + self.chunks(0..self.transforms.summary().output_rows, None) .map(|chunk| chunk.text) .collect() } - pub fn chunks<'a>( - &'a self, - rows: Range, - theme: Option<&'a SyntaxTheme>, - cx: Option<&'a AppContext>, - ) -> Chunks<'a> { + pub fn chunks<'a>(&'a self, rows: Range, theme: Option<&'a SyntaxTheme>) -> Chunks<'a> { let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows); let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); let input_end = { @@ -535,15 +507,13 @@ impl BlockSnapshot { Chunks { input_chunks: self.wrap_snapshot.chunks(input_start..input_end, theme), input_chunk: Default::default(), - block_chunks: None, transforms: cursor, output_row: rows.start, max_output_row, - cx, } } - pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> { + pub fn buffer_rows<'a>(&'a self, start_row: u32) -> BufferRows<'a> { let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); cursor.seek(&BlockRow(start_row), Bias::Right, &()); let (output_start, input_start) = cursor.start(); @@ -554,7 +524,6 @@ impl BlockSnapshot { }; let input_start_row = input_start.0 + overshoot; BufferRows { - cx, transforms: cursor, input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row), output_row: start_row, @@ -562,6 +531,29 @@ impl BlockSnapshot { } } + pub fn blocks_in_range<'a>( + &'a self, + rows: Range, + ) -> impl Iterator { + let mut cursor = self.transforms.cursor::(); + cursor.seek(&BlockRow(rows.start), Bias::Right, &()); + std::iter::from_fn(move || { + while let Some(transform) = cursor.item() { + let start_row = cursor.start().0; + if start_row >= rows.end { + break; + } + if let Some(block) = &transform.block { + cursor.next(&()); + return Some((start_row, block)); + } else { + cursor.next(&()); + } + } + None + }) + } + pub fn max_point(&self) -> BlockPoint { let row = self.transforms.summary().output_rows - 1; BlockPoint::new(row, self.line_len(row)) @@ -569,18 +561,7 @@ impl BlockSnapshot { pub fn longest_row(&self) -> u32 { let input_row = self.wrap_snapshot.longest_row(); - let input_row_chars = self.wrap_snapshot.line_char_count(input_row); - let TransformSummary { - longest_row_in_block: block_row, - longest_row_in_block_chars: block_row_chars, - .. - } = &self.transforms.summary(); - - if *block_row_chars > input_row_chars { - *block_row - } else { - self.to_block_point(WrapPoint::new(input_row, 0)).row - } + self.to_block_point(WrapPoint::new(input_row, 0)).row } pub fn line_len(&self, row: u32) -> u32 { @@ -589,12 +570,8 @@ impl BlockSnapshot { if let Some(transform) = cursor.item() { let (output_start, input_start) = cursor.start(); let overshoot = row - output_start.0; - if let Some(block) = &transform.block { - let mut len = block.text.line_len(overshoot); - if len > 0 { - len += block.column; - } - len + if transform.block.is_some() { + 0 } else { self.wrap_snapshot.line_len(input_start.0 + overshoot) } @@ -697,21 +674,16 @@ impl Transform { summary: TransformSummary { input_rows: rows, output_rows: rows, - longest_row_in_block: 0, - longest_row_in_block_chars: 0, }, block: None, } } fn block(block: Arc, column: u32) -> Self { - let text_summary = block.text.summary(); Self { summary: TransformSummary { input_rows: 0, - output_rows: text_summary.lines.row + 1, - longest_row_in_block: text_summary.longest_row, - longest_row_in_block_chars: column + text_summary.longest_row_chars, + output_rows: block.height as u32, }, block: Some(AlignedBlock { block, column }), } @@ -730,37 +702,20 @@ impl<'a> Iterator for Chunks<'a> { return None; } - if let Some(block_chunks) = self.block_chunks.as_mut() { - if let Some(block_chunk) = block_chunks.next() { - self.output_row += block_chunk.text.matches('\n').count() as u32; - return Some(block_chunk); - } else { - self.block_chunks.take(); - self.output_row += 1; - if self.output_row < self.max_output_row { - return Some(Chunk { - text: "\n", - ..Default::default() - }); - } else { - return None; - } - } - } - let transform = self.transforms.item()?; - if let Some(block) = transform.block.as_ref() { + if transform.block.is_some() { let block_start = self.transforms.start().0 .0; let block_end = self.transforms.end(&()).0 .0; 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(&()); - self.block_chunks = Some(BlockChunks::new( - block, - start_in_block..end_in_block, - self.cx, - )); - return self.next(); + return Some(Chunk { + text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) }, + highlight_style: None, + diagnostic: None, + }); } if self.input_chunk.text.is_empty() { @@ -797,107 +752,8 @@ impl<'a> Iterator for Chunks<'a> { } } -impl<'a> BlockChunks<'a> { - fn new(block: &'a AlignedBlock, rows: Range, cx: Option<&'a AppContext>) -> Self { - let offset_range = block.text.point_to_offset(Point::new(rows.start, 0)) - ..block.text.point_to_offset(Point::new(rows.end, 0)); - - let mut runs = block - .build_runs - .lock() - .as_ref() - .zip(cx) - .map(|(build_runs, cx)| build_runs(cx)) - .unwrap_or_default() - .into_iter() - .peekable(); - let mut run_start = 0; - while let Some((run_len, _)) = runs.peek() { - let run_end = run_start + run_len; - if run_end <= offset_range.start { - run_start = run_end; - runs.next(); - } else { - break; - } - } - - Self { - chunk: None, - run_start, - padding_column: block.column, - remaining_padding: block.column, - chunks: block.text.chunks_in_range(offset_range.clone()), - runs, - offset: offset_range.start, - } - } -} - -impl<'a> Iterator for BlockChunks<'a> { - type Item = Chunk<'a>; - - fn next(&mut self) -> Option { - if self.chunk.is_none() { - self.chunk = self.chunks.next(); - } - - let chunk = self.chunk?; - - if chunk.starts_with('\n') { - self.remaining_padding = 0; - } - - if self.remaining_padding > 0 { - const PADDING: &'static str = " "; - let padding_len = self.remaining_padding.min(PADDING.len() as u32); - self.remaining_padding -= padding_len; - return Some(Chunk { - text: &PADDING[..padding_len as usize], - ..Default::default() - }); - } - - let mut chunk_len = if let Some(ix) = chunk.find('\n') { - ix + 1 - } else { - chunk.len() - }; - - let mut highlight_style = None; - if let Some((run_len, style)) = self.runs.peek() { - highlight_style = Some(style.clone()); - let run_end_in_chunk = self.run_start + run_len - self.offset; - if run_end_in_chunk <= chunk_len { - chunk_len = run_end_in_chunk; - self.run_start += run_len; - self.runs.next(); - } - } - - self.offset += chunk_len; - let (chunk, suffix) = chunk.split_at(chunk_len); - - if chunk.ends_with('\n') { - self.remaining_padding = self.padding_column; - } - - self.chunk = if suffix.is_empty() { - None - } else { - Some(suffix) - }; - - Some(Chunk { - text: chunk, - highlight_style, - diagnostic: None, - }) - } -} - impl<'a> Iterator for BufferRows<'a> { - type Item = DisplayRow; + type Item = Option; fn next(&mut self) -> Option { if self.started { @@ -911,11 +767,8 @@ impl<'a> Iterator for BufferRows<'a> { } let transform = self.transforms.item()?; - if let Some(block) = &transform.block { - let style = self - .cx - .and_then(|cx| block.build_style.lock().as_ref().map(|f| f(cx))); - Some(DisplayRow::Block(block.id, style)) + if transform.block.is_some() { + Some(None) } else { Some(self.input_buffer_rows.next().unwrap()) } @@ -934,11 +787,6 @@ impl sum_tree::Summary for TransformSummary { type Context = (); fn add_summary(&mut self, summary: &Self, _: &()) { - if summary.longest_row_in_block_chars > self.longest_row_in_block_chars { - self.longest_row_in_block_chars = summary.longest_row_in_block_chars; - self.longest_row_in_block = self.output_rows + summary.longest_row_in_block; - } - self.input_rows += summary.input_rows; self.output_rows += summary.output_rows; } @@ -962,6 +810,20 @@ impl BlockDisposition { } } +impl AlignedBlock { + pub fn height(&self) -> u32 { + self.height as u32 + } + + pub fn column(&self) -> u32 { + self.column + } + + pub fn render(&self, cx: &BlockContext) -> ElementBox { + self.render.lock()(cx) + } +} + impl Deref for AlignedBlock { type Target = Block; @@ -975,7 +837,6 @@ impl Debug for Block { f.debug_struct("Block") .field("id", &self.id) .field("position", &self.position) - .field("text", &self.text) .field("disposition", &self.disposition) .finish() } @@ -1003,7 +864,7 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) { mod tests { use super::*; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; - use gpui::color::Color; + use gpui::{elements::Empty, Element}; use language::Buffer; use rand::prelude::*; use std::env; @@ -1023,76 +884,6 @@ mod tests { assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11)); } - #[gpui::test] - fn test_block_chunks(cx: &mut gpui::MutableAppContext) { - let red = Color::red(); - let blue = Color::blue(); - let clear = Color::default(); - - let block = AlignedBlock { - column: 5, - block: Arc::new(Block { - id: BlockId(0), - position: Anchor::min(), - text: "one!\ntwo three\nfour".into(), - build_style: Mutex::new(None), - build_runs: Mutex::new(Some(Arc::new(move |_| { - vec![(3, red.into()), (6, Default::default()), (5, blue.into())] - }))), - disposition: BlockDisposition::Above, - }), - }; - - assert_eq!( - colored_chunks(&block, 0..3, cx), - &[ - (" ", clear), - ("one", red), - ("!\n", clear), - (" ", clear), - ("two ", clear), - ("three", blue), - ("\n", clear), - (" ", clear), - ("four", clear) - ] - ); - assert_eq!( - colored_chunks(&block, 0..1, cx), - &[ - (" ", clear), // - ("one", red), - ("!\n", clear), - ] - ); - assert_eq!( - colored_chunks(&block, 1..3, cx), - &[ - (" ", clear), - ("two ", clear), - ("three", blue), - ("\n", clear), - (" ", clear), - ("four", clear) - ] - ); - - fn colored_chunks<'a>( - block: &'a AlignedBlock, - row_range: Range, - cx: &'a AppContext, - ) -> Vec<(&'a str, Color)> { - BlockChunks::new(block, row_range, Some(cx)) - .map(|c| { - ( - c.text, - c.highlight_style.map_or(Color::default(), |s| s.color), - ) - }) - .collect() - } - } - #[gpui::test] fn test_basic_blocks(cx: &mut gpui::MutableAppContext) { let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); @@ -1114,34 +905,56 @@ mod tests { vec![ BlockProperties { position: Point::new(1, 0), - text: "BLOCK 1", + height: 1, disposition: BlockDisposition::Above, - build_runs: None, - build_style: None, + render: Arc::new(|_| Empty::new().named("block 1")), }, BlockProperties { position: Point::new(1, 2), - text: "BLOCK 2", + height: 2, disposition: BlockDisposition::Above, - build_runs: None, - build_style: None, + render: Arc::new(|_| Empty::new().named("block 2")), }, BlockProperties { - position: Point::new(3, 2), - text: "BLOCK 3", + position: Point::new(3, 3), + height: 3, disposition: BlockDisposition::Below, - build_runs: None, - build_style: None, + render: Arc::new(|_| Empty::new().named("block 3")), }, ], cx, ); 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"); + + let blocks = snapshot + .blocks_in_range(0..8) + .map(|(start_row, block)| { + ( + start_row..start_row + block.height(), + block.column(), + block + .render(&BlockContext { + cx, + gutter_width: 0., + anchor_x: 0., + }) + .name() + .unwrap() + .to_string(), + ) + }) + .collect::>(); assert_eq!( - snapshot.text(), - "aaa\nBLOCK 1\n BLOCK 2\nbbb\nccc\nddd\n BLOCK 3" + blocks, + &[ + (1..2, 0, "block 1".to_string()), + (2..4, 2, "block 2".to_string()), + (7..10, 3, "block 3".to_string()), + ] ); + assert_eq!( snapshot.to_block_point(WrapPoint::new(0, 3)), BlockPoint::new(0, 3) @@ -1214,16 +1027,8 @@ mod tests { ); assert_eq!( - snapshot.buffer_rows(0, None).collect::>(), - &[ - DisplayRow::Buffer(0), - DisplayRow::Block(block_ids[0], None), - DisplayRow::Block(block_ids[1], None), - DisplayRow::Buffer(1), - DisplayRow::Buffer(2), - DisplayRow::Buffer(3), - DisplayRow::Block(block_ids[2], None) - ] + snapshot.buffer_rows(0).collect::>(), + &[Some(0), None, None, Some(1), Some(2), Some(3), None] ); // Insert a line break, separating two block decorations into separate @@ -1240,371 +1045,368 @@ mod tests { wrap_map.sync(tabs_snapshot, tab_edits, cx) }); let mut snapshot = block_map.read(wraps_snapshot, wrap_edits, cx); - assert_eq!( - snapshot.text(), - "aaa\nBLOCK 1\nb!!!\n BLOCK 2\nbb\nccc\nddd\n BLOCK 3" - ); + 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), + // 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; + // } + // } + // } + // } } diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 174e9df98eb7f35be03360973ab7512ed0ed6789..04b6c00d6b0476a52666631b9098c8777ed585f2 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1,7 +1,6 @@ use super::{ fold_map, tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint}, - DisplayRow, }; use gpui::{ fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext, @@ -607,13 +606,6 @@ impl Snapshot { len as u32 } - pub fn line_char_count(&self, row: u32) -> u32 { - self.text_chunks(row) - .flat_map(|c| c.chars()) - .take_while(|c| *c != '\n') - .count() as u32 - } - pub fn soft_wrap_indent(&self, row: u32) -> Option { let mut cursor = self.transforms.cursor::(); cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &()); @@ -719,11 +711,7 @@ impl Snapshot { prev_tab_row = tab_point.row(); soft_wrapped = false; } - expected_buffer_rows.push(if soft_wrapped { - DisplayRow::Wrap - } else { - DisplayRow::Buffer(buffer_row) - }); + expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) }); } for start_display_row in 0..expected_buffer_rows.len() { @@ -803,7 +791,7 @@ impl<'a> Iterator for Chunks<'a> { } impl<'a> Iterator for BufferRows<'a> { - type Item = DisplayRow; + type Item = Option; fn next(&mut self) -> Option { if self.output_row > self.max_output_row { @@ -823,11 +811,7 @@ impl<'a> Iterator for BufferRows<'a> { self.soft_wrapped = true; } - Some(if soft_wrapped { - DisplayRow::Wrap - } else { - DisplayRow::Buffer(buffer_row) - }) + Some(if soft_wrapped { None } else { Some(buffer_row) }) } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e61f1f79cdc7755e513bb4eb9fc90741ceca930d..c419b1dab5544fa5110f187fe774dc302f138957 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8,11 +8,12 @@ mod test; use aho_corasick::AhoCorasick; use clock::ReplicaId; +pub use display_map::DisplayPoint; use display_map::*; -pub use display_map::{DisplayPoint, DisplayRow}; pub use element::*; use gpui::{ action, + elements::Text, geometry::vector::{vec2f, Vector2F}, keymap::Binding, text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, @@ -28,14 +29,14 @@ use std::{ cmp, collections::HashMap, iter, mem, - ops::{Range, RangeInclusive}, + ops::{Deref, Range, RangeInclusive}, rc::Rc, sync::Arc, time::Duration, }; use sum_tree::Bias; use text::rope::TextDimension; -use theme::{DiagnosticStyle, EditorStyle, SyntaxTheme}; +use theme::{DiagnosticStyle, EditorStyle}; use util::post_inc; use workspace::{EntryOpener, Workspace}; @@ -2877,35 +2878,16 @@ impl Editor { active_diagnostics.is_valid = is_valid; let mut new_styles = HashMap::new(); for (block_id, diagnostic) in &active_diagnostics.blocks { - let severity = diagnostic.severity; - let message_len = diagnostic.message.len(); - new_styles.insert( - *block_id, - ( - Some({ - let build_settings = self.build_settings.clone(); - move |cx: &AppContext| { - let settings = build_settings.borrow()(cx); - vec![( - message_len, - diagnostic_style(severity, is_valid, &settings.style) - .text - .into(), - )] - } - }), - Some({ - let build_settings = self.build_settings.clone(); - move |cx: &AppContext| { - let settings = build_settings.borrow()(cx); - diagnostic_style(severity, is_valid, &settings.style).block - } - }), - ), - ); + let build_settings = self.build_settings.clone(); + let diagnostic = diagnostic.clone(); + new_styles.insert(*block_id, move |cx: &BlockContext| { + let diagnostic = diagnostic.clone(); + let settings = build_settings.borrow()(cx.cx); + render_diagnostic(diagnostic, &settings.style) + }); } self.display_map - .update(cx, |display_map, _| display_map.restyle_blocks(new_styles)); + .update(cx, |display_map, _| display_map.replace_blocks(new_styles)); } } } @@ -2940,30 +2922,17 @@ impl Editor { .insert_blocks( diagnostic_group.iter().map(|(range, diagnostic)| { let build_settings = self.build_settings.clone(); - let message_len = diagnostic.message.len(); - let severity = diagnostic.severity; + let diagnostic = diagnostic.clone(); + let message_height = diagnostic.message.lines().count() as u8; + BlockProperties { position: range.start, - text: diagnostic.message.as_str(), - build_runs: Some(Arc::new({ - let build_settings = build_settings.clone(); - move |cx| { - let settings = build_settings.borrow()(cx); - vec![( - message_len, - diagnostic_style(severity, true, &settings.style) - .text - .into(), - )] - } - })), - build_style: Some(Arc::new({ - let build_settings = build_settings.clone(); - move |cx| { - let settings = build_settings.borrow()(cx); - diagnostic_style(severity, true, &settings.style).block - } - })), + height: message_height, + render: Arc::new(move |cx| { + let settings = build_settings.borrow()(cx.cx); + let diagnostic = diagnostic.clone(); + render_diagnostic(diagnostic, &settings.style) + }), disposition: BlockDisposition::Below, } }), @@ -3482,10 +3451,6 @@ impl Editor { } impl Snapshot { - pub fn is_empty(&self) -> bool { - self.display_snapshot.is_empty() - } - pub fn is_focused(&self) -> bool { self.is_focused } @@ -3494,23 +3459,6 @@ impl Snapshot { self.placeholder_text.as_ref() } - pub fn buffer_row_count(&self) -> u32 { - self.display_snapshot.buffer_row_count() - } - - pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: &'a AppContext) -> BufferRows<'a> { - self.display_snapshot.buffer_rows(start_row, Some(cx)) - } - - pub fn chunks<'a>( - &'a self, - display_rows: Range, - theme: Option<&'a SyntaxTheme>, - cx: &'a AppContext, - ) -> display_map::Chunks<'a> { - self.display_snapshot.chunks(display_rows, theme, cx) - } - pub fn scroll_position(&self) -> Vector2F { compute_scroll_position( &self.display_snapshot, @@ -3518,29 +3466,13 @@ impl Snapshot { &self.scroll_top_anchor, ) } +} - pub fn max_point(&self) -> DisplayPoint { - self.display_snapshot.max_point() - } - - pub fn longest_row(&self) -> u32 { - self.display_snapshot.longest_row() - } - - pub fn line_len(&self, display_row: u32) -> u32 { - self.display_snapshot.line_len(display_row) - } - - pub fn line(&self, display_row: u32) -> String { - self.display_snapshot.line(display_row) - } +impl Deref for Snapshot { + type Target = DisplayMapSnapshot; - pub fn prev_row_boundary(&self, point: DisplayPoint) -> (DisplayPoint, Point) { - self.display_snapshot.prev_row_boundary(point) - } - - pub fn next_row_boundary(&self, point: DisplayPoint) -> (DisplayPoint, Point) { - self.display_snapshot.next_row_boundary(point) + fn deref(&self) -> &Self::Target { + &self.display_snapshot } } @@ -3709,6 +3641,12 @@ impl SelectionExt for Selection { } } +fn render_diagnostic(diagnostic: Diagnostic, style: &EditorStyle) -> ElementBox { + let mut text_style = style.text.clone(); + text_style.color = diagnostic_style(diagnostic.severity, true, &style).text; + Text::new(diagnostic.message, text_style).boxed() +} + pub fn diagnostic_style( severity: DiagnosticSeverity, valid: bool, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 5acc5260140ca7509c99a6bddeda2cd3a2dd3b3b..13dd5cfdc78733203616885706787eca1738f769 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1,6 +1,8 @@ +use crate::display_map::BlockContext; + use super::{ - DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, - Select, SelectPhase, Snapshot, SoftWrap, MAX_LINE_LEN, + DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select, + SelectPhase, Snapshot, SoftWrap, MAX_LINE_LEN, }; use clock::ReplicaId; use gpui::{ @@ -13,7 +15,7 @@ use gpui::{ json::{self, ToJson}, keymap::Keystroke, text_layout::{self, RunStyle, TextLayoutCache}, - AppContext, Axis, Border, Element, Event, EventContext, FontCache, LayoutContext, + AppContext, Axis, Border, Element, ElementBox, Event, EventContext, FontCache, LayoutContext, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, }; use json::json; @@ -25,7 +27,6 @@ use std::{ fmt::Write, ops::Range, }; -use theme::BlockStyle; pub struct EditorElement { view: WeakViewHandle, @@ -278,51 +279,6 @@ impl EditorElement { }); } } - - // Draw block backgrounds - for (ixs, block_style) in &layout.block_layouts { - let row = start_row + ixs.start; - let offset = vec2f(0., row as f32 * layout.line_height - scroll_top); - let height = ixs.len() as f32 * layout.line_height; - cx.scene.push_quad(Quad { - bounds: RectF::new( - text_bounds.origin() + offset, - vec2f(text_bounds.width(), height), - ), - background: block_style.background, - border: block_style - .border - .map_or(Default::default(), |color| Border { - width: 1., - color, - overlay: true, - top: true, - right: false, - bottom: true, - left: false, - }), - corner_radius: 0., - }); - cx.scene.push_quad(Quad { - bounds: RectF::new( - gutter_bounds.origin() + offset, - vec2f(gutter_bounds.width(), height), - ), - background: block_style.gutter_background, - border: block_style - .gutter_border - .map_or(Default::default(), |color| Border { - width: 1., - color, - overlay: true, - top: true, - right: false, - bottom: true, - left: false, - }), - corner_radius: 0., - }); - } } fn paint_gutter( @@ -461,6 +417,18 @@ impl EditorElement { cx.scene.pop_layer(); } + fn paint_blocks( + &mut self, + bounds: RectF, + visible_bounds: RectF, + layout: &LayoutState, + cx: &mut PaintContext, + ) { + for (row_range, block) in &layout.blocks { + // + } + } + fn max_line_number_width(&self, snapshot: &Snapshot, cx: &LayoutContext) -> f32 { let digit_count = (snapshot.buffer_row_count() as f32).log10().floor() as usize + 1; let style = &self.settings.style; @@ -487,18 +455,13 @@ impl EditorElement { active_rows: &BTreeMap, snapshot: &Snapshot, cx: &LayoutContext, - ) -> ( - Vec>, - Vec<(Range, BlockStyle)>, - ) { + ) -> Vec> { let style = &self.settings.style; let include_line_numbers = snapshot.mode == EditorMode::Full; - let mut last_block_id = None; - let mut blocks = Vec::<(Range, BlockStyle)>::new(); let mut line_number_layouts = Vec::with_capacity(rows.len()); let mut line_number = String::new(); for (ix, row) in snapshot - .buffer_rows(rows.start, cx) + .buffer_rows(rows.start) .take((rows.end - rows.start) as usize) .enumerate() { @@ -508,46 +471,29 @@ impl EditorElement { } else { style.line_number }; - match row { - DisplayRow::Buffer(buffer_row) => { - if include_line_numbers { - line_number.clear(); - write!(&mut line_number, "{}", buffer_row + 1).unwrap(); - line_number_layouts.push(Some(cx.text_layout_cache.layout_str( - &line_number, - style.text.font_size, - &[( - line_number.len(), - RunStyle { - font_id: style.text.font_id, - color, - underline: None, - }, - )], - ))); - } - last_block_id = None; - } - DisplayRow::Block(block_id, style) => { - let ix = ix as u32; - if last_block_id == Some(block_id) { - if let Some((row_range, _)) = blocks.last_mut() { - row_range.end += 1; - } - } else if let Some(style) = style { - blocks.push((ix..ix + 1, style)); - } - line_number_layouts.push(None); - last_block_id = Some(block_id); - } - DisplayRow::Wrap => { - line_number_layouts.push(None); - last_block_id = None; + if let Some(buffer_row) = row { + if include_line_numbers { + line_number.clear(); + write!(&mut line_number, "{}", buffer_row + 1).unwrap(); + line_number_layouts.push(Some(cx.text_layout_cache.layout_str( + &line_number, + style.text.font_size, + &[( + line_number.len(), + RunStyle { + font_id: style.text.font_id, + color, + underline: None, + }, + )], + ))); } + } else { + line_number_layouts.push(None); } } - (line_number_layouts, blocks) + line_number_layouts } fn layout_lines( @@ -598,7 +544,7 @@ impl EditorElement { let mut styles = Vec::new(); let mut row = rows.start; let mut line_exceeded_max_len = false; - let chunks = snapshot.chunks(rows.clone(), Some(&style.syntax), cx); + let chunks = snapshot.chunks(rows.clone(), Some(&style.syntax)); let newline_chunk = Chunk { text: "\n", @@ -668,6 +614,27 @@ impl EditorElement { layouts } + + fn layout_blocks( + &mut self, + rows: Range, + snapshot: &Snapshot, + cx: &LayoutContext, + ) -> Vec<(Range, ElementBox)> { + snapshot + .blocks_in_range(rows) + .map(|(start_row, block)| { + ( + start_row..start_row + block.height(), + block.render(&BlockContext { + cx, + gutter_width: 0.0, + anchor_x: 0.0, + }), + ) + }) + .collect() + } } impl Element for EditorElement { @@ -773,8 +740,7 @@ impl Element for EditorElement { } }); - let (line_number_layouts, block_layouts) = - self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx); + let line_number_layouts = self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx); let mut max_visible_line_width = 0.0; let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx); @@ -784,6 +750,8 @@ impl Element for EditorElement { } } + let blocks = self.layout_blocks(start_row..end_row, &snapshot, cx); + let mut layout = LayoutState { size, gutter_size, @@ -797,7 +765,7 @@ impl Element for EditorElement { highlighted_row, line_layouts, line_number_layouts, - block_layouts, + blocks, line_height, em_width, em_advance, @@ -853,6 +821,7 @@ impl Element for EditorElement { self.paint_gutter(gutter_bounds, visible_bounds, layout, cx); } self.paint_text(text_bounds, visible_bounds, layout, cx); + self.paint_blocks(text_bounds, visible_bounds, layout, cx); cx.scene.pop_layer(); @@ -927,7 +896,7 @@ pub struct LayoutState { highlighted_row: Option, line_layouts: Vec, line_number_layouts: Vec>, - block_layouts: Vec<(Range, BlockStyle)>, + blocks: Vec<(Range, ElementBox)>, line_height: f32, em_width: f32, em_advance: f32, @@ -1185,7 +1154,7 @@ mod tests { }); let element = EditorElement::new(editor.downgrade(), settings); - let (layouts, _) = editor.update(cx, |editor, cx| { + let layouts = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); let mut presenter = cx.build_presenter(window_id, 30.); let mut layout_cx = presenter.build_layout_context(false, cx); diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index c8048ef3fa2f002656fb2a6943292e613f655aa0..c0b6cdd14391e6e8e6c7a628f04efc962e553e12 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -301,6 +301,10 @@ impl Default for Lifecycle { } impl ElementBox { + pub fn name(&self) -> Option<&str> { + self.0.name.as_deref() + } + pub fn metadata(&self) -> Option<&T> { let element = unsafe { &*self.0.element.as_ptr() }; element.metadata().and_then(|m| m.downcast_ref()) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 716ce4a46a42faa14409b3c18ba1e4be02cba5e6..6f685ce70d2118c6068ddcf26ff90239815d4de3 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -253,8 +253,6 @@ pub struct EditorStyle { #[derive(Copy, Clone, Deserialize, Default)] pub struct DiagnosticStyle { pub text: Color, - #[serde(flatten)] - pub block: BlockStyle, } #[derive(Clone, Copy, Default, Deserialize)] @@ -273,14 +271,6 @@ pub struct InputEditorStyle { pub selection: SelectionStyle, } -#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] -pub struct BlockStyle { - pub background: Option, - pub border: Option, - pub gutter_background: Option, - pub gutter_border: Option, -} - impl EditorStyle { pub fn placeholder_text(&self) -> &TextStyle { self.placeholder_text.as_ref().unwrap_or(&self.text) From 512a10b0374de813166df7f1bb373a36507d81b6 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Dec 2021 17:22:40 -0700 Subject: [PATCH 2/4] Use new BlockMap API to render diagnostics Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map/block_map.rs | 11 +- crates/editor/src/editor.rs | 18 ++- crates/editor/src/element.rs | 126 +++++++++++++-------- 3 files changed, 97 insertions(+), 58 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 1cb5d961eb433ded12c26924dceb78c09d2b334d..9b973d2457f5bedf47f05599350636bc23b14b54 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -66,7 +66,6 @@ where pub struct BlockContext<'a> { pub cx: &'a AppContext, - pub gutter_width: f32, pub anchor_x: f32, } @@ -822,6 +821,10 @@ impl AlignedBlock { pub fn render(&self, cx: &BlockContext) -> ElementBox { self.render.lock()(cx) } + + pub fn disposition(&self) -> BlockDisposition { + self.block.disposition + } } impl Deref for AlignedBlock { @@ -935,11 +938,7 @@ mod tests { start_row..start_row + block.height(), block.column(), block - .render(&BlockContext { - cx, - gutter_width: 0., - anchor_x: 0., - }) + .render(&BlockContext { cx, anchor_x: 0. }) .name() .unwrap() .to_string(), diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c419b1dab5544fa5110f187fe774dc302f138957..539736aca254e803fe9ac2ab29c51c63e0f014cb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2883,7 +2883,7 @@ impl Editor { new_styles.insert(*block_id, move |cx: &BlockContext| { let diagnostic = diagnostic.clone(); let settings = build_settings.borrow()(cx.cx); - render_diagnostic(diagnostic, &settings.style) + render_diagnostic(diagnostic, &settings.style, is_valid, cx.anchor_x) }); } self.display_map @@ -2931,7 +2931,7 @@ impl Editor { render: Arc::new(move |cx| { let settings = build_settings.borrow()(cx.cx); let diagnostic = diagnostic.clone(); - render_diagnostic(diagnostic, &settings.style) + render_diagnostic(diagnostic, &settings.style, true, cx.anchor_x) }), disposition: BlockDisposition::Below, } @@ -3641,10 +3641,18 @@ impl SelectionExt for Selection { } } -fn render_diagnostic(diagnostic: Diagnostic, style: &EditorStyle) -> ElementBox { +fn render_diagnostic( + diagnostic: Diagnostic, + style: &EditorStyle, + valid: bool, + anchor_x: f32, +) -> ElementBox { let mut text_style = style.text.clone(); - text_style.color = diagnostic_style(diagnostic.severity, true, &style).text; - Text::new(diagnostic.message, text_style).boxed() + text_style.color = diagnostic_style(diagnostic.severity, valid, &style).text; + Text::new(diagnostic.message, text_style) + .contained() + .with_margin_left(anchor_x) + .boxed() } pub fn diagnostic_style( diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 13dd5cfdc78733203616885706787eca1738f769..b50aa2d9c55d8d903e9d7ff06674f80fdd965647 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1,4 +1,4 @@ -use crate::display_map::BlockContext; +use crate::display_map::{BlockContext, BlockDisposition}; use super::{ DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select, @@ -220,7 +220,6 @@ impl EditorElement { ) { let bounds = gutter_bounds.union_rect(text_bounds); let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height; - let start_row = layout.snapshot.scroll_position().y() as u32; let editor = self.view(cx.app); let style = &self.settings.style; cx.scene.push_quad(Quad { @@ -419,13 +418,19 @@ impl EditorElement { fn paint_blocks( &mut self, - bounds: RectF, + text_bounds: RectF, visible_bounds: RectF, - layout: &LayoutState, + layout: &mut LayoutState, cx: &mut PaintContext, ) { - for (row_range, block) in &layout.blocks { - // + let scroll_position = layout.snapshot.scroll_position(); + let scroll_left = scroll_position.x() * layout.em_width; + let scroll_top = scroll_position.y() * layout.line_height; + + for (row, element) in &mut layout.blocks { + let origin = text_bounds.origin() + + vec2f(-scroll_left, *row as f32 * layout.line_height - scroll_top); + element.paint(origin, visible_bounds, cx); } } @@ -619,19 +624,37 @@ impl EditorElement { &mut self, rows: Range, snapshot: &Snapshot, - cx: &LayoutContext, - ) -> Vec<(Range, ElementBox)> { + text_width: f32, + line_height: f32, + style: &EditorStyle, + line_layouts: &[text_layout::Line], + cx: &mut LayoutContext, + ) -> Vec<(u32, ElementBox)> { snapshot - .blocks_in_range(rows) + .blocks_in_range(rows.clone()) .map(|(start_row, block)| { - ( - start_row..start_row + block.height(), - block.render(&BlockContext { - cx, - gutter_width: 0.0, - anchor_x: 0.0, - }), - ) + let anchor_row = match block.disposition() { + BlockDisposition::Above => start_row + block.height(), + BlockDisposition::Below => start_row - 1, + }; + + let anchor_x = if rows.contains(&anchor_row) { + line_layouts[(anchor_row - rows.start) as usize] + .x_for_index(block.column() as usize) + } else { + layout_line(anchor_row, snapshot, style, cx.text_layout_cache) + .x_for_index(block.column() as usize) + }; + + let mut element = block.render(&BlockContext { cx, anchor_x }); + element.layout( + SizeConstraint { + min: Vector2F::zero(), + max: vec2f(text_width, block.height() as f32 * line_height), + }, + cx, + ); + (start_row, element) }) .collect() } @@ -750,7 +773,15 @@ impl Element for EditorElement { } } - let blocks = self.layout_blocks(start_row..end_row, &snapshot, cx); + let blocks = self.layout_blocks( + start_row..end_row, + &snapshot, + text_size.x(), + line_height, + &style, + &line_layouts, + cx, + ); let mut layout = LayoutState { size, @@ -896,7 +927,7 @@ pub struct LayoutState { highlighted_row: Option, line_layouts: Vec, line_number_layouts: Vec>, - blocks: Vec<(Range, ElementBox)>, + blocks: Vec<(u32, ElementBox)>, line_height: f32, em_width: f32, em_advance: f32, @@ -909,7 +940,8 @@ pub struct LayoutState { impl LayoutState { fn scroll_width(&self, layout_cache: &TextLayoutCache) -> f32 { let row = self.snapshot.longest_row(); - let longest_line_width = self.layout_line(row, &self.snapshot, layout_cache).width(); + let longest_line_width = + layout_line(row, &self.snapshot, &self.style, layout_cache).width(); longest_line_width.max(self.max_visible_line_width) + self.overscroll.x() } @@ -924,36 +956,36 @@ impl LayoutState { max_row.saturating_sub(1) as f32, ) } +} - pub fn layout_line( - &self, - row: u32, - snapshot: &Snapshot, - layout_cache: &TextLayoutCache, - ) -> text_layout::Line { - let mut line = snapshot.line(row); - - if line.len() > MAX_LINE_LEN { - let mut len = MAX_LINE_LEN; - while !line.is_char_boundary(len) { - len -= 1; - } - line.truncate(len); +fn layout_line( + row: u32, + snapshot: &Snapshot, + style: &EditorStyle, + layout_cache: &TextLayoutCache, +) -> text_layout::Line { + let mut line = snapshot.line(row); + + if line.len() > MAX_LINE_LEN { + let mut len = MAX_LINE_LEN; + while !line.is_char_boundary(len) { + len -= 1; } - - layout_cache.layout_str( - &line, - self.style.text.font_size, - &[( - snapshot.line_len(row) as usize, - RunStyle { - font_id: self.style.text.font_id, - color: Color::black(), - underline: None, - }, - )], - ) + line.truncate(len); } + + layout_cache.layout_str( + &line, + style.text.font_size, + &[( + snapshot.line_len(row) as usize, + RunStyle { + font_id: style.text.font_id, + color: Color::black(), + underline: None, + }, + )], + ) } pub struct PaintState { From ee693a8d2b7d55e2454eedffc256dee50a9c7ab4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Dec 2021 17:52:34 -0700 Subject: [PATCH 3/4] 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; + } + } + } + } } From 5ae46709b0e11f05fa22417b8a3143678741ccb7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Dec 2021 17:58:44 -0700 Subject: [PATCH 4/4] Fix alignment of blocks adjacent to other blocks Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map/block_map.rs | 4 ++-- crates/editor/src/element.rs | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 5aa88a88c4af182242e57f2fd5b9834911db9ab8..58ae22403df48903b4ef4a45957340c2c0c27d3d 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -827,8 +827,8 @@ impl AlignedBlock { self.render.lock()(cx) } - pub fn disposition(&self) -> BlockDisposition { - self.block.disposition + pub fn position(&self) -> &Anchor { + &self.block.position } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b50aa2d9c55d8d903e9d7ff06674f80fdd965647..db16b3f01d5c04add650a3116a9812f809da1b72 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1,4 +1,4 @@ -use crate::display_map::{BlockContext, BlockDisposition}; +use crate::display_map::{BlockContext, ToDisplayPoint}; use super::{ DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select, @@ -19,7 +19,7 @@ use gpui::{ MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, }; use json::json; -use language::Chunk; +use language::{Chunk, ToPoint}; use smallvec::SmallVec; use std::{ cmp::{self, Ordering}, @@ -633,10 +633,11 @@ impl EditorElement { snapshot .blocks_in_range(rows.clone()) .map(|(start_row, block)| { - let anchor_row = match block.disposition() { - BlockDisposition::Above => start_row + block.height(), - BlockDisposition::Below => start_row - 1, - }; + let anchor_row = block + .position() + .to_point(&snapshot.buffer_snapshot) + .to_display_point(snapshot) + .row(); let anchor_x = if rows.contains(&anchor_row) { line_layouts[(anchor_row - rows.start) as usize]