Start on a randomized test for BlockMap
This is currently passing and ensures we maintain the input coordinate space correctly. Co-Authored-By: Max Brunsfeld <max@zed.dev>
This commit is contained in:
parent
d721c2ba4b
commit
c8e47a8c63
4 changed files with 117 additions and 47 deletions
|
@ -39,8 +39,7 @@ impl DisplayMap {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx);
|
let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx);
|
||||||
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
|
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
|
||||||
let wrap_map =
|
let (wrap_map, _) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx);
|
||||||
cx.add_model(|cx| WrapMap::new(snapshot, font_id, font_size, wrap_width, cx));
|
|
||||||
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
|
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
|
||||||
DisplayMap {
|
DisplayMap {
|
||||||
buffer,
|
buffer,
|
||||||
|
|
|
@ -11,7 +11,9 @@ struct BlockMap {
|
||||||
|
|
||||||
struct BlockMapWriter<'a>(&'a mut BlockMap);
|
struct BlockMapWriter<'a>(&'a mut BlockMap);
|
||||||
|
|
||||||
struct BlockSnapshot {}
|
struct BlockSnapshot {
|
||||||
|
transforms: SumTree<Transform>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Transform {
|
struct Transform {
|
||||||
|
@ -42,7 +44,9 @@ impl BlockMap {
|
||||||
|
|
||||||
fn read(&self, wrap_snapshot: WrapSnapshot, edits: Vec<WrapEdit>) -> BlockSnapshot {
|
fn read(&self, wrap_snapshot: WrapSnapshot, edits: Vec<WrapEdit>) -> BlockSnapshot {
|
||||||
self.sync(wrap_snapshot, edits);
|
self.sync(wrap_snapshot, edits);
|
||||||
BlockSnapshot {}
|
BlockSnapshot {
|
||||||
|
transforms: self.transforms.lock().clone(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Vec<WrapEdit>) -> BlockMapWriter {
|
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>) {
|
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 new_transforms = SumTree::new();
|
||||||
let mut cursor = transforms.cursor::<InputRow>();
|
let mut cursor = transforms.cursor::<InputRow>();
|
||||||
let mut edits = edits.into_iter().peekable();
|
let mut edits = edits.into_iter().peekable();
|
||||||
|
@ -76,8 +80,9 @@ impl BlockMap {
|
||||||
|
|
||||||
if let Some(next_edit) = edits.peek() {
|
if let Some(next_edit) = edits.peek() {
|
||||||
if edit.old.end >= next_edit.old.start {
|
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.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();
|
edits.next();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
@ -98,6 +103,8 @@ impl BlockMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new_transforms.push_tree(cursor.suffix(&()), &());
|
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;
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{cmp, mem, slice};
|
use std::{cmp, mem};
|
||||||
|
|
||||||
type Edit = buffer::Edit<u32>;
|
type Edit = buffer::Edit<u32>;
|
||||||
|
|
||||||
|
@ -10,6 +10,10 @@ impl Patch {
|
||||||
Self(edits)
|
Self(edits)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(self) -> Vec<Edit> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
pub fn compose(&self, other: &Self) -> Self {
|
pub fn compose(&self, other: &Self) -> Self {
|
||||||
let mut old_edits_iter = self.0.iter().cloned().peekable();
|
let mut old_edits_iter = self.0.iter().cloned().peekable();
|
||||||
let mut new_edits_iter = other.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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -3,7 +3,10 @@ use super::{
|
||||||
patch::Patch,
|
patch::Patch,
|
||||||
tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary},
|
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 language::{HighlightedChunk, Point};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use smol::future::yield_now;
|
use smol::future::yield_now;
|
||||||
|
@ -78,8 +81,9 @@ impl WrapMap {
|
||||||
font_id: FontId,
|
font_id: FontId,
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
wrap_width: Option<f32>,
|
wrap_width: Option<f32>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut MutableAppContext,
|
||||||
) -> Self {
|
) -> (ModelHandle<Self>, Snapshot) {
|
||||||
|
let handle = cx.add_model(|cx| {
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
font: (font_id, font_size),
|
font: (font_id, font_size),
|
||||||
wrap_width: None,
|
wrap_width: None,
|
||||||
|
@ -92,6 +96,9 @@ impl WrapMap {
|
||||||
this.set_wrap_width(wrap_width, cx);
|
this.set_wrap_width(wrap_width, cx);
|
||||||
mem::take(&mut this.edits_since_sync);
|
mem::take(&mut this.edits_since_sync);
|
||||||
this
|
this
|
||||||
|
});
|
||||||
|
let snapshot = handle.read(cx).snapshot.clone();
|
||||||
|
(handle, snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -104,10 +111,13 @@ impl WrapMap {
|
||||||
tab_snapshot: TabSnapshot,
|
tab_snapshot: TabSnapshot,
|
||||||
edits: Vec<TabEdit>,
|
edits: Vec<TabEdit>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> (Snapshot, Patch) {
|
) -> (Snapshot, Vec<Edit>) {
|
||||||
self.pending_edits.push_back((tab_snapshot, edits));
|
self.pending_edits.push_back((tab_snapshot, edits));
|
||||||
self.flush_edits(cx);
|
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>) {
|
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>) {
|
fn consolidate_wrap_edits(edits: &mut Vec<Edit>) {
|
||||||
let mut i = 1;
|
let mut i = 1;
|
||||||
while i < edits.len() {
|
while i < edits.len() {
|
||||||
|
@ -1062,17 +1066,14 @@ mod tests {
|
||||||
let unwrapped_text = tabs_snapshot.text();
|
let unwrapped_text = tabs_snapshot.text();
|
||||||
let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
|
let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
|
||||||
|
|
||||||
let wrap_map = cx.add_model(|cx| {
|
let (wrap_map, initial_snapshot) =
|
||||||
WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx)
|
cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx));
|
||||||
});
|
|
||||||
let (_observer, notifications) = Observer::new(&wrap_map, &mut cx);
|
let (_observer, notifications) = Observer::new(&wrap_map, &mut cx);
|
||||||
|
|
||||||
if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) {
|
if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) {
|
||||||
notifications.recv().await.unwrap();
|
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();
|
let actual_text = initial_snapshot.text();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual_text, expected_text,
|
actual_text, expected_text,
|
||||||
|
@ -1155,9 +1156,9 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut initial_text = Rope::from(initial_snapshot.text().as_str());
|
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());
|
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_start = initial_text.point_to_offset(Point::new(edit.new.start, 0));
|
||||||
let old_end = initial_text.point_to_offset(cmp::min(
|
let old_end = initial_text.point_to_offset(cmp::min(
|
||||||
Point::new(edit.new.start + edit.old.len() as u32, 0),
|
Point::new(edit.new.start + edit.old.len() as u32, 0),
|
||||||
|
@ -1206,7 +1207,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Snapshot {
|
impl Snapshot {
|
||||||
fn text(&self) -> String {
|
pub fn text(&self) -> String {
|
||||||
self.chunks_at(0).collect()
|
self.chunks_at(0).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue