mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 10:29:35 +03:00
Introduce Buffer::edits_since_in_range
Co-Authored-By: Nathan Sobo <nathan@zed.dev> Co-Authored-By: Max Brunsfeld <max@zed.dev>
This commit is contained in:
parent
e37908cf3b
commit
42eba7268d
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "language"
|
name = "language"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
path = "src/language.rs"
|
path = "src/language.rs"
|
||||||
|
@ -5,7 +5,7 @@ use parking_lot::Mutex;
|
|||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use std::{cmp, iter, mem, ops::Range};
|
use std::{cmp, iter, mem, ops::Range};
|
||||||
use sum_tree::{Bias, Cursor, SumTree};
|
use sum_tree::{Bias, Cursor, SumTree};
|
||||||
use text::TextSummary;
|
use text::{Anchor, AnchorRangeExt, TextSummary};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
|
|
||||||
const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
|
const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
|
||||||
@ -43,7 +43,7 @@ pub struct FragmentProperties<'a, T> {
|
|||||||
struct Entry {
|
struct Entry {
|
||||||
id: FragmentId,
|
id: FragmentId,
|
||||||
buffer: buffer::Snapshot,
|
buffer: buffer::Snapshot,
|
||||||
buffer_range: Range<usize>,
|
buffer_range: Range<Anchor>,
|
||||||
text_summary: TextSummary,
|
text_summary: TextSummary,
|
||||||
header_height: u8,
|
header_height: u8,
|
||||||
}
|
}
|
||||||
@ -86,7 +86,8 @@ impl FragmentList {
|
|||||||
self.sync(cx);
|
self.sync(cx);
|
||||||
|
|
||||||
let buffer = props.buffer.read(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 =
|
let mut text_summary =
|
||||||
buffer.text_summary_for_range::<TextSummary, _>(buffer_range.clone());
|
buffer.text_summary_for_range::<TextSummary, _>(buffer_range.clone());
|
||||||
if props.header_height > 0 {
|
if props.header_height > 0 {
|
||||||
@ -148,12 +149,13 @@ impl FragmentList {
|
|||||||
let old_fragments = mem::take(&mut snapshot.entries);
|
let old_fragments = mem::take(&mut snapshot.entries);
|
||||||
let mut cursor = old_fragments.cursor::<FragmentId>();
|
let mut cursor = old_fragments.cursor::<FragmentId>();
|
||||||
for (buffer, fragment_id, patch_ix) in fragments_to_edit {
|
for (buffer, fragment_id, patch_ix) in fragments_to_edit {
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
snapshot
|
snapshot
|
||||||
.entries
|
.entries
|
||||||
.push_tree(cursor.slice(fragment_id, Bias::Left, &()), &());
|
.push_tree(cursor.slice(fragment_id, Bias::Left, &()), &());
|
||||||
|
|
||||||
let fragment = cursor.item().unwrap();
|
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() {
|
for edit in patches[patch_ix].edits() {
|
||||||
let edit_start = edit.new.start;
|
let edit_start = edit.new.start;
|
||||||
let edit_end = edit.new.start + edit.old_len();
|
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());
|
let mut text_summary: TextSummary = buffer.text_summary_for_range(new_range.clone());
|
||||||
if fragment.header_height > 0 {
|
if fragment.header_height > 0 {
|
||||||
text_summary.first_line_chars = 0;
|
text_summary.first_line_chars = 0;
|
||||||
@ -189,7 +190,8 @@ impl FragmentList {
|
|||||||
Entry {
|
Entry {
|
||||||
id: fragment.id.clone(),
|
id: fragment.id.clone(),
|
||||||
buffer: buffer.snapshot(),
|
buffer: buffer.snapshot(),
|
||||||
buffer_range: new_range,
|
buffer_range: buffer.anchor_before(new_range.start)
|
||||||
|
..buffer.anchor_after(new_range.end),
|
||||||
text_summary,
|
text_summary,
|
||||||
header_height: fragment.header_height,
|
header_height: fragment.header_height,
|
||||||
},
|
},
|
||||||
@ -227,10 +229,11 @@ impl Snapshot {
|
|||||||
cursor.seek(&range.start, Bias::Right, &());
|
cursor.seek(&range.start, Bias::Right, &());
|
||||||
|
|
||||||
let entry_chunks = cursor.item().map(|entry| {
|
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(
|
let buffer_end = cmp::min(
|
||||||
entry.buffer_range.end,
|
buffer_range.end,
|
||||||
entry.buffer_range.start + (range.end - cursor.start()),
|
buffer_range.start + (range.end - cursor.start()),
|
||||||
);
|
);
|
||||||
entry.buffer.chunks(buffer_start..buffer_end, theme)
|
entry.buffer.chunks(buffer_start..buffer_end, theme)
|
||||||
});
|
});
|
||||||
@ -305,17 +308,17 @@ impl<'a> Iterator for Chunks<'a> {
|
|||||||
|
|
||||||
self.cursor.next(&());
|
self.cursor.next(&());
|
||||||
let entry = self.cursor.item()?;
|
let entry = self.cursor.item()?;
|
||||||
|
let buffer_range = entry.buffer_range.to_offset(&entry.buffer);
|
||||||
let buffer_end = cmp::min(
|
let buffer_end = cmp::min(
|
||||||
entry.buffer_range.end,
|
buffer_range.end,
|
||||||
entry.buffer_range.start + (self.range.end - self.cursor.start()),
|
buffer_range.start + (self.range.end - self.cursor.start()),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.header_height = entry.header_height;
|
self.header_height = entry.header_height;
|
||||||
self.entry_chunks = Some(
|
self.entry_chunks = Some(
|
||||||
entry
|
entry
|
||||||
.buffer
|
.buffer
|
||||||
.chunks(entry.buffer_range.start..buffer_end, self.theme),
|
.chunks(buffer_range.start..buffer_end, self.theme),
|
||||||
);
|
);
|
||||||
|
|
||||||
Some(Chunk {
|
Some(Chunk {
|
||||||
|
@ -102,6 +102,32 @@ fn test_random_edits(mut rng: StdRng) {
|
|||||||
}
|
}
|
||||||
assert_eq!(text.to_string(), buffer.text());
|
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::<String>();
|
||||||
|
let edits = buffer
|
||||||
|
.edits_since_in_range::<usize>(&old_buffer.version, range.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
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::<String>();
|
||||||
|
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();
|
let subscription_edits = subscription.consume();
|
||||||
log::info!(
|
log::info!(
|
||||||
"applying subscription edits since version {:?} to old text: {:?}: {:?}",
|
"applying subscription edits since version {:?} to old text: {:?}: {:?}",
|
||||||
|
@ -302,6 +302,7 @@ struct Edits<'a, D: TextDimension<'a>, F: FnMut(&FragmentSummary) -> bool> {
|
|||||||
since: &'a clock::Global,
|
since: &'a clock::Global,
|
||||||
old_end: D,
|
old_end: D,
|
||||||
new_end: D,
|
new_end: D,
|
||||||
|
range: Range<FullOffset>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
@ -402,6 +403,12 @@ struct FragmentTextSummary {
|
|||||||
deleted: usize,
|
deleted: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FragmentTextSummary {
|
||||||
|
pub fn full_offset(&self) -> FullOffset {
|
||||||
|
FullOffset(self.visible + self.deleted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentTextSummary {
|
impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentTextSummary {
|
||||||
fn add_summary(&mut self, summary: &'a FragmentSummary, _: &Option<clock::Global>) {
|
fn add_summary(&mut self, summary: &'a FragmentSummary, _: &Option<clock::Global>) {
|
||||||
self.visible += summary.text.visible;
|
self.visible += summary.text.visible;
|
||||||
@ -1873,6 +1880,17 @@ impl Snapshot {
|
|||||||
&'a self,
|
&'a self,
|
||||||
since: &'a clock::Global,
|
since: &'a clock::Global,
|
||||||
) -> impl 'a + Iterator<Item = Edit<D>>
|
) -> impl 'a + Iterator<Item = Edit<D>>
|
||||||
|
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<Anchor>,
|
||||||
|
) -> impl 'a + Iterator<Item = Edit<D>>
|
||||||
where
|
where
|
||||||
D: 'a + TextDimension<'a> + Ord,
|
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 {
|
Edits {
|
||||||
visible_cursor: self.visible_text.cursor(0),
|
visible_cursor: self.visible_text.cursor(visible_start),
|
||||||
deleted_cursor: self.deleted_text.cursor(0),
|
deleted_cursor: self.deleted_text.cursor(deleted_start),
|
||||||
fragments_cursor,
|
fragments_cursor,
|
||||||
undos: &self.undo_map,
|
undos: &self.undo_map,
|
||||||
since,
|
since,
|
||||||
old_end: Default::default(),
|
old_end: Default::default(),
|
||||||
new_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()?;
|
let cursor = self.fragments_cursor.as_mut()?;
|
||||||
|
|
||||||
while let Some(fragment) = cursor.item() {
|
while let Some(fragment) = cursor.item() {
|
||||||
let summary = self.visible_cursor.summary(cursor.start().visible);
|
if cursor.end(&None).full_offset() < self.range.start {
|
||||||
self.old_end.add_assign(&summary);
|
cursor.next(&None);
|
||||||
self.new_end.add_assign(&summary);
|
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
|
if pending_edit
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(false, |change| change.new.end < self.new_end)
|
.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 {
|
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();
|
let mut new_end = self.new_end.clone();
|
||||||
new_end.add_assign(&fragment_summary);
|
new_end.add_assign(&fragment_summary);
|
||||||
if let Some(pending_edit) = pending_edit.as_mut() {
|
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;
|
self.new_end = new_end;
|
||||||
} else if fragment.was_visible(&self.since, &self.undos) && !fragment.visible {
|
} else if fragment.was_visible(&self.since, &self.undos) && !fragment.visible {
|
||||||
self.deleted_cursor.seek_forward(cursor.start().deleted);
|
let deleted_end = cmp::min(
|
||||||
let fragment_summary = self.deleted_cursor.summary(cursor.end(&None).deleted);
|
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();
|
let mut old_end = self.old_end.clone();
|
||||||
old_end.add_assign(&fragment_summary);
|
old_end.add_assign(&fragment_summary);
|
||||||
if let Some(pending_edit) = pending_edit.as_mut() {
|
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 {
|
fn to_offset<'a>(&self, content: &Snapshot) -> usize {
|
||||||
content.summary_for_anchor(self)
|
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 {
|
impl<'a> ToOffset for &'a Anchor {
|
||||||
|
Loading…
Reference in New Issue
Block a user