Maintain row edits since last sync in WrapMap

This commit is contained in:
Antonio Scandurra 2021-11-12 17:05:49 +01:00
parent 4e32fabfdc
commit 3f11b8af56

View file

@ -1,5 +1,6 @@
use super::{
fold_map,
patch::Patch,
tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary},
};
use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task};
@ -14,8 +15,8 @@ pub type Edit = buffer::Edit<u32>;
pub struct WrapMap {
snapshot: Snapshot,
pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>,
interpolated_edits: Vec<Vec<Edit>>,
edits_since_sync: Vec<Vec<Edit>>,
interpolated_edits: Patch,
edits_since_sync: Patch,
wrap_width: Option<f32>,
background_task: Option<Task<()>>,
font: (FontId, f32),
@ -89,7 +90,7 @@ impl WrapMap {
background_task: None,
};
this.set_wrap_width(wrap_width, cx);
mem::take(&mut this.edits_since_sync);
this
}
@ -103,7 +104,7 @@ impl WrapMap {
tab_snapshot: TabSnapshot,
edits: Vec<TabEdit>,
cx: &mut ModelContext<Self>,
) -> (Snapshot, Vec<Vec<Edit>>) {
) -> (Snapshot, Patch) {
self.pending_edits.push_back((tab_snapshot, edits));
self.flush_edits(cx);
(self.snapshot.clone(), mem::take(&mut self.edits_since_sync))
@ -128,6 +129,8 @@ impl WrapMap {
fn rewrap(&mut self, cx: &mut ModelContext<Self>) {
self.background_task.take();
self.interpolated_edits.clear();
self.pending_edits.clear();
if let Some(wrap_width) = self.wrap_width {
let mut new_snapshot = self.snapshot.clone();
@ -157,7 +160,7 @@ impl WrapMap {
{
Ok((snapshot, edits)) => {
self.snapshot = snapshot;
self.edits_since_sync.push(edits);
self.edits_since_sync = self.edits_since_sync.compose(&edits);
cx.notify();
}
Err(wrap_task) => {
@ -165,11 +168,10 @@ impl WrapMap {
let (snapshot, edits) = wrap_task.await;
this.update(&mut cx, |this, cx| {
this.snapshot = snapshot;
for mut edits in this.interpolated_edits.drain(..) {
invert(&mut edits);
this.edits_since_sync.push(edits);
}
this.edits_since_sync.push(edits);
this.edits_since_sync = this
.edits_since_sync
.compose(mem::take(&mut this.interpolated_edits).invert())
.compose(&edits);
this.background_task = None;
this.flush_edits(cx);
cx.notify();
@ -188,12 +190,12 @@ impl WrapMap {
}
let new_rows = self.snapshot.transforms.summary().output.lines.row + 1;
self.snapshot.interpolated = false;
self.pending_edits.clear();
self.interpolated_edits.clear();
self.edits_since_sync.push(vec![Edit {
old: 0..old_rows,
new: 0..new_rows,
}]);
self.edits_since_sync = self.edits_since_sync.compose(&unsafe {
Patch::new_unchecked(vec![Edit {
old: 0..old_rows,
new: 0..new_rows,
}])
});
}
}
@ -223,14 +225,14 @@ impl WrapMap {
let update_task = cx.background().spawn(async move {
let mut line_wrapper = font_cache.line_wrapper(font_id, font_size);
let mut output_edits = Vec::new();
for (tab_snapshot, edits) in pending_edits {
let mut edits = Patch::default();
for (tab_snapshot, tab_edits) in pending_edits {
let wrap_edits = snapshot
.update(tab_snapshot, &edits, wrap_width, &mut line_wrapper)
.update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper)
.await;
output_edits.push(wrap_edits);
edits = edits.compose(&wrap_edits);
}
(snapshot, output_edits)
(snapshot, edits)
});
match cx
@ -239,18 +241,17 @@ impl WrapMap {
{
Ok((snapshot, output_edits)) => {
self.snapshot = snapshot;
self.edits_since_sync.extend(output_edits);
self.edits_since_sync = self.edits_since_sync.compose(&output_edits);
}
Err(update_task) => {
self.background_task = Some(cx.spawn(|this, mut cx| async move {
let (snapshot, output_edits) = update_task.await;
let (snapshot, edits) = update_task.await;
this.update(&mut cx, |this, cx| {
this.snapshot = snapshot;
for mut edits in this.interpolated_edits.drain(..) {
invert(&mut edits);
this.edits_since_sync.push(edits);
}
this.edits_since_sync.extend(output_edits);
this.edits_since_sync = this
.edits_since_sync
.compose(mem::take(&mut this.interpolated_edits).invert())
.compose(&edits);
this.background_task = None;
this.flush_edits(cx);
cx.notify();
@ -268,8 +269,8 @@ impl WrapMap {
to_remove_len += 1;
} else {
let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), &edits);
self.edits_since_sync.push(interpolated_edits.clone());
self.interpolated_edits.push(interpolated_edits);
self.edits_since_sync = self.edits_since_sync.compose(&interpolated_edits);
self.interpolated_edits = self.interpolated_edits.compose(&interpolated_edits);
}
}
@ -293,17 +294,20 @@ impl Snapshot {
}
}
fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, edits: &[TabEdit]) -> Vec<Edit> {
fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch {
let mut new_transforms;
if edits.is_empty() {
if tab_edits.is_empty() {
new_transforms = self.transforms.clone();
} else {
let mut old_cursor = self.transforms.cursor::<TabPoint>();
let mut edits = edits.into_iter().peekable();
new_transforms =
old_cursor.slice(&edits.peek().unwrap().old_lines.start, Bias::Right, &());
let mut tab_edits_iter = tab_edits.iter().peekable();
new_transforms = old_cursor.slice(
&tab_edits_iter.peek().unwrap().old_lines.start,
Bias::Right,
&(),
);
while let Some(edit) = edits.next() {
while let Some(edit) = tab_edits_iter.next() {
if edit.new_lines.start > TabPoint::from(new_transforms.summary().input.lines) {
let summary = new_tab_snapshot.text_summary_for_range(
TabPoint::from(new_transforms.summary().input.lines)..edit.new_lines.start,
@ -318,7 +322,7 @@ impl Snapshot {
}
old_cursor.seek_forward(&edit.old_lines.end, Bias::Right, &());
if let Some(next_edit) = edits.peek() {
if let Some(next_edit) = tab_edits_iter.peek() {
if next_edit.old_lines.start > old_cursor.end(&()) {
if old_cursor.end(&()) > edit.old_lines.end {
let summary = self
@ -345,39 +349,44 @@ impl Snapshot {
}
}
self.transforms = new_transforms;
self.tab_snapshot = new_tab_snapshot;
self.interpolated = true;
let old_snapshot = mem::replace(
self,
Snapshot {
tab_snapshot: new_tab_snapshot,
transforms: new_transforms,
interpolated: true,
},
);
self.check_invariants();
todo!()
old_snapshot.compute_edits(tab_edits, self)
}
async fn update(
&mut self,
new_tab_snapshot: TabSnapshot,
edits: &[TabEdit],
tab_edits: &[TabEdit],
wrap_width: f32,
line_wrapper: &mut LineWrapper,
) -> Vec<Edit> {
) -> Patch {
#[derive(Debug)]
struct RowEdit {
old_rows: Range<u32>,
new_rows: Range<u32>,
}
let mut edits = edits.into_iter().peekable();
let mut tab_edits_iter = tab_edits.into_iter().peekable();
let mut row_edits = Vec::new();
while let Some(edit) = edits.next() {
while let Some(edit) = tab_edits_iter.next() {
let mut row_edit = RowEdit {
old_rows: edit.old_lines.start.row()..edit.old_lines.end.row() + 1,
new_rows: edit.new_lines.start.row()..edit.new_lines.end.row() + 1,
};
while let Some(next_edit) = edits.peek() {
while let Some(next_edit) = tab_edits_iter.peek() {
if next_edit.old_lines.start.row() <= row_edit.old_rows.end {
row_edit.old_rows.end = next_edit.old_lines.end.row() + 1;
row_edit.new_rows.end = next_edit.new_lines.end.row() + 1;
edits.next();
tab_edits_iter.next();
} else {
break;
}
@ -484,11 +493,52 @@ impl Snapshot {
}
}
self.transforms = new_transforms;
self.tab_snapshot = new_tab_snapshot;
self.interpolated = false;
let old_snapshot = mem::replace(
self,
Snapshot {
tab_snapshot: new_tab_snapshot,
transforms: new_transforms,
interpolated: false,
},
);
self.check_invariants();
todo!()
old_snapshot.compute_edits(tab_edits, self)
}
fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &Snapshot) -> Patch {
let mut wrap_edits = Vec::new();
let mut old_cursor = self.transforms.cursor::<TransformSummary>();
let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>();
for mut tab_edit in tab_edits.iter().cloned() {
tab_edit.old_lines.start.0.column = 0;
tab_edit.old_lines.end.0 += Point::new(1, 0);
tab_edit.new_lines.start.0.column = 0;
tab_edit.new_lines.end.0 += Point::new(1, 0);
old_cursor.seek(&tab_edit.old_lines.start, Bias::Right, &());
let mut old_start = old_cursor.start().output.lines;
old_start += tab_edit.old_lines.start.0 - old_cursor.start().input.lines;
old_cursor.seek(&tab_edit.old_lines.end, Bias::Right, &());
let mut old_end = old_cursor.start().output.lines;
old_end += tab_edit.old_lines.end.0 - old_cursor.start().input.lines;
new_cursor.seek(&tab_edit.new_lines.start, Bias::Right, &());
let mut new_start = new_cursor.start().output.lines;
new_start += tab_edit.new_lines.start.0 - new_cursor.start().input.lines;
new_cursor.seek(&tab_edit.new_lines.end, Bias::Right, &());
let mut new_end = new_cursor.start().output.lines;
new_end += tab_edit.new_lines.end.0 - new_cursor.start().input.lines;
wrap_edits.push(Edit {
old: old_start.row..old_end.row,
new: new_start.row..new_end.row,
});
}
consolidate_wrap_edits(&mut wrap_edits);
unsafe { Patch::new_unchecked(wrap_edits) }
}
pub fn chunks_at(&self, wrap_row: u32) -> Chunks {
@ -921,6 +971,12 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
}
}
impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for TabPoint {
fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering {
Ord::cmp(&self.0, &cursor_location.input.lines)
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += summary.output.lines;
@ -933,6 +989,21 @@ fn invert(edits: &mut Vec<Edit>) {
}
}
fn consolidate_wrap_edits(edits: &mut Vec<Edit>) {
let mut i = 1;
while i < edits.len() {
let edit = edits[i].clone();
let prev_edit = &mut edits[i - 1];
if prev_edit.old.end >= edit.old.start {
prev_edit.old.end = edit.old.end;
prev_edit.new.end = edit.new.end;
edits.remove(i);
continue;
}
i += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -940,9 +1011,10 @@ mod tests {
display_map::{fold_map::FoldMap, tab_map::TabMap},
test::Observer,
};
use buffer::Rope;
use language::{Buffer, RandomCharIter};
use rand::prelude::*;
use std::env;
use std::{cmp, env};
#[gpui::test(iterations = 100)]
async fn test_random_wraps(mut cx: gpui::TestAppContext, mut rng: StdRng) {
@ -999,9 +1071,9 @@ mod tests {
notifications.recv().await.unwrap();
}
let (snapshot, _) =
let (initial_snapshot, _) =
wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx));
let actual_text = snapshot.text();
let actual_text = initial_snapshot.text();
assert_eq!(
actual_text, expected_text,
"unwrapped text is: {:?}",
@ -1009,6 +1081,7 @@ mod tests {
);
log::info!("Wrapped text: {:?}", actual_text);
let mut edits = Vec::new();
for _i in 0..operations {
match rng.gen_range(0..=100) {
0..=19 => {
@ -1021,14 +1094,15 @@ mod tests {
wrap_map.update(&mut cx, |map, cx| map.set_wrap_width(wrap_width, cx));
}
20..=39 => {
for (folds_snapshot, edits) in
for (folds_snapshot, fold_edits) in
cx.read(|cx| fold_map.randomly_mutate(&mut rng, cx))
{
let (tabs_snapshot, edits) = tab_map.sync(folds_snapshot, edits);
let (mut snapshot, _) =
wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, edits, cx));
let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
let (mut snapshot, wrap_edits) = wrap_map
.update(&mut cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
snapshot.check_invariants();
snapshot.verify_chunks(&mut rng);
edits.push((snapshot, wrap_edits));
}
}
_ => {
@ -1040,21 +1114,22 @@ mod tests {
"Unwrapped text (no folds): {:?}",
buffer.read_with(&cx, |buf, _| buf.text())
);
let (folds_snapshot, edits) = cx.read(|cx| fold_map.read(cx));
let (folds_snapshot, fold_edits) = cx.read(|cx| fold_map.read(cx));
log::info!(
"Unwrapped text (unexpanded tabs): {:?}",
folds_snapshot.text()
);
let (tabs_snapshot, edits) = tab_map.sync(folds_snapshot, edits);
let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
log::info!("Unwrapped text (expanded tabs): {:?}", tabs_snapshot.text());
let unwrapped_text = tabs_snapshot.text();
let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
let (mut snapshot, _) = wrap_map.update(&mut cx, |map, cx| {
map.sync(tabs_snapshot.clone(), edits, cx)
let (mut snapshot, wrap_edits) = wrap_map.update(&mut cx, |map, cx| {
map.sync(tabs_snapshot.clone(), tab_edits, cx)
});
snapshot.check_invariants();
snapshot.verify_chunks(&mut rng);
edits.push((snapshot, wrap_edits));
if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) {
log::info!("Waiting for wrapping to finish");
@ -1064,12 +1139,13 @@ mod tests {
}
if !wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) {
let (mut wrapped_snapshot, _) =
let (mut wrapped_snapshot, wrap_edits) =
wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx));
let actual_text = wrapped_snapshot.text();
log::info!("Wrapping finished: {:?}", actual_text);
wrapped_snapshot.check_invariants();
wrapped_snapshot.verify_chunks(&mut rng);
edits.push((wrapped_snapshot, wrap_edits));
assert_eq!(
actual_text, expected_text,
"unwrapped text is: {:?}",
@ -1077,6 +1153,29 @@ mod tests {
);
}
}
let mut initial_text = Rope::from(initial_snapshot.text().as_str());
for (snapshot, edits) in edits {
let snapshot_text = Rope::from(snapshot.text().as_str());
for edit in &edits {
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),
initial_text.max_point(),
));
let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0));
let new_end = snapshot_text.point_to_offset(cmp::min(
Point::new(edit.new.end, 0),
snapshot_text.max_point(),
));
let new_text = snapshot_text
.chunks_in_range(new_start..new_end)
.collect::<String>();
initial_text.replace(old_start..old_end, &new_text);
}
assert_eq!(initial_text.to_string(), snapshot_text.to_string());
}
}
fn wrap_text(