From faba276fdce0f7785b3242f90669c517f22e8ddf Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 30 Nov 2021 17:23:02 -0800 Subject: [PATCH 1/5] 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 | 76 +++++--- crates/text/src/text.rs | 21 +- 11 files changed, 280 insertions(+), 164 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 bbf03637e1f924bf78b9188a267b6efe1c4fb10a..71b6c72a3dc11dc69d3875d501efcd04e9d8ca86 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 d28d8d7efd591dfa4a784ce7989cd1b83f3a2b7f..0800f8d1e7f4ec9428e215c19f7cd5e6fedb2d17 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 16ce634e1de79bc1f84cf01f661a2ddb30040799..4ca74ad90613fed9ed8efbc805c94a47d0726150 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 778e63b6a10a9127c021be4e00904a1e582b1cee..6e930fefd0e87c9185bed771d06b0e0eb3b62e35 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 2349b1974df41a6437bbd7078ab1a86fe87d4184..d7c453675c1b4be2223ddc47d5c78951bf8b4d73 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 05e97a1a3739d02b2117a1c528db59f560947009..bbdcf6f79be7ab9bf47d16a21d89035604c516d5 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 7e7cff890c685b254a9896cf53b8e436d3019ff9..037b9f8a12dbf6b0a0c680fdd709e1282c765757 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 cff74af1e3bee4b9c65416ecdd5986fbb787f4ac..6326d056937dda0af6ee9519d43126bb0f5e5ec3 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 65ef0ea4dbb0a66d846cc6ace32dd91d803ed193..9fe3792144cdfaa504f162ee3bd28b477cc94718 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 fd6117d3a6b765aa2469a7c3e2d6230f923ace63..8e51b5445f67f3c044f335f6e710c843f02e9771 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/text/src/patch.rs @@ -1,16 +1,40 @@ -use std::{cmp, mem}; - -type Edit = text::Edit; - -#[derive(Default, Debug, PartialEq, Eq)] -pub struct Patch(Vec); +use crate::Edit; +use std::{ + cmp, mem, + ops::{Add, AddAssign, Sub}, +}; + +#[derive(Clone, Default, Debug, PartialEq, Eq)] +pub struct Patch(Vec>); + +impl Patch +where + T: Clone + + Copy + + Ord + + Sub + + Add + + AddAssign + + Default + + PartialEq, +{ + pub unsafe fn new_unchecked(edits: Vec>) -> Self { + Self(edits) + } -impl Patch { - pub unsafe fn new_unchecked(edits: Vec) -> Self { + 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 { + 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 8d941d6d023954b949e316df84f2f8a30c246388..a5f014274b464d984c98057f70144036a4be96e6 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) { ( From b841b3eb7974d65db1c721f257c417838854747c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Dec 2021 11:44:33 +0100 Subject: [PATCH 2/5] Don't produce invalid intermediate edits in `Patch::compose` --- crates/text/src/patch.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/text/src/patch.rs b/crates/text/src/patch.rs index 8e51b5445f67f3c044f335f6e710c843f02e9771..266ad04f25b61cc99a1fa656514b9d7e587659ed 100644 --- a/crates/text/src/patch.rs +++ b/crates/text/src/patch.rs @@ -106,7 +106,7 @@ where new: new_start..new_end, }); - old_edit.old.start += overshoot; + old_edit.old.start = old_end; old_edit.new.start += overshoot; old_start = old_end; new_start = new_end; @@ -124,7 +124,7 @@ where }); new_edit.old.start += overshoot; - new_edit.new.start += overshoot; + new_edit.new.start = new_end; old_start = old_end; new_start = new_end; } From 47c467dafc40cf6b801c8246a6d3ced00bbecfc9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Dec 2021 12:05:02 +0100 Subject: [PATCH 3/5] Bump FoldMap's version in `FoldMap::sync` (and not in `DisplayMap`) --- crates/editor/src/display_map.rs | 43 +- crates/editor/src/display_map/block_map.rs | 27 +- crates/editor/src/display_map/fold_map.rs | 596 +++++++++++---------- crates/editor/src/display_map/tab_map.rs | 7 +- crates/editor/src/display_map/wrap_map.rs | 20 +- crates/language/src/buffer.rs | 28 +- 6 files changed, 380 insertions(+), 341 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 71b6c72a3dc11dc69d3875d501efcd04e9d8ca86..77357616d9567dd16c83339e52bb3f60d6d43d82 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -33,7 +33,7 @@ pub struct DisplayMap { tab_map: TabMap, wrap_map: ModelHandle, block_map: BlockMap, - edits_since_sync: Mutex>, + buffer_edits_since_sync: Mutex>, } impl Entity for DisplayMap { @@ -49,7 +49,7 @@ impl DisplayMap { wrap_width: Option, cx: &mut ModelContext, ) -> Self { - let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx); + let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot()); let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx); let block_map = BlockMap::new(buffer.clone(), snapshot); @@ -61,14 +61,15 @@ impl DisplayMap { tab_map, wrap_map, block_map, - edits_since_sync: Default::default(), + buffer_edits_since_sync: Default::default(), } } pub fn snapshot(&self, cx: &mut ModelContext) -> DisplayMapSnapshot { + let buffer_snapshot = self.buffer.read(cx).snapshot(); let (folds_snapshot, edits) = self.fold_map.read( - mem::take(&mut *self.edits_since_sync.lock()).into_inner(), - cx, + buffer_snapshot, + mem::take(&mut *self.buffer_edits_since_sync.lock()).into_inner(), ); let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits); let (wraps_snapshot, edits) = self @@ -90,16 +91,17 @@ impl DisplayMap { ranges: impl IntoIterator>, cx: &mut ModelContext, ) { + let snapshot = self.buffer.read(cx).snapshot(); let (mut fold_map, snapshot, edits) = self.fold_map.write( - mem::take(&mut *self.edits_since_sync.lock()).into_inner(), - cx, + snapshot, + mem::take(&mut *self.buffer_edits_since_sync.lock()).into_inner(), ); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits, cx); - let (snapshot, edits) = fold_map.fold(ranges, cx); + let (snapshot, edits) = fold_map.fold(ranges); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map @@ -112,16 +114,17 @@ impl DisplayMap { ranges: impl IntoIterator>, cx: &mut ModelContext, ) { + let snapshot = self.buffer.read(cx).snapshot(); let (mut fold_map, snapshot, edits) = self.fold_map.write( - mem::take(&mut *self.edits_since_sync.lock()).into_inner(), - cx, + snapshot, + mem::take(&mut *self.buffer_edits_since_sync.lock()).into_inner(), ); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits, cx); - let (snapshot, edits) = fold_map.unfold(ranges, cx); + let (snapshot, edits) = fold_map.unfold(ranges); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map @@ -138,9 +141,10 @@ impl DisplayMap { P: ToOffset + Clone, T: Into + Clone, { + let snapshot = self.buffer.read(cx).snapshot(); let (snapshot, edits) = self.fold_map.read( - mem::take(&mut *self.edits_since_sync.lock()).into_inner(), - cx, + snapshot, + mem::take(&mut *self.buffer_edits_since_sync.lock()).into_inner(), ); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self @@ -159,9 +163,10 @@ impl DisplayMap { } pub fn remove_blocks(&mut self, ids: HashSet, cx: &mut ModelContext) { + let snapshot = self.buffer.read(cx).snapshot(); let (snapshot, edits) = self.fold_map.read( - mem::take(&mut *self.edits_since_sync.lock()).into_inner(), - cx, + snapshot, + mem::take(&mut *self.buffer_edits_since_sync.lock()).into_inner(), ); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self @@ -194,13 +199,9 @@ impl DisplayMap { ) { match event { language::Event::Edited(patch) => { - self.fold_map.version += 1; - let mut edits_since_sync = self.edits_since_sync.lock(); + let mut edits_since_sync = self.buffer_edits_since_sync.lock(); *edits_since_sync = edits_since_sync.compose(patch); } - language::Event::Reparsed | language::Event::DiagnosticsUpdated => { - self.fold_map.version += 1; - } _ => {} } } @@ -563,7 +564,7 @@ mod tests { } } _ => { - buffer.update(&mut cx, |buffer, _| buffer.randomly_edit(&mut rng, 5)); + buffer.update(&mut cx, |buffer, cx| buffer.randomly_edit(&mut rng, 5, cx)); } } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 0800f8d1e7f4ec9428e215c19f7cd5e6fedb2d17..5ed1ac65fb8919a076251e79e5e7ad2e43961ee3 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1104,7 +1104,7 @@ mod tests { let text = "aaa\nbbb\nccc\nddd"; let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); - let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx); + let (fold_map, folds_snapshot) = FoldMap::new(buffer.read(cx).snapshot()); let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1); let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, None, cx); let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone()); @@ -1228,13 +1228,13 @@ mod tests { // Insert a line break, separating two block decorations into separate // lines. - let buffer_edits = buffer.update(cx, |buffer, cx| { + let (buffer_snapshot, 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() + (buffer.snapshot(), buffer.edits_since(&v0).collect()) }); - let (folds_snapshot, fold_edits) = fold_map.read(buffer_edits, cx); + let (folds_snapshot, fold_edits) = fold_map.read(buffer_snapshot, buffer_edits); 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) @@ -1257,7 +1257,7 @@ mod tests { let text = "one two three\nfour five six\nseven eight"; let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); - let (_, folds_snapshot) = FoldMap::new(buffer.clone(), cx); + let (_, folds_snapshot) = FoldMap::new(buffer.read(cx).snapshot()); let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1); let (_, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, Some(60.), cx); let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone()); @@ -1319,7 +1319,8 @@ mod tests { log::info!("initial buffer text: {:?}", text); Buffer::new(0, text, cx) }); - let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx); + let mut buffer_snapshot = buffer.read(cx).snapshot(); + let (fold_map, folds_snapshot) = FoldMap::new(buffer_snapshot.clone()); 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); @@ -1378,7 +1379,8 @@ mod tests { }) .collect::>(); - let (folds_snapshot, fold_edits) = fold_map.read(vec![], cx); + let (folds_snapshot, fold_edits) = + fold_map.read(buffer_snapshot.clone(), vec![]); 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) @@ -1399,7 +1401,8 @@ mod tests { }) .collect(); - let (folds_snapshot, fold_edits) = fold_map.read(vec![], cx); + let (folds_snapshot, fold_edits) = + fold_map.read(buffer_snapshot.clone(), vec![]); 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) @@ -1408,16 +1411,18 @@ mod tests { block_map.remove(block_ids_to_remove, cx); } _ => { - buffer.update(cx, |buffer, _| { + buffer.update(cx, |buffer, cx| { let v0 = buffer.version(); - buffer.randomly_edit(&mut rng, 1); + let edit_count = rng.gen_range(1..=5); + buffer.randomly_edit(&mut rng, edit_count, cx); log::info!("buffer text: {:?}", buffer.text()); buffer_edits.extend(buffer.edits_since(&v0)); + buffer_snapshot = buffer.snapshot(); }); } } - let (folds_snapshot, fold_edits) = fold_map.read(buffer_edits, cx); + let (folds_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); 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 4ca74ad90613fed9ed8efbc805c94a47d0726150..0e920c5e6aebd3556528cb0d985a05657ba52582 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1,12 +1,13 @@ -use gpui::{AppContext, ModelHandle}; use language::{ - Anchor, AnchorRangeExt, Buffer, Chunk, Edit, Point, PointUtf16, TextSummary, ToOffset, + Anchor, AnchorRangeExt, Chunk, Edit, Point, PointUtf16, Snapshot as BufferSnapshot, + TextSummary, ToOffset, }; use parking_lot::Mutex; use std::{ cmp::{self, Ordering}, iter, ops::Range, + sync::atomic::{AtomicUsize, Ordering::SeqCst}, }; use sum_tree::{Bias, Cursor, FilterCursor, SumTree}; use theme::SyntaxTheme; @@ -101,11 +102,10 @@ impl<'a> FoldMapWriter<'a> { pub fn fold( &mut self, ranges: impl IntoIterator>, - cx: &AppContext, ) -> (Snapshot, Vec) { let mut edits = Vec::new(); let mut folds = Vec::new(); - let buffer = self.0.buffer.read(cx).snapshot(); + let buffer = self.0.buffer.lock().clone(); for range in ranges.into_iter() { let range = range.start.to_offset(&buffer)..range.end.to_offset(&buffer); if range.start != range.end { @@ -132,12 +132,12 @@ impl<'a> FoldMapWriter<'a> { }; consolidate_buffer_edits(&mut edits); - let edits = self.0.sync(edits, cx); + let edits = self.0.sync(buffer.clone(), edits); 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, + buffer_snapshot: buffer, + version: self.0.version.load(SeqCst), }; (snapshot, edits) } @@ -145,11 +145,10 @@ impl<'a> FoldMapWriter<'a> { pub fn unfold( &mut self, ranges: impl IntoIterator>, - cx: &AppContext, ) -> (Snapshot, Vec) { let mut edits = Vec::new(); let mut fold_ixs_to_delete = Vec::new(); - let buffer = self.0.buffer.read(cx).snapshot(); + let buffer = self.0.buffer.lock().clone(); for range in ranges.into_iter() { // 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); @@ -179,29 +178,28 @@ impl<'a> FoldMapWriter<'a> { }; consolidate_buffer_edits(&mut edits); - let edits = self.0.sync(edits, cx); + let edits = self.0.sync(buffer.clone(), edits); 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, + buffer_snapshot: buffer, + version: self.0.version.load(SeqCst), }; (snapshot, edits) } } pub struct FoldMap { - buffer: ModelHandle, + buffer: Mutex, transforms: Mutex>, folds: SumTree, - pub version: usize, + version: AtomicUsize, } impl FoldMap { - pub fn new(buffer_handle: ModelHandle, cx: &AppContext) -> (Self, Snapshot) { - let buffer = buffer_handle.read(cx); + pub fn new(buffer: BufferSnapshot) -> (Self, Snapshot) { let this = Self { - buffer: buffer_handle, + buffer: Mutex::new(buffer.clone()), folds: Default::default(), transforms: Mutex::new(SumTree::from_item( Transform { @@ -213,124 +211,185 @@ impl FoldMap { }, &(), )), - version: 0, + version: Default::default(), + }; + + let snapshot = Snapshot { + transforms: this.transforms.lock().clone(), + folds: this.folds.clone(), + buffer_snapshot: this.buffer.lock().clone(), + version: this.version.load(SeqCst), }; - let (snapshot, _) = this.read(Vec::new(), cx); (this, snapshot) } - pub fn read(&self, edits: Vec>, cx: &AppContext) -> (Snapshot, Vec) { - let edits = self.sync(edits, cx); - self.check_invariants(cx); + pub fn read( + &self, + buffer: BufferSnapshot, + edits: Vec>, + ) -> (Snapshot, Vec) { + let edits = self.sync(buffer, edits); + self.check_invariants(); let snapshot = Snapshot { transforms: self.transforms.lock().clone(), folds: self.folds.clone(), - buffer_snapshot: self.buffer.read(cx).snapshot(), - version: self.version, + buffer_snapshot: self.buffer.lock().clone(), + version: self.version.load(SeqCst), }; (snapshot, edits) } pub fn write( &mut self, + buffer: BufferSnapshot, edits: Vec>, - cx: &AppContext, ) -> (FoldMapWriter, Snapshot, Vec) { - let (snapshot, edits) = self.read(edits, cx); + let (snapshot, edits) = self.read(buffer, edits); (FoldMapWriter(self), snapshot, edits) } - fn check_invariants(&self, cx: &AppContext) { + fn check_invariants(&self) { if cfg!(test) { - let buffer = self.buffer.read(cx); assert_eq!( self.transforms.lock().summary().input.bytes, - buffer.len(), + self.buffer.lock().len(), "transform tree does not match buffer's length" ); } } - fn sync(&self, buffer_edits: Vec>, cx: &AppContext) -> Vec { + fn sync( + &self, + new_buffer: BufferSnapshot, + buffer_edits: Vec>, + ) -> Vec { if buffer_edits.is_empty() { - return Vec::new(); - } + let mut buffer = self.buffer.lock(); + if buffer.parse_count() != new_buffer.parse_count() + || buffer.diagnostics_update_count() != new_buffer.diagnostics_update_count() + { + self.version.fetch_add(1, SeqCst); + } + *buffer = new_buffer; + Vec::new() + } else { + let mut buffer_edits_iter = buffer_edits.iter().cloned().peekable(); - let buffer = self.buffer.read(cx).snapshot(); - let mut buffer_edits_iter = buffer_edits.iter().cloned().peekable(); + let mut new_transforms = SumTree::new(); + let mut transforms = self.transforms.lock(); + let mut cursor = transforms.cursor::(); + cursor.seek(&0, Bias::Right, &()); - let mut new_transforms = SumTree::new(); - let mut transforms = self.transforms.lock(); - let mut cursor = transforms.cursor::(); - cursor.seek(&0, Bias::Right, &()); + while let Some(mut edit) = buffer_edits_iter.next() { + new_transforms.push_tree(cursor.slice(&edit.old.start, Bias::Left, &()), &()); + edit.new.start -= edit.old.start - cursor.start(); + edit.old.start = *cursor.start(); - while let Some(mut edit) = buffer_edits_iter.next() { - new_transforms.push_tree(cursor.slice(&edit.old.start, Bias::Left, &()), &()); - edit.new.start -= edit.old.start - cursor.start(); - edit.old.start = *cursor.start(); + cursor.seek(&edit.old.end, Bias::Right, &()); + cursor.next(&()); - cursor.seek(&edit.old.end, Bias::Right, &()); - cursor.next(&()); + let mut delta = edit.new.len() as isize - edit.old.len() as isize; + loop { + edit.old.end = *cursor.start(); - let mut delta = edit.new.len() as isize - edit.old.len() as isize; - loop { - edit.old.end = *cursor.start(); + if let Some(next_edit) = buffer_edits_iter.peek() { + if next_edit.old.start > edit.old.end { + break; + } + + let next_edit = buffer_edits_iter.next().unwrap(); + delta += next_edit.new.len() as isize - next_edit.old.len() as isize; - if let Some(next_edit) = buffer_edits_iter.peek() { - if next_edit.old.start > edit.old.end { + if next_edit.old.end >= edit.old.end { + edit.old.end = next_edit.old.end; + cursor.seek(&edit.old.end, Bias::Right, &()); + cursor.next(&()); + } + } else { break; } + } - let next_edit = buffer_edits_iter.next().unwrap(); - delta += next_edit.new.len() as isize - next_edit.old.len() as isize; + edit.new.end = ((edit.new.start + edit.old.len()) as isize + delta) as usize; - if next_edit.old.end >= edit.old.end { - edit.old.end = next_edit.old.end; - cursor.seek(&edit.old.end, Bias::Right, &()); - cursor.next(&()); - } - } else { - break; - } - } + let anchor = new_buffer.anchor_before(edit.new.start); + let mut folds_cursor = self.folds.cursor::(); + folds_cursor.seek(&Fold(anchor..Anchor::max()), Bias::Left, &new_buffer); - edit.new.end = ((edit.new.start + edit.old.len()) as isize + delta) as usize; + let mut folds = iter::from_fn({ + let buffer = &new_buffer; + move || { + let item = folds_cursor + .item() + .map(|f| f.0.start.to_offset(buffer)..f.0.end.to_offset(buffer)); + folds_cursor.next(buffer); + item + } + }) + .peekable(); - let anchor = buffer.anchor_before(edit.new.start); - let mut folds_cursor = self.folds.cursor::(); - folds_cursor.seek(&Fold(anchor..Anchor::max()), Bias::Left, &buffer); + while folds.peek().map_or(false, |fold| fold.start < edit.new.end) { + let mut fold = folds.next().unwrap(); + let sum = new_transforms.summary(); - let mut folds = iter::from_fn({ - let buffer = &buffer; - move || { - let item = folds_cursor - .item() - .map(|f| f.0.start.to_offset(buffer)..f.0.end.to_offset(buffer)); - folds_cursor.next(buffer); - item - } - }) - .peekable(); + assert!(fold.start >= sum.input.bytes); - while folds.peek().map_or(false, |fold| fold.start < edit.new.end) { - let mut fold = folds.next().unwrap(); - let sum = new_transforms.summary(); + while folds + .peek() + .map_or(false, |next_fold| next_fold.start <= fold.end) + { + let next_fold = folds.next().unwrap(); + if next_fold.end > fold.end { + fold.end = next_fold.end; + } + } - assert!(fold.start >= sum.input.bytes); + if fold.start > sum.input.bytes { + let text_summary = new_buffer + .text_summary_for_range::(sum.input.bytes..fold.start); + new_transforms.push( + Transform { + summary: TransformSummary { + output: text_summary.clone(), + input: text_summary, + }, + output_text: None, + }, + &(), + ); + } - while folds - .peek() - .map_or(false, |next_fold| next_fold.start <= fold.end) - { - let next_fold = folds.next().unwrap(); - if next_fold.end > fold.end { - fold.end = next_fold.end; + if fold.end > fold.start { + let output_text = "…"; + let chars = output_text.chars().count() as u32; + let lines = Point::new(0, output_text.len() as u32); + let lines_utf16 = + PointUtf16::new(0, output_text.encode_utf16().count() as u32); + new_transforms.push( + Transform { + summary: TransformSummary { + output: TextSummary { + bytes: output_text.len(), + lines, + lines_utf16, + first_line_chars: chars, + last_line_chars: chars, + longest_row: 0, + longest_row_chars: chars, + }, + input: new_buffer.text_summary_for_range(fold.start..fold.end), + }, + output_text: Some(output_text), + }, + &(), + ); } } - if fold.start > sum.input.bytes { - let text_summary = buffer - .text_summary_for_range::(sum.input.bytes..fold.start); + let sum = new_transforms.summary(); + if sum.input.bytes < edit.new.end { + let text_summary = new_buffer + .text_summary_for_range::(sum.input.bytes..edit.new.end); new_transforms.push( Transform { summary: TransformSummary { @@ -342,37 +401,11 @@ impl FoldMap { &(), ); } - - if fold.end > fold.start { - let output_text = "…"; - let chars = output_text.chars().count() as u32; - let lines = Point::new(0, output_text.len() as u32); - let lines_utf16 = PointUtf16::new(0, output_text.encode_utf16().count() as u32); - new_transforms.push( - Transform { - summary: TransformSummary { - output: TextSummary { - bytes: output_text.len(), - lines, - lines_utf16, - first_line_chars: chars, - last_line_chars: chars, - longest_row: 0, - longest_row_chars: chars, - }, - input: buffer.text_summary_for_range(fold.start..fold.end), - }, - output_text: Some(output_text), - }, - &(), - ); - } } - let sum = new_transforms.summary(); - if sum.input.bytes < edit.new.end { - let text_summary = - buffer.text_summary_for_range::(sum.input.bytes..edit.new.end); + new_transforms.push_tree(cursor.suffix(&()), &()); + if new_transforms.is_empty() { + let text_summary = new_buffer.text_summary(); new_transforms.push( Transform { summary: TransformSummary { @@ -384,72 +417,59 @@ impl FoldMap { &(), ); } - } - new_transforms.push_tree(cursor.suffix(&()), &()); - if new_transforms.is_empty() { - let text_summary = buffer.text_summary(); - new_transforms.push( - Transform { - summary: TransformSummary { - output: text_summary.clone(), - input: text_summary, - }, - output_text: None, - }, - &(), - ); - } + drop(cursor); - drop(cursor); + let mut fold_edits = Vec::with_capacity(buffer_edits.len()); + { + let mut old_transforms = transforms.cursor::<(usize, FoldOffset)>(); + let mut new_transforms = new_transforms.cursor::<(usize, FoldOffset)>(); - let mut fold_edits = Vec::with_capacity(buffer_edits.len()); - { - let mut old_transforms = transforms.cursor::<(usize, FoldOffset)>(); - let mut new_transforms = new_transforms.cursor::<(usize, FoldOffset)>(); + for mut edit in buffer_edits { + old_transforms.seek(&edit.old.start, Bias::Left, &()); + if old_transforms.item().map_or(false, |t| t.is_fold()) { + edit.old.start = old_transforms.start().0; + } + let old_start = + old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0); - for mut edit in buffer_edits { - old_transforms.seek(&edit.old.start, Bias::Left, &()); - if old_transforms.item().map_or(false, |t| t.is_fold()) { - edit.old.start = old_transforms.start().0; - } - let old_start = - old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0); + old_transforms.seek_forward(&edit.old.end, Bias::Right, &()); + if old_transforms.item().map_or(false, |t| t.is_fold()) { + old_transforms.next(&()); + edit.old.end = old_transforms.start().0; + } + let old_end = + old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0); - old_transforms.seek_forward(&edit.old.end, Bias::Right, &()); - if old_transforms.item().map_or(false, |t| t.is_fold()) { - old_transforms.next(&()); - edit.old.end = old_transforms.start().0; - } - let old_end = - old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0); + new_transforms.seek(&edit.new.start, Bias::Left, &()); + if new_transforms.item().map_or(false, |t| t.is_fold()) { + edit.new.start = new_transforms.start().0; + } + let new_start = + new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0); - new_transforms.seek(&edit.new.start, Bias::Left, &()); - if new_transforms.item().map_or(false, |t| t.is_fold()) { - edit.new.start = new_transforms.start().0; - } - let new_start = - new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0); + new_transforms.seek_forward(&edit.new.end, Bias::Right, &()); + if new_transforms.item().map_or(false, |t| t.is_fold()) { + new_transforms.next(&()); + edit.new.end = new_transforms.start().0; + } + let new_end = + new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0); - new_transforms.seek_forward(&edit.new.end, Bias::Right, &()); - if new_transforms.item().map_or(false, |t| t.is_fold()) { - new_transforms.next(&()); - edit.new.end = new_transforms.start().0; + fold_edits.push(FoldEdit { + old_bytes: FoldOffset(old_start)..FoldOffset(old_end), + new_bytes: FoldOffset(new_start)..FoldOffset(new_end), + }); } - let new_end = - new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0); - fold_edits.push(FoldEdit { - old_bytes: FoldOffset(old_start)..FoldOffset(old_end), - new_bytes: FoldOffset(new_start)..FoldOffset(new_end), - }); + consolidate_fold_edits(&mut fold_edits); } - consolidate_fold_edits(&mut fold_edits); + *transforms = new_transforms; + *self.buffer.lock() = new_buffer; + self.version.fetch_add(1, SeqCst); + fold_edits } - - *transforms = new_transforms; - fold_edits } } @@ -1045,6 +1065,7 @@ impl FoldEdit { mod tests { use super::*; use crate::{test::sample_text, ToPoint}; + use language::Buffer; use rand::prelude::*; use std::{env, mem}; use text::RandomCharIter; @@ -1053,16 +1074,14 @@ mod tests { #[gpui::test] fn test_basic_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(vec![], cx.as_ref()); - let (snapshot2, edits) = writer.fold( - vec![ - Point::new(0, 2)..Point::new(2, 2), - Point::new(2, 4)..Point::new(4, 1), - ], - cx.as_ref(), - ); + let buffer_snapshot = buffer.read(cx).snapshot(); + let mut map = FoldMap::new(buffer_snapshot.clone()).0; + + let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (snapshot2, edits) = writer.fold(vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(2, 4)..Point::new(4, 1), + ]); assert_eq!(snapshot2.text(), "aa…cc…eeeee"); assert_eq!( edits, @@ -1078,7 +1097,7 @@ mod tests { ] ); - let edits = buffer.update(cx, |buffer, cx| { + let (buffer_snapshot, edits) = buffer.update(cx, |buffer, cx| { let v0 = buffer.version(); buffer.edit( vec![ @@ -1088,9 +1107,9 @@ mod tests { "123", cx, ); - buffer.edits_since(&v0).collect() + (buffer.snapshot(), buffer.edits_since(&v0).collect()) }); - let (snapshot3, edits) = map.read(edits, cx.as_ref()); + let (snapshot3, edits) = map.read(buffer_snapshot.clone(), edits); assert_eq!(snapshot3.text(), "123a…c123c…eeeee"); assert_eq!( edits, @@ -1106,61 +1125,62 @@ mod tests { ] ); - let edits = buffer.update(cx, |buffer, cx| { + let (buffer_snapshot, 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() + (buffer.snapshot(), buffer.edits_since(&v0).collect()) }); - let (snapshot4, _) = map.read(edits, cx.as_ref()); + let (snapshot4, _) = map.read(buffer_snapshot.clone(), edits); assert_eq!(snapshot4.text(), "123a…c123456eee"); - 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(vec![], cx.as_ref()); + let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + writer.unfold(Some(Point::new(0, 4)..Point::new(0, 5))); + let (snapshot5, _) = map.read(buffer_snapshot.clone(), vec![]); assert_eq!(snapshot5.text(), "123aaaaa\nbbbbbb\nccc123456eee"); } #[gpui::test] fn test_adjacent_folds(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "abcdefghijkl", cx)); + let buffer_snapshot = buffer.read(cx).snapshot(); { - let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0; + let mut map = FoldMap::new(buffer_snapshot.clone()).0; - let (mut writer, _, _) = map.write(vec![], cx.as_ref()); - writer.fold(vec![5..8], cx.as_ref()); - let (snapshot, _) = map.read(vec![], cx.as_ref()); + let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + writer.fold(vec![5..8]); + let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "abcde…ijkl"); // Create an fold adjacent to the start of the first fold. - let (mut writer, _, _) = map.write(vec![], cx.as_ref()); - writer.fold(vec![0..1, 2..5], cx.as_ref()); - let (snapshot, _) = map.read(vec![], cx.as_ref()); + let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + writer.fold(vec![0..1, 2..5]); + let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "…b…ijkl"); // Create an fold adjacent to the end of the first fold. - let (mut writer, _, _) = map.write(vec![], cx.as_ref()); - writer.fold(vec![11..11, 8..10], cx.as_ref()); - let (snapshot, _) = map.read(vec![], cx.as_ref()); + let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + writer.fold(vec![11..11, 8..10]); + let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "…b…kl"); } { - let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0; + let mut map = FoldMap::new(buffer_snapshot.clone()).0; // Create two adjacent folds. - let (mut writer, _, _) = map.write(vec![], cx.as_ref()); - writer.fold(vec![0..2, 2..5], cx.as_ref()); - let (snapshot, _) = map.read(vec![], cx.as_ref()); + let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + writer.fold(vec![0..2, 2..5]); + let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "…fghijkl"); // Edit within one of the folds. - let edits = buffer.update(cx, |buffer, cx| { + let (buffer_snapshot, edits) = buffer.update(cx, |buffer, cx| { let v0 = buffer.version(); buffer.edit(vec![0..1], "12345", cx); - buffer.edits_since(&v0).collect() + (buffer.snapshot(), buffer.edits_since(&v0).collect()) }); - let (snapshot, _) = map.read(edits, cx.as_ref()); + let (snapshot, _) = map.read(buffer_snapshot.clone(), edits); assert_eq!(snapshot.text(), "12345…fghijkl"); } } @@ -1168,63 +1188,57 @@ mod tests { #[gpui::test] 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(vec![], cx.as_ref()); - writer.fold( - vec![ - Point::new(0, 2)..Point::new(2, 2), - Point::new(0, 4)..Point::new(1, 0), - Point::new(1, 2)..Point::new(3, 2), - Point::new(3, 1)..Point::new(4, 1), - ], - cx.as_ref(), - ); - let (snapshot, _) = map.read(vec![], cx.as_ref()); + let buffer_snapshot = buffer.read(cx).snapshot(); + let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + writer.fold(vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(0, 4)..Point::new(1, 0), + Point::new(1, 2)..Point::new(3, 2), + Point::new(3, 1)..Point::new(4, 1), + ]); + let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "aa…eeeee"); } #[gpui::test] fn test_merging_folds_via_edit(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(vec![], cx.as_ref()); - writer.fold( - vec![ - Point::new(0, 2)..Point::new(2, 2), - Point::new(3, 1)..Point::new(4, 1), - ], - cx.as_ref(), - ); - let (snapshot, _) = map.read(vec![], cx.as_ref()); + let buffer_snapshot = buffer.read(cx).snapshot(); + let mut map = FoldMap::new(buffer_snapshot.clone()).0; + + let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + writer.fold(vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(3, 1)..Point::new(4, 1), + ]); + let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "aa…cccc\nd…eeeee"); - let edits = buffer.update(cx, |buffer, cx| { + let (buffer_snapshot, 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() + (buffer.snapshot(), buffer.edits_since(&v0).collect()) }); - let (snapshot, _) = map.read(edits, cx.as_ref()); + let (snapshot, _) = map.read(buffer_snapshot.clone(), edits); assert_eq!(snapshot.text(), "aa…eeeee"); } #[gpui::test] fn test_folds_in_range(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 buffer_snapshot = buffer.read(cx).snapshot(); + let mut map = FoldMap::new(buffer_snapshot.clone()).0; let buffer = buffer.read(cx); - let (mut writer, _, _) = map.write(vec![], cx.as_ref()); - writer.fold( - vec![ - Point::new(0, 2)..Point::new(2, 2), - Point::new(0, 4)..Point::new(1, 0), - Point::new(1, 2)..Point::new(3, 2), - Point::new(3, 1)..Point::new(4, 1), - ], - cx.as_ref(), - ); - let (snapshot, _) = map.read(vec![], cx.as_ref()); + let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + writer.fold(vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(0, 4)..Point::new(1, 0), + Point::new(1, 2)..Point::new(3, 2), + Point::new(3, 1)..Point::new(4, 1), + ]); + let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); 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)) @@ -1249,22 +1263,23 @@ mod tests { let text = RandomCharIter::new(&mut rng).take(len).collect::(); Buffer::new(0, text, cx) }); - let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0; + let buffer_snapshot = buffer.read(cx).snapshot(); + let mut map = FoldMap::new(buffer_snapshot.clone()).0; - let (mut initial_snapshot, _) = map.read(vec![], cx.as_ref()); + let (mut initial_snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); let mut snapshot_edits = Vec::new(); for _ in 0..operations { log::info!("text: {:?}", buffer.read(cx).text()); let buffer_edits = match rng.gen_range(0..=100) { 0..=59 => { - snapshot_edits.extend(map.randomly_mutate(&mut rng, cx.as_ref())); + snapshot_edits.extend(map.randomly_mutate(&mut rng)); vec![] } - _ => buffer.update(cx, |buffer, _| { + _ => buffer.update(cx, |buffer, cx| { let start_version = buffer.version.clone(); let edit_count = rng.gen_range(1..=5); - buffer.randomly_edit(&mut rng, edit_count); + buffer.randomly_edit(&mut rng, edit_count, cx); let edits = buffer .edits_since::(&start_version) .collect::>(); @@ -1272,14 +1287,17 @@ mod tests { buffer.edits_since::(&start_version).collect() }), }; + let buffer_snapshot = buffer.read(cx).snapshot(); + + let (snapshot, edits) = map.read(buffer_snapshot.clone(), buffer_edits); + snapshot_edits.push((snapshot.clone(), edits)); - let buffer = map.buffer.read(cx).snapshot(); - let mut expected_text: String = buffer.text().to_string(); + let mut expected_text: String = buffer_snapshot.text().to_string(); let mut expected_buffer_rows = Vec::new(); - let mut next_row = buffer.max_point().row; - for fold_range in map.merged_fold_ranges(cx.as_ref()).into_iter().rev() { - let fold_start = buffer.point_for_offset(fold_range.start).unwrap(); - let fold_end = buffer.point_for_offset(fold_range.end).unwrap(); + let mut next_row = buffer_snapshot.max_point().row; + for fold_range in map.merged_fold_ranges().into_iter().rev() { + let fold_start = buffer_snapshot.point_for_offset(fold_range.start).unwrap(); + let fold_end = buffer_snapshot.point_for_offset(fold_range.end).unwrap(); expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev()); next_row = fold_start.row; @@ -1288,9 +1306,7 @@ mod tests { expected_buffer_rows.extend((0..=next_row).rev()); expected_buffer_rows.reverse(); - let (snapshot, edits) = map.read(buffer_edits, cx.as_ref()); assert_eq!(snapshot.text(), expected_text); - snapshot_edits.push((snapshot.clone(), edits)); for (output_row, line) in expected_text.lines().enumerate() { let line_len = snapshot.line_len(output_row as u32); @@ -1309,7 +1325,7 @@ mod tests { let mut char_column = 0; for c in expected_text.chars() { let buffer_point = fold_point.to_buffer_point(&snapshot); - let buffer_offset = buffer_point.to_offset(&buffer); + let buffer_offset = buffer_point.to_offset(&buffer_snapshot); assert_eq!( buffer_point.to_fold_point(&snapshot, Right), fold_point, @@ -1379,26 +1395,28 @@ mod tests { ); } - for fold_range in map.merged_fold_ranges(cx.as_ref()) { + for fold_range in map.merged_fold_ranges() { let fold_point = fold_range .start - .to_point(&buffer) + .to_point(&buffer_snapshot) .to_fold_point(&snapshot, Right); assert!(snapshot.is_line_folded(fold_point.row())); } for _ in 0..5 { - let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); - let start = buffer.clip_offset(rng.gen_range(0..=end), Left); + let end = + buffer_snapshot.clip_offset(rng.gen_range(0..=buffer_snapshot.len()), Right); + let start = buffer_snapshot.clip_offset(rng.gen_range(0..=end), Left); let expected_folds = map .folds - .items(&buffer) + .items(&buffer_snapshot) .into_iter() .filter(|fold| { - let start = buffer.anchor_before(start); - let end = buffer.anchor_after(end); - start.cmp(&fold.0.end, &buffer).unwrap() == Ordering::Less - && end.cmp(&fold.0.start, &buffer).unwrap() == Ordering::Greater + let start = buffer_snapshot.anchor_before(start); + let end = buffer_snapshot.anchor_after(end); + start.cmp(&fold.0.end, &buffer_snapshot).unwrap() == Ordering::Less + && end.cmp(&fold.0.start, &buffer_snapshot).unwrap() + == Ordering::Greater }) .map(|fold| fold.0) .collect::>(); @@ -1456,26 +1474,24 @@ mod tests { let text = sample_text(6, 6) + "\n"; let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); - let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0; + let buffer_snapshot = buffer.read(cx).snapshot(); + let mut map = FoldMap::new(buffer_snapshot.clone()).0; - let (mut writer, _, _) = map.write(vec![], cx.as_ref()); - writer.fold( - vec![ - Point::new(0, 2)..Point::new(2, 2), - Point::new(3, 1)..Point::new(4, 1), - ], - cx.as_ref(), - ); + let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + writer.fold(vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(3, 1)..Point::new(4, 1), + ]); - let (snapshot, _) = map.read(vec![], cx.as_ref()); + let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); 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]); } impl FoldMap { - fn merged_fold_ranges(&self, cx: &AppContext) -> Vec> { - let buffer = self.buffer.read(cx).snapshot(); + fn merged_fold_ranges(&self) -> Vec> { + let buffer = self.buffer.lock().clone(); let mut folds = self.folds.items(&buffer); // Ensure sorting doesn't change how folds get merged and displayed. folds.sort_by(|a, b| a.0.cmp(&b.0, &buffer).unwrap()); @@ -1503,15 +1519,11 @@ mod tests { merged_ranges } - pub fn randomly_mutate( - &mut self, - rng: &mut impl Rng, - cx: &AppContext, - ) -> Vec<(Snapshot, Vec)> { + pub fn randomly_mutate(&mut self, rng: &mut impl Rng) -> Vec<(Snapshot, Vec)> { let mut snapshot_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=39 if !self.folds.is_empty() => { - let buffer = self.buffer.read(cx); + let buffer = self.buffer.lock().clone(); let mut to_unfold = Vec::new(); for _ in 0..rng.gen_range(1..=3) { let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); @@ -1519,13 +1531,13 @@ mod tests { to_unfold.push(start..end); } log::info!("unfolding {:?}", to_unfold); - let (mut writer, snapshot, edits) = self.write(vec![], cx.as_ref()); + let (mut writer, snapshot, edits) = self.write(buffer, vec![]); snapshot_edits.push((snapshot, edits)); - let (snapshot, edits) = writer.fold(to_unfold, cx.as_ref()); + let (snapshot, edits) = writer.fold(to_unfold); snapshot_edits.push((snapshot, edits)); } _ => { - let buffer = self.buffer.read(cx); + let buffer = self.buffer.lock().clone(); let mut to_fold = Vec::new(); for _ in 0..rng.gen_range(1..=2) { let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); @@ -1533,9 +1545,9 @@ mod tests { to_fold.push(start..end); } log::info!("folding {:?}", to_fold); - let (mut writer, snapshot, edits) = self.write(vec![], cx.as_ref()); + let (mut writer, snapshot, edits) = self.write(buffer, vec![]); snapshot_edits.push((snapshot, edits)); - let (snapshot, edits) = writer.fold(to_fold, cx.as_ref()); + let (snapshot, edits) = writer.fold(to_fold); 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 6e930fefd0e87c9185bed771d06b0e0eb3b62e35..1a9163ba2b0227b07bba913508dd160d70ebd749 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -470,11 +470,12 @@ mod tests { let text = RandomCharIter::new(&mut rng).take(len).collect::(); Buffer::new(0, text, cx) }); + let buffer_snapshot = buffer.read(cx).snapshot(); log::info!("Buffer text: {:?}", buffer.read(cx).text()); - let (mut fold_map, _) = FoldMap::new(buffer.clone(), cx); - fold_map.randomly_mutate(&mut rng, cx); - let (folds_snapshot, _) = fold_map.read(vec![], cx); + let (mut fold_map, _) = FoldMap::new(buffer_snapshot.clone()); + fold_map.randomly_mutate(&mut rng); + let (folds_snapshot, _) = fold_map.read(buffer_snapshot.clone(), vec![]); 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 d7c453675c1b4be2223ddc47d5c78951bf8b4d73..6d8ddcece0f3c26f0b8f600b4f1def40807ae252 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1026,7 +1026,8 @@ mod tests { let text = RandomCharIter::new(&mut rng).take(len).collect::(); Buffer::new(0, text, cx) }); - let (mut fold_map, folds_snapshot) = cx.read(|cx| FoldMap::new(buffer.clone(), cx)); + let buffer_snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot()); + let (mut fold_map, folds_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); log::info!( "Unwrapped text (no folds): {:?}", @@ -1079,9 +1080,7 @@ mod tests { wrap_map.update(&mut cx, |map, cx| map.set_wrap_width(wrap_width, cx)); } 20..=39 => { - for (folds_snapshot, fold_edits) in - cx.read(|cx| fold_map.randomly_mutate(&mut rng, cx)) - { + for (folds_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { 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)); @@ -1091,19 +1090,18 @@ mod tests { } } _ => { - buffer.update(&mut cx, |buffer, _| { + buffer.update(&mut cx, |buffer, cx| { let v0 = buffer.version(); - buffer.randomly_mutate(&mut rng); + let edit_count = rng.gen_range(1..=5); + buffer.randomly_edit(&mut rng, edit_count, cx); buffer_edits.extend(buffer.edits_since(&v0)); }); } } - log::info!( - "Unwrapped text (no folds): {:?}", - buffer.read_with(&cx, |buf, _| buf.text()) - ); - let (folds_snapshot, fold_edits) = cx.read(|cx| fold_map.read(buffer_edits, cx)); + let buffer_snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot()); + log::info!("Unwrapped text (no folds): {:?}", buffer_snapshot.text()); + let (folds_snapshot, fold_edits) = fold_map.read(buffer_snapshot, buffer_edits); log::info!( "Unwrapped text (unexpanded tabs): {:?}", folds_snapshot.text() diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 037b9f8a12dbf6b0a0c680fdd709e1282c765757..1c45bf15a7cefbef630f86da2c95abfd84daf262 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -72,8 +72,10 @@ pub struct Snapshot { text: text::Snapshot, tree: Option, diagnostics: AnchorRangeMultimap, + diagnostics_update_count: usize, is_parsing: bool, language: Option>, + parse_count: usize, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -339,8 +341,10 @@ impl Buffer { text: self.text.snapshot(), tree: self.syntax_tree(), diagnostics: self.diagnostics.clone(), + diagnostics_update_count: self.diagnostics_update_count, is_parsing: self.parsing_in_background, language: self.language.clone(), + parse_count: self.parse_count, } } @@ -1465,18 +1469,26 @@ impl Buffer { #[cfg(any(test, feature = "test-support"))] impl Buffer { - pub fn randomly_edit(&mut self, rng: &mut T, old_range_count: usize) - where + pub fn randomly_edit( + &mut self, + rng: &mut T, + old_range_count: usize, + cx: &mut ModelContext, + ) where T: rand::Rng, { + self.start_transaction(None).unwrap(); self.text.randomly_edit(rng, old_range_count); + self.end_transaction(None, cx).unwrap(); } - pub fn randomly_mutate(&mut self, rng: &mut T) + pub fn randomly_mutate(&mut self, rng: &mut T, cx: &mut ModelContext) where T: rand::Rng, { + self.start_transaction(None).unwrap(); self.text.randomly_mutate(rng); + self.end_transaction(None, cx).unwrap(); } } @@ -1703,6 +1715,14 @@ impl Snapshot { .as_ref() .and_then(|language| language.grammar.as_ref()) } + + pub fn diagnostics_update_count(&self) -> usize { + self.diagnostics_update_count + } + + pub fn parse_count(&self) -> usize { + self.parse_count + } } impl Clone for Snapshot { @@ -1711,8 +1731,10 @@ impl Clone for Snapshot { text: self.text.clone(), tree: self.tree.clone(), diagnostics: self.diagnostics.clone(), + diagnostics_update_count: self.diagnostics_update_count, is_parsing: self.is_parsing, language: self.language.clone(), + parse_count: self.parse_count, } } } From 3b536f153f191f7251b87c48816dbc0c948272bc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Dec 2021 15:55:05 +0100 Subject: [PATCH 4/5] Introduce `text::Buffer::subscribe` Co-Authored-By: Nathan Sobo --- Cargo.lock | 3 + crates/editor/src/display_map/wrap_map.rs | 12 +- crates/language/src/buffer.rs | 27 +---- crates/text/Cargo.toml | 3 + crates/text/src/patch.rs | 21 ++-- crates/text/src/tests.rs | 36 ++++-- crates/text/src/text.rs | 130 ++++++++++++++++++---- 7 files changed, 159 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d84236e1cf710068a7c88c84dae4b4b9f2f1f667..f6227543798b09ad0afa62b93631e863fb7cdb2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4849,8 +4849,11 @@ dependencies = [ "arrayvec 0.7.1", "clock", "collections", + "ctor", + "env_logger", "gpui", "log", + "parking_lot", "rand 0.8.3", "smallvec", "sum_tree", diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 6d8ddcece0f3c26f0b8f600b4f1def40807ae252..174e9df98eb7f35be03360973ab7512ed0ed6789 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -204,12 +204,10 @@ impl WrapMap { } let new_rows = self.snapshot.transforms.summary().output.lines.row + 1; self.snapshot.interpolated = false; - self.edits_since_sync = self.edits_since_sync.compose(&unsafe { - Patch::new_unchecked(vec![Edit { - old: 0..old_rows, - new: 0..new_rows, - }]) - }); + self.edits_since_sync = self.edits_since_sync.compose(&Patch::new(vec![Edit { + old: 0..old_rows, + new: 0..new_rows, + }])); } } @@ -559,7 +557,7 @@ impl Snapshot { } consolidate_wrap_edits(&mut wrap_edits); - unsafe { Patch::new_unchecked(wrap_edits) } + Patch::new(wrap_edits) } pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 1c45bf15a7cefbef630f86da2c95abfd84daf262..bfe842246c3f440f356c015ae258d56ae081739f 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1322,8 +1322,7 @@ impl Buffer { was_dirty: bool, cx: &mut ModelContext, ) { - let patch = - unsafe { Patch::new_unchecked(self.edits_since::(old_version).collect()) }; + let patch = Patch::new(self.edits_since::(old_version).collect()); if patch.is_empty() { return; } @@ -1502,30 +1501,6 @@ impl Entity for Buffer { } } -// TODO: Do we need to clone a buffer? -impl Clone for Buffer { - fn clone(&self) -> Self { - Self { - text: self.text.clone(), - saved_version: self.saved_version.clone(), - saved_mtime: self.saved_mtime, - file: self.file.as_ref().map(|f| f.boxed_clone()), - language: self.language.clone(), - syntax_tree: Mutex::new(self.syntax_tree.lock().clone()), - parsing_in_background: false, - sync_parse_timeout: self.sync_parse_timeout, - parse_count: self.parse_count, - autoindent_requests: Default::default(), - pending_autoindent: Default::default(), - diagnostics: self.diagnostics.clone(), - diagnostics_update_count: self.diagnostics_update_count, - language_server: None, - #[cfg(test)] - operations: self.operations.clone(), - } - } -} - impl Deref for Buffer { type Target = TextBuffer; diff --git a/crates/text/Cargo.toml b/crates/text/Cargo.toml index bda69006bec88003ec9804cbe951a3ea4b591198..2b68a71a080ea412ee534d662b85e656fa51edc3 100644 --- a/crates/text/Cargo.toml +++ b/crates/text/Cargo.toml @@ -16,10 +16,13 @@ sum_tree = { path = "../sum_tree" } anyhow = "1.0.38" arrayvec = "0.7.1" log = "0.4" +parking_lot = "0.11" rand = { version = "0.8.3", optional = true } smallvec = { version = "1.6", features = ["union"] } [dev-dependencies] collections = { path = "../collections", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } +ctor = "0.1" +env_logger = "0.8" rand = "0.8.3" diff --git a/crates/text/src/patch.rs b/crates/text/src/patch.rs index 266ad04f25b61cc99a1fa656514b9d7e587659ed..85d354d3b01ddc4a33bd4da298a0dfa7a0809399 100644 --- a/crates/text/src/patch.rs +++ b/crates/text/src/patch.rs @@ -18,18 +18,17 @@ where + Default + PartialEq, { - pub unsafe fn new_unchecked(edits: Vec>) -> Self { - Self(edits) - } - 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); + #[cfg(debug_assertions)] + { + 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); } - last_edit = Some(edit); } Self(edits) } @@ -179,7 +178,7 @@ where self.0.is_empty() } - fn push(&mut self, edit: Edit) { + pub fn push(&mut self, edit: Edit) { if edit.is_empty() { return; } diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index 9c681b1071f8b329a33e8fe73075dedae6962cd1..a13273b898b9d6febd865add8cfee83d30f81fc1 100644 --- a/crates/text/src/tests.rs +++ b/crates/text/src/tests.rs @@ -8,6 +8,13 @@ use std::{ time::{Duration, Instant}, }; +#[cfg(test)] +#[ctor::ctor] +fn init_logger() { + // std::env::set_var("RUST_LOG", "info"); + env_logger::init(); +} + #[test] fn test_edit() { let mut buffer = Buffer::new(0, 0, History::new("abc".into())); @@ -72,30 +79,43 @@ fn test_random_edits(mut rng: StdRng) { ); if rng.gen_bool(0.3) { - buffer_versions.push(buffer.clone()); + buffer_versions.push((buffer.clone(), buffer.subscribe())); } } - for mut old_buffer in buffer_versions { + for (old_buffer, subscription) in buffer_versions { let edits = buffer .edits_since::(&old_buffer.version) .collect::>(); log::info!( - "mutating old buffer version {:?}, text: {:?}, edits since: {:?}", + "applying edits since version {:?} to old text: {:?}: {:?}", old_buffer.version(), old_buffer.text(), edits, ); + let mut text = old_buffer.visible_text.clone(); for edit in edits { let new_text: String = buffer.text_for_range(edit.new.clone()).collect(); - old_buffer.edit( - Some(edit.new.start..edit.new.start + edit.old.len()), - new_text, - ); + text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text); + } + assert_eq!(text.to_string(), buffer.text()); + + let subscription_edits = subscription.consume(); + log::info!( + "applying subscription edits since version {:?} to old text: {:?}: {:?}", + old_buffer.version(), + old_buffer.text(), + subscription_edits, + ); + + let mut text = old_buffer.visible_text.clone(); + for edit in subscription_edits.into_inner() { + let new_text: String = buffer.text_for_range(edit.new.clone()).collect(); + text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text); } - assert_eq!(old_buffer.text(), buffer.text()); + assert_eq!(text.to_string(), buffer.text()); } } diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index a5f014274b464d984c98057f70144036a4be96e6..50b8dd10fe5e47daba0ad5456f20d25154147337 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -15,6 +15,7 @@ use anyhow::{anyhow, Result}; use clock::ReplicaId; use collections::{HashMap, HashSet}; use operation_queue::OperationQueue; +use parking_lot::Mutex; pub use patch::Patch; pub use point::*; pub use point_utf16::*; @@ -28,13 +29,12 @@ use std::{ iter::Iterator, ops::{self, Deref, Range, Sub}, str, - sync::Arc, + sync::{Arc, Weak}, time::{Duration, Instant}, }; pub use sum_tree::Bias; use sum_tree::{FilterCursor, SumTree}; -#[derive(Clone)] pub struct Buffer { snapshot: Snapshot, last_edit: clock::Local, @@ -46,6 +46,7 @@ pub struct Buffer { remote_id: u64, local_clock: clock::Local, lamport_clock: clock::Lamport, + subscriptions: Vec>, } #[derive(Clone)] @@ -341,6 +342,25 @@ impl Edit<(D1, D2)> { } } +#[derive(Default)] +pub struct Subscription(Arc>>>); + +impl Subscription { + pub fn consume(&self) -> Patch { + let mut patches = self.0.lock(); + let mut changes = Patch::default(); + for patch in patches.drain(..) { + changes = changes.compose(&patch); + } + changes + } + + pub fn publish(&self, patch: Patch) { + let mut changes = self.0.lock(); + changes.push(patch); + } +} + #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct InsertionTimestamp { pub replica_id: ReplicaId, @@ -473,13 +493,14 @@ impl Buffer { }, last_edit: clock::Local::default(), history, - selections: HashMap::default(), + selections: Default::default(), deferred_ops: OperationQueue::new(), deferred_replicas: HashSet::default(), replica_id, remote_id, local_clock, lamport_clock, + subscriptions: Default::default(), } } @@ -546,7 +567,8 @@ impl Buffer { new_text: Option, timestamp: InsertionTimestamp, ) -> EditOperation { - let mut edit = EditOperation { + let mut edits = Patch::default(); + let mut edit_op = EditOperation { timestamp, version: self.version(), ranges: Vec::with_capacity(ranges.len()), @@ -602,6 +624,11 @@ impl Buffer { // Insert the new text before any existing fragments within the range. if let Some(new_text) = new_text.as_deref() { + let new_start = new_fragments.summary().text.visible; + edits.push(Edit { + old: fragment_start..fragment_start, + new: new_start..new_start + new_text.len(), + }); new_ropes.push_str(new_text); new_fragments.push( Fragment { @@ -628,6 +655,13 @@ impl Buffer { intersection.visible = false; } if intersection.len > 0 { + if fragment.visible && !intersection.visible { + let new_start = new_fragments.summary().text.visible; + edits.push(Edit { + old: fragment_start..intersection_end, + new: new_start..new_start, + }); + } new_ropes.push_fragment(&intersection, fragment.visible); new_fragments.push(intersection, &None); fragment_start = intersection_end; @@ -638,7 +672,7 @@ impl Buffer { } let full_range_end = FullOffset(range.end + old_fragments.start().deleted); - edit.ranges.push(full_range_start..full_range_end); + edit_op.ranges.push(full_range_start..full_range_end); } // If the current fragment has been partially consumed, then consume the rest of it @@ -663,8 +697,9 @@ impl Buffer { self.snapshot.fragments = new_fragments; self.snapshot.visible_text = visible_text; self.snapshot.deleted_text = deleted_text; - edit.new_text = new_text; - edit + self.update_subscriptions(edits); + edit_op.new_text = new_text; + edit_op } pub fn apply_ops>(&mut self, ops: I) -> Result<()> { @@ -764,10 +799,11 @@ impl Buffer { return; } + let mut edits = Patch::default(); let cx = Some(version.clone()); let mut new_ropes = RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); - let mut old_fragments = self.fragments.cursor::(); + let mut old_fragments = self.fragments.cursor::<(VersionedFullOffset, usize)>(); let mut new_fragments = old_fragments.slice( &VersionedFullOffset::Offset(ranges[0].start), Bias::Left, @@ -775,16 +811,16 @@ impl Buffer { ); new_ropes.push_tree(new_fragments.summary().text); - let mut fragment_start = old_fragments.start().full_offset(); + let mut fragment_start = old_fragments.start().0.full_offset(); for range in ranges { - let fragment_end = old_fragments.end(&cx).full_offset(); + let fragment_end = old_fragments.end(&cx).0.full_offset(); // If the current fragment ends before this range, then jump ahead to the first fragment // that extends past the start of this range, reusing any intervening fragments. if fragment_end < range.start { // If the current fragment has been partially consumed, then consume the rest of it // and advance to the next fragment before slicing. - if fragment_start > old_fragments.start().full_offset() { + if fragment_start > old_fragments.start().0.full_offset() { if fragment_end > fragment_start { let mut suffix = old_fragments.item().unwrap().clone(); suffix.len = fragment_end.0 - fragment_start.0; @@ -798,18 +834,18 @@ impl Buffer { old_fragments.slice(&VersionedFullOffset::Offset(range.start), Bias::Left, &cx); new_ropes.push_tree(slice.summary().text); new_fragments.push_tree(slice, &None); - fragment_start = old_fragments.start().full_offset(); + fragment_start = old_fragments.start().0.full_offset(); } // If we are at the end of a non-concurrent fragment, advance to the next one. - let fragment_end = old_fragments.end(&cx).full_offset(); + let fragment_end = old_fragments.end(&cx).0.full_offset(); if fragment_end == range.start && fragment_end > fragment_start { let mut fragment = old_fragments.item().unwrap().clone(); fragment.len = fragment_end.0 - fragment_start.0; new_ropes.push_fragment(&fragment, fragment.visible); new_fragments.push(fragment, &None); old_fragments.next(&cx); - fragment_start = old_fragments.start().full_offset(); + fragment_start = old_fragments.start().0.full_offset(); } // Skip over insertions that are concurrent to this edit, but have a lower lamport @@ -839,6 +875,15 @@ impl Buffer { // Insert the new text before any existing fragments within the range. if let Some(new_text) = new_text { + let mut old_start = old_fragments.start().1; + if old_fragments.item().map_or(false, |f| f.visible) { + old_start += fragment_start.0 - old_fragments.start().0.full_offset().0; + } + let new_start = new_fragments.summary().text.visible; + edits.push(Edit { + old: old_start..old_start, + new: new_start..new_start + new_text.len(), + }); new_ropes.push_str(new_text); new_fragments.push( Fragment { @@ -856,7 +901,7 @@ impl Buffer { // portions as deleted. while fragment_start < range.end { let fragment = old_fragments.item().unwrap(); - let fragment_end = old_fragments.end(&cx).full_offset(); + let fragment_end = old_fragments.end(&cx).0.full_offset(); let mut intersection = fragment.clone(); let intersection_end = cmp::min(range.end, fragment_end); if fragment.was_visible(version, &self.undo_map) { @@ -865,6 +910,15 @@ impl Buffer { intersection.visible = false; } if intersection.len > 0 { + if fragment.visible && !intersection.visible { + let old_start = old_fragments.start().1 + + (fragment_start.0 - old_fragments.start().0.full_offset().0); + let new_start = new_fragments.summary().text.visible; + edits.push(Edit { + old: old_start..old_start + intersection.len, + new: new_start..new_start, + }); + } new_ropes.push_fragment(&intersection, fragment.visible); new_fragments.push(intersection, &None); fragment_start = intersection_end; @@ -877,8 +931,8 @@ impl Buffer { // If the current fragment has been partially consumed, then consume the rest of it // and advance to the next fragment before slicing. - if fragment_start > old_fragments.start().full_offset() { - let fragment_end = old_fragments.end(&cx).full_offset(); + if fragment_start > old_fragments.start().0.full_offset() { + let fragment_end = old_fragments.end(&cx).0.full_offset(); if fragment_end > fragment_start { let mut suffix = old_fragments.item().unwrap().clone(); suffix.len = fragment_end.0 - fragment_start.0; @@ -899,9 +953,11 @@ impl Buffer { self.snapshot.deleted_text = deleted_text; self.local_clock.observe(timestamp.local()); self.lamport_clock.observe(timestamp.lamport()); + self.update_subscriptions(edits); } fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> { + let mut edits = Patch::default(); self.snapshot.undo_map.insert(undo); let mut cx = undo.version.clone(); @@ -910,7 +966,7 @@ impl Buffer { } let cx = Some(cx); - let mut old_fragments = self.fragments.cursor::(); + let mut old_fragments = self.fragments.cursor::<(VersionedFullOffset, usize)>(); let mut new_fragments = old_fragments.slice( &VersionedFullOffset::Offset(undo.ranges[0].start), Bias::Right, @@ -921,7 +977,7 @@ impl Buffer { new_ropes.push_tree(new_fragments.summary().text); for range in &undo.ranges { - let mut end_offset = old_fragments.end(&cx).full_offset(); + let mut end_offset = old_fragments.end(&cx).0.full_offset(); if end_offset < range.start { let preceding_fragments = old_fragments.slice( @@ -944,11 +1000,25 @@ impl Buffer { fragment.visible = fragment.is_visible(&self.undo_map); fragment.max_undos.observe(undo.id); } + + let old_start = old_fragments.start().1; + let new_start = new_fragments.summary().text.visible; + if fragment_was_visible && !fragment.visible { + edits.push(Edit { + old: old_start..old_start + fragment.len, + new: new_start..new_start, + }); + } else if !fragment_was_visible && fragment.visible { + edits.push(Edit { + old: old_start..old_start, + new: new_start..new_start + fragment.len, + }); + } new_ropes.push_fragment(&fragment, fragment_was_visible); new_fragments.push(fragment, &None); old_fragments.next(&cx); - if end_offset == old_fragments.end(&cx).full_offset() { + if end_offset == old_fragments.end(&cx).0.full_offset() { let unseen_fragments = old_fragments.slice( &VersionedFullOffset::Offset(end_offset), Bias::Right, @@ -957,7 +1027,7 @@ impl Buffer { new_ropes.push_tree(unseen_fragments.summary().text); new_fragments.push_tree(unseen_fragments, &None); } - end_offset = old_fragments.end(&cx).full_offset(); + end_offset = old_fragments.end(&cx).0.full_offset(); } else { break; } @@ -973,6 +1043,7 @@ impl Buffer { self.snapshot.fragments = new_fragments; self.snapshot.visible_text = visible_text; self.snapshot.deleted_text = deleted_text; + self.update_subscriptions(edits); Ok(()) } @@ -1129,6 +1200,23 @@ impl Buffer { }) } + pub fn subscribe(&mut self) -> Arc { + let subscription = Arc::new(Default::default()); + self.subscriptions.push(Arc::downgrade(&subscription)); + subscription + } + + fn update_subscriptions(&mut self, edits: Patch) { + self.subscriptions.retain(|subscription| { + if let Some(subscription) = subscription.upgrade() { + subscription.publish(edits.clone()); + true + } else { + false + } + }); + } + pub fn selection_set(&self, set_id: SelectionSetId) -> Result<&SelectionSet> { self.selections .get(&set_id) From 733e0cb21b3516aaaf509c66fb7fd35b1b5d0f81 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Dec 2021 16:08:53 +0100 Subject: [PATCH 5/5] Use the new buffer subscription API to keep `DisplayMap` in sync Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 55 ++++++++------------------------ crates/editor/src/editor.rs | 2 +- crates/language/src/buffer.rs | 8 +++-- crates/language/src/tests.rs | 27 ++-------------- crates/project/src/worktree.rs | 28 +++------------- crates/text/src/text.rs | 17 ++++------ 6 files changed, 34 insertions(+), 103 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 77357616d9567dd16c83339e52bb3f60d6d43d82..8137e46583003e13bd5beb79be6d168769fc4925 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -10,11 +10,9 @@ use gpui::{ fonts::{FontId, HighlightStyle}, AppContext, Entity, ModelContext, ModelHandle, }; -use language::{Anchor, Buffer, Patch, Point, ToOffset, ToPoint}; -use parking_lot::Mutex; +use language::{Anchor, Buffer, Point, Subscription as BufferSubscription, ToOffset, ToPoint}; use std::{ collections::{HashMap, HashSet}, - mem, ops::Range, }; use sum_tree::Bias; @@ -29,11 +27,11 @@ pub trait ToDisplayPoint { pub struct DisplayMap { buffer: ModelHandle, + buffer_subscription: BufferSubscription, fold_map: FoldMap, tab_map: TabMap, wrap_map: ModelHandle, block_map: BlockMap, - buffer_edits_since_sync: Mutex>, } impl Entity for DisplayMap { @@ -49,28 +47,26 @@ impl DisplayMap { wrap_width: Option, cx: &mut ModelContext, ) -> Self { + let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot()); let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); 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, + buffer_subscription, fold_map, tab_map, wrap_map, block_map, - buffer_edits_since_sync: Default::default(), } } pub fn snapshot(&self, cx: &mut ModelContext) -> DisplayMapSnapshot { let buffer_snapshot = self.buffer.read(cx).snapshot(); - let (folds_snapshot, edits) = self.fold_map.read( - buffer_snapshot, - mem::take(&mut *self.buffer_edits_since_sync.lock()).into_inner(), - ); + let edits = self.buffer_subscription.consume().into_inner(); + let (folds_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits); let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits); let (wraps_snapshot, edits) = self .wrap_map @@ -92,10 +88,8 @@ impl DisplayMap { cx: &mut ModelContext, ) { let snapshot = self.buffer.read(cx).snapshot(); - let (mut fold_map, snapshot, edits) = self.fold_map.write( - snapshot, - mem::take(&mut *self.buffer_edits_since_sync.lock()).into_inner(), - ); + let edits = self.buffer_subscription.consume().into_inner(); + let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map @@ -115,10 +109,8 @@ impl DisplayMap { cx: &mut ModelContext, ) { let snapshot = self.buffer.read(cx).snapshot(); - let (mut fold_map, snapshot, edits) = self.fold_map.write( - snapshot, - mem::take(&mut *self.buffer_edits_since_sync.lock()).into_inner(), - ); + let edits = self.buffer_subscription.consume().into_inner(); + let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map @@ -142,10 +134,8 @@ impl DisplayMap { T: Into + Clone, { let snapshot = self.buffer.read(cx).snapshot(); - let (snapshot, edits) = self.fold_map.read( - snapshot, - mem::take(&mut *self.buffer_edits_since_sync.lock()).into_inner(), - ); + let edits = self.buffer_subscription.consume().into_inner(); + let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map @@ -164,10 +154,8 @@ impl DisplayMap { pub fn remove_blocks(&mut self, ids: HashSet, cx: &mut ModelContext) { let snapshot = self.buffer.read(cx).snapshot(); - let (snapshot, edits) = self.fold_map.read( - snapshot, - mem::take(&mut *self.buffer_edits_since_sync.lock()).into_inner(), - ); + let edits = self.buffer_subscription.consume().into_inner(); + let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map @@ -190,21 +178,6 @@ 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) => { - let mut edits_since_sync = self.buffer_edits_since_sync.lock(); - *edits_since_sync = edits_since_sync.compose(patch); - } - _ => {} - } - } } pub struct DisplayMapSnapshot { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bbdcf6f79be7ab9bf47d16a21d89035604c516d5..e61f1f79cdc7755e513bb4eb9fc90741ceca930d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3466,7 +3466,7 @@ 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), diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index bfe842246c3f440f356c015ae258d56ae081739f..29c092c3ce218332ca7f0aa3cd76ed310a04d0fa 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -109,7 +109,7 @@ pub enum Operation { #[derive(Clone, Debug, Eq, PartialEq)] pub enum Event { - Edited(Patch), + Edited, Dirtied, Saved, FileHandleChanged, @@ -1138,6 +1138,10 @@ impl Buffer { .map_or(false, |file| file.mtime() > self.saved_mtime) } + pub fn subscribe(&mut self) -> Subscription { + self.text.subscribe() + } + pub fn start_transaction( &mut self, selection_set_ids: impl IntoIterator, @@ -1330,7 +1334,7 @@ impl Buffer { self.reparse(cx); self.update_language_server(); - cx.emit(Event::Edited(patch)); + cx.emit(Event::Edited); if !was_dirty { cx.emit(Event::Dirtied); } diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 6326d056937dda0af6ee9519d43126bb0f5e5ec3..cff74af1e3bee4b9c65416ecdd5986fbb787f4ac 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -110,34 +110,11 @@ fn test_edit_events(cx: &mut gpui::MutableAppContext) { let buffer_1_events = buffer_1_events.borrow(); assert_eq!( *buffer_1_events, - 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, - }])) - ] + vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited] ); let buffer_2_events = buffer_2_events.borrow(); - assert_eq!( - *buffer_2_events, - vec![ - Event::Edited(Patch::new(vec![Edit { - old: 2..4, - new: 2..5 - }])), - Event::Dirtied - ] - ); + assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]); } #[gpui::test] diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 9fe3792144cdfaa504f162ee3bd28b477cc94718..29bc230b97c3c42d79eee264cd47816d3a413ffb 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3016,7 +3016,7 @@ mod tests { fmt::Write, time::{SystemTime, UNIX_EPOCH}, }; - use text::{Patch, Point}; + use text::Point; use util::test::temp_tree; #[gpui::test] @@ -3469,13 +3469,7 @@ mod tests { assert!(buffer.is_dirty()); assert_eq!( *events.borrow(), - &[ - language::Event::Edited(Patch::new(vec![text::Edit { - old: 1..2, - new: 1..1 - }])), - language::Event::Dirtied - ] + &[language::Event::Edited, language::Event::Dirtied] ); events.borrow_mut().clear(); buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx); @@ -3498,15 +3492,9 @@ mod tests { assert_eq!( *events.borrow(), &[ - language::Event::Edited(Patch::new(vec![text::Edit { - old: 1..1, - new: 1..2 - }])), + language::Event::Edited, language::Event::Dirtied, - language::Event::Edited(Patch::new(vec![text::Edit { - old: 2..2, - new: 2..3 - }])), + language::Event::Edited, ], ); events.borrow_mut().clear(); @@ -3518,13 +3506,7 @@ mod tests { assert!(buffer.is_dirty()); }); - assert_eq!( - *events.borrow(), - &[language::Event::Edited(Patch::new(vec![text::Edit { - old: 1..3, - new: 1..1 - }]))] - ); + assert_eq!(*events.borrow(), &[language::Event::Edited]); // When a file is deleted, the buffer is considered dirty. let events = Rc::new(RefCell::new(Vec::new())); diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 50b8dd10fe5e47daba0ad5456f20d25154147337..a434e97e2ee66acf05171c500b6c4be5f98fdb33 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -46,7 +46,7 @@ pub struct Buffer { remote_id: u64, local_clock: clock::Local, lamport_clock: clock::Lamport, - subscriptions: Vec>, + subscriptions: Vec>>>>, } #[derive(Clone)] @@ -342,7 +342,7 @@ impl Edit<(D1, D2)> { } } -#[derive(Default)] +#[derive(Clone, Default)] pub struct Subscription(Arc>>>); impl Subscription { @@ -354,11 +354,6 @@ impl Subscription { } changes } - - pub fn publish(&self, patch: Patch) { - let mut changes = self.0.lock(); - changes.push(patch); - } } #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] @@ -1200,16 +1195,16 @@ impl Buffer { }) } - pub fn subscribe(&mut self) -> Arc { - let subscription = Arc::new(Default::default()); - self.subscriptions.push(Arc::downgrade(&subscription)); + pub fn subscribe(&mut self) -> Subscription { + let subscription = Subscription(Default::default()); + self.subscriptions.push(Arc::downgrade(&subscription.0)); subscription } fn update_subscriptions(&mut self, edits: Patch) { self.subscriptions.retain(|subscription| { if let Some(subscription) = subscription.upgrade() { - subscription.publish(edits.clone()); + subscription.lock().push(edits.clone()); true } else { false