Start on maintaining edits in FragmentList

This commit is contained in:
Antonio Scandurra 2021-12-06 12:10:25 +01:00
parent 8f90d42723
commit 45d6f5ab04
4 changed files with 200 additions and 36 deletions

2
Cargo.lock generated
View File

@ -2575,6 +2575,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clock",
"collections",
"futures",
"gpui",
"lazy_static",
@ -2586,6 +2587,7 @@ dependencies = [
"rpc",
"serde",
"similar",
"smallvec",
"smol",
"sum_tree",
"text",

View File

@ -9,19 +9,21 @@ path = "src/language.rs"
[features]
test-support = [
"rand",
"text/test-support",
"collections/test-support",
"lsp/test-support",
"text/test-support",
"tree-sitter-rust",
"util/test-support",
]
[dependencies]
text = { path = "../text" }
clock = { path = "../clock" }
collections = { path = "../collections" }
gpui = { path = "../gpui" }
lsp = { path = "../lsp" }
rpc = { path = "../rpc" }
sum_tree = { path = "../sum_tree" }
text = { path = "../text" }
theme = { path = "../theme" }
util = { path = "../util" }
anyhow = "1.0.38"
@ -33,14 +35,16 @@ postage = { version = "0.4.1", features = ["futures-traits"] }
rand = { version = "0.8.3", optional = true }
serde = { version = "1", features = ["derive"] }
similar = "1.3"
smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
tree-sitter = "0.20.0"
tree-sitter-rust = { version = "0.20.0", optional = true }
[dev-dependencies]
text = { path = "../text", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
text = { path = "../text", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
rand = "0.8.3"
tree-sitter-rust = "0.20.0"

View File

@ -1,14 +1,12 @@
use std::{
cmp,
ops::{Deref, Range},
};
use crate::{buffer, Buffer, Chunk};
use collections::HashMap;
use gpui::{AppContext, Entity, ModelContext, ModelHandle};
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 theme::SyntaxTheme;
use util::post_inc;
use crate::{buffer, Buffer, Chunk};
use gpui::{Entity, ModelContext, ModelHandle};
const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
@ -16,12 +14,12 @@ pub trait ToOffset {
fn to_offset<'a>(&self, content: &Snapshot) -> usize;
}
pub type FragmentId = usize;
pub type FragmentId = Location;
#[derive(Default)]
pub struct FragmentList {
snapshot: Snapshot,
next_fragment_id: FragmentId,
snapshot: Mutex<Snapshot>,
buffers: HashMap<usize, (ModelHandle<Buffer>, text::Subscription, Vec<FragmentId>)>,
}
#[derive(Clone, Default)]
@ -37,8 +35,8 @@ pub struct FragmentProperties<'a, T> {
#[derive(Clone)]
struct Entry {
id: FragmentId,
buffer: buffer::Snapshot,
buffer_id: usize,
buffer_range: Range<usize>,
text_summary: TextSummary,
header_height: u8,
@ -46,11 +44,13 @@ struct Entry {
#[derive(Clone, Debug, Default)]
struct EntrySummary {
min_buffer_id: usize,
max_buffer_id: usize,
fragment_id: FragmentId,
text: TextSummary,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Location(SmallVec<[usize; 4]>);
pub struct Chunks<'a> {
range: Range<usize>,
cursor: Cursor<'a, Entry, usize>,
@ -64,12 +64,20 @@ impl FragmentList {
Self::default()
}
pub fn push<'a, O: text::ToOffset>(
pub fn snapshot(&self, cx: &AppContext) -> Snapshot {
self.sync(cx);
self.snapshot.lock().clone()
}
pub fn push<O>(
&mut self,
props: FragmentProperties<'a, O>,
props: FragmentProperties<O>,
cx: &mut ModelContext<Self>,
) -> FragmentId {
let id = post_inc(&mut self.next_fragment_id);
) -> FragmentId
where
O: text::ToOffset,
{
self.sync(cx);
let buffer = props.buffer.read(cx);
let buffer_range = props.range.start.to_offset(buffer)..props.range.end.to_offset(buffer);
@ -82,10 +90,13 @@ impl FragmentList {
text_summary.bytes += props.header_height as usize;
}
self.snapshot.entries.push(
let mut snapshot = self.snapshot.lock();
let prev_id = snapshot.entries.last().map(|e| &e.id);
let id = FragmentId::between(prev_id.unwrap_or(&FragmentId::min()), &FragmentId::max());
snapshot.entries.push(
Entry {
id: id.clone(),
buffer: props.buffer.read(cx).snapshot(),
buffer_id: props.buffer.id(),
buffer_range,
text_summary,
header_height: props.header_height,
@ -93,15 +104,89 @@ impl FragmentList {
&(),
);
self.buffers
.entry(props.buffer.id())
.or_insert_with(|| {
let subscription = props.buffer.update(cx, |buffer, _| buffer.subscribe());
(props.buffer.clone(), subscription, Default::default())
})
.2
.push(id.clone());
id
}
}
impl Deref for FragmentList {
type Target = Snapshot;
fn sync(&self, cx: &AppContext) {
let mut snapshot = self.snapshot.lock();
let mut patches = Vec::new();
let mut fragments_to_edit = Vec::new();
for (buffer, subscription, fragment_ids) in self.buffers.values() {
let patch = subscription.consume();
if !patch.is_empty() {
let patch_ix = patches.len();
patches.push(patch);
fragments_to_edit.extend(
fragment_ids
.iter()
.map(|fragment_id| (buffer, fragment_id, patch_ix)),
)
}
}
fragments_to_edit.sort_unstable_by_key(|(_, fragment_id, _)| *fragment_id);
fn deref(&self) -> &Self::Target {
&self.snapshot
let old_fragments = mem::take(&mut snapshot.entries);
let mut cursor = old_fragments.cursor::<FragmentId>();
for (buffer, fragment_id, patch_ix) in fragments_to_edit {
snapshot
.entries
.push_tree(cursor.slice(fragment_id, Bias::Left, &()), &());
let fragment = cursor.item().unwrap();
let mut new_range = fragment.buffer_range.clone();
for edit in patches[patch_ix].edits() {
let edit_start = edit.new.start;
let edit_end = edit.new.start + edit.old_len();
if edit_end < new_range.start {
let delta = edit.new_len() as isize - edit.old_len() as isize;
new_range.start = (new_range.start as isize + delta) as usize;
new_range.end = (new_range.end as isize + delta) as usize;
} else if edit_start >= new_range.end {
break;
} else {
let mut new_range_len = new_range.len();
new_range_len -=
cmp::min(new_range.end, edit_end) - cmp::max(new_range.start, edit_start);
if edit_start > new_range.start {
new_range_len += edit.new_len();
}
new_range.start = cmp::min(new_range.start, edit.new.end);
new_range.end = new_range.start + new_range_len;
}
}
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;
text_summary.lines.row += fragment.header_height as u32;
text_summary.lines_utf16.row += fragment.header_height as u32;
text_summary.bytes += fragment.header_height as usize;
}
snapshot.entries.push(
Entry {
id: fragment.id.clone(),
buffer: buffer.snapshot(),
buffer_range: new_range,
text_summary,
header_height: fragment.header_height,
},
&(),
);
cursor.next(&());
}
snapshot.entries.push_tree(cursor.suffix(&()), &());
}
}
@ -154,8 +239,7 @@ impl sum_tree::Item for Entry {
fn summary(&self) -> Self::Summary {
EntrySummary {
min_buffer_id: self.buffer_id,
max_buffer_id: self.buffer_id,
fragment_id: self.id.clone(),
text: self.text_summary.clone(),
}
}
@ -165,15 +249,22 @@ impl sum_tree::Summary for EntrySummary {
type Context = ();
fn add_summary(&mut self, summary: &Self, _: &()) {
self.min_buffer_id = cmp::min(self.min_buffer_id, summary.min_buffer_id);
self.max_buffer_id = cmp::max(self.max_buffer_id, summary.max_buffer_id);
debug_assert!(summary.fragment_id > self.fragment_id);
self.fragment_id = summary.fragment_id.clone();
self.text.add_summary(&summary.text, &());
}
}
impl<'a> sum_tree::Dimension<'a, EntrySummary> for usize {
fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
*self += summary.text.bytes
*self += summary.text.bytes;
}
}
impl<'a> sum_tree::Dimension<'a, EntrySummary> for FragmentId {
fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
debug_assert!(summary.fragment_id > *self);
*self = summary.fragment_id.clone();
}
}
@ -228,11 +319,42 @@ impl ToOffset for usize {
}
}
impl Default for Location {
fn default() -> Self {
Self::min()
}
}
impl Location {
pub fn min() -> Self {
Self(smallvec![usize::MIN])
}
pub fn max() -> Self {
Self(smallvec![usize::MAX])
}
pub fn between(lhs: &Self, rhs: &Self) -> Self {
let lhs = lhs.0.iter().copied().chain(iter::repeat(usize::MIN));
let rhs = rhs.0.iter().copied().chain(iter::repeat(usize::MAX));
let mut location = SmallVec::new();
for (lhs, rhs) in lhs.zip(rhs) {
let mid = lhs + (rhs.saturating_sub(lhs)) / 2;
location.push(mid);
if mid > lhs {
break;
}
}
Self(location)
}
}
#[cfg(test)]
mod tests {
use super::{FragmentList, FragmentProperties};
use super::*;
use crate::Buffer;
use gpui::MutableAppContext;
use rand::prelude::*;
use text::Point;
use util::test::sample_text;
@ -272,7 +394,7 @@ mod tests {
});
assert_eq!(
list.read(cx).text(),
list.read(cx).snapshot(cx).text(),
concat!(
"\n", // Preserve newlines
"\n", //
@ -300,7 +422,7 @@ mod tests {
});
assert_eq!(
list.read(cx).text(),
list.read(cx).snapshot(cx).text(),
concat!(
"\n", // Preserve newlines
"\n", //
@ -317,4 +439,36 @@ mod tests {
)
);
}
#[gpui::test(iterations = 10000)]
fn test_location(mut rng: StdRng) {
let mut lhs = Default::default();
let mut rhs = Default::default();
while lhs == rhs {
lhs = Location(
(0..rng.gen_range(1..=5))
.map(|_| rng.gen_range(0..=100))
.collect(),
);
rhs = Location(
(0..rng.gen_range(1..=5))
.map(|_| rng.gen_range(0..=100))
.collect(),
);
}
if lhs > rhs {
mem::swap(&mut lhs, &mut rhs);
}
let middle = Location::between(&lhs, &rhs);
assert!(middle > lhs);
assert!(middle < rhs);
for ix in 0..middle.0.len() - 1 {
assert!(
middle.0[ix] == *lhs.0.get(ix).unwrap_or(&0)
|| middle.0[ix] == *rhs.0.get(ix).unwrap_or(&0)
);
}
}
}

View File

@ -33,6 +33,10 @@ where
Self(edits)
}
pub fn edits(&self) -> &[Edit<T>] {
&self.0
}
pub fn into_inner(self) -> Vec<Edit<T>> {
self.0
}