From a67789d6d7b0673fb57fb189ee3edb520e380441 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 23 Sep 2024 18:57:06 -0600 Subject: [PATCH] Checkpoint Co-Authored-By: Nathan --- crates/multi_buffer2/src/multi_buffer2.rs | 212 ++++++++++++++++++++-- 1 file changed, 193 insertions(+), 19 deletions(-) diff --git a/crates/multi_buffer2/src/multi_buffer2.rs b/crates/multi_buffer2/src/multi_buffer2.rs index 5de276dc18..51c29e4ad1 100644 --- a/crates/multi_buffer2/src/multi_buffer2.rs +++ b/crates/multi_buffer2/src/multi_buffer2.rs @@ -1,15 +1,18 @@ -use gpui::{AppContext, Context, Model, ModelContext}; -use language::{Buffer, BufferSnapshot, OffsetRangeExt, ReplicaId, ToOffset}; +use gpui::{Model, ModelContext}; +use language::{Bias, Buffer, BufferSnapshot, OffsetRangeExt as _, ReplicaId}; use std::{ - cmp, + cmp::{self, Ordering}, fmt::{self, Debug, Formatter}, ops::Range, - path::{Path, PathBuf}, + path::Path, sync::Arc, }; -use sum_tree::{SumTree, TreeMap}; +use sum_tree::{SeekTarget, SumTree, TreeMap}; +use text::TextSummary; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize]; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] struct BufferId { remote_id: text::BufferId, replica_id: ReplicaId, @@ -26,11 +29,13 @@ impl MultiBuffer { } } - pub fn insert_excerpts( + pub fn insert_excerpts( &mut self, new_excerpts: impl IntoIterator, Range)>, cx: &mut ModelContext, ) { + self.sync(cx); + let mut new_excerpts = new_excerpts .into_iter() .map(|(buffer_handle, range)| { @@ -44,7 +49,7 @@ impl MultiBuffer { }, range, }; - (buffer, key) + (buffer_handle, key) }) .collect::>(); new_excerpts.sort_unstable_by_key(|(_, key)| key.clone()); @@ -57,19 +62,119 @@ impl MultiBuffer { false } }); + + let mut cursor = self.snapshot.excerpts.cursor::>(&()); + let mut new_tree = SumTree::::default(); + + for (buffer, key) in new_excerpts { + let start_offset = ExcerptOffset { + path: key.path.clone(), + buffer_id: key.buffer_id, + offset: key.range.start, + }; + let end_offset = ExcerptOffset { + path: key.path.clone(), + buffer_id: key.buffer_id, + offset: key.range.end, + }; + + if start_offset.cmp(cursor.start(), &()).is_ge() { + new_tree.append(cursor.slice(&start_offset, Bias::Left, &()), &()); + if let Some(excerpt) = cursor.item() { + if excerpt.key.intersects(&key) { + push_excerpt(&mut new_tree, excerpt.clone()); + cursor.next(&()); + } + } + } + + push_excerpt( + &mut new_tree, + Excerpt { + key: key.clone(), + snapshot: buffer.read(cx).snapshot(), + text_summary: buffer.read(cx).text_summary_for_range(key.range.clone()), + }, + ); + + if end_offset.cmp(cursor.start(), &()).is_ge() { + cursor.seek(&end_offset, Bias::Left, &()); + if let Some(excerpt) = cursor.item() { + if excerpt.key.intersects(&key) { + push_excerpt(&mut new_tree, excerpt.clone()); + cursor.next(&()); + } + } + } + } + + new_tree.append(cursor.suffix(&()), &()); + drop(cursor); + self.snapshot.excerpts = new_tree; + } + + fn sync(&mut self, cx: &mut ModelContext) {} + + fn snapshot(&mut self, cx: &mut ModelContext) -> MultiBufferSnapshot { + self.sync(cx); + self.snapshot.clone() } } -#[derive(Default)] +fn push_excerpt(excerpts: &mut SumTree, excerpt: Excerpt) { + let mut excerpt = Some(excerpt); + excerpts.update_last( + |last_excerpt| { + if last_excerpt.key.intersects(&excerpt.as_ref().unwrap().key) { + let excerpt = excerpt.take().unwrap(); + last_excerpt.key.range.start = + cmp::min(last_excerpt.key.range.start, excerpt.key.range.start); + last_excerpt.key.range.end = + cmp::max(last_excerpt.key.range.end, excerpt.key.range.end); + last_excerpt.text_summary = last_excerpt + .snapshot + .text_summary_for_range(last_excerpt.key.range.clone()); + } + }, + &(), + ); + + if let Some(excerpt) = excerpt { + excerpts.push(excerpt, &()); + } +} + +#[derive(Clone, Default)] pub struct MultiBufferSnapshot { excerpts: SumTree, snapshots: TreeMap, } +impl MultiBufferSnapshot { + #[cfg(any(test, feature = "test-support"))] + fn text(&self) -> String { + let mut text = String::new(); + for excerpt in self.excerpts.iter() { + text.push('\n'); + text.extend(excerpt.snapshot.text_for_range(excerpt.key.range.clone())); + } + text + } + + pub fn len(&self) -> usize { + self.excerpts.summary().text.len + } + + pub fn chunks(&self, range: Range, language_aware: bool) { + todo!() + } +} + #[derive(Clone)] struct Excerpt { key: ExcerptKey, snapshot: BufferSnapshot, + text_summary: TextSummary, } impl Debug for Excerpt { @@ -86,6 +191,7 @@ impl sum_tree::Item for Excerpt { fn summary(&self) -> Self::Summary { ExcerptSummary { max_key: Some(self.key.clone()), + text: self.text_summary.clone(), } } } @@ -93,6 +199,7 @@ impl sum_tree::Item for Excerpt { #[derive(Clone, Debug, Default)] struct ExcerptSummary { max_key: Option, + text: TextSummary, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -115,8 +222,8 @@ impl Ord for ExcerptKey { self.path .cmp(&other.path) .then_with(|| self.buffer_id.cmp(&other.buffer_id)) - .then_with(|| self.range.start.cmp(&other.range.start)) - .then_with(|| other.range.end.cmp(&self.range.end)) + .then_with(|| Ord::cmp(&self.range.start, &other.range.start)) + .then_with(|| Ord::cmp(&other.range.end, &self.range.end)) } } @@ -126,6 +233,26 @@ impl PartialOrd for ExcerptKey { } } +#[derive(Clone, Debug, Eq, PartialEq)] +struct ExcerptOffset { + path: Option>, + buffer_id: BufferId, + offset: usize, +} + +impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, Option> for ExcerptOffset { + fn cmp(&self, cursor_location: &Option, _: &()) -> Ordering { + if let Some(cursor_location) = cursor_location { + self.path + .cmp(&cursor_location.path) + .then_with(|| self.buffer_id.cmp(&cursor_location.buffer_id)) + .then_with(|| Ord::cmp(&self.offset, &cursor_location.range.end)) + } else { + Ordering::Greater + } + } +} + impl sum_tree::Summary for ExcerptSummary { type Context = (); @@ -133,26 +260,73 @@ impl sum_tree::Summary for ExcerptSummary { Self::default() } - fn add_summary(&mut self, summary: &Self, cx: &Self::Context) { + fn add_summary(&mut self, summary: &Self, _cx: &Self::Context) { self.max_key = summary.max_key.clone(); + self.text.add_summary(&summary.text, &()); + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for usize { + fn zero(_cx: &()) -> Self { + 0 + } + + fn add_summary(&mut self, summary: &'a ExcerptSummary, _cx: &()) { + *self += summary.text.len; + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option { + fn zero(_cx: &()) -> Self { + None + } + + fn add_summary(&mut self, summary: &'a ExcerptSummary, _cx: &()) { + debug_assert!(summary.max_key >= *self); + *self = summary.max_key.clone(); + } +} + +pub trait ToOffset { + fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize; +} + +impl ToOffset for usize { + fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + assert!(*self <= snapshot.len(), "offset is out of range"); + *self } } #[cfg(test)] mod tests { use super::*; - use gpui::AppContext; + use gpui::{AppContext, Context}; use language::Buffer; #[gpui::test] fn test_insert_excerpts(cx: &mut AppContext) { - let buffer1 = cx.new_model(|cx| Buffer::local("abc\ndef\nghi", cx)); - let buffer2 = cx.new_model(|cx| Buffer::local("jkl\nmno\npqr", cx)); + let buffer1 = cx.new_model(|cx| Buffer::local("abcdefghijklmnopqrstuvwxyz", cx)); + cx.new_model(|cx| { + let mut multibuffer = MultiBuffer::new(); + multibuffer + .insert_excerpts(vec![(buffer1.clone(), 0..2), (buffer1.clone(), 4..12)], cx); + assert_eq!(multibuffer.snapshot(cx).text(), "\nab\nefghijkl"); - let multi = cx.new_model(|cx| { - let mut multi = MultiBuffer::new(); - multi.insert_excerpts(vec![(buffer1.clone(), 0..4), (buffer2.clone(), 8..11)], cx); - multi + multibuffer + .insert_excerpts(vec![(buffer1.clone(), 4..6), (buffer1.clone(), 8..10)], cx); + assert_eq!(multibuffer.snapshot(cx).text(), "\nab\nefghijkl"); + + multibuffer.insert_excerpts( + vec![(buffer1.clone(), 10..14), (buffer1.clone(), 16..18)], + cx, + ); + assert_eq!(multibuffer.snapshot(cx).text(), "\nab\nefghijklmn\nqr"); + + multibuffer.insert_excerpts(vec![(buffer1.clone(), 12..17)], cx); + assert_eq!(multibuffer.snapshot(cx).text(), "\nab\nefghijklmnopqr"); + + multibuffer }); } }