diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index f507e501d3..b7d1063980 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -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; pub struct WrapMap { snapshot: Snapshot, pending_edits: VecDeque<(TabSnapshot, Vec)>, - interpolated_edits: Vec>, - edits_since_sync: Vec>, + interpolated_edits: Patch, + edits_since_sync: Patch, wrap_width: Option, background_task: Option>, 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, cx: &mut ModelContext, - ) -> (Snapshot, Vec>) { + ) -> (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.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 { + 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::(); - 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 { + ) -> Patch { #[derive(Debug)] struct RowEdit { old_rows: Range, new_rows: Range, } - 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::(); + let mut new_cursor = new_snapshot.transforms.cursor::(); + 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) { } } +fn consolidate_wrap_edits(edits: &mut Vec) { + 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::(); + + initial_text.replace(old_start..old_end, &new_text); + } + assert_eq!(initial_text.to_string(), snapshot_text.to_string()); + } } fn wrap_text(