From faba276fdce0f7785b3242f90669c517f22e8ddf Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 30 Nov 2021 17:23:02 -0800 Subject: [PATCH] WIP - maintain foldmap with Buffer::edits_since Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 52 ++++- crates/editor/src/display_map/block_map.rs | 17 +- crates/editor/src/display_map/fold_map.rs | 183 ++++++++---------- crates/editor/src/display_map/tab_map.rs | 2 +- crates/editor/src/display_map/wrap_map.rs | 21 +- crates/editor/src/editor.rs | 4 +- crates/language/src/buffer.rs | 10 +- crates/language/src/tests.rs | 27 ++- crates/project/src/worktree.rs | 31 ++- .../src/display_map => text/src}/patch.rs | 74 ++++--- crates/text/src/text.rs | 21 +- 11 files changed, 279 insertions(+), 163 deletions(-) rename crates/{editor/src/display_map => text/src}/patch.rs (88%) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index bbf03637e1..71b6c72a3d 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1,6 +1,5 @@ mod block_map; mod fold_map; -mod patch; mod tab_map; mod wrap_map; @@ -11,9 +10,11 @@ use gpui::{ fonts::{FontId, HighlightStyle}, AppContext, Entity, ModelContext, ModelHandle, }; -use language::{Anchor, Buffer, Point, ToOffset, ToPoint}; +use language::{Anchor, Buffer, Patch, Point, ToOffset, ToPoint}; +use parking_lot::Mutex; use std::{ collections::{HashMap, HashSet}, + mem, ops::Range, }; use sum_tree::Bias; @@ -32,6 +33,7 @@ pub struct DisplayMap { tab_map: TabMap, wrap_map: ModelHandle, block_map: BlockMap, + edits_since_sync: Mutex>, } impl Entity for DisplayMap { @@ -52,17 +54,22 @@ impl DisplayMap { let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx); let block_map = BlockMap::new(buffer.clone(), snapshot); cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach(); + cx.subscribe(&buffer, Self::handle_buffer_event).detach(); DisplayMap { buffer, fold_map, tab_map, wrap_map, block_map, + edits_since_sync: Default::default(), } } pub fn snapshot(&self, cx: &mut ModelContext) -> DisplayMapSnapshot { - let (folds_snapshot, edits) = self.fold_map.read(cx); + let (folds_snapshot, edits) = self.fold_map.read( + mem::take(&mut *self.edits_since_sync.lock()).into_inner(), + cx, + ); let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits); let (wraps_snapshot, edits) = self .wrap_map @@ -83,7 +90,10 @@ impl DisplayMap { ranges: impl IntoIterator>, cx: &mut ModelContext, ) { - let (mut fold_map, snapshot, edits) = self.fold_map.write(cx); + let (mut fold_map, snapshot, edits) = self.fold_map.write( + mem::take(&mut *self.edits_since_sync.lock()).into_inner(), + cx, + ); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map @@ -102,7 +112,10 @@ impl DisplayMap { ranges: impl IntoIterator>, cx: &mut ModelContext, ) { - let (mut fold_map, snapshot, edits) = self.fold_map.write(cx); + let (mut fold_map, snapshot, edits) = self.fold_map.write( + mem::take(&mut *self.edits_since_sync.lock()).into_inner(), + cx, + ); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map @@ -125,7 +138,10 @@ impl DisplayMap { P: ToOffset + Clone, T: Into + Clone, { - let (snapshot, edits) = self.fold_map.read(cx); + let (snapshot, edits) = self.fold_map.read( + mem::take(&mut *self.edits_since_sync.lock()).into_inner(), + cx, + ); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map @@ -143,7 +159,10 @@ impl DisplayMap { } pub fn remove_blocks(&mut self, ids: HashSet, cx: &mut ModelContext) { - let (snapshot, edits) = self.fold_map.read(cx); + let (snapshot, edits) = self.fold_map.read( + mem::take(&mut *self.edits_since_sync.lock()).into_inner(), + cx, + ); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map @@ -166,6 +185,25 @@ impl DisplayMap { pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool { self.wrap_map.read(cx).is_rewrapping() } + + fn handle_buffer_event( + &mut self, + _: ModelHandle, + event: &language::Event, + _: &mut ModelContext, + ) { + match event { + language::Event::Edited(patch) => { + self.fold_map.version += 1; + let mut edits_since_sync = self.edits_since_sync.lock(); + *edits_since_sync = edits_since_sync.compose(patch); + } + language::Event::Reparsed | language::Event::DiagnosticsUpdated => { + self.fold_map.version += 1; + } + _ => {} + } + } } pub struct DisplayMapSnapshot { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index d28d8d7efd..0800f8d1e7 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1228,11 +1228,13 @@ mod tests { // Insert a line break, separating two block decorations into separate // lines. - buffer.update(cx, |buffer, cx| { - buffer.edit([Point::new(1, 1)..Point::new(1, 1)], "!!!\n", cx) + let buffer_edits = buffer.update(cx, |buffer, cx| { + let v0 = buffer.version(); + buffer.edit([Point::new(1, 1)..Point::new(1, 1)], "!!!\n", cx); + buffer.edits_since(&v0).collect() }); - let (folds_snapshot, fold_edits) = fold_map.read(cx); + let (folds_snapshot, fold_edits) = fold_map.read(buffer_edits, 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) @@ -1325,6 +1327,7 @@ mod tests { let mut expected_blocks = Vec::new(); for _ in 0..operations { + let mut buffer_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=19 => { let wrap_width = if rng.gen_bool(0.2) { @@ -1375,7 +1378,7 @@ mod tests { }) .collect::>(); - let (folds_snapshot, fold_edits) = fold_map.read(cx); + let (folds_snapshot, fold_edits) = fold_map.read(vec![], 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) @@ -1396,7 +1399,7 @@ mod tests { }) .collect(); - let (folds_snapshot, fold_edits) = fold_map.read(cx); + let (folds_snapshot, fold_edits) = fold_map.read(vec![], 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) @@ -1406,13 +1409,15 @@ mod tests { } _ => { buffer.update(cx, |buffer, _| { + let v0 = buffer.version(); buffer.randomly_edit(&mut rng, 1); log::info!("buffer text: {:?}", buffer.text()); + buffer_edits.extend(buffer.edits_since(&v0)); }); } } - let (folds_snapshot, fold_edits) = fold_map.read(cx); + let (folds_snapshot, fold_edits) = fold_map.read(buffer_edits, 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) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 16ce634e1d..4ca74ad906 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1,11 +1,12 @@ use gpui::{AppContext, ModelHandle}; -use language::{Anchor, AnchorRangeExt, Buffer, Chunk, Point, PointUtf16, TextSummary, ToOffset}; +use language::{ + Anchor, AnchorRangeExt, Buffer, Chunk, Edit, Point, PointUtf16, TextSummary, ToOffset, +}; use parking_lot::Mutex; use std::{ cmp::{self, Ordering}, - iter, mem, + iter, ops::Range, - sync::atomic::{AtomicUsize, Ordering::SeqCst}, }; use sum_tree::{Bias, Cursor, FilterCursor, SumTree}; use theme::SyntaxTheme; @@ -131,12 +132,12 @@ impl<'a> FoldMapWriter<'a> { }; consolidate_buffer_edits(&mut edits); - let edits = self.0.apply_edits(edits, cx); + let edits = self.0.sync(edits, cx); let snapshot = Snapshot { transforms: self.0.transforms.lock().clone(), folds: self.0.folds.clone(), buffer_snapshot: self.0.buffer.read(cx).snapshot(), - version: self.0.version.load(SeqCst), + version: self.0.version, }; (snapshot, edits) } @@ -150,7 +151,7 @@ impl<'a> FoldMapWriter<'a> { let mut fold_ixs_to_delete = Vec::new(); let buffer = self.0.buffer.read(cx).snapshot(); for range in ranges.into_iter() { - // Remove intersecting folds and add their ranges to edits that are passed to apply_edits. + // Remove intersecting folds and add their ranges to edits that are passed to sync. let mut folds_cursor = intersecting_folds(&buffer, &self.0.folds, range, true); while let Some(fold) = folds_cursor.item() { let offset_range = fold.0.start.to_offset(&buffer)..fold.0.end.to_offset(&buffer); @@ -178,12 +179,12 @@ impl<'a> FoldMapWriter<'a> { }; consolidate_buffer_edits(&mut edits); - let edits = self.0.apply_edits(edits, cx); + let edits = self.0.sync(edits, cx); let snapshot = Snapshot { transforms: self.0.transforms.lock().clone(), folds: self.0.folds.clone(), buffer_snapshot: self.0.buffer.read(cx).snapshot(), - version: self.0.version.load(SeqCst), + version: self.0.version, }; (snapshot, edits) } @@ -193,15 +194,7 @@ pub struct FoldMap { buffer: ModelHandle, transforms: Mutex>, folds: SumTree, - last_sync: Mutex, - version: AtomicUsize, -} - -#[derive(Clone)] -struct SyncState { - version: clock::Global, - parse_count: usize, - diagnostics_update_count: usize, + pub version: usize, } impl FoldMap { @@ -220,60 +213,33 @@ impl FoldMap { }, &(), )), - last_sync: Mutex::new(SyncState { - version: buffer.version(), - parse_count: buffer.parse_count(), - diagnostics_update_count: buffer.diagnostics_update_count(), - }), - version: AtomicUsize::new(0), + version: 0, }; - let (snapshot, _) = this.read(cx); + let (snapshot, _) = this.read(Vec::new(), cx); (this, snapshot) } - pub fn read(&self, cx: &AppContext) -> (Snapshot, Vec) { - let edits = self.sync(cx); + pub fn read(&self, edits: Vec>, cx: &AppContext) -> (Snapshot, Vec) { + let edits = self.sync(edits, cx); self.check_invariants(cx); let snapshot = Snapshot { transforms: self.transforms.lock().clone(), folds: self.folds.clone(), buffer_snapshot: self.buffer.read(cx).snapshot(), - version: self.version.load(SeqCst), + version: self.version, }; (snapshot, edits) } - pub fn write(&mut self, cx: &AppContext) -> (FoldMapWriter, Snapshot, Vec) { - let (snapshot, edits) = self.read(cx); + pub fn write( + &mut self, + edits: Vec>, + cx: &AppContext, + ) -> (FoldMapWriter, Snapshot, Vec) { + let (snapshot, edits) = self.read(edits, cx); (FoldMapWriter(self), snapshot, edits) } - fn sync(&self, cx: &AppContext) -> Vec { - let buffer = self.buffer.read(cx); - let last_sync = mem::replace( - &mut *self.last_sync.lock(), - SyncState { - version: buffer.version(), - parse_count: buffer.parse_count(), - diagnostics_update_count: buffer.diagnostics_update_count(), - }, - ); - let edits = buffer - .edits_since(&last_sync.version) - .map(Into::into) - .collect::>(); - if edits.is_empty() { - if last_sync.parse_count != buffer.parse_count() - || last_sync.diagnostics_update_count != buffer.diagnostics_update_count() - { - self.version.fetch_add(1, SeqCst); - } - Vec::new() - } else { - self.apply_edits(edits, cx) - } - } - fn check_invariants(&self, cx: &AppContext) { if cfg!(test) { let buffer = self.buffer.read(cx); @@ -285,7 +251,11 @@ impl FoldMap { } } - fn apply_edits(&self, buffer_edits: Vec>, cx: &AppContext) -> Vec { + fn sync(&self, buffer_edits: Vec>, cx: &AppContext) -> Vec { + if buffer_edits.is_empty() { + return Vec::new(); + } + let buffer = self.buffer.read(cx).snapshot(); let mut buffer_edits_iter = buffer_edits.iter().cloned().peekable(); @@ -479,7 +449,6 @@ impl FoldMap { } *transforms = new_transforms; - self.version.fetch_add(1, SeqCst); fold_edits } } @@ -1086,7 +1055,7 @@ mod tests { let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx)); let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0; - let (mut writer, _, _) = map.write(cx.as_ref()); + let (mut writer, _, _) = map.write(vec![], cx.as_ref()); let (snapshot2, edits) = writer.fold( vec![ Point::new(0, 2)..Point::new(2, 2), @@ -1109,7 +1078,8 @@ mod tests { ] ); - buffer.update(cx, |buffer, cx| { + let edits = buffer.update(cx, |buffer, cx| { + let v0 = buffer.version(); buffer.edit( vec![ Point::new(0, 0)..Point::new(0, 1), @@ -1118,8 +1088,9 @@ mod tests { "123", cx, ); + buffer.edits_since(&v0).collect() }); - let (snapshot3, edits) = map.read(cx.as_ref()); + let (snapshot3, edits) = map.read(edits, cx.as_ref()); assert_eq!(snapshot3.text(), "123a…c123c…eeeee"); assert_eq!( edits, @@ -1135,15 +1106,17 @@ mod tests { ] ); - buffer.update(cx, |buffer, cx| { - buffer.edit(vec![Point::new(2, 6)..Point::new(4, 3)], "456", cx) + let edits = buffer.update(cx, |buffer, cx| { + let v0 = buffer.version(); + buffer.edit(vec![Point::new(2, 6)..Point::new(4, 3)], "456", cx); + buffer.edits_since(&v0).collect() }); - let (snapshot4, _) = map.read(cx.as_ref()); + let (snapshot4, _) = map.read(edits, cx.as_ref()); assert_eq!(snapshot4.text(), "123a…c123456eee"); - let (mut writer, _, _) = map.write(cx.as_ref()); + let (mut writer, _, _) = map.write(vec![], cx.as_ref()); writer.unfold(Some(Point::new(0, 4)..Point::new(0, 5)), cx.as_ref()); - let (snapshot5, _) = map.read(cx.as_ref()); + let (snapshot5, _) = map.read(vec![], cx.as_ref()); assert_eq!(snapshot5.text(), "123aaaaa\nbbbbbb\nccc123456eee"); } @@ -1154,21 +1127,21 @@ mod tests { { let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0; - let (mut writer, _, _) = map.write(cx.as_ref()); + let (mut writer, _, _) = map.write(vec![], cx.as_ref()); writer.fold(vec![5..8], cx.as_ref()); - let (snapshot, _) = map.read(cx.as_ref()); + let (snapshot, _) = map.read(vec![], cx.as_ref()); assert_eq!(snapshot.text(), "abcde…ijkl"); // Create an fold adjacent to the start of the first fold. - let (mut writer, _, _) = map.write(cx.as_ref()); + let (mut writer, _, _) = map.write(vec![], cx.as_ref()); writer.fold(vec![0..1, 2..5], cx.as_ref()); - let (snapshot, _) = map.read(cx.as_ref()); + let (snapshot, _) = map.read(vec![], cx.as_ref()); assert_eq!(snapshot.text(), "…b…ijkl"); // Create an fold adjacent to the end of the first fold. - let (mut writer, _, _) = map.write(cx.as_ref()); + let (mut writer, _, _) = map.write(vec![], cx.as_ref()); writer.fold(vec![11..11, 8..10], cx.as_ref()); - let (snapshot, _) = map.read(cx.as_ref()); + let (snapshot, _) = map.read(vec![], cx.as_ref()); assert_eq!(snapshot.text(), "…b…kl"); } @@ -1176,14 +1149,18 @@ mod tests { let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0; // Create two adjacent folds. - let (mut writer, _, _) = map.write(cx.as_ref()); + let (mut writer, _, _) = map.write(vec![], cx.as_ref()); writer.fold(vec![0..2, 2..5], cx.as_ref()); - let (snapshot, _) = map.read(cx.as_ref()); + let (snapshot, _) = map.read(vec![], cx.as_ref()); assert_eq!(snapshot.text(), "…fghijkl"); // Edit within one of the folds. - buffer.update(cx, |buffer, cx| buffer.edit(vec![0..1], "12345", cx)); - let (snapshot, _) = map.read(cx.as_ref()); + let edits = buffer.update(cx, |buffer, cx| { + let v0 = buffer.version(); + buffer.edit(vec![0..1], "12345", cx); + buffer.edits_since(&v0).collect() + }); + let (snapshot, _) = map.read(edits, cx.as_ref()); assert_eq!(snapshot.text(), "12345…fghijkl"); } } @@ -1192,7 +1169,7 @@ mod tests { fn test_overlapping_folds(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx)); let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0; - let (mut writer, _, _) = map.write(cx.as_ref()); + let (mut writer, _, _) = map.write(vec![], cx.as_ref()); writer.fold( vec![ Point::new(0, 2)..Point::new(2, 2), @@ -1202,7 +1179,7 @@ mod tests { ], cx.as_ref(), ); - let (snapshot, _) = map.read(cx.as_ref()); + let (snapshot, _) = map.read(vec![], cx.as_ref()); assert_eq!(snapshot.text(), "aa…eeeee"); } @@ -1211,7 +1188,7 @@ mod tests { let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx)); let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0; - let (mut writer, _, _) = map.write(cx.as_ref()); + let (mut writer, _, _) = map.write(vec![], cx.as_ref()); writer.fold( vec![ Point::new(0, 2)..Point::new(2, 2), @@ -1219,13 +1196,15 @@ mod tests { ], cx.as_ref(), ); - let (snapshot, _) = map.read(cx.as_ref()); + let (snapshot, _) = map.read(vec![], cx.as_ref()); assert_eq!(snapshot.text(), "aa…cccc\nd…eeeee"); - buffer.update(cx, |buffer, cx| { - buffer.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", cx) + let edits = buffer.update(cx, |buffer, cx| { + let v0 = buffer.version(); + buffer.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", cx); + buffer.edits_since(&v0).collect() }); - let (snapshot, _) = map.read(cx.as_ref()); + let (snapshot, _) = map.read(edits, cx.as_ref()); assert_eq!(snapshot.text(), "aa…eeeee"); } @@ -1235,7 +1214,7 @@ mod tests { let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0; let buffer = buffer.read(cx); - let (mut writer, _, _) = map.write(cx.as_ref()); + let (mut writer, _, _) = map.write(vec![], cx.as_ref()); writer.fold( vec![ Point::new(0, 2)..Point::new(2, 2), @@ -1245,7 +1224,7 @@ mod tests { ], cx.as_ref(), ); - let (snapshot, _) = map.read(cx.as_ref()); + let (snapshot, _) = map.read(vec![], cx.as_ref()); let fold_ranges = snapshot .folds_in_range(Point::new(1, 0)..Point::new(1, 3)) .map(|fold| fold.start.to_point(buffer)..fold.end.to_point(buffer)) @@ -1272,27 +1251,27 @@ mod tests { }); let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0; - let (mut initial_snapshot, _) = map.read(cx.as_ref()); + let (mut initial_snapshot, _) = map.read(vec![], cx.as_ref()); let mut snapshot_edits = Vec::new(); for _ in 0..operations { log::info!("text: {:?}", buffer.read(cx).text()); - match rng.gen_range(0..=100) { + let buffer_edits = match rng.gen_range(0..=100) { 0..=59 => { snapshot_edits.extend(map.randomly_mutate(&mut rng, cx.as_ref())); + vec![] } - _ => { - let edits = buffer.update(cx, |buffer, _| { - let start_version = buffer.version.clone(); - let edit_count = rng.gen_range(1..=5); - buffer.randomly_edit(&mut rng, edit_count); - buffer - .edits_since::(&start_version) - .collect::>() - }); + _ => buffer.update(cx, |buffer, _| { + let start_version = buffer.version.clone(); + let edit_count = rng.gen_range(1..=5); + buffer.randomly_edit(&mut rng, edit_count); + let edits = buffer + .edits_since::(&start_version) + .collect::>(); log::info!("editing {:?}", edits); - } - } + buffer.edits_since::(&start_version).collect() + }), + }; let buffer = map.buffer.read(cx).snapshot(); let mut expected_text: String = buffer.text().to_string(); @@ -1309,7 +1288,7 @@ mod tests { expected_buffer_rows.extend((0..=next_row).rev()); expected_buffer_rows.reverse(); - let (snapshot, edits) = map.read(cx.as_ref()); + let (snapshot, edits) = map.read(buffer_edits, cx.as_ref()); assert_eq!(snapshot.text(), expected_text); snapshot_edits.push((snapshot.clone(), edits)); @@ -1479,7 +1458,7 @@ mod tests { let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0; - let (mut writer, _, _) = map.write(cx.as_ref()); + let (mut writer, _, _) = map.write(vec![], cx.as_ref()); writer.fold( vec![ Point::new(0, 2)..Point::new(2, 2), @@ -1488,7 +1467,7 @@ mod tests { cx.as_ref(), ); - let (snapshot, _) = map.read(cx.as_ref()); + let (snapshot, _) = map.read(vec![], cx.as_ref()); assert_eq!(snapshot.text(), "aa…cccc\nd…eeeee\nffffff\n"); assert_eq!(snapshot.buffer_rows(0).collect::>(), [0, 3, 5, 6]); assert_eq!(snapshot.buffer_rows(3).collect::>(), [6]); @@ -1540,7 +1519,7 @@ mod tests { to_unfold.push(start..end); } log::info!("unfolding {:?}", to_unfold); - let (mut writer, snapshot, edits) = self.write(cx.as_ref()); + let (mut writer, snapshot, edits) = self.write(vec![], cx.as_ref()); snapshot_edits.push((snapshot, edits)); let (snapshot, edits) = writer.fold(to_unfold, cx.as_ref()); snapshot_edits.push((snapshot, edits)); @@ -1554,7 +1533,7 @@ mod tests { to_fold.push(start..end); } log::info!("folding {:?}", to_fold); - let (mut writer, snapshot, edits) = self.write(cx.as_ref()); + let (mut writer, snapshot, edits) = self.write(vec![], cx.as_ref()); snapshot_edits.push((snapshot, edits)); let (snapshot, edits) = writer.fold(to_fold, cx.as_ref()); snapshot_edits.push((snapshot, edits)); diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 778e63b6a1..6e930fefd0 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -474,7 +474,7 @@ mod tests { let (mut fold_map, _) = FoldMap::new(buffer.clone(), cx); fold_map.randomly_mutate(&mut rng, cx); - let (folds_snapshot, _) = fold_map.read(cx); + let (folds_snapshot, _) = fold_map.read(vec![], cx); log::info!("FoldMap text: {:?}", folds_snapshot.text()); let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 2349b1974d..d7c453675c 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1,6 +1,5 @@ use super::{ fold_map, - patch::Patch, tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint}, DisplayRow, }; @@ -13,6 +12,7 @@ use lazy_static::lazy_static; use smol::future::yield_now; use std::{collections::VecDeque, mem, ops::Range, time::Duration}; use sum_tree::{Bias, Cursor, SumTree}; +use text::Patch; use theme::SyntaxTheme; pub use super::tab_map::TextSummary; @@ -21,8 +21,8 @@ pub type Edit = text::Edit; pub struct WrapMap { snapshot: Snapshot, pending_edits: VecDeque<(TabSnapshot, Vec)>, - interpolated_edits: Patch, - edits_since_sync: Patch, + interpolated_edits: Patch, + edits_since_sync: Patch, wrap_width: Option, background_task: Option>, font: (FontId, f32), @@ -308,7 +308,7 @@ impl Snapshot { } } - fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch { + fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch { let mut new_transforms; if tab_edits.is_empty() { new_transforms = self.transforms.clone(); @@ -383,7 +383,7 @@ impl Snapshot { tab_edits: &[TabEdit], wrap_width: f32, line_wrapper: &mut LineWrapper, - ) -> Patch { + ) -> Patch { #[derive(Debug)] struct RowEdit { old_rows: Range, @@ -526,7 +526,7 @@ impl Snapshot { old_snapshot.compute_edits(tab_edits, self) } - fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &Snapshot) -> Patch { + 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::(); @@ -1067,6 +1067,7 @@ mod tests { for _i in 0..operations { log::info!("{} ==============================================", _i); + let mut buffer_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=19 => { wrap_width = if rng.gen_bool(0.2) { @@ -1090,7 +1091,11 @@ mod tests { } } _ => { - buffer.update(&mut cx, |buffer, _| buffer.randomly_mutate(&mut rng)); + buffer.update(&mut cx, |buffer, _| { + let v0 = buffer.version(); + buffer.randomly_mutate(&mut rng); + buffer_edits.extend(buffer.edits_since(&v0)); + }); } } @@ -1098,7 +1103,7 @@ mod tests { "Unwrapped text (no folds): {:?}", buffer.read_with(&cx, |buf, _| buf.text()) ); - let (folds_snapshot, fold_edits) = cx.read(|cx| fold_map.read(cx)); + let (folds_snapshot, fold_edits) = cx.read(|cx| fold_map.read(buffer_edits, cx)); log::info!( "Unwrapped text (unexpanded tabs): {:?}", folds_snapshot.text() diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 05e97a1a37..bbdcf6f79b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3466,13 +3466,13 @@ impl Editor { cx: &mut ViewContext, ) { match event { - language::Event::Edited => cx.emit(Event::Edited), + language::Event::Edited(_) => cx.emit(Event::Edited), language::Event::Dirtied => cx.emit(Event::Dirtied), language::Event::Saved => cx.emit(Event::Saved), language::Event::FileHandleChanged => cx.emit(Event::FileHandleChanged), language::Event::Reloaded => cx.emit(Event::FileHandleChanged), language::Event::Closed => cx.emit(Event::Closed), - language::Event::Reparsed => {} + _ => {} } } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 7e7cff890c..037b9f8a12 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -107,12 +107,13 @@ pub enum Operation { #[derive(Clone, Debug, Eq, PartialEq)] pub enum Event { - Edited, + Edited(Patch), Dirtied, Saved, FileHandleChanged, Reloaded, Reparsed, + DiagnosticsUpdated, Closed, } @@ -805,6 +806,7 @@ impl Buffer { self.diagnostics_update_count += 1; cx.notify(); + cx.emit(Event::DiagnosticsUpdated); Ok(Operation::UpdateDiagnostics(self.diagnostics.clone())) } @@ -1316,14 +1318,16 @@ impl Buffer { was_dirty: bool, cx: &mut ModelContext, ) { - if self.edits_since::(old_version).next().is_none() { + let patch = + unsafe { Patch::new_unchecked(self.edits_since::(old_version).collect()) }; + if patch.is_empty() { return; } self.reparse(cx); self.update_language_server(); - cx.emit(Event::Edited); + cx.emit(Event::Edited(patch)); if !was_dirty { cx.emit(Event::Dirtied); } diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index cff74af1e3..6326d05693 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -110,11 +110,34 @@ fn test_edit_events(cx: &mut gpui::MutableAppContext) { let buffer_1_events = buffer_1_events.borrow(); assert_eq!( *buffer_1_events, - vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited] + vec![ + Event::Edited(Patch::new(vec![Edit { + old: 2..4, + new: 2..5 + }])), + Event::Dirtied, + Event::Edited(Patch::new(vec![Edit { + old: 5..5, + new: 5..7 + }])), + Event::Edited(Patch::new(vec![Edit { + old: 5..7, + new: 5..5, + }])) + ] ); let buffer_2_events = buffer_2_events.borrow(); - assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]); + assert_eq!( + *buffer_2_events, + vec![ + Event::Edited(Patch::new(vec![Edit { + old: 2..4, + new: 2..5 + }])), + Event::Dirtied + ] + ); } #[gpui::test] diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 65ef0ea4db..9fe3792144 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -20,7 +20,6 @@ use postage::{ prelude::{Sink as _, Stream as _}, watch, }; - use serde::Deserialize; use smol::channel::{self, Sender}; use std::{ @@ -3017,7 +3016,7 @@ mod tests { fmt::Write, time::{SystemTime, UNIX_EPOCH}, }; - use text::Point; + use text::{Patch, Point}; use util::test::temp_tree; #[gpui::test] @@ -3470,7 +3469,13 @@ mod tests { assert!(buffer.is_dirty()); assert_eq!( *events.borrow(), - &[language::Event::Edited, language::Event::Dirtied] + &[ + language::Event::Edited(Patch::new(vec![text::Edit { + old: 1..2, + new: 1..1 + }])), + language::Event::Dirtied + ] ); events.borrow_mut().clear(); buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx); @@ -3493,21 +3498,33 @@ mod tests { assert_eq!( *events.borrow(), &[ - language::Event::Edited, + language::Event::Edited(Patch::new(vec![text::Edit { + old: 1..1, + new: 1..2 + }])), language::Event::Dirtied, - language::Event::Edited + language::Event::Edited(Patch::new(vec![text::Edit { + old: 2..2, + new: 2..3 + }])), ], ); events.borrow_mut().clear(); // TODO - currently, after restoring the buffer to its // previously-saved state, the is still considered dirty. - buffer.edit(vec![1..3], "", cx); + buffer.edit([1..3], "", cx); assert!(buffer.text() == "ac"); assert!(buffer.is_dirty()); }); - assert_eq!(*events.borrow(), &[language::Event::Edited]); + assert_eq!( + *events.borrow(), + &[language::Event::Edited(Patch::new(vec![text::Edit { + old: 1..3, + new: 1..1 + }]))] + ); // When a file is deleted, the buffer is considered dirty. let events = Rc::new(RefCell::new(Vec::new())); diff --git a/crates/editor/src/display_map/patch.rs b/crates/text/src/patch.rs similarity index 88% rename from crates/editor/src/display_map/patch.rs rename to crates/text/src/patch.rs index fd6117d3a6..8e51b5445f 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/text/src/patch.rs @@ -1,16 +1,40 @@ -use std::{cmp, mem}; +use crate::Edit; +use std::{ + cmp, mem, + ops::{Add, AddAssign, Sub}, +}; -type Edit = text::Edit; +#[derive(Clone, Default, Debug, PartialEq, Eq)] +pub struct Patch(Vec>); -#[derive(Default, Debug, PartialEq, Eq)] -pub struct Patch(Vec); - -impl Patch { - pub unsafe fn new_unchecked(edits: Vec) -> Self { +impl Patch +where + T: Clone + + Copy + + Ord + + Sub + + Add + + AddAssign + + Default + + PartialEq, +{ + pub unsafe fn new_unchecked(edits: Vec>) -> Self { Self(edits) } - pub fn into_inner(self) -> Vec { + pub fn new(edits: Vec>) -> Self { + let mut last_edit: Option<&Edit> = None; + for edit in &edits { + if let Some(last_edit) = last_edit { + assert!(edit.old.start > last_edit.old.end); + assert!(edit.new.start > last_edit.new.end); + } + last_edit = Some(edit); + } + Self(edits) + } + + pub fn into_inner(self) -> Vec> { self.0 } @@ -19,8 +43,8 @@ impl Patch { let mut new_edits_iter = other.0.iter().cloned().peekable(); let mut composed = Patch(Vec::new()); - let mut old_start = 0; - let mut new_start = 0; + let mut old_start = T::default(); + let mut new_start = T::default(); loop { let old_edit = old_edits_iter.peek_mut(); let new_edit = new_edits_iter.peek_mut(); @@ -33,8 +57,8 @@ impl Patch { old_start += catchup; new_start += catchup; - let old_end = old_start + old_edit.old.len() as u32; - let new_end = new_start + old_edit.new.len() as u32; + let old_end = old_start + old_edit.old_len(); + let new_end = new_start + old_edit.new_len(); composed.push(Edit { old: old_start..old_end, new: new_start..new_end, @@ -54,8 +78,8 @@ impl Patch { old_start += catchup; new_start += catchup; - let old_end = old_start + new_edit.old.len() as u32; - let new_end = new_start + new_edit.new.len() as u32; + let old_end = old_start + new_edit.old_len(); + let new_end = new_start + new_edit.new_len(); composed.push(Edit { old: old_start..old_end, new: new_start..new_end, @@ -106,9 +130,8 @@ impl Patch { } if old_edit.new.end > new_edit.old.end { - let old_end = - old_start + cmp::min(old_edit.old.len() as u32, new_edit.old.len() as u32); - let new_end = new_start + new_edit.new.len() as u32; + let old_end = old_start + cmp::min(old_edit.old_len(), new_edit.old_len()); + let new_end = new_start + new_edit.new_len(); composed.push(Edit { old: old_start..old_end, new: new_start..new_end, @@ -120,9 +143,8 @@ impl Patch { new_start = new_end; new_edits_iter.next(); } else { - let old_end = old_start + old_edit.old.len() as u32; - let new_end = - new_start + cmp::min(old_edit.new.len() as u32, new_edit.new.len() as u32); + let old_end = old_start + old_edit.old_len(); + let new_end = new_start + cmp::min(old_edit.new_len(), new_edit.new_len()); composed.push(Edit { old: old_start..old_end, new: new_start..new_end, @@ -153,8 +175,12 @@ impl Patch { self.0.clear(); } - fn push(&mut self, edit: Edit) { - if edit.old.len() == 0 && edit.new.len() == 0 { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + fn push(&mut self, edit: Edit) { + if edit.is_empty() { return; } @@ -479,7 +505,7 @@ mod tests { } #[track_caller] - fn assert_patch_composition(old: Patch, new: Patch, composed: Patch) { + fn assert_patch_composition(old: Patch, new: Patch, composed: Patch) { let original = ('a'..'z').collect::>(); let inserted = ('A'..'Z').collect::>(); @@ -498,7 +524,7 @@ mod tests { assert_eq!(old.compose(&new), composed); } - fn apply_patch(text: &mut Vec, patch: &Patch, new_text: &[char]) { + fn apply_patch(text: &mut Vec, patch: &Patch, new_text: &[char]) { for edit in patch.0.iter().rev() { text.splice( edit.old.start as usize..edit.old.end as usize, diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 8d941d6d02..a5f014274b 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1,5 +1,6 @@ mod anchor; mod operation_queue; +mod patch; mod point; mod point_utf16; #[cfg(any(test, feature = "test-support"))] @@ -14,6 +15,7 @@ use anyhow::{anyhow, Result}; use clock::ReplicaId; use collections::{HashMap, HashSet}; use operation_queue::OperationQueue; +pub use patch::Patch; pub use point::*; pub use point_utf16::*; #[cfg(any(test, feature = "test-support"))] @@ -24,7 +26,7 @@ pub use selection::*; use std::{ cmp::{self, Reverse}, iter::Iterator, - ops::{self, Deref, Range}, + ops::{self, Deref, Range, Sub}, str, sync::Arc, time::{Duration, Instant}, @@ -307,6 +309,23 @@ pub struct Edit { pub new: Range, } +impl Edit +where + D: Sub + PartialEq + Copy, +{ + pub fn old_len(&self) -> D { + self.old.end - self.old.start + } + + pub fn new_len(&self) -> D { + self.new.end - self.new.start + } + + pub fn is_empty(&self) -> bool { + self.old.start == self.old.end && self.new.start == self.new.end + } +} + impl Edit<(D1, D2)> { pub fn flatten(self) -> (Edit, Edit) { (