diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index d6121cc7bd15644b17d7131f2fb295354a70b84b..a9a781e604b87af2386f674fbdee1bc668c44afa 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "language" version = "0.1.0" -edition = "2018" +edition = "2021" [lib] path = "src/language.rs" diff --git a/crates/language/src/fragment_list.rs b/crates/language/src/fragment_list.rs index e8312aeda0272f558a3f668121c745da9443cae4..6f16e9ab51881577437c167b90ddf2efcc3e4bf7 100644 --- a/crates/language/src/fragment_list.rs +++ b/crates/language/src/fragment_list.rs @@ -5,7 +5,7 @@ use parking_lot::Mutex; use smallvec::{smallvec, SmallVec}; use std::{cmp, iter, mem, ops::Range}; use sum_tree::{Bias, Cursor, SumTree}; -use text::TextSummary; +use text::{Anchor, AnchorRangeExt, TextSummary}; use theme::SyntaxTheme; const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize]; @@ -43,7 +43,7 @@ pub struct FragmentProperties<'a, T> { struct Entry { id: FragmentId, buffer: buffer::Snapshot, - buffer_range: Range, + buffer_range: Range, text_summary: TextSummary, header_height: u8, } @@ -86,7 +86,8 @@ impl FragmentList { self.sync(cx); let buffer = props.buffer.read(cx); - let buffer_range = props.range.start.to_offset(buffer)..props.range.end.to_offset(buffer); + let buffer_range = + buffer.anchor_before(props.range.start)..buffer.anchor_after(props.range.end); let mut text_summary = buffer.text_summary_for_range::(buffer_range.clone()); if props.header_height > 0 { @@ -148,12 +149,13 @@ impl FragmentList { let old_fragments = mem::take(&mut snapshot.entries); let mut cursor = old_fragments.cursor::(); for (buffer, fragment_id, patch_ix) in fragments_to_edit { + let buffer = buffer.read(cx); snapshot .entries .push_tree(cursor.slice(fragment_id, Bias::Left, &()), &()); let fragment = cursor.item().unwrap(); - let mut new_range = fragment.buffer_range.clone(); + let mut new_range = fragment.buffer_range.to_offset(buffer); for edit in patches[patch_ix].edits() { let edit_start = edit.new.start; let edit_end = edit.new.start + edit.old_len(); @@ -177,7 +179,6 @@ impl FragmentList { } } - let buffer = buffer.read(cx); let mut text_summary: TextSummary = buffer.text_summary_for_range(new_range.clone()); if fragment.header_height > 0 { text_summary.first_line_chars = 0; @@ -189,7 +190,8 @@ impl FragmentList { Entry { id: fragment.id.clone(), buffer: buffer.snapshot(), - buffer_range: new_range, + buffer_range: buffer.anchor_before(new_range.start) + ..buffer.anchor_after(new_range.end), text_summary, header_height: fragment.header_height, }, @@ -227,10 +229,11 @@ impl Snapshot { cursor.seek(&range.start, Bias::Right, &()); let entry_chunks = cursor.item().map(|entry| { - let buffer_start = entry.buffer_range.start + (range.start - cursor.start()); + let buffer_range = entry.buffer_range.to_offset(&entry.buffer); + let buffer_start = buffer_range.start + (range.start - cursor.start()); let buffer_end = cmp::min( - entry.buffer_range.end, - entry.buffer_range.start + (range.end - cursor.start()), + buffer_range.end, + buffer_range.start + (range.end - cursor.start()), ); entry.buffer.chunks(buffer_start..buffer_end, theme) }); @@ -305,17 +308,17 @@ impl<'a> Iterator for Chunks<'a> { self.cursor.next(&()); let entry = self.cursor.item()?; - + let buffer_range = entry.buffer_range.to_offset(&entry.buffer); let buffer_end = cmp::min( - entry.buffer_range.end, - entry.buffer_range.start + (self.range.end - self.cursor.start()), + buffer_range.end, + buffer_range.start + (self.range.end - self.cursor.start()), ); self.header_height = entry.header_height; self.entry_chunks = Some( entry .buffer - .chunks(entry.buffer_range.start..buffer_end, self.theme), + .chunks(buffer_range.start..buffer_end, self.theme), ); Some(Chunk { diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index a13273b898b9d6febd865add8cfee83d30f81fc1..ff1a3d9ec8b4fbb086d1a9a1c2f83ce13b46e033 100644 --- a/crates/text/src/tests.rs +++ b/crates/text/src/tests.rs @@ -102,6 +102,32 @@ fn test_random_edits(mut rng: StdRng) { } assert_eq!(text.to_string(), buffer.text()); + for _ in 0..5 { + let end_ix = old_buffer.clip_offset(rng.gen_range(0..=old_buffer.len()), Bias::Right); + let start_ix = old_buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); + let range = old_buffer.anchor_before(start_ix)..old_buffer.anchor_after(end_ix); + let mut old_text = old_buffer.text_for_range(range.clone()).collect::(); + let edits = buffer + .edits_since_in_range::(&old_buffer.version, range.clone()) + .collect::>(); + log::info!( + "applying edits since version {:?} to old text in range {:?}: {:?}: {:?}", + old_buffer.version(), + start_ix..end_ix, + old_text, + edits, + ); + + let new_text = buffer.text_for_range(range).collect::(); + for edit in edits { + old_text.replace_range( + edit.new.start..edit.new.start + edit.old_len(), + &new_text[edit.new], + ); + } + assert_eq!(old_text, new_text); + } + let subscription_edits = subscription.consume(); log::info!( "applying subscription edits since version {:?} to old text: {:?}: {:?}", diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 0fef4aac82bb300e66fda5000af62ab215865bfc..06c92ca91efe07d22fbc30bb3eacf115d5a47304 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -302,6 +302,7 @@ struct Edits<'a, D: TextDimension<'a>, F: FnMut(&FragmentSummary) -> bool> { since: &'a clock::Global, old_end: D, new_end: D, + range: Range, } #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -402,6 +403,12 @@ struct FragmentTextSummary { deleted: usize, } +impl FragmentTextSummary { + pub fn full_offset(&self) -> FullOffset { + FullOffset(self.visible + self.deleted) + } +} + impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentTextSummary { fn add_summary(&mut self, summary: &'a FragmentSummary, _: &Option) { self.visible += summary.text.visible; @@ -1873,6 +1880,17 @@ impl Snapshot { &'a self, since: &'a clock::Global, ) -> impl 'a + Iterator> + where + D: 'a + TextDimension<'a> + Ord, + { + self.edits_since_in_range(since, Anchor::min()..Anchor::max()) + } + + pub fn edits_since_in_range<'a, D>( + &'a self, + since: &'a clock::Global, + range: Range, + ) -> impl 'a + Iterator> where D: 'a + TextDimension<'a> + Ord, { @@ -1885,14 +1903,36 @@ impl Snapshot { ) }; + let mut cursor = self + .fragments + .cursor::<(VersionedFullOffset, FragmentTextSummary)>(); + cursor.seek( + &VersionedFullOffset::Offset(range.start.full_offset), + range.start.bias, + &Some(range.start.version), + ); + let mut visible_start = cursor.start().1.visible; + let mut deleted_start = cursor.start().1.deleted; + if let Some(fragment) = cursor.item() { + let overshoot = range.start.full_offset.0 - cursor.start().0.full_offset().0; + if fragment.visible { + visible_start += overshoot; + } else { + deleted_start += overshoot; + } + } + + let full_offset_start = FullOffset(visible_start + deleted_start); + let full_offset_end = range.end.to_full_offset(self, range.end.bias); Edits { - visible_cursor: self.visible_text.cursor(0), - deleted_cursor: self.deleted_text.cursor(0), + visible_cursor: self.visible_text.cursor(visible_start), + deleted_cursor: self.deleted_text.cursor(deleted_start), fragments_cursor, undos: &self.undo_map, since, old_end: Default::default(), new_end: Default::default(), + range: full_offset_start..full_offset_end, } } } @@ -1960,9 +2000,19 @@ impl<'a, D: TextDimension<'a> + Ord, F: FnMut(&FragmentSummary) -> bool> Iterato let cursor = self.fragments_cursor.as_mut()?; while let Some(fragment) = cursor.item() { - let summary = self.visible_cursor.summary(cursor.start().visible); - self.old_end.add_assign(&summary); - self.new_end.add_assign(&summary); + if cursor.end(&None).full_offset() < self.range.start { + cursor.next(&None); + continue; + } else if cursor.start().full_offset() >= self.range.end { + break; + } + + if cursor.start().visible > self.visible_cursor.offset() { + let summary = self.visible_cursor.summary(cursor.start().visible); + self.old_end.add_assign(&summary); + self.new_end.add_assign(&summary); + } + if pending_edit .as_ref() .map_or(false, |change| change.new.end < self.new_end) @@ -1971,7 +2021,12 @@ impl<'a, D: TextDimension<'a> + Ord, F: FnMut(&FragmentSummary) -> bool> Iterato } if !fragment.was_visible(&self.since, &self.undos) && fragment.visible { - let fragment_summary = self.visible_cursor.summary(cursor.end(&None).visible); + let visible_end = cmp::min( + cursor.end(&None).visible, + cursor.start().visible + (self.range.end - cursor.start().full_offset()), + ); + + let fragment_summary = self.visible_cursor.summary(visible_end); let mut new_end = self.new_end.clone(); new_end.add_assign(&fragment_summary); if let Some(pending_edit) = pending_edit.as_mut() { @@ -1985,8 +2040,15 @@ impl<'a, D: TextDimension<'a> + Ord, F: FnMut(&FragmentSummary) -> bool> Iterato self.new_end = new_end; } else if fragment.was_visible(&self.since, &self.undos) && !fragment.visible { - self.deleted_cursor.seek_forward(cursor.start().deleted); - let fragment_summary = self.deleted_cursor.summary(cursor.end(&None).deleted); + let deleted_end = cmp::min( + cursor.end(&None).deleted, + cursor.start().deleted + (self.range.end - cursor.start().full_offset()), + ); + + if cursor.start().deleted > self.deleted_cursor.offset() { + self.deleted_cursor.seek_forward(cursor.start().deleted); + } + let fragment_summary = self.deleted_cursor.summary(deleted_end); let mut old_end = self.old_end.clone(); old_end.add_assign(&fragment_summary); if let Some(pending_edit) = pending_edit.as_mut() { @@ -2251,6 +2313,28 @@ impl ToOffset for Anchor { fn to_offset<'a>(&self, content: &Snapshot) -> usize { content.summary_for_anchor(self) } + + fn to_full_offset<'a>(&self, content: &Snapshot, bias: Bias) -> FullOffset { + if content.version == self.version { + self.full_offset + } else { + let mut cursor = content + .fragments + .cursor::<(VersionedFullOffset, FragmentTextSummary)>(); + cursor.seek( + &VersionedFullOffset::Offset(self.full_offset), + bias, + &Some(self.version.clone()), + ); + + let mut full_offset = cursor.start().1.full_offset().0; + if cursor.item().is_some() { + full_offset += self.full_offset - cursor.start().0.full_offset(); + } + + FullOffset(full_offset) + } + } } impl<'a> ToOffset for &'a Anchor {