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 = [ dependencies = [
"anyhow", "anyhow",
"clock", "clock",
"collections",
"futures", "futures",
"gpui", "gpui",
"lazy_static", "lazy_static",
@ -2586,6 +2587,7 @@ dependencies = [
"rpc", "rpc",
"serde", "serde",
"similar", "similar",
"smallvec",
"smol", "smol",
"sum_tree", "sum_tree",
"text", "text",

View file

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

View file

@ -1,14 +1,12 @@
use std::{ use crate::{buffer, Buffer, Chunk};
cmp, use collections::HashMap;
ops::{Deref, Range}, 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 sum_tree::{Bias, Cursor, SumTree};
use text::TextSummary; use text::TextSummary;
use theme::SyntaxTheme; 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]; 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; fn to_offset<'a>(&self, content: &Snapshot) -> usize;
} }
pub type FragmentId = usize; pub type FragmentId = Location;
#[derive(Default)] #[derive(Default)]
pub struct FragmentList { pub struct FragmentList {
snapshot: Snapshot, snapshot: Mutex<Snapshot>,
next_fragment_id: FragmentId, buffers: HashMap<usize, (ModelHandle<Buffer>, text::Subscription, Vec<FragmentId>)>,
} }
#[derive(Clone, Default)] #[derive(Clone, Default)]
@ -37,8 +35,8 @@ pub struct FragmentProperties<'a, T> {
#[derive(Clone)] #[derive(Clone)]
struct Entry { struct Entry {
id: FragmentId,
buffer: buffer::Snapshot, buffer: buffer::Snapshot,
buffer_id: usize,
buffer_range: Range<usize>, buffer_range: Range<usize>,
text_summary: TextSummary, text_summary: TextSummary,
header_height: u8, header_height: u8,
@ -46,11 +44,13 @@ struct Entry {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct EntrySummary { struct EntrySummary {
min_buffer_id: usize, fragment_id: FragmentId,
max_buffer_id: usize,
text: TextSummary, text: TextSummary,
} }
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Location(SmallVec<[usize; 4]>);
pub struct Chunks<'a> { pub struct Chunks<'a> {
range: Range<usize>, range: Range<usize>,
cursor: Cursor<'a, Entry, usize>, cursor: Cursor<'a, Entry, usize>,
@ -64,12 +64,20 @@ impl FragmentList {
Self::default() 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, &mut self,
props: FragmentProperties<'a, O>, props: FragmentProperties<O>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> FragmentId { ) -> FragmentId
let id = post_inc(&mut self.next_fragment_id); where
O: text::ToOffset,
{
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 = 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; 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 { Entry {
id: id.clone(),
buffer: props.buffer.read(cx).snapshot(), buffer: props.buffer.read(cx).snapshot(),
buffer_id: props.buffer.id(),
buffer_range, buffer_range,
text_summary, text_summary,
header_height: props.header_height, 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 id
} }
}
impl Deref for FragmentList { fn sync(&self, cx: &AppContext) {
type Target = Snapshot; 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 { let old_fragments = mem::take(&mut snapshot.entries);
&self.snapshot 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 { fn summary(&self) -> Self::Summary {
EntrySummary { EntrySummary {
min_buffer_id: self.buffer_id, fragment_id: self.id.clone(),
max_buffer_id: self.buffer_id,
text: self.text_summary.clone(), text: self.text_summary.clone(),
} }
} }
@ -165,15 +249,22 @@ impl sum_tree::Summary for EntrySummary {
type Context = (); type Context = ();
fn add_summary(&mut self, summary: &Self, _: &()) { fn add_summary(&mut self, summary: &Self, _: &()) {
self.min_buffer_id = cmp::min(self.min_buffer_id, summary.min_buffer_id); debug_assert!(summary.fragment_id > self.fragment_id);
self.max_buffer_id = cmp::max(self.max_buffer_id, summary.max_buffer_id); self.fragment_id = summary.fragment_id.clone();
self.text.add_summary(&summary.text, &()); self.text.add_summary(&summary.text, &());
} }
} }
impl<'a> sum_tree::Dimension<'a, EntrySummary> for usize { impl<'a> sum_tree::Dimension<'a, EntrySummary> for usize {
fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) { 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)] #[cfg(test)]
mod tests { mod tests {
use super::{FragmentList, FragmentProperties}; use super::*;
use crate::Buffer; use crate::Buffer;
use gpui::MutableAppContext; use gpui::MutableAppContext;
use rand::prelude::*;
use text::Point; use text::Point;
use util::test::sample_text; use util::test::sample_text;
@ -272,7 +394,7 @@ mod tests {
}); });
assert_eq!( assert_eq!(
list.read(cx).text(), list.read(cx).snapshot(cx).text(),
concat!( concat!(
"\n", // Preserve newlines "\n", // Preserve newlines
"\n", // "\n", //
@ -300,7 +422,7 @@ mod tests {
}); });
assert_eq!( assert_eq!(
list.read(cx).text(), list.read(cx).snapshot(cx).text(),
concat!( concat!(
"\n", // Preserve newlines "\n", // Preserve newlines
"\n", // "\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) Self(edits)
} }
pub fn edits(&self) -> &[Edit<T>] {
&self.0
}
pub fn into_inner(self) -> Vec<Edit<T>> { pub fn into_inner(self) -> Vec<Edit<T>> {
self.0 self.0
} }