Merge branch 'master' into close-window

This commit is contained in:
Max Brunsfeld
2021-05-07 14:24:48 -07:00
19 changed files with 1842 additions and 1018 deletions
+253 -161
View File
@@ -8,6 +8,7 @@ use futures_core::future::LocalBoxFuture;
pub use point::*;
use seahash::SeaHasher;
pub use selection::*;
use smol::future::FutureExt;
pub use text::*;
use crate::{
@@ -64,6 +65,7 @@ pub struct Buffer {
last_edit: time::Local,
undo_map: UndoMap,
history: History,
file: Option<FileHandle>,
selections: HashMap<SelectionSetId, Arc<[Selection]>>,
pub selections_last_update: SelectionsVersion,
deferred_ops: OperationQueue<Operation>,
@@ -351,15 +353,33 @@ pub struct UndoOperation {
}
impl Buffer {
pub fn new<T: Into<Arc<str>>>(replica_id: ReplicaId, base_text: T) -> Self {
Self::build(replica_id, History::new(base_text.into()))
pub fn new<T: Into<Arc<str>>>(
replica_id: ReplicaId,
base_text: T,
ctx: &mut ModelContext<Self>,
) -> Self {
Self::build(replica_id, History::new(base_text.into()), None, ctx)
}
pub fn from_history(replica_id: ReplicaId, history: History) -> Self {
Self::build(replica_id, history)
pub fn from_history(
replica_id: ReplicaId,
history: History,
file: Option<FileHandle>,
ctx: &mut ModelContext<Self>,
) -> Self {
Self::build(replica_id, history, file, ctx)
}
fn build(replica_id: ReplicaId, history: History) -> Self {
fn build(
replica_id: ReplicaId,
history: History,
file: Option<FileHandle>,
ctx: &mut ModelContext<Self>,
) -> Self {
if let Some(file) = file.as_ref() {
file.observe_from_model(ctx, |_, _, ctx| ctx.emit(Event::FileHandleChanged));
}
let mut insertion_splits = HashMap::default();
let mut fragments = SumTree::new();
@@ -373,39 +393,48 @@ impl Buffer {
insertion_splits.insert(
base_insertion.id,
SumTree::from_item(InsertionSplit {
fragment_id: FragmentId::min_value().clone(),
extent: 0,
}),
SumTree::from_item(
InsertionSplit {
fragment_id: FragmentId::min_value().clone(),
extent: 0,
},
&(),
),
);
fragments.push(
Fragment {
id: FragmentId::min_value().clone(),
insertion: base_insertion.clone(),
text: base_insertion.text.slice(0..0),
deletions: Default::default(),
max_undos: Default::default(),
visible: true,
},
&(),
);
fragments.push(Fragment {
id: FragmentId::min_value().clone(),
insertion: base_insertion.clone(),
text: base_insertion.text.slice(0..0),
deletions: Default::default(),
max_undos: Default::default(),
visible: true,
});
if base_insertion.text.len() > 0 {
let base_fragment_id =
FragmentId::between(&FragmentId::min_value(), &FragmentId::max_value());
insertion_splits
.get_mut(&base_insertion.id)
.unwrap()
.push(InsertionSplit {
insertion_splits.get_mut(&base_insertion.id).unwrap().push(
InsertionSplit {
fragment_id: base_fragment_id.clone(),
extent: base_insertion.text.len(),
});
fragments.push(Fragment {
id: base_fragment_id,
text: base_insertion.text.clone(),
insertion: base_insertion,
deletions: Default::default(),
max_undos: Default::default(),
visible: true,
});
},
&(),
);
fragments.push(
Fragment {
id: base_fragment_id,
text: base_insertion.text.clone(),
insertion: base_insertion,
deletions: Default::default(),
max_undos: Default::default(),
visible: true,
},
&(),
);
}
Self {
@@ -416,6 +445,7 @@ impl Buffer {
last_edit: time::Local::default(),
undo_map: Default::default(),
history,
file,
selections: HashMap::default(),
selections_last_update: 0,
deferred_ops: OperationQueue::new(),
@@ -432,24 +462,40 @@ impl Buffer {
}
}
pub fn file(&self) -> Option<&FileHandle> {
self.file.as_ref()
}
pub fn save(
&mut self,
file: &FileHandle,
new_file: Option<FileHandle>,
ctx: &mut ModelContext<Self>,
) -> LocalBoxFuture<'static, Result<()>> {
let snapshot = self.snapshot();
let version = self.version.clone();
let save_task = file.save(snapshot, ctx.as_ref());
let task = ctx.spawn(save_task, |me, save_result, ctx| {
if save_result.is_ok() {
me.did_save(version, ctx);
}
save_result
});
Box::pin(task)
if let Some(file) = new_file.as_ref().or(self.file.as_ref()) {
let save_task = file.save(snapshot, ctx.as_ref());
ctx.spawn(save_task, |me, save_result, ctx| {
if save_result.is_ok() {
me.did_save(version, new_file, ctx);
}
save_result
})
.boxed_local()
} else {
async { Ok(()) }.boxed_local()
}
}
fn did_save(&mut self, version: time::Global, ctx: &mut ModelContext<Buffer>) {
fn did_save(
&mut self,
version: time::Global,
file: Option<FileHandle>,
ctx: &mut ModelContext<Buffer>,
) {
if file.is_some() {
self.file = file;
}
self.saved_version = version;
ctx.emit(Event::Saved);
}
@@ -470,22 +516,22 @@ impl Buffer {
let mut summary = TextSummary::default();
let mut cursor = self.fragments.cursor::<usize, usize>();
cursor.seek(&range.start, SeekBias::Right);
cursor.seek(&range.start, SeekBias::Right, &());
if let Some(fragment) = cursor.item() {
let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
summary += &fragment.text.slice(summary_start..summary_end).summary();
summary += fragment.text.slice(summary_start..summary_end).summary();
cursor.next();
}
if range.end > *cursor.start() {
summary += &cursor.summary::<TextSummary>(&range.end, SeekBias::Right);
summary += cursor.summary::<TextSummary>(&range.end, SeekBias::Right, &());
if let Some(fragment) = cursor.item() {
let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
summary += &fragment.text.slice(summary_start..summary_end).summary();
summary += fragment.text.slice(summary_start..summary_end).summary();
}
}
@@ -515,22 +561,22 @@ impl Buffer {
let mut summary = TextSummary::default();
let mut cursor = self.fragments.cursor::<usize, usize>();
cursor.seek(&range.start, SeekBias::Right);
cursor.seek(&range.start, SeekBias::Right, &());
if let Some(fragment) = cursor.item() {
let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
summary += &fragment.text.slice(summary_start..summary_end).summary();
summary += fragment.text.slice(summary_start..summary_end).summary();
cursor.next();
}
if range.end > *cursor.start() {
summary += &cursor.summary::<TextSummary>(&range.end, SeekBias::Right);
summary += cursor.summary::<TextSummary>(&range.end, SeekBias::Right, &());
if let Some(fragment) = cursor.item() {
let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
summary += &fragment.text.slice(summary_start..summary_end).summary();
summary += fragment.text.slice(summary_start..summary_end).summary();
}
}
@@ -933,10 +979,10 @@ impl Buffer {
let mut cursor = old_fragments.cursor::<FragmentIdRef, ()>();
let mut new_fragments =
cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left);
cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left, &());
if start_offset == cursor.item().unwrap().end_offset() {
new_fragments.push(cursor.item().unwrap().clone());
new_fragments.push(cursor.item().unwrap().clone(), &());
cursor.next();
}
@@ -975,30 +1021,33 @@ impl Buffer {
None
};
if let Some(fragment) = before_range {
new_fragments.push(fragment);
new_fragments.push(fragment, &());
}
if let Some(fragment) = insertion {
new_fragments.push(fragment);
new_fragments.push(fragment, &());
}
if let Some(mut fragment) = within_range {
if fragment.was_visible(&version_in_range, &self.undo_map) {
fragment.deletions.insert(local_timestamp);
fragment.visible = false;
}
new_fragments.push(fragment);
new_fragments.push(fragment, &());
}
if let Some(fragment) = after_range {
new_fragments.push(fragment);
new_fragments.push(fragment, &());
}
} else {
if new_text.is_some() && lamport_timestamp > fragment.insertion.lamport_timestamp {
new_fragments.push(self.build_fragment_to_insert(
cursor.prev_item().as_ref().unwrap(),
Some(&fragment),
new_text.take().unwrap(),
local_timestamp,
lamport_timestamp,
));
new_fragments.push(
self.build_fragment_to_insert(
cursor.prev_item().as_ref().unwrap(),
Some(&fragment),
new_text.take().unwrap(),
local_timestamp,
lamport_timestamp,
),
&(),
);
}
if fragment.id < end_fragment_id
@@ -1007,23 +1056,26 @@ impl Buffer {
fragment.deletions.insert(local_timestamp);
fragment.visible = false;
}
new_fragments.push(fragment);
new_fragments.push(fragment, &());
}
cursor.next();
}
if let Some(new_text) = new_text {
new_fragments.push(self.build_fragment_to_insert(
cursor.prev_item().as_ref().unwrap(),
None,
new_text,
local_timestamp,
lamport_timestamp,
));
new_fragments.push(
self.build_fragment_to_insert(
cursor.prev_item().as_ref().unwrap(),
None,
new_text,
local_timestamp,
lamport_timestamp,
),
&(),
);
}
new_fragments.push_tree(cursor.slice(&last_id_ref, SeekBias::Right));
new_fragments.push_tree(cursor.slice(&last_id_ref, SeekBias::Right, &()), &());
self.fragments = new_fragments;
self.local_clock.observe(local_timestamp);
self.lamport_clock.observe(lamport_timestamp);
@@ -1111,23 +1163,26 @@ impl Buffer {
let mut insertion_splits = splits.cursor::<(), ()>().map(|s| &s.fragment_id).peekable();
let first_split_id = insertion_splits.next().unwrap();
new_fragments = cursor.slice(&FragmentIdRef::new(first_split_id), SeekBias::Left);
new_fragments = cursor.slice(&FragmentIdRef::new(first_split_id), SeekBias::Left, &());
loop {
let mut fragment = cursor.item().unwrap().clone();
fragment.visible = fragment.is_visible(&self.undo_map);
fragment.max_undos.observe(undo.id);
new_fragments.push(fragment);
new_fragments.push(fragment, &());
cursor.next();
if let Some(split_id) = insertion_splits.next() {
new_fragments
.push_tree(cursor.slice(&FragmentIdRef::new(split_id), SeekBias::Left));
new_fragments.push_tree(
cursor.slice(&FragmentIdRef::new(split_id), SeekBias::Left, &()),
&(),
);
} else {
break;
}
}
} else {
new_fragments = cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left);
new_fragments =
cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left, &());
while let Some(fragment) = cursor.item() {
if fragment.id > end_fragment_id {
break;
@@ -1139,13 +1194,13 @@ impl Buffer {
fragment.visible = fragment.is_visible(&self.undo_map);
fragment.max_undos.observe(undo.id);
}
new_fragments.push(fragment);
new_fragments.push(fragment, &());
cursor.next();
}
}
}
new_fragments.push_tree(cursor.suffix());
new_fragments.push_tree(cursor.suffix(&()), &());
drop(cursor);
self.fragments = new_fragments;
@@ -1209,7 +1264,7 @@ impl Buffer {
.get(&edit_id)
.ok_or_else(|| anyhow!("invalid operation"))?;
let mut cursor = split_tree.cursor::<usize, ()>();
cursor.seek(&offset, SeekBias::Left);
cursor.seek(&offset, SeekBias::Left, &());
Ok(cursor
.item()
.ok_or_else(|| anyhow!("invalid operation"))?
@@ -1231,7 +1286,10 @@ impl Buffer {
let old_fragments = self.fragments.clone();
let mut cursor = old_fragments.cursor::<usize, usize>();
let mut new_fragments = SumTree::new();
new_fragments.push_tree(cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right));
new_fragments.push_tree(
cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right, &()),
&(),
);
let mut start_id = None;
let mut start_offset = None;
@@ -1253,7 +1311,8 @@ impl Buffer {
.remove(&fragment.insertion.id)
.unwrap();
let mut splits_cursor = old_split_tree.cursor::<usize, ()>();
let mut new_split_tree = splits_cursor.slice(&fragment.start_offset(), SeekBias::Right);
let mut new_split_tree =
splits_cursor.slice(&fragment.start_offset(), SeekBias::Right, &());
// Find all splices that start or end within the current fragment. Then, split the
// fragment and reassemble it in both trees accounting for the deleted and the newly
@@ -1266,11 +1325,14 @@ impl Buffer {
prefix.id =
FragmentId::between(&new_fragments.last().unwrap().id, &fragment.id);
fragment.set_start_offset(prefix.end_offset());
new_fragments.push(prefix.clone());
new_split_tree.push(InsertionSplit {
extent: prefix.end_offset() - prefix.start_offset(),
fragment_id: prefix.id,
});
new_fragments.push(prefix.clone(), &());
new_split_tree.push(
InsertionSplit {
extent: prefix.end_offset() - prefix.start_offset(),
fragment_id: prefix.id,
},
&(),
);
fragment_start = range.start;
}
@@ -1294,7 +1356,7 @@ impl Buffer {
local_timestamp,
lamport_timestamp,
);
new_fragments.push(new_fragment);
new_fragments.push(new_fragment, &());
}
}
@@ -1310,11 +1372,14 @@ impl Buffer {
prefix.visible = false;
}
fragment.set_start_offset(prefix.end_offset());
new_fragments.push(prefix.clone());
new_split_tree.push(InsertionSplit {
extent: prefix.end_offset() - prefix.start_offset(),
fragment_id: prefix.id,
});
new_fragments.push(prefix.clone(), &());
new_split_tree.push(
InsertionSplit {
extent: prefix.end_offset() - prefix.start_offset(),
fragment_id: prefix.id,
},
&(),
);
fragment_start = range.end;
end_id = Some(fragment.insertion.id);
end_offset = Some(fragment.start_offset());
@@ -1358,16 +1423,21 @@ impl Buffer {
break;
}
}
new_split_tree.push(InsertionSplit {
extent: fragment.end_offset() - fragment.start_offset(),
fragment_id: fragment.id.clone(),
});
new_split_tree.push(
InsertionSplit {
extent: fragment.end_offset() - fragment.start_offset(),
fragment_id: fragment.id.clone(),
},
&(),
);
splits_cursor.next();
new_split_tree
.push_tree(splits_cursor.slice(&old_split_tree.extent::<usize>(), SeekBias::Right));
new_split_tree.push_tree(
splits_cursor.slice(&old_split_tree.extent::<usize>(), SeekBias::Right, &()),
&(),
);
self.insertion_splits
.insert(fragment.insertion.id, new_split_tree);
new_fragments.push(fragment);
new_fragments.push(fragment, &());
// Scan forward until we find a fragment that is not fully contained by the current splice.
cursor.next();
@@ -1383,7 +1453,7 @@ impl Buffer {
new_fragment.deletions.insert(local_timestamp);
new_fragment.visible = false;
}
new_fragments.push(new_fragment);
new_fragments.push(new_fragment, &());
cursor.next();
if range.end == fragment_end {
@@ -1425,7 +1495,8 @@ impl Buffer {
// and push all the fragments in between into the new tree.
if cur_range.as_ref().map_or(false, |r| r.start > fragment_end) {
new_fragments.push_tree(
cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right),
cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right, &()),
&(),
);
}
}
@@ -1457,11 +1528,13 @@ impl Buffer {
local_timestamp,
lamport_timestamp,
);
new_fragments.push(new_fragment);
new_fragments.push(new_fragment, &());
}
} else {
new_fragments
.push_tree(cursor.slice(&old_fragments.extent::<usize>(), SeekBias::Right));
new_fragments.push_tree(
cursor.slice(&old_fragments.extent::<usize>(), SeekBias::Right, &()),
&(),
);
}
self.fragments = new_fragments;
@@ -1519,32 +1592,43 @@ impl Buffer {
.remove(&fragment.insertion.id)
.unwrap();
let mut cursor = old_split_tree.cursor::<usize, ()>();
let mut new_split_tree = cursor.slice(&fragment.start_offset(), SeekBias::Right);
let mut new_split_tree = cursor.slice(&fragment.start_offset(), SeekBias::Right, &());
if let Some(ref fragment) = before_range {
new_split_tree.push(InsertionSplit {
extent: range.start - fragment.start_offset(),
fragment_id: fragment.id.clone(),
});
new_split_tree.push(
InsertionSplit {
extent: range.start - fragment.start_offset(),
fragment_id: fragment.id.clone(),
},
&(),
);
}
if let Some(ref fragment) = within_range {
new_split_tree.push(InsertionSplit {
extent: range.end - range.start,
fragment_id: fragment.id.clone(),
});
new_split_tree.push(
InsertionSplit {
extent: range.end - range.start,
fragment_id: fragment.id.clone(),
},
&(),
);
}
if let Some(ref fragment) = after_range {
new_split_tree.push(InsertionSplit {
extent: fragment.end_offset() - range.end,
fragment_id: fragment.id.clone(),
});
new_split_tree.push(
InsertionSplit {
extent: fragment.end_offset() - range.end,
fragment_id: fragment.id.clone(),
},
&(),
);
}
cursor.next();
new_split_tree
.push_tree(cursor.slice(&old_split_tree.extent::<usize>(), SeekBias::Right));
new_split_tree.push_tree(
cursor.slice(&old_split_tree.extent::<usize>(), SeekBias::Right, &()),
&(),
);
self.insertion_splits
.insert(fragment.insertion.id, new_split_tree);
@@ -1569,10 +1653,13 @@ impl Buffer {
);
let mut split_tree = SumTree::new();
split_tree.push(InsertionSplit {
extent: text.len(),
fragment_id: new_fragment_id.clone(),
});
split_tree.push(
InsertionSplit {
extent: text.len(),
fragment_id: new_fragment_id.clone(),
},
&(),
);
self.insertion_splits.insert(local_timestamp, split_tree);
Fragment::new(
@@ -1621,7 +1708,7 @@ impl Buffer {
};
let mut cursor = self.fragments.cursor::<usize, usize>();
cursor.seek(&offset, seek_bias);
cursor.seek(&offset, seek_bias, &());
let fragment = cursor.item().unwrap();
let offset_in_fragment = offset - cursor.start();
let offset_in_insertion = fragment.start_offset() + offset_in_fragment;
@@ -1653,7 +1740,7 @@ impl Buffer {
.get(&insertion_id)
.ok_or_else(|| anyhow!("split does not exist for insertion id"))?;
let mut splits_cursor = splits.cursor::<usize, ()>();
splits_cursor.seek(offset, seek_bias);
splits_cursor.seek(offset, seek_bias, &());
splits_cursor
.item()
.ok_or_else(|| anyhow!("split offset is out of range"))
@@ -1681,13 +1768,13 @@ impl Buffer {
.get(&insertion_id)
.ok_or_else(|| anyhow!("split does not exist for insertion id"))?;
let mut splits_cursor = splits.cursor::<usize, ()>();
splits_cursor.seek(offset, seek_bias);
splits_cursor.seek(offset, seek_bias, &());
let split = splits_cursor
.item()
.ok_or_else(|| anyhow!("split offset is out of range"))?;
let mut fragments_cursor = self.fragments.cursor::<FragmentIdRef, TextSummary>();
fragments_cursor.seek(&FragmentIdRef::new(&split.fragment_id), SeekBias::Left);
fragments_cursor.seek(&FragmentIdRef::new(&split.fragment_id), SeekBias::Left, &());
let fragment = fragments_cursor
.item()
.ok_or_else(|| anyhow!("fragment id does not exist"))?;
@@ -1707,7 +1794,7 @@ impl Buffer {
#[allow(dead_code)]
pub fn point_for_offset(&self, offset: usize) -> Result<Point> {
let mut fragments_cursor = self.fragments.cursor::<usize, TextSummary>();
fragments_cursor.seek(&offset, SeekBias::Left);
fragments_cursor.seek(&offset, SeekBias::Left, &());
fragments_cursor
.item()
.ok_or_else(|| anyhow!("offset is out of range"))
@@ -1733,6 +1820,7 @@ impl Clone for Buffer {
selections: self.selections.clone(),
selections_last_update: self.selections_last_update.clone(),
deferred_ops: self.deferred_ops.clone(),
file: self.file.clone(),
deferred_replicas: self.deferred_replicas.clone(),
replica_id: self.replica_id,
local_clock: self.local_clock.clone(),
@@ -1772,7 +1860,7 @@ impl<'a> sum_tree::Dimension<'a, FragmentSummary> for Point {
impl<'a> CharIter<'a> {
fn new(fragments: &'a SumTree<Fragment>, offset: usize) -> Self {
let mut fragments_cursor = fragments.cursor::<usize, usize>();
fragments_cursor.seek(&offset, SeekBias::Right);
fragments_cursor.seek(&offset, SeekBias::Right, &());
let fragment_chars = fragments_cursor.item().map_or("".chars(), |fragment| {
let offset_in_fragment = offset - fragments_cursor.start();
fragment.text[offset_in_fragment..].chars()
@@ -1809,7 +1897,7 @@ impl<'a> Iterator for CharIter<'a> {
impl<'a> FragmentIter<'a> {
fn new(fragments: &'a SumTree<Fragment>) -> Self {
let mut cursor = fragments.cursor::<usize, usize>();
cursor.seek(&0, SeekBias::Right);
cursor.seek(&0, SeekBias::Right, &());
Self {
cursor,
started: false,
@@ -2102,8 +2190,10 @@ impl sum_tree::Item for Fragment {
}
}
impl<'a> AddAssign<&'a FragmentSummary> for FragmentSummary {
fn add_assign(&mut self, other: &Self) {
impl sum_tree::Summary for FragmentSummary {
type Context = ();
fn add_summary(&mut self, other: &Self, _: &()) {
self.text_summary += &other.text_summary;
debug_assert!(self.max_fragment_id <= other.max_fragment_id);
self.max_fragment_id = other.max_fragment_id.clone();
@@ -2166,8 +2256,10 @@ impl sum_tree::Item for InsertionSplit {
}
}
impl<'a> AddAssign<&'a InsertionSplitSummary> for InsertionSplitSummary {
fn add_assign(&mut self, other: &Self) {
impl sum_tree::Summary for InsertionSplitSummary {
type Context = ();
fn add_summary(&mut self, other: &Self, _: &()) {
self.extent += other.extent;
}
}
@@ -2224,7 +2316,7 @@ pub trait ToOffset {
impl ToOffset for Point {
fn to_offset(&self, buffer: &Buffer) -> Result<usize> {
let mut fragments_cursor = buffer.fragments.cursor::<Point, TextSummary>();
fragments_cursor.seek(self, SeekBias::Left);
fragments_cursor.seek(self, SeekBias::Left, &());
fragments_cursor
.item()
.ok_or_else(|| anyhow!("point is out of range"))
@@ -2268,7 +2360,7 @@ impl ToPoint for Anchor {
impl ToPoint for usize {
fn to_point(&self, buffer: &Buffer) -> Result<Point> {
let mut fragments_cursor = buffer.fragments.cursor::<usize, TextSummary>();
fragments_cursor.seek(&self, SeekBias::Left);
fragments_cursor.seek(&self, SeekBias::Left, &());
fragments_cursor
.item()
.ok_or_else(|| anyhow!("offset is out of range"))
@@ -2292,8 +2384,8 @@ mod tests {
#[test]
fn test_edit() {
App::test((), |ctx| {
ctx.add_model(|_| {
let mut buffer = Buffer::new(0, "abc");
ctx.add_model(|ctx| {
let mut buffer = Buffer::new(0, "abc", ctx);
assert_eq!(buffer.text(), "abc");
buffer.edit(vec![3..3], "def", None).unwrap();
assert_eq!(buffer.text(), "abcdef");
@@ -2317,8 +2409,8 @@ mod tests {
let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
let buffer1 = app.add_model(|_| Buffer::new(0, "abcdef"));
let buffer2 = app.add_model(|_| Buffer::new(1, "abcdef"));
let buffer1 = app.add_model(|ctx| Buffer::new(0, "abcdef", ctx));
let buffer2 = app.add_model(|ctx| Buffer::new(1, "abcdef", ctx));
let mut buffer_ops = Vec::new();
buffer1.update(app, |buffer, ctx| {
let buffer_1_events = buffer_1_events.clone();
@@ -2381,8 +2473,8 @@ mod tests {
let mut reference_string = RandomCharIter::new(&mut rng)
.take(reference_string_len)
.collect::<String>();
ctx.add_model(|_| {
let mut buffer = Buffer::new(0, reference_string.as_str());
ctx.add_model(|ctx| {
let mut buffer = Buffer::new(0, reference_string.as_str(), ctx);
let mut buffer_versions = Vec::new();
for _i in 0..10 {
let (old_ranges, new_text, _) = buffer.randomly_mutate(rng, None);
@@ -2467,8 +2559,8 @@ mod tests {
#[test]
fn test_line_len() {
App::test((), |ctx| {
ctx.add_model(|_| {
let mut buffer = Buffer::new(0, "");
ctx.add_model(|ctx| {
let mut buffer = Buffer::new(0, "", ctx);
buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap();
buffer.edit(vec![12..12], "kl\nmno", None).unwrap();
buffer.edit(vec![18..18], "\npqrs\n", None).unwrap();
@@ -2489,8 +2581,8 @@ mod tests {
#[test]
fn test_rightmost_point() {
App::test((), |ctx| {
ctx.add_model(|_| {
let mut buffer = Buffer::new(0, "");
ctx.add_model(|ctx| {
let mut buffer = Buffer::new(0, "", ctx);
assert_eq!(buffer.rightmost_point().row, 0);
buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap();
assert_eq!(buffer.rightmost_point().row, 0);
@@ -2510,8 +2602,8 @@ mod tests {
#[test]
fn test_text_summary_for_range() {
App::test((), |ctx| {
ctx.add_model(|_| {
let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz");
ctx.add_model(|ctx| {
let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz", ctx);
let text = Text::from(buffer.text());
assert_eq!(
buffer.text_summary_for_range(1..3),
@@ -2541,8 +2633,8 @@ mod tests {
#[test]
fn test_chars_at() {
App::test((), |ctx| {
ctx.add_model(|_| {
let mut buffer = Buffer::new(0, "");
ctx.add_model(|ctx| {
let mut buffer = Buffer::new(0, "", ctx);
buffer.edit(vec![0..0], "abcd\nefgh\nij", None).unwrap();
buffer.edit(vec![12..12], "kl\nmno", None).unwrap();
buffer.edit(vec![18..18], "\npqrs", None).unwrap();
@@ -2564,7 +2656,7 @@ mod tests {
assert_eq!(chars.collect::<String>(), "PQrs");
// Regression test:
let mut buffer = Buffer::new(0, "");
let mut buffer = Buffer::new(0, "", ctx);
buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n", None).unwrap();
buffer.edit(vec![60..60], "\n", None).unwrap();
@@ -2693,8 +2785,8 @@ mod tests {
#[test]
fn test_anchors() {
App::test((), |ctx| {
ctx.add_model(|_| {
let mut buffer = Buffer::new(0, "");
ctx.add_model(|ctx| {
let mut buffer = Buffer::new(0, "", ctx);
buffer.edit(vec![0..0], "abc", None).unwrap();
let left_anchor = buffer.anchor_before(2).unwrap();
let right_anchor = buffer.anchor_after(2).unwrap();
@@ -2858,8 +2950,8 @@ mod tests {
#[test]
fn test_anchors_at_start_and_end() {
App::test((), |ctx| {
ctx.add_model(|_| {
let mut buffer = Buffer::new(0, "");
ctx.add_model(|ctx| {
let mut buffer = Buffer::new(0, "", ctx);
let before_start_anchor = buffer.anchor_before(0).unwrap();
let after_end_anchor = buffer.anchor_after(0).unwrap();
@@ -2886,7 +2978,7 @@ mod tests {
#[test]
fn test_is_modified() {
App::test((), |app| {
let model = app.add_model(|_| Buffer::new(0, "abc"));
let model = app.add_model(|ctx| Buffer::new(0, "abc", ctx));
let events = Rc::new(RefCell::new(Vec::new()));
// initially, the buffer isn't dirty.
@@ -2909,7 +3001,7 @@ mod tests {
assert_eq!(*events.borrow(), &[Event::Edited, Event::Dirtied]);
events.borrow_mut().clear();
buffer.did_save(buffer.version(), ctx);
buffer.did_save(buffer.version(), None, ctx);
});
// after saving, the buffer is not dirty, and emits a saved event.
@@ -2948,8 +3040,8 @@ mod tests {
#[test]
fn test_undo_redo() {
App::test((), |app| {
app.add_model(|_| {
let mut buffer = Buffer::new(0, "1234");
app.add_model(|ctx| {
let mut buffer = Buffer::new(0, "1234", ctx);
let edit1 = buffer.edit(vec![1..1], "abx", None).unwrap();
let edit2 = buffer.edit(vec![3..4], "yzef", None).unwrap();
@@ -2985,9 +3077,9 @@ mod tests {
#[test]
fn test_history() {
App::test((), |app| {
app.add_model(|_| {
app.add_model(|ctx| {
let mut now = Instant::now();
let mut buffer = Buffer::new(0, "123456");
let mut buffer = Buffer::new(0, "123456", ctx);
let (set_id, _) = buffer
.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), None);
@@ -3072,7 +3164,7 @@ mod tests {
let mut network = Network::new();
for i in 0..PEERS {
let buffer =
ctx.add_model(|_| Buffer::new(i as ReplicaId, base_text.as_str()));
ctx.add_model(|ctx| Buffer::new(i as ReplicaId, base_text.as_str(), ctx));
buffers.push(buffer);
replica_ids.push(i as u16);
network.add_peer(i as u16);
+3 -1
View File
@@ -75,6 +75,7 @@ impl Selection {
pub fn buffer_rows_for_display_rows(
&self,
include_end_if_at_line_start: bool,
map: &DisplayMap,
ctx: &AppContext,
) -> (Range<u32>, Range<u32>) {
@@ -84,7 +85,8 @@ impl Selection {
.unwrap();
let mut display_end = self.end.to_display_point(map, ctx).unwrap();
if display_end.row() != map.max_point(ctx).row()
if !include_end_if_at_line_start
&& display_end.row() != map.max_point(ctx).row()
&& display_start.row() != display_end.row()
&& display_end.column() == 0
{
+21 -11
View File
@@ -58,6 +58,14 @@ pub struct TextSummary {
pub rightmost_point: Point,
}
impl sum_tree::Summary for TextSummary {
type Context = ();
fn add_summary(&mut self, other: &Self, _: &()) {
*self += other;
}
}
impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
fn add_assign(&mut self, other: &'a Self) {
let joined_line_len = self.lines.column + other.first_line_len;
@@ -85,8 +93,8 @@ impl std::ops::AddAssign<Self> for TextSummary {
}
impl<'a> sum_tree::Dimension<'a, TextSummary> for TextSummary {
fn add_summary(&mut self, summary: &TextSummary) {
*self += summary;
fn add_summary(&mut self, other: &TextSummary) {
*self += other;
}
}
@@ -157,7 +165,7 @@ impl From<Arc<str>> for Text {
}
let mut tree = SumTree::new();
tree.extend(runs);
tree.extend(runs, &());
Text {
text,
runs: tree,
@@ -231,13 +239,14 @@ impl Text {
pub fn line_len(&self, row: u32) -> u32 {
let mut cursor = self.runs.cursor::<usize, Point>();
cursor.seek(&self.range.start, SeekBias::Right);
cursor.seek(&self.range.start, SeekBias::Right, &());
let absolute_row = cursor.start().row + row;
let mut cursor = self.runs.cursor::<Point, usize>();
cursor.seek(&Point::new(absolute_row, 0), SeekBias::Right);
cursor.seek(&Point::new(absolute_row, 0), SeekBias::Right, &());
let prefix_len = self.range.start.saturating_sub(*cursor.start());
let line_len = cursor.summary::<usize>(&Point::new(absolute_row + 1, 0), SeekBias::Left);
let line_len =
cursor.summary::<usize>(&Point::new(absolute_row + 1, 0), SeekBias::Left, &());
let suffix_len = cursor.start().saturating_sub(self.range.end);
line_len
@@ -262,14 +271,15 @@ impl Text {
candidates.push(Point::new(0, self.line_len(0)));
if lines.row > 1 {
let mut cursor = self.runs.cursor::<usize, Point>();
cursor.seek(&self.range.start, SeekBias::Right);
cursor.seek(&self.range.start, SeekBias::Right, &());
let absolute_start_row = cursor.start().row;
let mut cursor = self.runs.cursor::<Point, usize>();
cursor.seek(&Point::new(absolute_start_row + 1, 0), SeekBias::Right);
cursor.seek(&Point::new(absolute_start_row + 1, 0), SeekBias::Right, &());
let summary = cursor.summary::<TextSummary>(
&Point::new(absolute_start_row + lines.row, 0),
SeekBias::Left,
&(),
);
candidates.push(Point::new(1, 0) + &summary.rightmost_point);
@@ -287,7 +297,7 @@ impl Text {
pub fn offset_for_point(&self, point: Point) -> usize {
let mut cursor = self.runs.cursor::<Point, TextSummary>();
let abs_point = self.abs_point_for_offset(self.range.start) + &point;
cursor.seek(&abs_point, SeekBias::Right);
cursor.seek(&abs_point, SeekBias::Right, &());
let overshoot = abs_point - &cursor.start().lines;
let abs_offset = cursor.start().chars + overshoot.column as usize;
abs_offset - self.range.start
@@ -307,14 +317,14 @@ impl Text {
fn abs_point_for_offset(&self, offset: usize) -> Point {
let mut cursor = self.runs.cursor::<usize, TextSummary>();
cursor.seek(&offset, SeekBias::Right);
cursor.seek(&offset, SeekBias::Right, &());
let overshoot = (offset - cursor.start().chars) as u32;
cursor.start().lines + &Point::new(0, overshoot)
}
fn abs_byte_offset_for_offset(&self, offset: usize) -> usize {
let mut cursor = self.runs.cursor::<usize, TextSummary>();
cursor.seek(&offset, SeekBias::Right);
cursor.seek(&offset, SeekBias::Right, &());
let overshoot = offset - cursor.start().chars;
cursor.start().bytes + overshoot * cursor.item().map_or(0, |run| run.char_size()) as usize
}
+280 -81
View File
@@ -6,11 +6,10 @@ use crate::{settings::Settings, workspace, worktree::FileHandle};
use anyhow::Result;
use futures_core::future::LocalBoxFuture;
use gpui::{
fonts::Properties as FontProperties, keymap::Binding, text_layout, AppContext, ClipboardItem,
Element, ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, View, ViewContext,
WeakViewHandle,
fonts::Properties as FontProperties, geometry::vector::Vector2F, keymap::Binding, text_layout,
AppContext, ClipboardItem, Element, ElementBox, Entity, FontCache, ModelHandle,
MutableAppContext, TextLayoutCache, View, ViewContext, WeakViewHandle,
};
use gpui::{geometry::vector::Vector2F, TextLayoutCache};
use parking_lot::Mutex;
use postage::watch;
use serde::{Deserialize, Serialize};
@@ -69,6 +68,10 @@ pub fn init(app: &mut MutableAppContext) {
Binding::new("down", "buffer:move_down", Some("BufferView")),
Binding::new("left", "buffer:move_left", Some("BufferView")),
Binding::new("right", "buffer:move_right", Some("BufferView")),
Binding::new("ctrl-p", "buffer:move_up", Some("BufferView")),
Binding::new("ctrl-n", "buffer:move_down", Some("BufferView")),
Binding::new("ctrl-b", "buffer:move_left", Some("BufferView")),
Binding::new("ctrl-f", "buffer:move_right", Some("BufferView")),
Binding::new(
"alt-left",
"buffer:move_to_previous_word_boundary",
@@ -140,6 +143,12 @@ pub fn init(app: &mut MutableAppContext) {
),
Binding::new("cmd-shift-down", "buffer:select_to_end", Some("BufferView")),
Binding::new("cmd-a", "buffer:select_all", Some("BufferView")),
Binding::new("cmd-l", "buffer:select_line", Some("BufferView")),
Binding::new(
"cmd-shift-L",
"buffer:split_selection_into_lines",
Some("BufferView"),
),
Binding::new("pageup", "buffer:page_up", Some("BufferView")),
Binding::new("pagedown", "buffer:page_down", Some("BufferView")),
Binding::new("alt-cmd-[", "buffer:fold", Some("BufferView")),
@@ -230,6 +239,11 @@ pub fn init(app: &mut MutableAppContext) {
);
app.add_action("buffer:select_to_end", BufferView::select_to_end);
app.add_action("buffer:select_all", BufferView::select_all);
app.add_action("buffer:select_line", BufferView::select_line);
app.add_action(
"buffer:split_selection_into_lines",
BufferView::split_selection_into_lines,
);
app.add_action("buffer:page_up", BufferView::page_up);
app.add_action("buffer:page_down", BufferView::page_down);
app.add_action("buffer:fold", BufferView::fold);
@@ -255,7 +269,6 @@ pub enum SelectAction {
pub struct BufferView {
handle: WeakViewHandle<Self>,
buffer: ModelHandle<Buffer>,
file: Option<FileHandle>,
display_map: DisplayMap,
selection_set_id: SelectionSetId,
pending_selection: Option<Selection>,
@@ -277,22 +290,17 @@ struct ClipboardSelection {
impl BufferView {
pub fn single_line(settings: watch::Receiver<Settings>, ctx: &mut ViewContext<Self>) -> Self {
let buffer = ctx.add_model(|_| Buffer::new(0, String::new()));
let mut view = Self::for_buffer(buffer, None, settings, ctx);
let buffer = ctx.add_model(|ctx| Buffer::new(0, String::new(), ctx));
let mut view = Self::for_buffer(buffer, settings, ctx);
view.single_line = true;
view
}
pub fn for_buffer(
buffer: ModelHandle<Buffer>,
file: Option<FileHandle>,
settings: watch::Receiver<Settings>,
ctx: &mut ViewContext<Self>,
) -> Self {
if let Some(file) = file.as_ref() {
file.observe_from_view(ctx, |_, _, ctx| ctx.emit(Event::FileHandleChanged));
}
ctx.observe_model(&buffer, Self::on_buffer_changed);
ctx.subscribe_to_model(&buffer, Self::on_buffer_event);
let display_map = DisplayMap::new(buffer.clone(), settings.borrow().tab_size, ctx.as_ref());
@@ -311,7 +319,6 @@ impl BufferView {
Self {
handle: ctx.handle().downgrade(),
buffer,
file,
display_map,
selection_set_id,
pending_selection: None,
@@ -707,7 +714,8 @@ impl BufferView {
let mut selections = self.selections(app).iter().peekable();
while let Some(selection) = selections.next() {
let (mut rows, _) = selection.buffer_rows_for_display_rows(&self.display_map, app);
let (mut rows, _) =
selection.buffer_rows_for_display_rows(false, &self.display_map, app);
let goal_display_column = selection
.head()
.to_display_point(&self.display_map, app)
@@ -717,7 +725,7 @@ impl BufferView {
// Accumulate contiguous regions of rows that we want to delete.
while let Some(next_selection) = selections.peek() {
let (next_rows, _) =
next_selection.buffer_rows_for_display_rows(&self.display_map, app);
next_selection.buffer_rows_for_display_rows(false, &self.display_map, app);
if next_rows.start <= rows.end {
rows.end = next_rows.end;
selections.next().unwrap();
@@ -798,10 +806,11 @@ impl BufferView {
let mut selections_iter = selections.iter_mut().peekable();
while let Some(selection) = selections_iter.next() {
// Avoid duplicating the same lines twice.
let (mut rows, _) = selection.buffer_rows_for_display_rows(&self.display_map, app);
let (mut rows, _) =
selection.buffer_rows_for_display_rows(false, &self.display_map, app);
while let Some(next_selection) = selections_iter.peek() {
let (next_rows, _) =
next_selection.buffer_rows_for_display_rows(&self.display_map, app);
next_selection.buffer_rows_for_display_rows(false, &self.display_map, app);
if next_rows.start <= rows.end - 1 {
rows.end = next_rows.end;
selections_iter.next().unwrap();
@@ -855,10 +864,10 @@ impl BufferView {
// Accumulate contiguous regions of rows that we want to move.
contiguous_selections.push(selection.range(buffer));
let (mut buffer_rows, mut display_rows) =
selection.buffer_rows_for_display_rows(&self.display_map, app);
selection.buffer_rows_for_display_rows(false, &self.display_map, app);
while let Some(next_selection) = selections.peek() {
let (next_buffer_rows, next_display_rows) =
next_selection.buffer_rows_for_display_rows(&self.display_map, app);
next_selection.buffer_rows_for_display_rows(false, &self.display_map, app);
if next_buffer_rows.start <= buffer_rows.end {
buffer_rows.end = next_buffer_rows.end;
display_rows.end = next_display_rows.end;
@@ -945,10 +954,10 @@ impl BufferView {
// Accumulate contiguous regions of rows that we want to move.
contiguous_selections.push(selection.range(buffer));
let (mut buffer_rows, mut display_rows) =
selection.buffer_rows_for_display_rows(&self.display_map, app);
selection.buffer_rows_for_display_rows(false, &self.display_map, app);
while let Some(next_selection) = selections.peek() {
let (next_buffer_rows, next_display_rows) =
next_selection.buffer_rows_for_display_rows(&self.display_map, app);
next_selection.buffer_rows_for_display_rows(false, &self.display_map, app);
if next_buffer_rows.start <= buffer_rows.end {
buffer_rows.end = next_buffer_rows.end;
display_rows.end = next_display_rows.end;
@@ -1657,6 +1666,63 @@ impl BufferView {
self.update_selections(vec![selection], false, ctx);
}
pub fn select_line(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
let app = ctx.as_ref();
let buffer = self.buffer.read(app);
let mut selections = self.selections(app).to_vec();
let max_point = buffer.max_point();
for selection in &mut selections {
let (rows, _) = selection.buffer_rows_for_display_rows(true, &self.display_map, app);
selection.start = buffer.anchor_before(Point::new(rows.start, 0)).unwrap();
selection.end = buffer
.anchor_before(cmp::min(max_point, Point::new(rows.end, 0)))
.unwrap();
selection.reversed = false;
}
self.update_selections(selections, true, ctx);
}
pub fn split_selection_into_lines(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
use super::RangeExt;
let app = ctx.as_ref();
let buffer = self.buffer.read(app);
let mut to_unfold = Vec::new();
let mut new_selections = Vec::new();
for selection in self.selections(app) {
let range = selection.range(buffer).sorted();
if range.start.row != range.end.row {
new_selections.push(Selection {
start: selection.start.clone(),
end: selection.start.clone(),
reversed: false,
goal_column: None,
});
}
for row in range.start.row + 1..range.end.row {
let cursor = buffer
.anchor_before(Point::new(row, buffer.line_len(row).unwrap()))
.unwrap();
new_selections.push(Selection {
start: cursor.clone(),
end: cursor,
reversed: false,
goal_column: None,
});
}
new_selections.push(Selection {
start: selection.end.clone(),
end: selection.end.clone(),
reversed: false,
goal_column: None,
});
to_unfold.push(range);
}
self.unfold_ranges(to_unfold, ctx);
self.update_selections(new_selections, true, ctx);
}
pub fn selections_in_range<'a>(
&'a self,
range: Range<DisplayPoint>,
@@ -1877,9 +1943,8 @@ impl BufferView {
.selections(ctx.as_ref())
.iter()
.map(|s| s.range(buffer).sorted())
.collect::<Vec<_>>();
self.display_map.fold(ranges, ctx.as_ref()).unwrap();
ctx.notify();
.collect();
self.fold_ranges(ranges, ctx);
}
fn fold_ranges<T: ToOffset>(&mut self, ranges: Vec<Range<T>>, ctx: &mut ViewContext<Self>) {
@@ -2177,6 +2242,22 @@ impl View for BufferView {
}
}
impl workspace::Item for Buffer {
type View = BufferView;
fn file(&self) -> Option<&FileHandle> {
self.file()
}
fn build_view(
handle: ModelHandle<Self>,
settings: watch::Receiver<Settings>,
ctx: &mut ViewContext<Self::View>,
) -> Self::View {
BufferView::for_buffer(handle, settings, ctx)
}
}
impl workspace::ItemView for BufferView {
fn should_activate_item_on_event(event: &Self::Event) -> bool {
matches!(event, Event::Activate)
@@ -2190,7 +2271,11 @@ impl workspace::ItemView for BufferView {
}
fn title(&self, app: &AppContext) -> std::string::String {
let filename = self.file.as_ref().and_then(|file| file.file_name(app));
let filename = self
.buffer
.read(app)
.file()
.and_then(|file| file.file_name(app));
if let Some(name) = filename {
name.to_string_lossy().into()
} else {
@@ -2198,31 +2283,25 @@ impl workspace::ItemView for BufferView {
}
}
fn entry_id(&self, _: &AppContext) -> Option<(usize, Arc<Path>)> {
self.file.as_ref().map(|file| file.entry_id())
fn entry_id(&self, ctx: &AppContext) -> Option<(usize, Arc<Path>)> {
self.buffer.read(ctx).file().map(|file| file.entry_id())
}
fn clone_on_split(&self, ctx: &mut ViewContext<Self>) -> Option<Self>
where
Self: Sized,
{
let clone = BufferView::for_buffer(
self.buffer.clone(),
self.file.clone(),
self.settings.clone(),
ctx,
);
let clone = BufferView::for_buffer(self.buffer.clone(), self.settings.clone(), ctx);
*clone.scroll_position.lock() = *self.scroll_position.lock();
Some(clone)
}
fn save(&self, ctx: &mut ViewContext<Self>) -> LocalBoxFuture<'static, Result<()>> {
if let Some(file) = self.file.as_ref() {
self.buffer
.update(ctx, |buffer, ctx| buffer.save(file, ctx))
} else {
Box::pin(async { Ok(()) })
}
fn save(
&mut self,
new_file: Option<FileHandle>,
ctx: &mut ViewContext<Self>,
) -> LocalBoxFuture<'static, Result<()>> {
self.buffer.update(ctx, |b, ctx| b.save(new_file, ctx))
}
fn is_dirty(&self, ctx: &AppContext) -> bool {
@@ -2240,10 +2319,11 @@ mod tests {
#[test]
fn test_selection_with_mouse() {
App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n"));
let buffer =
app.add_model(|ctx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, buffer_view) =
app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
buffer_view.update(app, |view, ctx| {
view.begin_selection(DisplayPoint::new(2, 2), false, ctx);
@@ -2354,11 +2434,11 @@ mod tests {
let layout_cache = TextLayoutCache::new(app.platform().fonts());
let font_cache = app.font_cache().clone();
let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 6), ctx));
let settings = settings::channel(&font_cache).unwrap().1;
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx));
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
let layouts = view
.read(app)
@@ -2371,7 +2451,7 @@ mod tests {
#[test]
fn test_fold() {
App::test((), |app| {
let buffer = app.add_model(|_| {
let buffer = app.add_model(|ctx| {
Buffer::new(
0,
"
@@ -2392,11 +2472,12 @@ mod tests {
}
"
.unindent(),
ctx,
)
});
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx));
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
@@ -2465,10 +2546,10 @@ mod tests {
#[test]
fn test_move_cursor() {
App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 6), ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx));
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
buffer.update(app, |buffer, ctx| {
buffer
@@ -2543,10 +2624,9 @@ mod tests {
#[test]
fn test_beginning_end_of_line() {
App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, "abc\n def"));
let buffer = app.add_model(|ctx| Buffer::new(0, "abc\n def", ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
&[
@@ -2672,11 +2752,10 @@ mod tests {
#[test]
fn test_prev_next_word_boundary() {
App::test((), |app| {
let buffer =
app.add_model(|_| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}"));
let buffer = app
.add_model(|ctx| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}", ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
&[
@@ -2855,12 +2934,16 @@ mod tests {
#[test]
fn test_backspace() {
App::test((), |app| {
let buffer = app.add_model(|_| {
Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n")
let buffer = app.add_model(|ctx| {
Buffer::new(
0,
"one two three\nfour five six\nseven eight nine\nten\n",
ctx,
)
});
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx));
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
@@ -2888,12 +2971,16 @@ mod tests {
#[test]
fn test_delete() {
App::test((), |app| {
let buffer = app.add_model(|_| {
Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n")
let buffer = app.add_model(|ctx| {
Buffer::new(
0,
"one two three\nfour five six\nseven eight nine\nten\n",
ctx,
)
});
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx));
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
@@ -2922,9 +3009,8 @@ mod tests {
fn test_delete_line() {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1;
let buffer = app.add_model(|_| Buffer::new(0, "abc\ndef\nghi\n"));
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx));
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
&[
@@ -2947,9 +3033,8 @@ mod tests {
);
let settings = settings::channel(&app.font_cache()).unwrap().1;
let buffer = app.add_model(|_| Buffer::new(0, "abc\ndef\nghi\n"));
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx));
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
&[DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)],
@@ -2970,9 +3055,8 @@ mod tests {
fn test_duplicate_line() {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1;
let buffer = app.add_model(|_| Buffer::new(0, "abc\ndef\nghi\n"));
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx));
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
&[
@@ -3001,9 +3085,8 @@ mod tests {
);
let settings = settings::channel(&app.font_cache()).unwrap().1;
let buffer = app.add_model(|_| Buffer::new(0, "abc\ndef\nghi\n"));
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx));
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
&[
@@ -3033,9 +3116,8 @@ mod tests {
fn test_move_line_up_down() {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1;
let buffer = app.add_model(|_| Buffer::new(0, sample_text(10, 5)));
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(10, 5), ctx));
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.fold_ranges(
vec![
@@ -3126,10 +3208,10 @@ mod tests {
#[test]
fn test_clipboard() {
App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, "one two three four five six "));
let buffer = app.add_model(|ctx| Buffer::new(0, "one two three four five six ", ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let view = app
.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx))
.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx))
.1;
// Cut with three selections. Clipboard text is divided into three slices.
@@ -3267,10 +3349,9 @@ mod tests {
#[test]
fn test_select_all() {
App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, "abc\nde\nfgh"));
let buffer = app.add_model(|ctx| Buffer::new(0, "abc\nde\nfgh", ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |b, ctx| b.select_all(&(), ctx));
assert_eq!(
view.read(app).selection_ranges(app.as_ref()),
@@ -3279,6 +3360,124 @@ mod tests {
});
}
#[test]
fn test_select_line() {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1;
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 5), ctx));
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
&[
DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
],
ctx,
)
.unwrap();
view.select_line(&(), ctx);
});
assert_eq!(
view.read(app).selection_ranges(app.as_ref()),
vec![
DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
]
);
view.update(app, |view, ctx| view.select_line(&(), ctx));
assert_eq!(
view.read(app).selection_ranges(app.as_ref()),
vec![
DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
]
);
view.update(app, |view, ctx| view.select_line(&(), ctx));
assert_eq!(
view.read(app).selection_ranges(app.as_ref()),
vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
);
});
}
#[test]
fn test_split_selection_into_lines() {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1;
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(9, 5), ctx));
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.fold_ranges(
vec![
Point::new(0, 2)..Point::new(1, 2),
Point::new(2, 3)..Point::new(4, 1),
Point::new(7, 0)..Point::new(8, 4),
],
ctx,
);
view.select_display_ranges(
&[
DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
],
ctx,
)
.unwrap();
});
assert_eq!(
view.read(app).text(app.as_ref()),
"aa…bbb\nccc…eeee\nfffff\nggggg\n…i"
);
view.update(app, |view, ctx| view.split_selection_into_lines(&(), ctx));
assert_eq!(
view.read(app).text(app.as_ref()),
"aa…bbb\nccc…eeee\nfffff\nggggg\n…i"
);
assert_eq!(
view.read(app).selection_ranges(app.as_ref()),
[
DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2)
]
);
view.update(app, |view, ctx| {
view.select_display_ranges(
&[DisplayPoint::new(4, 0)..DisplayPoint::new(0, 1)],
ctx,
)
.unwrap();
view.split_selection_into_lines(&(), ctx);
});
assert_eq!(
view.read(app).text(app.as_ref()),
"aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\n…i"
);
assert_eq!(
view.read(app).selection_ranges(app.as_ref()),
[
DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
]
);
});
}
impl BufferView {
fn selection_ranges(&self, app: &AppContext) -> Vec<Range<DisplayPoint>> {
self.selections_in_range(DisplayPoint::zero()..self.max_point(app), app)
+316 -138
View File
@@ -1,10 +1,10 @@
use super::{
buffer, Anchor, AnchorRangeExt, Buffer, DisplayPoint, Edit, Point, TextSummary, ToOffset,
buffer::{self, AnchorRangeExt},
Anchor, Buffer, DisplayPoint, Edit, Point, TextSummary, ToOffset,
};
use crate::{
sum_tree::{self, Cursor, SumTree},
sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree},
time,
util::find_insertion_index,
};
use anyhow::{anyhow, Result};
use gpui::{AppContext, ModelHandle};
@@ -14,12 +14,11 @@ use std::{
iter::Take,
ops::Range,
};
use sum_tree::{Dimension, SeekBias};
pub struct FoldMap {
buffer: ModelHandle<Buffer>,
transforms: Mutex<SumTree<Transform>>,
folds: Vec<Range<Anchor>>,
folds: SumTree<Fold>,
last_sync: Mutex<time::Global>,
}
@@ -29,14 +28,17 @@ impl FoldMap {
let text_summary = buffer.text_summary();
Self {
buffer: buffer_handle,
folds: Vec::new(),
transforms: Mutex::new(SumTree::from_item(Transform {
summary: TransformSummary {
buffer: text_summary.clone(),
display: text_summary,
folds: Default::default(),
transforms: Mutex::new(SumTree::from_item(
Transform {
summary: TransformSummary {
buffer: text_summary.clone(),
display: text_summary,
},
display_text: None,
},
display_text: None,
})),
&(),
)),
last_sync: Mutex::new(buffer.version()),
}
}
@@ -76,17 +78,12 @@ impl FoldMap {
pub fn folds_in_range<'a, T>(
&'a self,
range: Range<T>,
app: &'a AppContext,
ctx: &'a AppContext,
) -> Result<impl Iterator<Item = &'a Range<Anchor>>>
where
T: ToOffset,
{
let buffer = self.buffer.read(app);
let range = buffer.anchor_before(range.start)?..buffer.anchor_before(range.end)?;
Ok(self.folds.iter().filter(move |fold| {
range.start.cmp(&fold.end, buffer).unwrap() == Ordering::Less
&& range.end.cmp(&fold.start, buffer).unwrap() == Ordering::Greater
}))
Ok(self.intersecting_folds(range, ctx)?.map(|f| &f.0))
}
pub fn fold<T: ToOffset>(
@@ -97,19 +94,22 @@ impl FoldMap {
let _ = self.sync(ctx);
let mut edits = Vec::new();
let mut folds = Vec::new();
let buffer = self.buffer.read(ctx);
for range in ranges.into_iter() {
let start = range.start.to_offset(buffer)?;
let end = range.end.to_offset(buffer)?;
edits.push(Edit {
old_range: start..end,
new_range: start..end,
});
let fold = buffer.anchor_after(start)?..buffer.anchor_before(end)?;
let ix = find_insertion_index(&self.folds, |probe| probe.cmp(&fold, buffer))?;
self.folds.insert(ix, fold);
let range = range.start.to_offset(buffer)?..range.end.to_offset(buffer)?;
if range.start != range.end {
let fold =
Fold(buffer.anchor_after(range.start)?..buffer.anchor_before(range.end)?);
folds.push(fold);
edits.push(Edit {
old_range: range.clone(),
new_range: range.clone(),
});
}
}
folds.sort_unstable_by(|a, b| sum_tree::SeekDimension::cmp(a, b, buffer));
edits.sort_unstable_by(|a, b| {
a.old_range
.start
@@ -117,6 +117,16 @@ impl FoldMap {
.then_with(|| b.old_range.end.cmp(&a.old_range.end))
});
self.folds = {
let mut new_tree = SumTree::new();
let mut cursor = self.folds.cursor::<_, ()>();
for fold in folds {
new_tree.push_tree(cursor.slice(&fold, SeekBias::Right, buffer), buffer);
new_tree.push(fold, buffer);
}
new_tree.push_tree(cursor.suffix(buffer), buffer);
new_tree
};
self.apply_edits(edits, ctx);
Ok(())
}
@@ -131,36 +141,66 @@ impl FoldMap {
let buffer = self.buffer.read(ctx);
let mut edits = Vec::new();
let mut fold_ixs_to_delete = Vec::new();
for range in ranges.into_iter() {
let start = buffer.anchor_before(range.start.to_offset(buffer)?)?;
let end = buffer.anchor_after(range.end.to_offset(buffer)?)?;
// Remove intersecting folds and add their ranges to edits that are passed to apply_edits
self.folds.retain(|fold| {
if fold.start.cmp(&end, buffer).unwrap() > Ordering::Equal
|| fold.end.cmp(&start, buffer).unwrap() < Ordering::Equal
{
true
} else {
let offset_range =
fold.start.to_offset(buffer).unwrap()..fold.end.to_offset(buffer).unwrap();
edits.push(Edit {
old_range: offset_range.clone(),
new_range: offset_range,
});
false
}
});
// Remove intersecting folds and add their ranges to edits that are passed to apply_edits.
let mut folds_cursor = self.intersecting_folds(range, ctx)?;
while let Some(fold) = folds_cursor.item() {
let offset_range =
fold.0.start.to_offset(buffer).unwrap()..fold.0.end.to_offset(buffer).unwrap();
edits.push(Edit {
old_range: offset_range.clone(),
new_range: offset_range,
});
fold_ixs_to_delete.push(*folds_cursor.start());
folds_cursor.next();
}
}
fold_ixs_to_delete.sort_unstable();
fold_ixs_to_delete.dedup();
edits.sort_unstable_by(|a, b| {
a.old_range
.start
.cmp(&b.old_range.start)
.then_with(|| b.old_range.end.cmp(&a.old_range.end))
});
self.folds = {
let mut cursor = self.folds.cursor::<_, ()>();
let mut folds = SumTree::new();
for fold_ix in fold_ixs_to_delete {
folds.push_tree(cursor.slice(&fold_ix, SeekBias::Right, buffer), buffer);
cursor.next();
}
folds.push_tree(cursor.suffix(buffer), buffer);
folds
};
self.apply_edits(edits, ctx);
Ok(())
}
fn intersecting_folds<'a, T>(
&self,
range: Range<T>,
ctx: &'a AppContext,
) -> Result<FilterCursor<impl 'a + Fn(&FoldSummary) -> bool, Fold, usize>>
where
T: ToOffset,
{
let buffer = self.buffer.read(ctx);
let start = buffer.anchor_before(range.start.to_offset(buffer)?)?;
let end = buffer.anchor_after(range.end.to_offset(buffer)?)?;
Ok(self.folds.filter::<_, usize>(move |summary| {
start.cmp(&summary.max_end, buffer).unwrap() == Ordering::Less
&& end.cmp(&summary.min_start, buffer).unwrap() == Ordering::Greater
}))
}
pub fn is_line_folded(&self, display_row: u32, ctx: &AppContext) -> bool {
let transforms = self.sync(ctx);
let mut cursor = transforms.cursor::<DisplayPoint, DisplayPoint>();
cursor.seek(&DisplayPoint::new(display_row, 0), SeekBias::Right);
cursor.seek(&DisplayPoint::new(display_row, 0), SeekBias::Right, &());
while let Some(transform) = cursor.item() {
if transform.display_text.is_some() {
return true;
@@ -177,7 +217,7 @@ impl FoldMap {
pub fn to_buffer_offset(&self, point: DisplayPoint, ctx: &AppContext) -> Result<usize> {
let transforms = self.sync(ctx);
let mut cursor = transforms.cursor::<DisplayPoint, TransformSummary>();
cursor.seek(&point, SeekBias::Right);
cursor.seek(&point, SeekBias::Right, &());
let overshoot = point.0 - cursor.start().display.lines;
(cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(ctx))
}
@@ -193,7 +233,7 @@ impl FoldMap {
pub fn to_buffer_point(&self, display_point: DisplayPoint, ctx: &AppContext) -> Point {
let transforms = self.sync(ctx);
let mut cursor = transforms.cursor::<DisplayPoint, TransformSummary>();
cursor.seek(&display_point, SeekBias::Right);
cursor.seek(&display_point, SeekBias::Right, &());
let overshoot = display_point.0 - cursor.start().display.lines;
cursor.start().buffer.lines + overshoot
}
@@ -201,7 +241,7 @@ impl FoldMap {
pub fn to_display_point(&self, point: Point, ctx: &AppContext) -> DisplayPoint {
let transforms = self.sync(ctx);
let mut cursor = transforms.cursor::<Point, TransformSummary>();
cursor.seek(&point, SeekBias::Right);
cursor.seek(&point, SeekBias::Right, &());
let overshoot = point - cursor.start().buffer.lines;
DisplayPoint(cmp::min(
cursor.start().display.lines + overshoot,
@@ -226,14 +266,17 @@ impl FoldMap {
let mut new_transforms = SumTree::new();
let mut transforms = self.transforms.lock();
let mut cursor = transforms.cursor::<usize, usize>();
cursor.seek(&0, SeekBias::Right);
cursor.seek(&0, SeekBias::Right, &());
while let Some(mut edit) = edits.next() {
new_transforms.push_tree(cursor.slice(&edit.old_range.start, SeekBias::Left));
new_transforms.push_tree(
cursor.slice(&edit.old_range.start, SeekBias::Left, &()),
&(),
);
edit.new_range.start -= edit.old_range.start - cursor.start();
edit.old_range.start = *cursor.start();
cursor.seek(&edit.old_range.end, SeekBias::Right);
cursor.seek(&edit.old_range.end, SeekBias::Right, &());
cursor.next();
let mut delta = edit.delta();
@@ -250,7 +293,7 @@ impl FoldMap {
if next_edit.old_range.end >= edit.old_range.end {
edit.old_range.end = next_edit.old_range.end;
cursor.seek(&edit.old_range.end, SeekBias::Right);
cursor.seek(&edit.old_range.end, SeekBias::Right, &());
cursor.next();
}
} else {
@@ -262,14 +305,10 @@ impl FoldMap {
((edit.new_range.start + edit.old_extent()) as isize + delta) as usize;
let anchor = buffer.anchor_before(edit.new_range.start).unwrap();
let folds_start =
find_insertion_index(&self.folds, |probe| probe.start.cmp(&anchor, buffer))
.unwrap();
let mut folds = self.folds[folds_start..]
.iter()
.map(|fold| {
fold.start.to_offset(buffer).unwrap()..fold.end.to_offset(buffer).unwrap()
})
let mut folds_cursor = self.folds.cursor::<_, ()>();
folds_cursor.seek(&Fold(anchor..Anchor::End), SeekBias::Left, buffer);
let mut folds = folds_cursor
.map(|f| f.0.start.to_offset(buffer).unwrap()..f.0.end.to_offset(buffer).unwrap())
.peekable();
while folds
@@ -293,29 +332,35 @@ impl FoldMap {
if fold.start > sum.buffer.chars {
let text_summary = buffer.text_summary_for_range(sum.buffer.chars..fold.start);
new_transforms.push(Transform {
summary: TransformSummary {
display: text_summary.clone(),
buffer: text_summary,
new_transforms.push(
Transform {
summary: TransformSummary {
display: text_summary.clone(),
buffer: text_summary,
},
display_text: None,
},
display_text: None,
});
&(),
);
}
if fold.end > fold.start {
new_transforms.push(Transform {
summary: TransformSummary {
display: TextSummary {
chars: 1,
bytes: '…'.len_utf8(),
lines: Point::new(0, 1),
first_line_len: 1,
rightmost_point: Point::new(0, 1),
new_transforms.push(
Transform {
summary: TransformSummary {
display: TextSummary {
chars: 1,
bytes: '…'.len_utf8(),
lines: Point::new(0, 1),
first_line_len: 1,
rightmost_point: Point::new(0, 1),
},
buffer: buffer.text_summary_for_range(fold.start..fold.end),
},
buffer: buffer.text_summary_for_range(fold.start..fold.end),
display_text: Some('…'),
},
display_text: Some('…'),
});
&(),
);
}
}
@@ -323,26 +368,32 @@ impl FoldMap {
if sum.buffer.chars < edit.new_range.end {
let text_summary =
buffer.text_summary_for_range(sum.buffer.chars..edit.new_range.end);
new_transforms.push(Transform {
new_transforms.push(
Transform {
summary: TransformSummary {
display: text_summary.clone(),
buffer: text_summary,
},
display_text: None,
},
&(),
);
}
}
new_transforms.push_tree(cursor.suffix(&()), &());
if new_transforms.is_empty() {
let text_summary = buffer.text_summary();
new_transforms.push(
Transform {
summary: TransformSummary {
display: text_summary.clone(),
buffer: text_summary,
},
display_text: None,
});
}
}
new_transforms.push_tree(cursor.suffix());
if new_transforms.is_empty() {
let text_summary = buffer.text_summary();
new_transforms.push(Transform {
summary: TransformSummary {
display: text_summary.clone(),
buffer: text_summary,
},
display_text: None,
});
&(),
);
}
drop(cursor);
@@ -363,7 +414,7 @@ impl FoldMapSnapshot {
let display_point = Point::new(start_row, 0);
let mut cursor = self.transforms.cursor();
cursor.seek(&DisplayPoint(display_point), SeekBias::Left);
cursor.seek(&DisplayPoint(display_point), SeekBias::Left, &());
Ok(BufferRows {
display_point,
@@ -374,7 +425,7 @@ impl FoldMapSnapshot {
pub fn chars_at<'a>(&'a self, point: DisplayPoint, ctx: &'a AppContext) -> Result<Chars<'a>> {
let offset = self.to_display_offset(point, ctx)?;
let mut cursor = self.transforms.cursor();
cursor.seek(&offset, SeekBias::Right);
cursor.seek(&offset, SeekBias::Right, &());
Ok(Chars {
cursor,
offset: offset.0,
@@ -385,7 +436,7 @@ impl FoldMapSnapshot {
fn to_display_offset(&self, point: DisplayPoint, ctx: &AppContext) -> Result<DisplayOffset> {
let mut cursor = self.transforms.cursor::<DisplayPoint, TransformSummary>();
cursor.seek(&point, SeekBias::Right);
cursor.seek(&point, SeekBias::Right, &());
let overshoot = point.0 - cursor.start().display.lines;
let mut offset = cursor.start().display.chars;
if !overshoot.is_zero() {
@@ -421,16 +472,106 @@ impl sum_tree::Item for Transform {
}
}
impl<'a> std::ops::AddAssign<&'a Self> for TransformSummary {
fn add_assign(&mut self, other: &'a Self) {
impl sum_tree::Summary for TransformSummary {
type Context = ();
fn add_summary(&mut self, other: &Self, _: &()) {
self.buffer += &other.buffer;
self.display += &other.display;
}
}
impl<'a> Dimension<'a, TransformSummary> for TransformSummary {
impl<'a> sum_tree::Dimension<'a, TransformSummary> for TransformSummary {
fn add_summary(&mut self, summary: &'a TransformSummary) {
*self += summary;
sum_tree::Summary::add_summary(self, summary, &());
}
}
#[derive(Clone, Debug)]
struct Fold(Range<Anchor>);
impl Default for Fold {
fn default() -> Self {
Self(Anchor::Start..Anchor::End)
}
}
impl sum_tree::Item for Fold {
type Summary = FoldSummary;
fn summary(&self) -> Self::Summary {
FoldSummary {
start: self.0.start.clone(),
end: self.0.end.clone(),
min_start: self.0.start.clone(),
max_end: self.0.end.clone(),
count: 1,
}
}
}
#[derive(Clone, Debug)]
struct FoldSummary {
start: Anchor,
end: Anchor,
min_start: Anchor,
max_end: Anchor,
count: usize,
}
impl Default for FoldSummary {
fn default() -> Self {
Self {
start: Anchor::Start,
end: Anchor::End,
min_start: Anchor::End,
max_end: Anchor::Start,
count: 0,
}
}
}
impl sum_tree::Summary for FoldSummary {
type Context = Buffer;
fn add_summary(&mut self, other: &Self, buffer: &Buffer) {
if other.min_start.cmp(&self.min_start, buffer).unwrap() == Ordering::Less {
self.min_start = other.min_start.clone();
}
if other.max_end.cmp(&self.max_end, buffer).unwrap() == Ordering::Greater {
self.max_end = other.max_end.clone();
}
#[cfg(debug_assertions)]
{
let start_comparison = self.start.cmp(&other.start, buffer).unwrap();
assert!(start_comparison <= Ordering::Equal);
if start_comparison == Ordering::Equal {
assert!(self.end.cmp(&other.end, buffer).unwrap() >= Ordering::Equal);
}
}
self.start = other.start.clone();
self.end = other.end.clone();
self.count += other.count;
}
}
impl<'a> sum_tree::Dimension<'a, FoldSummary> for Fold {
fn add_summary(&mut self, summary: &'a FoldSummary) {
self.0.start = summary.start.clone();
self.0.end = summary.end.clone();
}
}
impl<'a> sum_tree::SeekDimension<'a, FoldSummary> for Fold {
fn cmp(&self, other: &Self, buffer: &Buffer) -> Ordering {
self.0.cmp(&other.0, buffer).unwrap()
}
}
impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
fn add_summary(&mut self, summary: &'a FoldSummary) {
*self += summary.count;
}
}
@@ -498,7 +639,7 @@ impl<'a> Iterator for Chars<'a> {
}
}
impl<'a> Dimension<'a, TransformSummary> for DisplayPoint {
impl<'a> sum_tree::Dimension<'a, TransformSummary> for DisplayPoint {
fn add_summary(&mut self, summary: &'a TransformSummary) {
self.0 += &summary.display.lines;
}
@@ -507,19 +648,19 @@ impl<'a> Dimension<'a, TransformSummary> for DisplayPoint {
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct DisplayOffset(usize);
impl<'a> Dimension<'a, TransformSummary> for DisplayOffset {
impl<'a> sum_tree::Dimension<'a, TransformSummary> for DisplayOffset {
fn add_summary(&mut self, summary: &'a TransformSummary) {
self.0 += &summary.display.chars;
}
}
impl<'a> Dimension<'a, TransformSummary> for Point {
impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
fn add_summary(&mut self, summary: &'a TransformSummary) {
*self += &summary.buffer.lines;
}
}
impl<'a> Dimension<'a, TransformSummary> for usize {
impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
fn add_summary(&mut self, summary: &'a TransformSummary) {
*self += &summary.buffer.chars;
}
@@ -535,7 +676,7 @@ mod tests {
#[test]
fn test_basic_folds() {
App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
map.fold(
@@ -571,7 +712,7 @@ mod tests {
});
assert_eq!(map.text(app.as_ref()), "123a…c123456eee");
map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app.as_ref())
map.unfold(Some(Point::new(0, 4)..Point::new(0, 5)), app.as_ref())
.unwrap();
assert_eq!(map.text(app.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee");
});
@@ -580,7 +721,7 @@ mod tests {
#[test]
fn test_adjacent_folds() {
App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, "abcdefghijkl"));
let buffer = app.add_model(|ctx| Buffer::new(0, "abcdefghijkl", ctx));
{
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
@@ -623,7 +764,7 @@ mod tests {
#[test]
fn test_overlapping_folds() {
App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
map.fold(
vec![
@@ -642,7 +783,7 @@ mod tests {
#[test]
fn test_merging_folds_via_edit() {
App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
map.fold(
@@ -667,7 +808,7 @@ mod tests {
#[test]
fn test_folds_in_range() {
App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
let buffer = buffer.read(app);
@@ -719,39 +860,51 @@ mod tests {
};
for seed in seed_range {
println!("{:?}", seed);
dbg!(seed);
let mut rng = StdRng::seed_from_u64(seed);
App::test((), |app| {
let buffer = app.add_model(|_| {
let buffer = app.add_model(|ctx| {
let len = rng.gen_range(0..10);
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
Buffer::new(0, text)
Buffer::new(0, text, ctx)
});
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
for _ in 0..operations {
log::info!("text: {:?}", buffer.read(app).text());
if rng.gen() {
let buffer = buffer.read(app);
let fold_count = rng.gen_range(1..=5);
let mut fold_ranges: Vec<Range<usize>> = Vec::new();
for _ in 0..fold_count {
let end = rng.gen_range(0..buffer.len() + 1);
let start = rng.gen_range(0..end + 1);
fold_ranges.push(start..end);
match rng.gen_range(0..=100) {
0..=34 => {
let buffer = buffer.read(app);
let mut to_fold = Vec::new();
for _ in 0..rng.gen_range(1..=5) {
let end = rng.gen_range(0..=buffer.len());
let start = rng.gen_range(0..=end);
to_fold.push(start..end);
}
log::info!("folding {:?}", to_fold);
map.fold(to_fold, app.as_ref()).unwrap();
}
35..=59 if !map.folds.is_empty() => {
let buffer = buffer.read(app);
let mut to_unfold = Vec::new();
for _ in 0..rng.gen_range(1..=3) {
let end = rng.gen_range(0..=buffer.len());
let start = rng.gen_range(0..=end);
to_unfold.push(start..end);
}
log::info!("unfolding {:?}", to_unfold);
map.unfold(to_unfold, app.as_ref()).unwrap();
}
_ => {
let edits = buffer.update(app, |buffer, ctx| {
let start_version = buffer.version.clone();
let edit_count = rng.gen_range(1..=5);
buffer.randomly_edit(&mut rng, edit_count, Some(ctx));
buffer.edits_since(start_version).collect::<Vec<_>>()
});
log::info!("editing {:?}", edits);
}
log::info!("folding {:?}", fold_ranges);
map.fold(fold_ranges.clone(), app.as_ref()).unwrap();
} else {
let edits = buffer.update(app, |buffer, ctx| {
let start_version = buffer.version.clone();
let edit_count = rng.gen_range(1..=5);
buffer.randomly_edit(&mut rng, edit_count, Some(ctx));
buffer.edits_since(start_version).collect::<Vec<_>>()
});
log::info!("editing {:?}", edits);
}
map.check_invariants(app.as_ref());
@@ -844,6 +997,31 @@ mod tests {
);
assert!(map.is_line_folded(display_point.row(), app.as_ref()));
}
for _ in 0..5 {
let end = rng.gen_range(0..=buffer.len());
let start = rng.gen_range(0..=end);
let expected_folds = map
.folds
.items()
.into_iter()
.filter(|fold| {
let start = buffer.anchor_before(start).unwrap();
let end = buffer.anchor_after(end).unwrap();
start.cmp(&fold.0.end, buffer).unwrap() == Ordering::Less
&& end.cmp(&fold.0.start, buffer).unwrap() == Ordering::Greater
})
.map(|fold| fold.0)
.collect::<Vec<_>>();
assert_eq!(
map.folds_in_range(start..end, app.as_ref())
.unwrap()
.cloned()
.collect::<Vec<_>>(),
expected_folds
);
}
}
});
}
@@ -853,7 +1031,7 @@ mod tests {
fn test_buffer_rows() {
App::test((), |app| {
let text = sample_text(6, 6) + "\n";
let buffer = app.add_model(|_| Buffer::new(0, text));
let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
@@ -894,13 +1072,13 @@ mod tests {
fn merged_fold_ranges(&self, app: &AppContext) -> Vec<Range<usize>> {
let buffer = self.buffer.read(app);
let mut folds = self.folds.clone();
let mut folds = self.folds.items();
// Ensure sorting doesn't change how folds get merged and displayed.
folds.sort_by(|a, b| a.cmp(b, buffer).unwrap());
folds.sort_by(|a, b| a.0.cmp(&b.0, buffer).unwrap());
let mut fold_ranges = folds
.iter()
.map(|fold| {
fold.start.to_offset(buffer).unwrap()..fold.end.to_offset(buffer).unwrap()
fold.0.start.to_offset(buffer).unwrap()..fold.0.end.to_offset(buffer).unwrap()
})
.peekable();
+3 -3
View File
@@ -1,6 +1,6 @@
mod fold_map;
use super::{buffer, Anchor, AnchorRangeExt, Buffer, Edit, Point, TextSummary, ToOffset, ToPoint};
use super::{buffer, Anchor, Buffer, Edit, Point, TextSummary, ToOffset, ToPoint};
use anyhow::Result;
pub use fold_map::BufferRows;
use fold_map::{FoldMap, FoldMapSnapshot};
@@ -345,7 +345,7 @@ mod tests {
fn test_chars_at() {
App::test((), |app| {
let text = sample_text(6, 6);
let buffer = app.add_model(|_| Buffer::new(0, text));
let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
buffer
.update(app, |buffer, ctx| {
@@ -414,7 +414,7 @@ mod tests {
#[test]
fn test_max_point() {
App::test((), |app| {
let buffer = app.add_model(|_| Buffer::new(0, "aaa\n\t\tbbb"));
let buffer = app.add_model(|ctx| Buffer::new(0, "aaa\n\t\tbbb", ctx));
let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
assert_eq!(map.max_point(app.as_ref()), DisplayPoint::new(1, 11))
});