Start on maintaining edits in FragmentList
This commit is contained in:
parent
8f90d42723
commit
45d6f5ab04
4 changed files with 200 additions and 36 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for FragmentList {
|
new_range.start = cmp::min(new_range.start, edit.new.end);
|
||||||
type Target = Snapshot;
|
new_range.end = new_range.start + new_range_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
let buffer = buffer.read(cx);
|
||||||
&self.snapshot
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue