Add FoldMapSnapshot::edits_since_creation
This commit is contained in:
@@ -3,7 +3,7 @@ mod wrap_map;
|
||||
|
||||
use crate::settings::StyleId;
|
||||
|
||||
use super::{buffer, Anchor, Bias, Buffer, Edit, Point, ToOffset, ToPoint};
|
||||
use super::{buffer, Anchor, Bias, Buffer, Point, ToOffset, ToPoint};
|
||||
pub use fold_map::BufferRows;
|
||||
use fold_map::{FoldMap, FoldMapSnapshot};
|
||||
use gpui::{AppContext, ModelHandle};
|
||||
|
||||
@@ -13,8 +13,10 @@ use gpui::{AppContext, ModelHandle};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
collections::VecDeque,
|
||||
iter,
|
||||
ops::Range,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub struct FoldMap {
|
||||
@@ -22,6 +24,14 @@ pub struct FoldMap {
|
||||
transforms: Mutex<SumTree<Transform>>,
|
||||
folds: SumTree<Fold>,
|
||||
last_sync: Mutex<time::Global>,
|
||||
history: Arc<Mutex<FoldMapHistory>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FoldMapHistory {
|
||||
edits: VecDeque<Edit>,
|
||||
oldest_edit_version: usize,
|
||||
snapshot_versions: Vec<usize>,
|
||||
}
|
||||
|
||||
impl FoldMap {
|
||||
@@ -41,15 +51,21 @@ impl FoldMap {
|
||||
&(),
|
||||
)),
|
||||
last_sync: Mutex::new(buffer.version()),
|
||||
history: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn snapshot(&self, cx: &AppContext) -> FoldMapSnapshot {
|
||||
self.sync(cx);
|
||||
let mut history = self.history.lock();
|
||||
let version = history.oldest_edit_version + history.edits.len();
|
||||
history.snapshot_versions.push(version);
|
||||
FoldMapSnapshot {
|
||||
transforms: self.transforms.lock().clone(),
|
||||
folds: self.folds.clone(),
|
||||
buffer: self.buffer.read(cx).snapshot(),
|
||||
history: self.history.clone(),
|
||||
version,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,12 +92,6 @@ impl FoldMap {
|
||||
}
|
||||
|
||||
folds.sort_unstable_by(|a, b| sum_tree::SeekDimension::cmp(a, b, &buffer));
|
||||
edits.sort_unstable_by(|a, b| {
|
||||
a.old_bytes
|
||||
.start
|
||||
.cmp(&b.old_bytes.start)
|
||||
.then_with(|| b.old_bytes.end.cmp(&a.old_bytes.end))
|
||||
});
|
||||
|
||||
self.folds = {
|
||||
let mut new_tree = SumTree::new();
|
||||
@@ -93,6 +103,8 @@ impl FoldMap {
|
||||
new_tree.push_tree(cursor.suffix(&buffer), &buffer);
|
||||
new_tree
|
||||
};
|
||||
|
||||
self.consolidate_edits(&mut edits);
|
||||
self.apply_edits(edits, cx);
|
||||
}
|
||||
|
||||
@@ -103,14 +115,12 @@ impl FoldMap {
|
||||
) {
|
||||
self.sync(cx);
|
||||
|
||||
let buffer = self.buffer.read(cx).snapshot();
|
||||
let snapshot = self.snapshot(cx);
|
||||
|
||||
let mut edits = Vec::new();
|
||||
let mut fold_ixs_to_delete = Vec::new();
|
||||
let buffer = self.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.
|
||||
let mut folds_cursor = snapshot.intersecting_folds(range);
|
||||
let mut folds_cursor = intersecting_folds(&buffer, &self.folds, range);
|
||||
while let Some(fold) = folds_cursor.item() {
|
||||
let offset_range = fold.0.start.to_offset(&buffer)..fold.0.end.to_offset(&buffer);
|
||||
edits.push(Edit {
|
||||
@@ -124,12 +134,6 @@ impl FoldMap {
|
||||
|
||||
fold_ixs_to_delete.sort_unstable();
|
||||
fold_ixs_to_delete.dedup();
|
||||
edits.sort_unstable_by(|a, b| {
|
||||
a.old_bytes
|
||||
.start
|
||||
.cmp(&b.old_bytes.start)
|
||||
.then_with(|| b.old_bytes.end.cmp(&a.old_bytes.end))
|
||||
});
|
||||
|
||||
self.folds = {
|
||||
let mut cursor = self.folds.cursor::<_, ()>();
|
||||
@@ -141,25 +145,24 @@ impl FoldMap {
|
||||
folds.push_tree(cursor.suffix(&buffer), &buffer);
|
||||
folds
|
||||
};
|
||||
|
||||
self.consolidate_edits(&mut edits);
|
||||
self.apply_edits(edits, cx);
|
||||
}
|
||||
|
||||
fn sync(&self, cx: &AppContext) -> Vec<Edit> {
|
||||
fn sync(&self, cx: &AppContext) {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let mut edits = buffer
|
||||
let edits = buffer
|
||||
.edits_since(self.last_sync.lock().clone())
|
||||
.map(Into::into)
|
||||
.peekable();
|
||||
let edits = if edits.peek().is_some() {
|
||||
self.apply_edits(edits, cx)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
.collect::<Vec<_>>();
|
||||
if !edits.is_empty() {
|
||||
self.apply_edits(edits, cx);
|
||||
}
|
||||
*self.last_sync.lock() = buffer.version();
|
||||
edits
|
||||
}
|
||||
|
||||
fn apply_edits(&self, edits: Vec<Edit>, cx: &AppContext) -> Vec<Edit> {
|
||||
fn apply_edits(&self, mut edits: Vec<Edit>, cx: &AppContext) {
|
||||
let buffer = self.buffer.read(cx).snapshot();
|
||||
let mut edits_iter = edits.iter().cloned().peekable();
|
||||
|
||||
@@ -311,6 +314,8 @@ impl FoldMap {
|
||||
{
|
||||
let mut old_transforms = transforms.cursor::<usize, DisplayOffset>();
|
||||
let mut new_transforms = new_transforms.cursor::<usize, DisplayOffset>();
|
||||
|
||||
let mut delta = 0;
|
||||
for edit in &mut edits {
|
||||
old_transforms.seek_forward(&edit.old_bytes.start, Bias::Right, &());
|
||||
edit.old_bytes.start = old_transforms.sum_start().0
|
||||
@@ -324,12 +329,38 @@ impl FoldMap {
|
||||
new_transforms.seek_forward(&edit.new_bytes.end, Bias::Right, &());
|
||||
edit.new_bytes.end = new_transforms.sum_start().0
|
||||
+ (edit.new_bytes.end - new_transforms.seek_start());
|
||||
|
||||
edit.old_bytes.start = (edit.old_bytes.start as isize + delta) as usize;
|
||||
edit.old_bytes.end = (edit.old_bytes.end as isize + delta) as usize;
|
||||
delta += edit.delta();
|
||||
}
|
||||
}
|
||||
|
||||
*transforms = new_transforms;
|
||||
self.history.lock().edits.extend(edits);
|
||||
}
|
||||
|
||||
edits
|
||||
fn consolidate_edits(&self, edits: &mut Vec<Edit>) {
|
||||
edits.sort_unstable_by(|a, b| {
|
||||
a.old_bytes
|
||||
.start
|
||||
.cmp(&b.old_bytes.start)
|
||||
.then_with(|| b.old_bytes.end.cmp(&a.old_bytes.end))
|
||||
});
|
||||
|
||||
let mut i = 0;
|
||||
while i < edits.len() {
|
||||
let range = edits[i].old_bytes.clone();
|
||||
if i > 0 {
|
||||
if edits[i - 1].old_bytes.end >= range.start {
|
||||
edits[i - 1].old_bytes.end = edits[i - 1].old_bytes.end.max(range.end);
|
||||
edits[i - 1].new_bytes.end = edits[i - 1].new_bytes.end.max(range.end);
|
||||
edits.remove(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,9 +368,22 @@ pub struct FoldMapSnapshot {
|
||||
transforms: SumTree<Transform>,
|
||||
folds: SumTree<Fold>,
|
||||
buffer: buffer::Snapshot,
|
||||
history: Arc<Mutex<FoldMapHistory>>,
|
||||
version: usize,
|
||||
}
|
||||
|
||||
impl FoldMapSnapshot {
|
||||
pub fn edits_since_creation(&self) -> Vec<Edit> {
|
||||
let history = self.history.lock();
|
||||
let index = self.version - history.oldest_edit_version;
|
||||
history.edits.range(index..).cloned().collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks_at(DisplayOffset(0)).collect()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.transforms.summary().display.bytes
|
||||
}
|
||||
@@ -384,7 +428,7 @@ impl FoldMapSnapshot {
|
||||
where
|
||||
T: ToOffset,
|
||||
{
|
||||
let mut folds = self.intersecting_folds(range);
|
||||
let mut folds = intersecting_folds(&self.buffer, &self.folds, range);
|
||||
iter::from_fn(move || {
|
||||
let item = folds.item().map(|f| &f.0);
|
||||
folds.next(&self.buffer);
|
||||
@@ -392,26 +436,6 @@ impl FoldMapSnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
fn intersecting_folds<'a, T>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
) -> FilterCursor<impl 'a + Fn(&FoldSummary) -> bool, Fold, usize>
|
||||
where
|
||||
T: ToOffset,
|
||||
{
|
||||
let start = self
|
||||
.buffer
|
||||
.anchor_before(range.start.to_offset(&self.buffer));
|
||||
let end = self.buffer.anchor_after(range.end.to_offset(&self.buffer));
|
||||
self.folds.filter::<_, usize>(
|
||||
move |summary| {
|
||||
start.cmp(&summary.max_end, &self.buffer).unwrap() == Ordering::Less
|
||||
&& end.cmp(&summary.min_start, &self.buffer).unwrap() == Ordering::Greater
|
||||
},
|
||||
&self.buffer,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn intersects_fold<T>(&self, offset: T) -> bool
|
||||
where
|
||||
T: ToOffset,
|
||||
@@ -569,6 +593,56 @@ impl FoldMapSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FoldMapSnapshot {
|
||||
fn drop(&mut self) {
|
||||
let mut history = self.history.lock();
|
||||
|
||||
// Remove this snapshot's version number from the set of currently-snapshotted
|
||||
// version numbers.
|
||||
let mut i = 0;
|
||||
let mut min_snapshot_version = None;
|
||||
while i < history.snapshot_versions.len() {
|
||||
let version = history.snapshot_versions[i];
|
||||
if version == self.version {
|
||||
history.snapshot_versions.remove(i);
|
||||
} else {
|
||||
if min_snapshot_version.map_or(true, |min_version| min_version > version) {
|
||||
min_snapshot_version = Some(version);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Discard any edits that predate any outstanding snapshot.
|
||||
if let Some(min_snapshot_version) = min_snapshot_version {
|
||||
if min_snapshot_version > history.oldest_edit_version {
|
||||
let remove_count = min_snapshot_version - history.oldest_edit_version;
|
||||
drop(history.edits.drain(..remove_count));
|
||||
history.oldest_edit_version = min_snapshot_version;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn intersecting_folds<'a, T>(
|
||||
buffer: &'a buffer::Snapshot,
|
||||
folds: &'a SumTree<Fold>,
|
||||
range: Range<T>,
|
||||
) -> FilterCursor<'a, impl 'a + Fn(&FoldSummary) -> bool, Fold, usize>
|
||||
where
|
||||
T: ToOffset,
|
||||
{
|
||||
let start = buffer.anchor_before(range.start.to_offset(buffer));
|
||||
let end = buffer.anchor_after(range.end.to_offset(buffer));
|
||||
folds.filter::<_, usize>(
|
||||
move |summary| {
|
||||
start.cmp(&summary.max_end, buffer).unwrap() == Ordering::Less
|
||||
&& end.cmp(&summary.min_start, buffer).unwrap() == Ordering::Greater
|
||||
},
|
||||
buffer,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
struct Transform {
|
||||
summary: TransformSummary,
|
||||
@@ -667,6 +741,7 @@ impl sum_tree::Summary for FoldSummary {
|
||||
assert!(self.end.cmp(&other.end, buffer).unwrap() >= Ordering::Equal);
|
||||
}
|
||||
}
|
||||
|
||||
self.start = other.start.clone();
|
||||
self.end = other.end.clone();
|
||||
self.count += other.count;
|
||||
@@ -864,10 +939,10 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Edit {
|
||||
old_bytes: Range<usize>,
|
||||
new_bytes: Range<usize>,
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Edit {
|
||||
pub old_bytes: Range<usize>,
|
||||
pub new_bytes: Range<usize>,
|
||||
}
|
||||
|
||||
impl Edit {
|
||||
@@ -903,6 +978,7 @@ mod tests {
|
||||
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());
|
||||
let snapshot1 = map.snapshot(cx.as_ref());
|
||||
|
||||
map.fold(
|
||||
vec![
|
||||
@@ -911,7 +987,23 @@ mod tests {
|
||||
],
|
||||
cx.as_ref(),
|
||||
);
|
||||
assert_eq!(map.text(cx.as_ref()), "aa…cc…eeeee");
|
||||
let snapshot2 = map.snapshot(cx.as_ref());
|
||||
assert_eq!(snapshot2.text(), "aa…cc…eeeee");
|
||||
|
||||
assert_eq!(snapshot2.edits_since_creation(), &[]);
|
||||
assert_eq!(
|
||||
snapshot1.edits_since_creation(),
|
||||
&[
|
||||
Edit {
|
||||
old_bytes: 2..16,
|
||||
new_bytes: 2..5,
|
||||
},
|
||||
Edit {
|
||||
old_bytes: 7..18,
|
||||
new_bytes: 7..10
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
@@ -923,17 +1015,60 @@ mod tests {
|
||||
cx,
|
||||
);
|
||||
});
|
||||
assert_eq!(map.text(cx.as_ref()), "123a…c123c…eeeee");
|
||||
let snapshot3 = map.snapshot(cx.as_ref());
|
||||
assert_eq!(snapshot3.text(), "123a…c123c…eeeee");
|
||||
|
||||
assert_eq!(snapshot3.edits_since_creation(), &[]);
|
||||
assert_eq!(
|
||||
snapshot2.edits_since_creation(),
|
||||
&[
|
||||
Edit {
|
||||
old_bytes: 0..1,
|
||||
new_bytes: 0..3,
|
||||
},
|
||||
Edit {
|
||||
old_bytes: 8..8,
|
||||
new_bytes: 8..11,
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
snapshot1.edits_since_creation(),
|
||||
&[
|
||||
Edit {
|
||||
old_bytes: 2..16,
|
||||
new_bytes: 2..5,
|
||||
},
|
||||
Edit {
|
||||
old_bytes: 7..18,
|
||||
new_bytes: 7..10
|
||||
},
|
||||
Edit {
|
||||
old_bytes: 0..1,
|
||||
new_bytes: 0..3,
|
||||
},
|
||||
Edit {
|
||||
old_bytes: 8..8,
|
||||
new_bytes: 8..11,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
let start_version = buffer.version.clone();
|
||||
buffer.edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", cx);
|
||||
buffer.edits_since(start_version).collect::<Vec<_>>()
|
||||
buffer.edit(vec![Point::new(2, 6)..Point::new(4, 3)], "456", cx)
|
||||
});
|
||||
assert_eq!(map.text(cx.as_ref()), "123a…c123456eee");
|
||||
let snapshot4 = map.snapshot(cx.as_ref());
|
||||
assert_eq!(snapshot4.text(), "123a…c123456eee");
|
||||
|
||||
map.unfold(Some(Point::new(0, 4)..Point::new(0, 5)), cx.as_ref());
|
||||
assert_eq!(map.text(cx.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee");
|
||||
let snapshot5 = map.snapshot(cx.as_ref());
|
||||
assert_eq!(snapshot5.text(), "123aaaaa\nbbbbbb\nccc123456eee");
|
||||
|
||||
drop(snapshot1);
|
||||
drop(snapshot2);
|
||||
drop(snapshot3);
|
||||
drop(snapshot4);
|
||||
assert_eq!(map.history.lock().edits, &[]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -945,17 +1080,17 @@ mod tests {
|
||||
|
||||
map.fold(vec![5..8], cx.as_ref());
|
||||
map.check_invariants(cx.as_ref());
|
||||
assert_eq!(map.text(cx.as_ref()), "abcde…ijkl");
|
||||
assert_eq!(map.snapshot(cx.as_ref()).text(), "abcde…ijkl");
|
||||
|
||||
// Create an fold adjacent to the start of the first fold.
|
||||
map.fold(vec![0..1, 2..5], cx.as_ref());
|
||||
map.check_invariants(cx.as_ref());
|
||||
assert_eq!(map.text(cx.as_ref()), "…b…ijkl");
|
||||
assert_eq!(map.snapshot(cx.as_ref()).text(), "…b…ijkl");
|
||||
|
||||
// Create an fold adjacent to the end of the first fold.
|
||||
map.fold(vec![11..11, 8..10], cx.as_ref());
|
||||
map.check_invariants(cx.as_ref());
|
||||
assert_eq!(map.text(cx.as_ref()), "…b…kl");
|
||||
assert_eq!(map.snapshot(cx.as_ref()).text(), "…b…kl");
|
||||
}
|
||||
|
||||
{
|
||||
@@ -964,7 +1099,7 @@ mod tests {
|
||||
// Create two adjacent folds.
|
||||
map.fold(vec![0..2, 2..5], cx.as_ref());
|
||||
map.check_invariants(cx.as_ref());
|
||||
assert_eq!(map.text(cx.as_ref()), "…fghijkl");
|
||||
assert_eq!(map.snapshot(cx.as_ref()).text(), "…fghijkl");
|
||||
|
||||
// Edit within one of the folds.
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
@@ -973,7 +1108,7 @@ mod tests {
|
||||
buffer.edits_since(version).collect::<Vec<_>>()
|
||||
});
|
||||
map.check_invariants(cx.as_ref());
|
||||
assert_eq!(map.text(cx.as_ref()), "12345…fghijkl");
|
||||
assert_eq!(map.snapshot(cx.as_ref()).text(), "12345…fghijkl");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -990,7 +1125,7 @@ mod tests {
|
||||
],
|
||||
cx.as_ref(),
|
||||
);
|
||||
assert_eq!(map.text(cx.as_ref()), "aa…eeeee");
|
||||
assert_eq!(map.snapshot(cx.as_ref()).text(), "aa…eeeee");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -1005,12 +1140,12 @@ mod tests {
|
||||
],
|
||||
cx.as_ref(),
|
||||
);
|
||||
assert_eq!(map.text(cx.as_ref()), "aa…cccc\nd…eeeee");
|
||||
assert_eq!(map.snapshot(cx.as_ref()).text(), "aa…cccc\nd…eeeee");
|
||||
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", cx)
|
||||
});
|
||||
assert_eq!(map.text(cx.as_ref()), "aa…eeeee");
|
||||
assert_eq!(map.snapshot(cx.as_ref()).text(), "aa…eeeee");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -1126,7 +1261,7 @@ mod tests {
|
||||
expected_buffer_rows.extend((0..=next_row).rev());
|
||||
expected_buffer_rows.reverse();
|
||||
|
||||
assert_eq!(map.text(cx.as_ref()), expected_text);
|
||||
assert_eq!(map.snapshot(cx.as_ref()).text(), expected_text);
|
||||
|
||||
let snapshot = map.snapshot(cx.as_ref());
|
||||
for (display_row, line) in expected_text.lines().enumerate() {
|
||||
@@ -1255,7 +1390,10 @@ mod tests {
|
||||
cx.as_ref(),
|
||||
);
|
||||
|
||||
assert_eq!(map.text(cx.as_ref()), "aa…cccc\nd…eeeee\nffffff\n");
|
||||
assert_eq!(
|
||||
map.snapshot(cx.as_ref()).text(),
|
||||
"aa…cccc\nd…eeeee\nffffff\n"
|
||||
);
|
||||
assert_eq!(
|
||||
map.snapshot(cx.as_ref()).buffer_rows(0).collect::<Vec<_>>(),
|
||||
vec![0, 3, 5, 6]
|
||||
@@ -1267,10 +1405,6 @@ mod tests {
|
||||
}
|
||||
|
||||
impl FoldMap {
|
||||
fn text(&self, cx: &AppContext) -> String {
|
||||
self.snapshot(cx).chunks_at(DisplayOffset(0)).collect()
|
||||
}
|
||||
|
||||
fn merged_fold_ranges(&self, cx: &AppContext) -> Vec<Range<usize>> {
|
||||
let buffer = self.buffer.read(cx).snapshot();
|
||||
let mut folds = self.folds.items(&buffer);
|
||||
|
||||
Reference in New Issue
Block a user