@@ -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,
@@ -11,7 +11,9 @@ struct BlockMap {
struct BlockMapWriter<'a>(&'a mut BlockMap);
-struct BlockSnapshot {}
+struct BlockSnapshot {
+ transforms: SumTree<Transform>,
+}
#[derive(Clone)]
struct Transform {
@@ -42,7 +44,9 @@ impl BlockMap {
fn read(&self, wrap_snapshot: WrapSnapshot, edits: Vec<WrapEdit>) -> BlockSnapshot {
self.sync(wrap_snapshot, edits);
- BlockSnapshot {}
+ BlockSnapshot {
+ transforms: self.transforms.lock().clone(),
+ }
}
fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Vec<WrapEdit>) -> BlockMapWriter {
@@ -51,7 +55,7 @@ impl BlockMap {
}
fn sync(&self, wrap_snapshot: WrapSnapshot, edits: Vec<WrapEdit>) {
- let transforms = self.transforms.lock();
+ let mut transforms = self.transforms.lock();
let mut new_transforms = SumTree::new();
let mut cursor = transforms.cursor::<InputRow>();
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::<String>();
+ 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
+ );
+ }
+ }
+}
@@ -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<f32>,
- cx: &mut ModelContext<Self>,
- ) -> 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<Self>, 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<TabEdit>,
cx: &mut ModelContext<Self>,
- ) -> (Snapshot, Patch) {
+ ) -> (Snapshot, Vec<Edit>) {
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<Self>) {
@@ -983,12 +993,6 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
}
}
-fn invert(edits: &mut Vec<Edit>) {
- for edit in edits {
- mem::swap(&mut edit.old, &mut edit.new);
- }
-}
-
fn consolidate_wrap_edits(edits: &mut Vec<Edit>) {
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()
}