diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index d7f49f68f7..984531af98 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -39,8 +39,7 @@ impl DisplayMap { ) -> Self { let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx); let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); - let wrap_map = - cx.add_model(|cx| WrapMap::new(snapshot, font_id, font_size, wrap_width, cx)); + let (wrap_map, _) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx); cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach(); DisplayMap { buffer, diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 44355adc69..95d3339c3e 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -11,7 +11,9 @@ struct BlockMap { struct BlockMapWriter<'a>(&'a mut BlockMap); -struct BlockSnapshot {} +struct BlockSnapshot { + transforms: SumTree, +} #[derive(Clone)] struct Transform { @@ -42,7 +44,9 @@ impl BlockMap { fn read(&self, wrap_snapshot: WrapSnapshot, edits: Vec) -> BlockSnapshot { self.sync(wrap_snapshot, edits); - BlockSnapshot {} + BlockSnapshot { + transforms: self.transforms.lock().clone(), + } } fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Vec) -> BlockMapWriter { @@ -51,7 +55,7 @@ impl BlockMap { } fn sync(&self, wrap_snapshot: WrapSnapshot, edits: Vec) { - let transforms = self.transforms.lock(); + let mut transforms = self.transforms.lock(); let mut new_transforms = SumTree::new(); let mut cursor = transforms.cursor::(); let mut edits = edits.into_iter().peekable(); @@ -76,8 +80,9 @@ impl BlockMap { if let Some(next_edit) = edits.peek() { if edit.old.end >= next_edit.old.start { + let delta = next_edit.new.len() as i32 - next_edit.old.len() as i32; edit.old.end = cmp::max(next_edit.old.end, edit.old.end); - edit.new.end += (edit.new.len() as i32 - edit.old.len() as i32) as u32; + edit.new.end = (edit.new.end as i32 + delta) as u32; edits.next(); } else { break; @@ -98,6 +103,8 @@ impl BlockMap { } } new_transforms.push_tree(cursor.suffix(&()), &()); + drop(cursor); + *transforms = new_transforms; } } @@ -140,3 +147,71 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for OutputRow { self.0 += summary.output_rows; } } + +#[cfg(test)] +mod tests { + use super::BlockMap; + use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; + use buffer::RandomCharIter; + use language::Buffer; + use rand::prelude::*; + use std::env; + + #[gpui::test(iterations = 100)] + fn test_random(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 = Some(rng.gen_range(0.0..=1000.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!("Tab size: {}", tab_size); + 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::(); + Buffer::new(0, text, cx) + }); + let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx); + 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 block_map = BlockMap::new(wraps_snapshot); + + for _ in 0..operations { + 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..=1000.0)) + }; + log::info!("Setting wrap width to {:?}", wrap_width); + wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); + } + _ => { + buffer.update(cx, |buffer, _| buffer.randomly_edit(&mut rng, 5)); + } + } + + let (folds_snapshot, fold_edits) = fold_map.read(cx); + 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 blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits); + assert_eq!( + blocks_snapshot.transforms.summary().input_rows, + wraps_snapshot.max_point().row() + 1 + ); + } + } +} diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index da78a7e2df..1fa940d91c 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -1,4 +1,4 @@ -use std::{cmp, mem, slice}; +use std::{cmp, mem}; type Edit = buffer::Edit; @@ -10,6 +10,10 @@ impl Patch { Self(edits) } + pub fn into_inner(self) -> Vec { + self.0 + } + pub fn compose(&self, other: &Self) -> Self { let mut old_edits_iter = self.0.iter().cloned().peekable(); let mut new_edits_iter = other.0.iter().cloned().peekable(); @@ -167,15 +171,6 @@ impl Patch { } } -impl<'a> IntoIterator for &'a Patch { - type Item = &'a Edit; - type IntoIter = slice::Iter<'a, Edit>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index b7d1063980..6faadc3d25 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -3,7 +3,10 @@ use super::{ patch::Patch, tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary}, }; -use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task}; +use gpui::{ + fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext, + Task, +}; use language::{HighlightedChunk, Point}; use lazy_static::lazy_static; use smol::future::yield_now; @@ -78,20 +81,24 @@ impl WrapMap { font_id: FontId, font_size: f32, wrap_width: Option, - cx: &mut ModelContext, - ) -> Self { - let mut this = Self { - font: (font_id, font_size), - wrap_width: None, - pending_edits: Default::default(), - interpolated_edits: Default::default(), - edits_since_sync: Default::default(), - snapshot: Snapshot::new(tab_snapshot), - background_task: None, - }; - this.set_wrap_width(wrap_width, cx); - mem::take(&mut this.edits_since_sync); - this + cx: &mut MutableAppContext, + ) -> (ModelHandle, Snapshot) { + let handle = cx.add_model(|cx| { + let mut this = Self { + font: (font_id, font_size), + wrap_width: None, + pending_edits: Default::default(), + interpolated_edits: Default::default(), + edits_since_sync: Default::default(), + snapshot: Snapshot::new(tab_snapshot), + background_task: None, + }; + this.set_wrap_width(wrap_width, cx); + mem::take(&mut this.edits_since_sync); + this + }); + let snapshot = handle.read(cx).snapshot.clone(); + (handle, snapshot) } #[cfg(test)] @@ -104,10 +111,13 @@ impl WrapMap { tab_snapshot: TabSnapshot, edits: Vec, cx: &mut ModelContext, - ) -> (Snapshot, Patch) { + ) -> (Snapshot, Vec) { self.pending_edits.push_back((tab_snapshot, edits)); self.flush_edits(cx); - (self.snapshot.clone(), mem::take(&mut self.edits_since_sync)) + ( + self.snapshot.clone(), + mem::take(&mut self.edits_since_sync).into_inner(), + ) } pub fn set_font(&mut self, font_id: FontId, font_size: f32, cx: &mut ModelContext) { @@ -983,12 +993,6 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint { } } -fn invert(edits: &mut Vec) { - for edit in edits { - mem::swap(&mut edit.old, &mut edit.new); - } -} - fn consolidate_wrap_edits(edits: &mut Vec) { let mut i = 1; while i < edits.len() { @@ -1062,17 +1066,14 @@ mod tests { let unwrapped_text = tabs_snapshot.text(); let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); - let wrap_map = cx.add_model(|cx| { - WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx) - }); + let (wrap_map, initial_snapshot) = + cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx)); let (_observer, notifications) = Observer::new(&wrap_map, &mut cx); if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) { notifications.recv().await.unwrap(); } - let (initial_snapshot, _) = - wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx)); let actual_text = initial_snapshot.text(); assert_eq!( actual_text, expected_text, @@ -1155,9 +1156,9 @@ mod tests { } let mut initial_text = Rope::from(initial_snapshot.text().as_str()); - for (snapshot, edits) in edits { + for (snapshot, patch) in edits { let snapshot_text = Rope::from(snapshot.text().as_str()); - for edit in &edits { + for edit in &patch { let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0)); let old_end = initial_text.point_to_offset(cmp::min( Point::new(edit.new.start + edit.old.len() as u32, 0), @@ -1206,7 +1207,7 @@ mod tests { } impl Snapshot { - fn text(&self) -> String { + pub fn text(&self) -> String { self.chunks_at(0).collect() }