diff --git a/Cargo.lock b/Cargo.lock index a1188259ac..2b49f5d4ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index a888d3d8eb..585f885d1a 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -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" diff --git a/crates/language/src/fragment_list.rs b/crates/language/src/fragment_list.rs index 725e628a1d..c5d6377ad8 100644 --- a/crates/language/src/fragment_list.rs +++ b/crates/language/src/fragment_list.rs @@ -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, + buffers: HashMap, text::Subscription, Vec)>, } #[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, 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, 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( &mut self, - props: FragmentProperties<'a, O>, + props: FragmentProperties, cx: &mut ModelContext, - ) -> 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::(); + 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) + ); + } + } } diff --git a/crates/text/src/patch.rs b/crates/text/src/patch.rs index 85d354d3b0..91a0761017 100644 --- a/crates/text/src/patch.rs +++ b/crates/text/src/patch.rs @@ -33,6 +33,10 @@ where Self(edits) } + pub fn edits(&self) -> &[Edit] { + &self.0 + } + pub fn into_inner(self) -> Vec> { self.0 }