WIP: Start on removing selections from buffer in favor of editor
This commit is contained in:
parent
f35c419f43
commit
8432daef6a
10 changed files with 342 additions and 951 deletions
|
@ -23,11 +23,11 @@ use gpui::{
|
||||||
use items::BufferItemHandle;
|
use items::BufferItemHandle;
|
||||||
use language::{
|
use language::{
|
||||||
BracketPair, Buffer, Diagnostic, DiagnosticSeverity, Language, Point, Selection, SelectionGoal,
|
BracketPair, Buffer, Diagnostic, DiagnosticSeverity, Language, Point, Selection, SelectionGoal,
|
||||||
SelectionSetId,
|
SelectionSetId, TransactionId,
|
||||||
};
|
};
|
||||||
pub use multi_buffer::MultiBuffer;
|
pub use multi_buffer::MultiBuffer;
|
||||||
use multi_buffer::{
|
use multi_buffer::{
|
||||||
Anchor, AnchorRangeExt, MultiBufferChunks, MultiBufferSnapshot, SelectionSet, ToOffset, ToPoint,
|
Anchor, AnchorRangeExt, MultiBufferChunks, MultiBufferSnapshot, ToOffset, ToPoint,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -36,7 +36,8 @@ use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
cmp,
|
cmp,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
iter, mem,
|
iter::{self, FromIterator},
|
||||||
|
mem,
|
||||||
ops::{Deref, Range, RangeInclusive, Sub},
|
ops::{Deref, Range, RangeInclusive, Sub},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -359,12 +360,14 @@ pub struct Editor {
|
||||||
handle: WeakViewHandle<Self>,
|
handle: WeakViewHandle<Self>,
|
||||||
buffer: ModelHandle<MultiBuffer>,
|
buffer: ModelHandle<MultiBuffer>,
|
||||||
display_map: ModelHandle<DisplayMap>,
|
display_map: ModelHandle<DisplayMap>,
|
||||||
selection_set_id: SelectionSetId,
|
next_selection_id: usize,
|
||||||
|
selections: Arc<[Selection<Anchor>]>,
|
||||||
pending_selection: Option<PendingSelection>,
|
pending_selection: Option<PendingSelection>,
|
||||||
columnar_selection_tail: Option<Anchor>,
|
columnar_selection_tail: Option<Anchor>,
|
||||||
next_selection_id: usize,
|
|
||||||
add_selections_state: Option<AddSelectionsState>,
|
add_selections_state: Option<AddSelectionsState>,
|
||||||
select_next_state: Option<SelectNextState>,
|
select_next_state: Option<SelectNextState>,
|
||||||
|
selection_history:
|
||||||
|
HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
|
||||||
autoclose_stack: Vec<BracketPairState>,
|
autoclose_stack: Vec<BracketPairState>,
|
||||||
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
|
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
|
||||||
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
||||||
|
@ -487,28 +490,27 @@ impl Editor {
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let mut next_selection_id = 0;
|
let mut next_selection_id = 0;
|
||||||
let selection_set_id = buffer.update(cx, |buffer, cx| {
|
let selections = Arc::from(
|
||||||
buffer.add_selection_set(
|
|
||||||
&[Selection {
|
&[Selection {
|
||||||
id: post_inc(&mut next_selection_id),
|
id: post_inc(&mut next_selection_id),
|
||||||
start: 0,
|
start: Anchor::min(),
|
||||||
end: 0,
|
end: Anchor::min(),
|
||||||
reversed: false,
|
reversed: false,
|
||||||
goal: SelectionGoal::None,
|
goal: SelectionGoal::None,
|
||||||
}],
|
}][..],
|
||||||
cx,
|
);
|
||||||
)
|
|
||||||
});
|
|
||||||
Self {
|
Self {
|
||||||
handle: cx.weak_handle(),
|
handle: cx.weak_handle(),
|
||||||
buffer,
|
buffer,
|
||||||
display_map,
|
display_map,
|
||||||
selection_set_id,
|
selections,
|
||||||
pending_selection: None,
|
pending_selection: None,
|
||||||
columnar_selection_tail: None,
|
columnar_selection_tail: None,
|
||||||
next_selection_id,
|
next_selection_id,
|
||||||
add_selections_state: None,
|
add_selections_state: None,
|
||||||
select_next_state: None,
|
select_next_state: None,
|
||||||
|
selection_history: Default::default(),
|
||||||
autoclose_stack: Default::default(),
|
autoclose_stack: Default::default(),
|
||||||
select_larger_syntax_node_stack: Vec::new(),
|
select_larger_syntax_node_stack: Vec::new(),
|
||||||
active_diagnostics: None,
|
active_diagnostics: None,
|
||||||
|
@ -636,7 +638,7 @@ impl Editor {
|
||||||
let first_cursor_top;
|
let first_cursor_top;
|
||||||
let last_cursor_bottom;
|
let last_cursor_bottom;
|
||||||
if autoscroll == Autoscroll::Newest {
|
if autoscroll == Autoscroll::Newest {
|
||||||
let newest_selection = self.newest_selection::<Point>(cx);
|
let newest_selection = self.newest_selection::<Point>(&display_map.buffer_snapshot, cx);
|
||||||
first_cursor_top = newest_selection.head().to_display_point(&display_map).row() as f32;
|
first_cursor_top = newest_selection.head().to_display_point(&display_map).row() as f32;
|
||||||
last_cursor_bottom = first_cursor_top + 1.;
|
last_cursor_bottom = first_cursor_top + 1.;
|
||||||
} else {
|
} else {
|
||||||
|
@ -769,7 +771,9 @@ impl Editor {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
let tail = self.newest_selection::<usize>(cx).tail();
|
let tail = self
|
||||||
|
.newest_selection::<usize>(&display_map.buffer_snapshot, cx)
|
||||||
|
.tail();
|
||||||
self.begin_selection(position, false, click_count, cx);
|
self.begin_selection(position, false, click_count, cx);
|
||||||
|
|
||||||
let position = position.to_offset(&display_map, Bias::Left);
|
let position = position.to_offset(&display_map, Bias::Left);
|
||||||
|
@ -851,7 +855,7 @@ impl Editor {
|
||||||
self.update_selections::<usize>(Vec::new(), None, cx);
|
self.update_selections::<usize>(Vec::new(), None, cx);
|
||||||
} else if click_count > 1 {
|
} else if click_count > 1 {
|
||||||
// Remove the newest selection since it was only added as part of this multi-click.
|
// Remove the newest selection since it was only added as part of this multi-click.
|
||||||
let newest_selection = self.newest_selection::<usize>(cx);
|
let newest_selection = self.newest_selection::<usize>(buffer, cx);
|
||||||
let mut selections = self.selections(cx);
|
let mut selections = self.selections(cx);
|
||||||
selections.retain(|selection| selection.id != newest_selection.id);
|
selections.retain(|selection| selection.id != newest_selection.id);
|
||||||
self.update_selections::<usize>(selections, None, cx)
|
self.update_selections::<usize>(selections, None, cx)
|
||||||
|
@ -874,7 +878,9 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
let tail = self.newest_selection::<Point>(cx).tail();
|
let tail = self
|
||||||
|
.newest_selection::<Point>(&display_map.buffer_snapshot, cx)
|
||||||
|
.tail();
|
||||||
self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
|
self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
|
||||||
|
|
||||||
self.select_columns(
|
self.select_columns(
|
||||||
|
@ -2812,7 +2818,7 @@ impl Editor {
|
||||||
|
|
||||||
pub fn show_next_diagnostic(&mut self, _: &ShowNextDiagnostic, cx: &mut ViewContext<Self>) {
|
pub fn show_next_diagnostic(&mut self, _: &ShowNextDiagnostic, cx: &mut ViewContext<Self>) {
|
||||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||||
let selection = self.newest_selection::<usize>(cx);
|
let selection = self.newest_selection::<usize>(&buffer, cx);
|
||||||
let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
|
let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
|
||||||
active_diagnostics
|
active_diagnostics
|
||||||
.primary_range
|
.primary_range
|
||||||
|
@ -2992,120 +2998,119 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_selection_sets<'a>(
|
|
||||||
&'a self,
|
|
||||||
cx: &'a AppContext,
|
|
||||||
) -> impl 'a + Iterator<Item = SelectionSetId> {
|
|
||||||
let buffer = self.buffer.read(cx);
|
|
||||||
let replica_id = buffer.replica_id();
|
|
||||||
buffer
|
|
||||||
.selection_sets(cx)
|
|
||||||
.filter(move |(set_id, set)| {
|
|
||||||
set.active && (set_id.replica_id != replica_id || **set_id == self.selection_set_id)
|
|
||||||
})
|
|
||||||
.map(|(set_id, _)| *set_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn intersecting_selections<'a>(
|
pub fn intersecting_selections<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
set_id: SelectionSetId,
|
set_id: SelectionSetId,
|
||||||
range: Range<DisplayPoint>,
|
range: Range<DisplayPoint>,
|
||||||
cx: &'a mut MutableAppContext,
|
cx: &'a mut MutableAppContext,
|
||||||
) -> Vec<Selection<DisplayPoint>> {
|
) -> Vec<Selection<DisplayPoint>> {
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
todo!()
|
||||||
let buffer = self.buffer.read(cx);
|
// let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
|
// let buffer = self.buffer.read(cx);
|
||||||
|
|
||||||
let pending_selection = if set_id == self.selection_set_id {
|
// let pending_selection = if set_id == self.selection_set_id {
|
||||||
self.pending_selection.as_ref().and_then(|pending| {
|
// self.pending_selection.as_ref().and_then(|pending| {
|
||||||
let selection_start = pending.selection.start.to_display_point(&display_map);
|
// let selection_start = pending.selection.start.to_display_point(&display_map);
|
||||||
let selection_end = pending.selection.end.to_display_point(&display_map);
|
// let selection_end = pending.selection.end.to_display_point(&display_map);
|
||||||
if selection_start <= range.end || selection_end <= range.end {
|
// if selection_start <= range.end || selection_end <= range.end {
|
||||||
Some(Selection {
|
// Some(Selection {
|
||||||
id: pending.selection.id,
|
// id: pending.selection.id,
|
||||||
start: selection_start,
|
// start: selection_start,
|
||||||
end: selection_end,
|
// end: selection_end,
|
||||||
reversed: pending.selection.reversed,
|
// reversed: pending.selection.reversed,
|
||||||
goal: pending.selection.goal,
|
// goal: pending.selection.goal,
|
||||||
})
|
// })
|
||||||
} else {
|
// } else {
|
||||||
None
|
// None
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
} else {
|
// } else {
|
||||||
None
|
// None
|
||||||
};
|
// };
|
||||||
|
|
||||||
let range = (range.start.to_offset(&display_map, Bias::Left), Bias::Left)
|
// let range = (range.start.to_offset(&display_map, Bias::Left), Bias::Left)
|
||||||
..(range.end.to_offset(&display_map, Bias::Left), Bias::Right);
|
// ..(range.end.to_offset(&display_map, Bias::Left), Bias::Right);
|
||||||
buffer
|
// buffer
|
||||||
.selection_set(set_id, cx)
|
// .selection_set(set_id, cx)
|
||||||
.unwrap()
|
// .unwrap()
|
||||||
.intersecting_selections::<Point, _>(range, &buffer.read(cx))
|
// .intersecting_selections::<Point, _>(range, &buffer.read(cx))
|
||||||
.map(move |s| Selection {
|
// .map(move |s| Selection {
|
||||||
id: s.id,
|
// id: s.id,
|
||||||
start: s.start.to_display_point(&display_map),
|
// start: s.start.to_display_point(&display_map),
|
||||||
end: s.end.to_display_point(&display_map),
|
// end: s.end.to_display_point(&display_map),
|
||||||
reversed: s.reversed,
|
// reversed: s.reversed,
|
||||||
goal: s.goal,
|
// goal: s.goal,
|
||||||
})
|
// })
|
||||||
.chain(pending_selection)
|
// .chain(pending_selection)
|
||||||
.collect()
|
// .collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selections<'a, D>(&self, cx: &'a AppContext) -> Vec<Selection<D>>
|
pub fn selections<'a, D>(&self, cx: &'a AppContext) -> Vec<Selection<D>>
|
||||||
where
|
where
|
||||||
D: 'a + TextDimension + Ord + Sub<D, Output = D>,
|
D: 'a + TextDimension + Ord + Sub<D, Output = D>,
|
||||||
{
|
{
|
||||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
// let buffer = self.buffer.read(cx).snapshot(cx);
|
||||||
let mut selections = self.selection_set(cx).selections::<D>(&buffer).peekable();
|
// let mut selections = self.selection_set(cx).selections::<D>(&buffer).peekable();
|
||||||
let mut pending_selection = self.pending_selection(cx);
|
// let mut pending_selection = self.pending_selection(cx);
|
||||||
|
|
||||||
iter::from_fn(move || {
|
// iter::from_fn(move || {
|
||||||
if let Some(pending) = pending_selection.as_mut() {
|
// if let Some(pending) = pending_selection.as_mut() {
|
||||||
while let Some(next_selection) = selections.peek() {
|
// while let Some(next_selection) = selections.peek() {
|
||||||
if pending.start <= next_selection.end && pending.end >= next_selection.start {
|
// if pending.start <= next_selection.end && pending.end >= next_selection.start {
|
||||||
let next_selection = selections.next().unwrap();
|
// let next_selection = selections.next().unwrap();
|
||||||
if next_selection.start < pending.start {
|
// if next_selection.start < pending.start {
|
||||||
pending.start = next_selection.start;
|
// pending.start = next_selection.start;
|
||||||
}
|
// }
|
||||||
if next_selection.end > pending.end {
|
// if next_selection.end > pending.end {
|
||||||
pending.end = next_selection.end;
|
// pending.end = next_selection.end;
|
||||||
}
|
// }
|
||||||
} else if next_selection.end < pending.start {
|
// } else if next_selection.end < pending.start {
|
||||||
return selections.next();
|
// return selections.next();
|
||||||
} else {
|
// } else {
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
pending_selection.take()
|
// pending_selection.take()
|
||||||
} else {
|
// } else {
|
||||||
selections.next()
|
// selections.next()
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
.collect()
|
// .collect()
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pending_selection<D: TextDimension + Ord + Sub<D, Output = D>>(
|
fn pending_selection<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||||
&self,
|
&self,
|
||||||
|
snapshot: &MultiBufferSnapshot,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Option<Selection<D>> {
|
) -> Option<Selection<D>> {
|
||||||
let buffer = self.buffer.read(cx).read(cx);
|
self.pending_selection
|
||||||
self.pending_selection.as_ref().map(|pending| Selection {
|
.as_ref()
|
||||||
id: pending.selection.id,
|
.map(|pending| self.resolve_selection(&pending.selection, &snapshot, cx))
|
||||||
start: pending.selection.start.summary::<D>(&buffer),
|
}
|
||||||
end: pending.selection.end.summary::<D>(&buffer),
|
|
||||||
reversed: pending.selection.reversed,
|
fn resolve_selection<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||||
goal: pending.selection.goal,
|
&self,
|
||||||
})
|
selection: &Selection<Anchor>,
|
||||||
|
buffer: &MultiBufferSnapshot,
|
||||||
|
cx: &AppContext,
|
||||||
|
) -> Selection<D> {
|
||||||
|
Selection {
|
||||||
|
id: selection.id,
|
||||||
|
start: selection.start.summary::<D>(&buffer),
|
||||||
|
end: selection.end.summary::<D>(&buffer),
|
||||||
|
reversed: selection.reversed,
|
||||||
|
goal: selection.goal,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selection_count<'a>(&self, cx: &'a AppContext) -> usize {
|
fn selection_count<'a>(&self, cx: &'a AppContext) -> usize {
|
||||||
let mut selection_count = self.selection_set(cx).len();
|
let mut count = self.selections.len();
|
||||||
if self.pending_selection.is_some() {
|
if self.pending_selection.is_some() {
|
||||||
selection_count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
selection_count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn oldest_selection<D: TextDimension + Ord + Sub<D, Output = D>>(
|
pub fn oldest_selection<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||||
|
@ -3113,31 +3118,29 @@ impl Editor {
|
||||||
snapshot: &MultiBufferSnapshot,
|
snapshot: &MultiBufferSnapshot,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Selection<D> {
|
) -> Selection<D> {
|
||||||
self.selection_set(cx)
|
self.selections
|
||||||
.oldest_selection(snapshot)
|
.iter()
|
||||||
.or_else(|| self.pending_selection(cx))
|
.min_by_key(|s| s.id)
|
||||||
|
.map(|selection| self.resolve_selection(selection, snapshot, cx))
|
||||||
|
.or_else(|| self.pending_selection(snapshot, cx))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newest_selection<D: TextDimension + Ord + Sub<D, Output = D>>(
|
pub fn newest_selection<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||||
&self,
|
&self,
|
||||||
|
snapshot: &MultiBufferSnapshot,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Selection<D> {
|
) -> Selection<D> {
|
||||||
self.pending_selection(cx)
|
self.pending_selection(snapshot, cx)
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
self.selection_set(cx)
|
self.selections
|
||||||
.newest_selection(&self.buffer.read(cx).read(cx))
|
.iter()
|
||||||
|
.min_by_key(|s| s.id)
|
||||||
|
.map(|selection| self.resolve_selection(selection, snapshot, cx))
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selection_set<'a>(&self, cx: &'a AppContext) -> &'a SelectionSet {
|
|
||||||
self.buffer
|
|
||||||
.read(cx)
|
|
||||||
.selection_set(self.selection_set_id, cx)
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_selections<T>(
|
pub fn update_selections<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut selections: Vec<Selection<T>>,
|
mut selections: Vec<Selection<T>>,
|
||||||
|
@ -3193,11 +3196,13 @@ impl Editor {
|
||||||
}
|
}
|
||||||
self.pause_cursor_blinking(cx);
|
self.pause_cursor_blinking(cx);
|
||||||
|
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
self.selections = Arc::from_iter(selections.into_iter().map(|selection| Selection {
|
||||||
buffer
|
id: selection.id,
|
||||||
.update_selection_set(self.selection_set_id, &selections, cx)
|
start: buffer.anchor_before(selection.start),
|
||||||
.unwrap();
|
end: buffer.anchor_before(selection.end),
|
||||||
});
|
reversed: selection.reversed,
|
||||||
|
goal: selection.goal,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_autoscroll(&mut self, autoscroll: Autoscroll, cx: &mut ViewContext<Self>) {
|
fn request_autoscroll(&mut self, autoscroll: Autoscroll, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -3208,13 +3213,13 @@ impl Editor {
|
||||||
fn start_transaction(&mut self, cx: &mut ViewContext<Self>) {
|
fn start_transaction(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.end_selection(cx);
|
self.end_selection(cx);
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
buffer.start_transaction([self.selection_set_id], cx);
|
buffer.start_transaction(cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_transaction(&self, cx: &mut ViewContext<Self>) {
|
fn end_transaction(&self, cx: &mut ViewContext<Self>) {
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
buffer.end_transaction([self.selection_set_id], cx);
|
buffer.end_transaction(cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3549,14 +3554,6 @@ pub enum Event {
|
||||||
|
|
||||||
impl Entity for Editor {
|
impl Entity for Editor {
|
||||||
type Event = Event;
|
type Event = Event;
|
||||||
|
|
||||||
fn release(&mut self, cx: &mut MutableAppContext) {
|
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
|
||||||
buffer
|
|
||||||
.remove_selection_set(self.selection_set_id, cx)
|
|
||||||
.unwrap();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for Editor {
|
impl View for Editor {
|
||||||
|
@ -3579,19 +3576,11 @@ impl View for Editor {
|
||||||
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
|
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.focused = true;
|
self.focused = true;
|
||||||
self.blink_cursors(self.blink_epoch, cx);
|
self.blink_cursors(self.blink_epoch, cx);
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
|
||||||
buffer
|
|
||||||
.set_active_selection_set(Some(self.selection_set_id), cx)
|
|
||||||
.unwrap();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_blur(&mut self, cx: &mut ViewContext<Self>) {
|
fn on_blur(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.focused = false;
|
self.focused = false;
|
||||||
self.show_local_cursors = false;
|
self.show_local_cursors = false;
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
|
||||||
buffer.set_active_selection_set(None, cx).unwrap();
|
|
||||||
});
|
|
||||||
cx.emit(Event::Blurred);
|
cx.emit(Event::Blurred);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -3710,6 +3699,8 @@ pub fn diagnostic_style(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use language::LanguageConfig;
|
use language::LanguageConfig;
|
||||||
use text::Point;
|
use text::Point;
|
||||||
|
@ -5670,18 +5661,16 @@ mod tests {
|
||||||
|
|
||||||
impl Editor {
|
impl Editor {
|
||||||
fn selection_ranges(&self, cx: &mut MutableAppContext) -> Vec<Range<DisplayPoint>> {
|
fn selection_ranges(&self, cx: &mut MutableAppContext) -> Vec<Range<DisplayPoint>> {
|
||||||
self.intersecting_selections(
|
let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
self.selection_set_id,
|
self.selections
|
||||||
DisplayPoint::zero()..self.max_point(cx),
|
.iter()
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.into_iter()
|
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
|
let mut range =
|
||||||
|
s.start.to_display_point(&snapshot)..s.end.to_display_point(&snapshot);
|
||||||
if s.reversed {
|
if s.reversed {
|
||||||
s.end..s.start
|
mem::swap(&mut range.start, &mut range.end);
|
||||||
} else {
|
|
||||||
s.start..s.end
|
|
||||||
}
|
}
|
||||||
|
range
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -733,34 +733,37 @@ impl Element for EditorElement {
|
||||||
let scroll_top = scroll_position.y() * line_height;
|
let scroll_top = scroll_position.y() * line_height;
|
||||||
let end_row = ((scroll_top + size.y()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
|
let end_row = ((scroll_top + size.y()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
|
||||||
|
|
||||||
let mut selections = HashMap::new();
|
let selections = HashMap::new();
|
||||||
let mut active_rows = BTreeMap::new();
|
let active_rows = BTreeMap::new();
|
||||||
let mut highlighted_row = None;
|
let mut highlighted_row = None;
|
||||||
self.update_view(cx.app, |view, cx| {
|
self.update_view(cx.app, |view, cx| {
|
||||||
highlighted_row = view.highlighted_row();
|
highlighted_row = view.highlighted_row();
|
||||||
for selection_set_id in view.active_selection_sets(cx).collect::<Vec<_>>() {
|
|
||||||
let replica_selections = view.intersecting_selections(
|
|
||||||
selection_set_id,
|
|
||||||
DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
for selection in &replica_selections {
|
|
||||||
if selection_set_id == view.selection_set_id {
|
|
||||||
let is_empty = selection.start == selection.end;
|
|
||||||
let selection_start = snapshot.prev_row_boundary(selection.start).0;
|
|
||||||
let selection_end = snapshot.next_row_boundary(selection.end).0;
|
|
||||||
for row in cmp::max(selection_start.row(), start_row)
|
|
||||||
..=cmp::min(selection_end.row(), end_row)
|
|
||||||
{
|
|
||||||
let contains_non_empty_selection =
|
|
||||||
active_rows.entry(row).or_insert(!is_empty);
|
|
||||||
*contains_non_empty_selection |= !is_empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selections.insert(selection_set_id.replica_id, replica_selections);
|
// TODO: Get this working with editors owning their own selections
|
||||||
}
|
|
||||||
|
// for selection_set_id in view.active_selection_sets(cx).collect::<Vec<_>>() {
|
||||||
|
// let replica_selections = view.intersecting_selections(
|
||||||
|
// selection_set_id,
|
||||||
|
// DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0),
|
||||||
|
// cx,
|
||||||
|
// );
|
||||||
|
// for selection in &replica_selections {
|
||||||
|
// if selection_set_id == view.selection_set_id {
|
||||||
|
// let is_empty = selection.start == selection.end;
|
||||||
|
// let selection_start = snapshot.prev_row_boundary(selection.start).0;
|
||||||
|
// let selection_end = snapshot.next_row_boundary(selection.end).0;
|
||||||
|
// for row in cmp::max(selection_start.row(), start_row)
|
||||||
|
// ..=cmp::min(selection_end.row(), end_row)
|
||||||
|
// {
|
||||||
|
// let contains_non_empty_selection =
|
||||||
|
// active_rows.entry(row).or_insert(!is_empty);
|
||||||
|
// *contains_non_empty_selection |= !is_empty;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// selections.insert(selection_set_id.replica_id, replica_selections);
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
|
|
||||||
let line_number_layouts = self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx);
|
let line_number_layouts = self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx);
|
||||||
|
|
|
@ -322,8 +322,10 @@ impl DiagnosticMessage {
|
||||||
|
|
||||||
fn update(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
|
fn update(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
|
||||||
let editor = editor.read(cx);
|
let editor = editor.read(cx);
|
||||||
let cursor_position = editor.newest_selection::<usize>(cx).head();
|
|
||||||
let buffer = editor.buffer().read(cx);
|
let buffer = editor.buffer().read(cx);
|
||||||
|
let cursor_position = editor
|
||||||
|
.newest_selection::<usize>(&buffer.read(cx), cx)
|
||||||
|
.head();
|
||||||
let new_diagnostic = buffer
|
let new_diagnostic = buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.diagnostics_in_range::<_, usize>(cursor_position..cursor_position)
|
.diagnostics_in_range::<_, usize>(cursor_position..cursor_position)
|
||||||
|
|
|
@ -24,7 +24,7 @@ use text::{
|
||||||
locator::Locator,
|
locator::Locator,
|
||||||
rope::TextDimension,
|
rope::TextDimension,
|
||||||
subscription::{Subscription, Topic},
|
subscription::{Subscription, Topic},
|
||||||
AnchorRangeExt as _, Edit, Point, PointUtf16, Selection, SelectionSetId, TextSummary,
|
AnchorRangeExt as _, Edit, Point, PointUtf16, SelectionSetId, TextSummary,
|
||||||
};
|
};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ impl MultiBuffer {
|
||||||
pub fn singleton(buffer: ModelHandle<Buffer>, cx: &mut ModelContext<Self>) -> Self {
|
pub fn singleton(buffer: ModelHandle<Buffer>, cx: &mut ModelContext<Self>) -> Self {
|
||||||
let mut this = Self::new(buffer.read(cx).replica_id());
|
let mut this = Self::new(buffer.read(cx).replica_id());
|
||||||
this.singleton = true;
|
this.singleton = true;
|
||||||
this.push(
|
this.push_excerpt(
|
||||||
ExcerptProperties {
|
ExcerptProperties {
|
||||||
buffer: &buffer,
|
buffer: &buffer,
|
||||||
range: text::Anchor::min()..text::Anchor::max(),
|
range: text::Anchor::min()..text::Anchor::max(),
|
||||||
|
@ -202,26 +202,18 @@ impl MultiBuffer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_transaction(
|
pub fn start_transaction(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
|
||||||
&mut self,
|
|
||||||
selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Option<TransactionId> {
|
|
||||||
// TODO
|
// TODO
|
||||||
self.as_singleton()
|
self.as_singleton()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.update(cx, |buffer, _| buffer.start_transaction(selection_set_ids))
|
.update(cx, |buffer, _| buffer.start_transaction())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end_transaction(
|
pub fn end_transaction(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
|
||||||
&mut self,
|
|
||||||
selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Option<TransactionId> {
|
|
||||||
// TODO
|
// TODO
|
||||||
self.as_singleton().unwrap().update(cx, |buffer, cx| {
|
self.as_singleton()
|
||||||
buffer.end_transaction(selection_set_ids, cx)
|
.unwrap()
|
||||||
})
|
.update(cx, |buffer, cx| buffer.end_transaction(cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn undo(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
|
pub fn undo(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
|
||||||
|
@ -238,153 +230,11 @@ impl MultiBuffer {
|
||||||
.update(cx, |buffer, cx| buffer.redo(cx))
|
.update(cx, |buffer, cx| buffer.redo(cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selection_set(&self, set_id: SelectionSetId, cx: &AppContext) -> Result<&SelectionSet> {
|
pub fn push_excerpt<O>(
|
||||||
// TODO
|
|
||||||
let set = self
|
|
||||||
.as_singleton()
|
|
||||||
.unwrap()
|
|
||||||
.read(cx)
|
|
||||||
.selection_set(set_id)?;
|
|
||||||
let excerpt_id = self.snapshot.borrow().excerpts.first().unwrap().id.clone();
|
|
||||||
|
|
||||||
let selection_sets: &mut HashMap<SelectionSetId, SelectionSet> =
|
|
||||||
unsafe { &mut *(&self.selection_sets as *const _ as *mut _) };
|
|
||||||
selection_sets.insert(
|
|
||||||
set_id,
|
|
||||||
SelectionSet {
|
|
||||||
id: set.id,
|
|
||||||
active: set.active,
|
|
||||||
selections: set
|
|
||||||
.selections
|
|
||||||
.iter()
|
|
||||||
.map(|selection| Selection {
|
|
||||||
id: selection.id,
|
|
||||||
start: Anchor {
|
|
||||||
excerpt_id: excerpt_id.clone(),
|
|
||||||
text_anchor: selection.start.clone(),
|
|
||||||
},
|
|
||||||
end: Anchor {
|
|
||||||
excerpt_id: excerpt_id.clone(),
|
|
||||||
text_anchor: selection.end.clone(),
|
|
||||||
},
|
|
||||||
reversed: selection.reversed,
|
|
||||||
goal: selection.goal,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Ok(self.selection_sets.get(&set.id).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_selection_set<T: ToOffset>(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
selections: &[Selection<T>],
|
props: ExcerptProperties<O>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> SelectionSetId {
|
) -> ExcerptId
|
||||||
// TODO
|
|
||||||
let snapshot = self.read(cx);
|
|
||||||
self.as_singleton().unwrap().update(cx, |buffer, cx| {
|
|
||||||
buffer.add_selection_set(
|
|
||||||
&selections
|
|
||||||
.iter()
|
|
||||||
.map(|selection| Selection {
|
|
||||||
id: selection.id,
|
|
||||||
start: selection.start.to_offset(&snapshot),
|
|
||||||
end: selection.end.to_offset(&snapshot),
|
|
||||||
reversed: selection.reversed,
|
|
||||||
goal: selection.goal,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_selection_set(
|
|
||||||
&mut self,
|
|
||||||
set_id: SelectionSetId,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Result<()> {
|
|
||||||
// TODO
|
|
||||||
self.as_singleton()
|
|
||||||
.unwrap()
|
|
||||||
.update(cx, |buffer, cx| buffer.remove_selection_set(set_id, cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_selection_set<T: ToOffset>(
|
|
||||||
&mut self,
|
|
||||||
set_id: SelectionSetId,
|
|
||||||
selections: &[Selection<T>],
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Result<()> {
|
|
||||||
// TODO
|
|
||||||
let snapshot = self.read(cx);
|
|
||||||
self.as_singleton().unwrap().update(cx, |buffer, cx| {
|
|
||||||
buffer.update_selection_set(
|
|
||||||
set_id,
|
|
||||||
&selections
|
|
||||||
.iter()
|
|
||||||
.map(|selection| Selection {
|
|
||||||
id: selection.id,
|
|
||||||
start: selection.start.to_offset(&snapshot),
|
|
||||||
end: selection.end.to_offset(&snapshot),
|
|
||||||
reversed: selection.reversed,
|
|
||||||
goal: selection.goal,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_active_selection_set(
|
|
||||||
&mut self,
|
|
||||||
set_id: Option<SelectionSetId>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Result<()> {
|
|
||||||
self.as_singleton()
|
|
||||||
.unwrap()
|
|
||||||
.update(cx, |buffer, cx| buffer.set_active_selection_set(set_id, cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn selection_sets(
|
|
||||||
&self,
|
|
||||||
cx: &AppContext,
|
|
||||||
) -> impl Iterator<Item = (&SelectionSetId, &SelectionSet)> {
|
|
||||||
let excerpt_id = self.snapshot.borrow().excerpts.first().unwrap().id.clone();
|
|
||||||
let selection_sets: &mut HashMap<SelectionSetId, SelectionSet> =
|
|
||||||
unsafe { &mut *(&self.selection_sets as *const _ as *mut _) };
|
|
||||||
selection_sets.clear();
|
|
||||||
for (selection_set_id, set) in self.as_singleton().unwrap().read(cx).selection_sets() {
|
|
||||||
selection_sets.insert(
|
|
||||||
*selection_set_id,
|
|
||||||
SelectionSet {
|
|
||||||
id: set.id,
|
|
||||||
active: set.active,
|
|
||||||
selections: set
|
|
||||||
.selections
|
|
||||||
.iter()
|
|
||||||
.map(|selection| Selection {
|
|
||||||
id: selection.id,
|
|
||||||
start: Anchor {
|
|
||||||
excerpt_id: excerpt_id.clone(),
|
|
||||||
text_anchor: selection.start.clone(),
|
|
||||||
},
|
|
||||||
end: Anchor {
|
|
||||||
excerpt_id: excerpt_id.clone(),
|
|
||||||
text_anchor: selection.end.clone(),
|
|
||||||
},
|
|
||||||
reversed: selection.reversed,
|
|
||||||
goal: selection.goal,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.selection_sets.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push<O>(&mut self, props: ExcerptProperties<O>, cx: &mut ModelContext<Self>) -> ExcerptId
|
|
||||||
where
|
where
|
||||||
O: text::ToOffset,
|
O: text::ToOffset,
|
||||||
{
|
{
|
||||||
|
@ -555,13 +405,6 @@ impl MultiBuffer {
|
||||||
.update(cx, |buffer, cx| buffer.randomly_edit(rng, count, cx));
|
.update(cx, |buffer, cx| buffer.randomly_edit(rng, count, cx));
|
||||||
self.sync(cx);
|
self.sync(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn randomly_mutate<R: rand::Rng>(&mut self, rng: &mut R, cx: &mut ModelContext<Self>) {
|
|
||||||
self.as_singleton()
|
|
||||||
.unwrap()
|
|
||||||
.update(cx, |buffer, cx| buffer.randomly_mutate(rng, cx));
|
|
||||||
self.sync(cx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for MultiBuffer {
|
impl Entity for MultiBuffer {
|
||||||
|
@ -1389,7 +1232,7 @@ mod tests {
|
||||||
|
|
||||||
let subscription = multibuffer.update(cx, |multibuffer, cx| {
|
let subscription = multibuffer.update(cx, |multibuffer, cx| {
|
||||||
let subscription = multibuffer.subscribe();
|
let subscription = multibuffer.subscribe();
|
||||||
multibuffer.push(
|
multibuffer.push_excerpt(
|
||||||
ExcerptProperties {
|
ExcerptProperties {
|
||||||
buffer: &buffer_1,
|
buffer: &buffer_1,
|
||||||
range: Point::new(1, 2)..Point::new(2, 5),
|
range: Point::new(1, 2)..Point::new(2, 5),
|
||||||
|
@ -1405,7 +1248,7 @@ mod tests {
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
|
|
||||||
multibuffer.push(
|
multibuffer.push_excerpt(
|
||||||
ExcerptProperties {
|
ExcerptProperties {
|
||||||
buffer: &buffer_1,
|
buffer: &buffer_1,
|
||||||
range: Point::new(3, 3)..Point::new(4, 4),
|
range: Point::new(3, 3)..Point::new(4, 4),
|
||||||
|
@ -1413,7 +1256,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
multibuffer.push(
|
multibuffer.push_excerpt(
|
||||||
ExcerptProperties {
|
ExcerptProperties {
|
||||||
buffer: &buffer_2,
|
buffer: &buffer_2,
|
||||||
range: Point::new(3, 1)..Point::new(3, 3),
|
range: Point::new(3, 1)..Point::new(3, 3),
|
||||||
|
@ -1529,7 +1372,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let excerpt_id = list.update(cx, |list, cx| {
|
let excerpt_id = list.update(cx, |list, cx| {
|
||||||
list.push(
|
list.push_excerpt(
|
||||||
ExcerptProperties {
|
ExcerptProperties {
|
||||||
buffer: &buffer_handle,
|
buffer: &buffer_handle,
|
||||||
range: start_ix..end_ix,
|
range: start_ix..end_ix,
|
||||||
|
|
|
@ -178,7 +178,6 @@ struct SyntaxTree {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AutoindentRequest {
|
struct AutoindentRequest {
|
||||||
selection_set_ids: HashSet<SelectionSetId>,
|
|
||||||
before_edit: BufferSnapshot,
|
before_edit: BufferSnapshot,
|
||||||
edited: Vec<Anchor>,
|
edited: Vec<Anchor>,
|
||||||
inserted: Option<Vec<Range<Anchor>>>,
|
inserted: Option<Vec<Range<Anchor>>>,
|
||||||
|
@ -277,10 +276,6 @@ impl Buffer {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|op| text::Operation::Edit(proto::deserialize_edit_operation(op)));
|
.map(|op| text::Operation::Edit(proto::deserialize_edit_operation(op)));
|
||||||
buffer.apply_ops(ops)?;
|
buffer.apply_ops(ops)?;
|
||||||
for set in message.selections {
|
|
||||||
let set = proto::deserialize_selection_set(set);
|
|
||||||
buffer.add_raw_selection_set(set.id, set);
|
|
||||||
}
|
|
||||||
let mut this = Self::build(buffer, file);
|
let mut this = Self::build(buffer, file);
|
||||||
this.apply_diagnostic_update(
|
this.apply_diagnostic_update(
|
||||||
Arc::from(proto::deserialize_diagnostics(message.diagnostics)),
|
Arc::from(proto::deserialize_diagnostics(message.diagnostics)),
|
||||||
|
@ -299,10 +294,7 @@ impl Buffer {
|
||||||
.history()
|
.history()
|
||||||
.map(proto::serialize_edit_operation)
|
.map(proto::serialize_edit_operation)
|
||||||
.collect(),
|
.collect(),
|
||||||
selections: self
|
selections: Vec::new(),
|
||||||
.selection_sets()
|
|
||||||
.map(|(_, set)| proto::serialize_selection_set(set))
|
|
||||||
.collect(),
|
|
||||||
diagnostics: proto::serialize_diagnostics(self.diagnostics.iter()),
|
diagnostics: proto::serialize_diagnostics(self.diagnostics.iter()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -971,49 +963,11 @@ impl Buffer {
|
||||||
indent_columns: BTreeMap<u32, u32>,
|
indent_columns: BTreeMap<u32, u32>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let selection_set_ids = self
|
self.start_transaction();
|
||||||
.autoindent_requests
|
|
||||||
.drain(..)
|
|
||||||
.flat_map(|req| req.selection_set_ids.clone())
|
|
||||||
.collect::<HashSet<_>>();
|
|
||||||
|
|
||||||
self.start_transaction(selection_set_ids.iter().copied());
|
|
||||||
for (row, indent_column) in &indent_columns {
|
for (row, indent_column) in &indent_columns {
|
||||||
self.set_indent_column_for_line(*row, *indent_column, cx);
|
self.set_indent_column_for_line(*row, *indent_column, cx);
|
||||||
}
|
}
|
||||||
|
self.end_transaction(cx);
|
||||||
for selection_set_id in &selection_set_ids {
|
|
||||||
if let Ok(set) = self.selection_set(*selection_set_id) {
|
|
||||||
let new_selections = set
|
|
||||||
.selections::<Point>(&*self)
|
|
||||||
.map(|selection| {
|
|
||||||
if selection.start.column == 0 {
|
|
||||||
let delta = Point::new(
|
|
||||||
0,
|
|
||||||
indent_columns
|
|
||||||
.get(&selection.start.row)
|
|
||||||
.copied()
|
|
||||||
.unwrap_or(0),
|
|
||||||
);
|
|
||||||
if delta.column > 0 {
|
|
||||||
return Selection {
|
|
||||||
id: selection.id,
|
|
||||||
goal: selection.goal,
|
|
||||||
reversed: selection.reversed,
|
|
||||||
start: selection.start + delta,
|
|
||||||
end: selection.end + delta,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selection
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
self.update_selection_set(*selection_set_id, &new_selections, cx)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.end_transaction(selection_set_ids.iter().copied(), cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_indent_column_for_line(&mut self, row: u32, column: u32, cx: &mut ModelContext<Self>) {
|
fn set_indent_column_for_line(&mut self, row: u32, column: u32, cx: &mut ModelContext<Self>) {
|
||||||
|
@ -1053,7 +1007,7 @@ impl Buffer {
|
||||||
|
|
||||||
pub(crate) fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> bool {
|
pub(crate) fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> bool {
|
||||||
if self.version == diff.base_version {
|
if self.version == diff.base_version {
|
||||||
self.start_transaction(None);
|
self.start_transaction();
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
for (tag, len) in diff.changes {
|
for (tag, len) in diff.changes {
|
||||||
let range = offset..(offset + len);
|
let range = offset..(offset + len);
|
||||||
|
@ -1066,7 +1020,7 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.end_transaction(None, cx);
|
self.end_transaction(cx);
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -1090,38 +1044,24 @@ impl Buffer {
|
||||||
self.text.subscribe()
|
self.text.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_transaction(
|
pub fn start_transaction(&mut self) -> Option<TransactionId> {
|
||||||
&mut self,
|
self.start_transaction_at(Instant::now())
|
||||||
selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
|
|
||||||
) -> Option<TransactionId> {
|
|
||||||
self.start_transaction_at(selection_set_ids, Instant::now())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn start_transaction_at(
|
pub(crate) fn start_transaction_at(&mut self, now: Instant) -> Option<TransactionId> {
|
||||||
&mut self,
|
self.text.start_transaction_at(now)
|
||||||
selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
|
|
||||||
now: Instant,
|
|
||||||
) -> Option<TransactionId> {
|
|
||||||
self.text.start_transaction_at(selection_set_ids, now)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end_transaction(
|
pub fn end_transaction(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
|
||||||
&mut self,
|
self.end_transaction_at(Instant::now(), cx)
|
||||||
selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Option<TransactionId> {
|
|
||||||
self.end_transaction_at(selection_set_ids, Instant::now(), cx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn end_transaction_at(
|
pub(crate) fn end_transaction_at(
|
||||||
&mut self,
|
&mut self,
|
||||||
selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
|
|
||||||
now: Instant,
|
now: Instant,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<TransactionId> {
|
) -> Option<TransactionId> {
|
||||||
if let Some((transaction_id, start_version)) =
|
if let Some((transaction_id, start_version)) = self.text.end_transaction_at(now) {
|
||||||
self.text.end_transaction_at(selection_set_ids, now)
|
|
||||||
{
|
|
||||||
let was_dirty = start_version != self.saved_version;
|
let was_dirty = start_version != self.saved_version;
|
||||||
self.did_edit(&start_version, was_dirty, cx);
|
self.did_edit(&start_version, was_dirty, cx);
|
||||||
Some(transaction_id)
|
Some(transaction_id)
|
||||||
|
@ -1212,7 +1152,7 @@ impl Buffer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.start_transaction(None);
|
self.start_transaction();
|
||||||
self.pending_autoindent.take();
|
self.pending_autoindent.take();
|
||||||
let autoindent_request = if autoindent && self.language.is_some() {
|
let autoindent_request = if autoindent && self.language.is_some() {
|
||||||
let before_edit = self.snapshot();
|
let before_edit = self.snapshot();
|
||||||
|
@ -1256,21 +1196,14 @@ impl Buffer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let selection_set_ids = self
|
|
||||||
.text
|
|
||||||
.peek_undo_stack()
|
|
||||||
.unwrap()
|
|
||||||
.starting_selection_set_ids()
|
|
||||||
.collect();
|
|
||||||
self.autoindent_requests.push(Arc::new(AutoindentRequest {
|
self.autoindent_requests.push(Arc::new(AutoindentRequest {
|
||||||
selection_set_ids,
|
|
||||||
before_edit,
|
before_edit,
|
||||||
edited,
|
edited,
|
||||||
inserted,
|
inserted,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.end_transaction(None, cx);
|
self.end_transaction(cx);
|
||||||
self.send_operation(Operation::Buffer(text::Operation::Edit(edit)), cx);
|
self.send_operation(Operation::Buffer(text::Operation::Edit(edit)), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1298,55 +1231,6 @@ impl Buffer {
|
||||||
self.language.as_ref().and_then(|l| l.grammar.as_ref())
|
self.language.as_ref().and_then(|l| l.grammar.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_selection_set<T: ToOffset>(
|
|
||||||
&mut self,
|
|
||||||
selections: &[Selection<T>],
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> SelectionSetId {
|
|
||||||
let operation = self.text.add_selection_set(selections);
|
|
||||||
if let text::Operation::UpdateSelections { set_id, .. } = &operation {
|
|
||||||
let set_id = *set_id;
|
|
||||||
cx.notify();
|
|
||||||
self.send_operation(Operation::Buffer(operation), cx);
|
|
||||||
set_id
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_selection_set<T: ToOffset>(
|
|
||||||
&mut self,
|
|
||||||
set_id: SelectionSetId,
|
|
||||||
selections: &[Selection<T>],
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let operation = self.text.update_selection_set(set_id, selections)?;
|
|
||||||
cx.notify();
|
|
||||||
self.send_operation(Operation::Buffer(operation), cx);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_active_selection_set(
|
|
||||||
&mut self,
|
|
||||||
set_id: Option<SelectionSetId>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let operation = self.text.set_active_selection_set(set_id)?;
|
|
||||||
self.send_operation(Operation::Buffer(operation), cx);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_selection_set(
|
|
||||||
&mut self,
|
|
||||||
set_id: SelectionSetId,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let operation = self.text.remove_selection_set(set_id)?;
|
|
||||||
cx.notify();
|
|
||||||
self.send_operation(Operation::Buffer(operation), cx);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_ops<I: IntoIterator<Item = Operation>>(
|
pub fn apply_ops<I: IntoIterator<Item = Operation>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
ops: I,
|
ops: I,
|
||||||
|
@ -1447,10 +1331,8 @@ impl Buffer {
|
||||||
let was_dirty = self.is_dirty();
|
let was_dirty = self.is_dirty();
|
||||||
let old_version = self.version.clone();
|
let old_version = self.version.clone();
|
||||||
|
|
||||||
if let Some((transaction_id, operations)) = self.text.undo() {
|
if let Some((transaction_id, operation)) = self.text.undo() {
|
||||||
for operation in operations {
|
|
||||||
self.send_operation(Operation::Buffer(operation), cx);
|
self.send_operation(Operation::Buffer(operation), cx);
|
||||||
}
|
|
||||||
self.did_edit(&old_version, was_dirty, cx);
|
self.did_edit(&old_version, was_dirty, cx);
|
||||||
Some(transaction_id)
|
Some(transaction_id)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1462,10 +1344,8 @@ impl Buffer {
|
||||||
let was_dirty = self.is_dirty();
|
let was_dirty = self.is_dirty();
|
||||||
let old_version = self.version.clone();
|
let old_version = self.version.clone();
|
||||||
|
|
||||||
if let Some((transaction_id, operations)) = self.text.redo() {
|
if let Some((transaction_id, operation)) = self.text.redo() {
|
||||||
for operation in operations {
|
|
||||||
self.send_operation(Operation::Buffer(operation), cx);
|
self.send_operation(Operation::Buffer(operation), cx);
|
||||||
}
|
|
||||||
self.did_edit(&old_version, was_dirty, cx);
|
self.did_edit(&old_version, was_dirty, cx);
|
||||||
Some(transaction_id)
|
Some(transaction_id)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1484,18 +1364,9 @@ impl Buffer {
|
||||||
) where
|
) where
|
||||||
T: rand::Rng,
|
T: rand::Rng,
|
||||||
{
|
{
|
||||||
self.start_transaction(None);
|
self.start_transaction();
|
||||||
self.text.randomly_edit(rng, old_range_count);
|
self.text.randomly_edit(rng, old_range_count);
|
||||||
self.end_transaction(None, cx);
|
self.end_transaction(cx);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn randomly_mutate<T>(&mut self, rng: &mut T, cx: &mut ModelContext<Self>)
|
|
||||||
where
|
|
||||||
T: rand::Rng,
|
|
||||||
{
|
|
||||||
self.start_transaction(None);
|
|
||||||
self.text.randomly_mutate(rng);
|
|
||||||
self.end_transaction(None, cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,15 +92,15 @@ fn test_edit_events(cx: &mut gpui::MutableAppContext) {
|
||||||
buffer.edit(Some(2..4), "XYZ", cx);
|
buffer.edit(Some(2..4), "XYZ", cx);
|
||||||
|
|
||||||
// An empty transaction does not emit any events.
|
// An empty transaction does not emit any events.
|
||||||
buffer.start_transaction(None);
|
buffer.start_transaction();
|
||||||
buffer.end_transaction(None, cx);
|
buffer.end_transaction(cx);
|
||||||
|
|
||||||
// A transaction containing two edits emits one edited event.
|
// A transaction containing two edits emits one edited event.
|
||||||
now += Duration::from_secs(1);
|
now += Duration::from_secs(1);
|
||||||
buffer.start_transaction_at(None, now);
|
buffer.start_transaction_at(now);
|
||||||
buffer.edit(Some(5..5), "u", cx);
|
buffer.edit(Some(5..5), "u", cx);
|
||||||
buffer.edit(Some(6..6), "w", cx);
|
buffer.edit(Some(6..6), "w", cx);
|
||||||
buffer.end_transaction_at(None, now, cx);
|
buffer.end_transaction_at(now, cx);
|
||||||
|
|
||||||
// Undoing a transaction emits one edited event.
|
// Undoing a transaction emits one edited event.
|
||||||
buffer.undo(cx);
|
buffer.undo(cx);
|
||||||
|
@ -167,7 +167,7 @@ async fn test_reparse(mut cx: gpui::TestAppContext) {
|
||||||
// Perform some edits (add parameter and variable reference)
|
// Perform some edits (add parameter and variable reference)
|
||||||
// Parsing doesn't begin until the transaction is complete
|
// Parsing doesn't begin until the transaction is complete
|
||||||
buffer.update(&mut cx, |buf, cx| {
|
buffer.update(&mut cx, |buf, cx| {
|
||||||
buf.start_transaction(None);
|
buf.start_transaction();
|
||||||
|
|
||||||
let offset = buf.text().find(")").unwrap();
|
let offset = buf.text().find(")").unwrap();
|
||||||
buf.edit(vec![offset..offset], "b: C", cx);
|
buf.edit(vec![offset..offset], "b: C", cx);
|
||||||
|
@ -177,7 +177,7 @@ async fn test_reparse(mut cx: gpui::TestAppContext) {
|
||||||
buf.edit(vec![offset..offset], " d; ", cx);
|
buf.edit(vec![offset..offset], " d; ", cx);
|
||||||
assert!(!buf.is_parsing());
|
assert!(!buf.is_parsing());
|
||||||
|
|
||||||
buf.end_transaction(None, cx);
|
buf.end_transaction(cx);
|
||||||
assert_eq!(buf.text(), "fn a(b: C) { d; }");
|
assert_eq!(buf.text(), "fn a(b: C) { d; }");
|
||||||
assert!(buf.is_parsing());
|
assert!(buf.is_parsing());
|
||||||
});
|
});
|
||||||
|
@ -333,59 +333,62 @@ fn test_edit_with_autoindent(cx: &mut MutableAppContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
// We need another approach to managing selections with auto-indent
|
||||||
fn test_autoindent_moves_selections(cx: &mut MutableAppContext) {
|
|
||||||
cx.add_model(|cx| {
|
|
||||||
let text = "fn a() {}";
|
|
||||||
|
|
||||||
let mut buffer =
|
// #[gpui::test]
|
||||||
Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
|
// fn test_autoindent_moves_selections(cx: &mut MutableAppContext) {
|
||||||
|
// cx.add_model(|cx| {
|
||||||
|
// let text = "fn a() {}";
|
||||||
|
|
||||||
let selection_set_id = buffer.add_selection_set::<usize>(&[], cx);
|
// let mut buffer =
|
||||||
buffer.start_transaction(Some(selection_set_id));
|
// Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
|
||||||
buffer.edit_with_autoindent([5..5, 9..9], "\n\n", cx);
|
|
||||||
buffer
|
|
||||||
.update_selection_set(
|
|
||||||
selection_set_id,
|
|
||||||
&[
|
|
||||||
Selection {
|
|
||||||
id: 0,
|
|
||||||
start: Point::new(1, 0),
|
|
||||||
end: Point::new(1, 0),
|
|
||||||
reversed: false,
|
|
||||||
goal: SelectionGoal::None,
|
|
||||||
},
|
|
||||||
Selection {
|
|
||||||
id: 1,
|
|
||||||
start: Point::new(4, 0),
|
|
||||||
end: Point::new(4, 0),
|
|
||||||
reversed: false,
|
|
||||||
goal: SelectionGoal::None,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(buffer.text(), "fn a(\n\n) {}\n\n");
|
|
||||||
|
|
||||||
// Ending the transaction runs the auto-indent. The selection
|
// let selection_set_id = buffer.add_selection_set::<usize>(&[], cx);
|
||||||
// at the start of the auto-indented row is pushed to the right.
|
// buffer.start_transaction();
|
||||||
buffer.end_transaction(Some(selection_set_id), cx);
|
// buffer.edit_with_autoindent([5..5, 9..9], "\n\n", cx);
|
||||||
assert_eq!(buffer.text(), "fn a(\n \n) {}\n\n");
|
// buffer
|
||||||
let selection_ranges = buffer
|
// .update_selection_set(
|
||||||
.selection_set(selection_set_id)
|
// selection_set_id,
|
||||||
.unwrap()
|
// &[
|
||||||
.selections::<Point>(&buffer)
|
// Selection {
|
||||||
.map(|selection| selection.start.to_point(&buffer)..selection.end.to_point(&buffer))
|
// id: 0,
|
||||||
.collect::<Vec<_>>();
|
// start: Point::new(1, 0),
|
||||||
|
// end: Point::new(1, 0),
|
||||||
|
// reversed: false,
|
||||||
|
// goal: SelectionGoal::None,
|
||||||
|
// },
|
||||||
|
// Selection {
|
||||||
|
// id: 1,
|
||||||
|
// start: Point::new(4, 0),
|
||||||
|
// end: Point::new(4, 0),
|
||||||
|
// reversed: false,
|
||||||
|
// goal: SelectionGoal::None,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// cx,
|
||||||
|
// )
|
||||||
|
// .unwrap();
|
||||||
|
// assert_eq!(buffer.text(), "fn a(\n\n) {}\n\n");
|
||||||
|
|
||||||
assert_eq!(selection_ranges[0], empty(Point::new(1, 4)));
|
// // TODO! Come up with a different approach to moving selections now that we don't manage selection sets in the buffer
|
||||||
assert_eq!(selection_ranges[1], empty(Point::new(4, 0)));
|
|
||||||
|
|
||||||
buffer
|
// // Ending the transaction runs the auto-indent. The selection
|
||||||
});
|
// // at the start of the auto-indented row is pushed to the right.
|
||||||
}
|
// buffer.end_transaction(cx);
|
||||||
|
// assert_eq!(buffer.text(), "fn a(\n \n) {}\n\n");
|
||||||
|
// let selection_ranges = buffer
|
||||||
|
// .selection_set(selection_set_id)
|
||||||
|
// .unwrap()
|
||||||
|
// .selections::<Point>(&buffer)
|
||||||
|
// .map(|selection| selection.start.to_point(&buffer)..selection.end.to_point(&buffer))
|
||||||
|
// .collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// assert_eq!(selection_ranges[0], empty(Point::new(1, 4)));
|
||||||
|
// assert_eq!(selection_ranges[1], empty(Point::new(4, 0)));
|
||||||
|
|
||||||
|
// buffer
|
||||||
|
// });
|
||||||
|
// }
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) {
|
fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) {
|
||||||
cx.add_model(|cx| {
|
cx.add_model(|cx| {
|
||||||
|
|
|
@ -3559,7 +3559,6 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_buffer_file_changes_on_disk(mut cx: gpui::TestAppContext) {
|
async fn test_buffer_file_changes_on_disk(mut cx: gpui::TestAppContext) {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use text::{Point, Selection, SelectionGoal};
|
|
||||||
|
|
||||||
let initial_contents = "aaa\nbbbbb\nc\n";
|
let initial_contents = "aaa\nbbbbb\nc\n";
|
||||||
let dir = temp_tree(json!({ "the-file": initial_contents }));
|
let dir = temp_tree(json!({ "the-file": initial_contents }));
|
||||||
|
@ -3588,22 +3587,23 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
// TODO
|
||||||
// Add a cursor on each row.
|
// Add a cursor on each row.
|
||||||
let selection_set_id = buffer.update(&mut cx, |buffer, cx| {
|
// let selection_set_id = buffer.update(&mut cx, |buffer, cx| {
|
||||||
assert!(!buffer.is_dirty());
|
// assert!(!buffer.is_dirty());
|
||||||
buffer.add_selection_set(
|
// buffer.add_selection_set(
|
||||||
&(0..3)
|
// &(0..3)
|
||||||
.map(|row| Selection {
|
// .map(|row| Selection {
|
||||||
id: row as usize,
|
// id: row as usize,
|
||||||
start: Point::new(row, 1),
|
// start: Point::new(row, 1),
|
||||||
end: Point::new(row, 1),
|
// end: Point::new(row, 1),
|
||||||
reversed: false,
|
// reversed: false,
|
||||||
goal: SelectionGoal::None,
|
// goal: SelectionGoal::None,
|
||||||
})
|
// })
|
||||||
.collect::<Vec<_>>(),
|
// .collect::<Vec<_>>(),
|
||||||
cx,
|
// cx,
|
||||||
)
|
// )
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Change the file on disk, adding two new lines of text, and removing
|
// Change the file on disk, adding two new lines of text, and removing
|
||||||
// one line.
|
// one line.
|
||||||
|
@ -3626,19 +3626,20 @@ mod tests {
|
||||||
assert!(!buffer.is_dirty());
|
assert!(!buffer.is_dirty());
|
||||||
assert!(!buffer.has_conflict());
|
assert!(!buffer.has_conflict());
|
||||||
|
|
||||||
let cursor_positions = buffer
|
// TODO
|
||||||
.selection_set(selection_set_id)
|
// let cursor_positions = buffer
|
||||||
.unwrap()
|
// .selection_set(selection_set_id)
|
||||||
.selections::<Point>(&*buffer)
|
// .unwrap()
|
||||||
.map(|selection| {
|
// .selections::<Point>(&*buffer)
|
||||||
assert_eq!(selection.start, selection.end);
|
// .map(|selection| {
|
||||||
selection.start
|
// assert_eq!(selection.start, selection.end);
|
||||||
})
|
// selection.start
|
||||||
.collect::<Vec<_>>();
|
// })
|
||||||
assert_eq!(
|
// .collect::<Vec<_>>();
|
||||||
cursor_positions,
|
// assert_eq!(
|
||||||
[Point::new(1, 1), Point::new(3, 1), Point::new(4, 0)]
|
// cursor_positions,
|
||||||
);
|
// [Point::new(1, 1), Point::new(3, 1), Point::new(4, 0)]
|
||||||
|
// );
|
||||||
});
|
});
|
||||||
|
|
||||||
// Modify the buffer
|
// Modify the buffer
|
||||||
|
|
|
@ -1045,13 +1045,14 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Create a selection set as client B and see that selection set as client A.
|
|
||||||
let editor_b = cx_b.add_view(window_b, |cx| {
|
let editor_b = cx_b.add_view(window_b, |cx| {
|
||||||
Editor::for_buffer(buffer_b, |cx| EditorSettings::test(cx), cx)
|
Editor::for_buffer(buffer_b, |cx| EditorSettings::test(cx), cx)
|
||||||
});
|
});
|
||||||
buffer_a
|
// TODO
|
||||||
.condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 1)
|
// // Create a selection set as client B and see that selection set as client A.
|
||||||
.await;
|
// buffer_a
|
||||||
|
// .condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 1)
|
||||||
|
// .await;
|
||||||
|
|
||||||
// Edit the buffer as client B and see that edit as client A.
|
// Edit the buffer as client B and see that edit as client A.
|
||||||
editor_b.update(&mut cx_b, |editor, cx| {
|
editor_b.update(&mut cx_b, |editor, cx| {
|
||||||
|
@ -1061,11 +1062,12 @@ mod tests {
|
||||||
.condition(&cx_a, |buffer, _| buffer.text() == "ok, b-contents")
|
.condition(&cx_a, |buffer, _| buffer.text() == "ok, b-contents")
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Remove the selection set as client B, see those selections disappear as client A.
|
// TODO
|
||||||
cx_b.update(move |_| drop(editor_b));
|
// // Remove the selection set as client B, see those selections disappear as client A.
|
||||||
buffer_a
|
// cx_b.update(move |_| drop(editor_b));
|
||||||
.condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 0)
|
// buffer_a
|
||||||
.await;
|
// .condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 0)
|
||||||
|
// .await;
|
||||||
|
|
||||||
// Close the buffer as client A, see that the buffer is closed.
|
// Close the buffer as client A, see that the buffer is closed.
|
||||||
cx_a.update(move |_| drop(buffer_a));
|
cx_a.update(move |_| drop(buffer_a));
|
||||||
|
|
|
@ -460,63 +460,41 @@ fn test_history() {
|
||||||
let mut now = Instant::now();
|
let mut now = Instant::now();
|
||||||
let mut buffer = Buffer::new(0, 0, History::new("123456".into()));
|
let mut buffer = Buffer::new(0, 0, History::new("123456".into()));
|
||||||
|
|
||||||
let set_id = if let Operation::UpdateSelections { set_id, .. } =
|
buffer.start_transaction_at(now);
|
||||||
buffer.add_selection_set(&buffer.selections_from_ranges(vec![4..4]).unwrap())
|
|
||||||
{
|
|
||||||
set_id
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
|
||||||
buffer.start_transaction_at(Some(set_id), now);
|
|
||||||
buffer.edit(vec![2..4], "cd");
|
buffer.edit(vec![2..4], "cd");
|
||||||
buffer.end_transaction_at(Some(set_id), now);
|
buffer.end_transaction_at(now);
|
||||||
assert_eq!(buffer.text(), "12cd56");
|
assert_eq!(buffer.text(), "12cd56");
|
||||||
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
|
|
||||||
|
|
||||||
buffer.start_transaction_at(Some(set_id), now);
|
buffer.start_transaction_at(now);
|
||||||
buffer
|
|
||||||
.update_selection_set(set_id, &buffer.selections_from_ranges(vec![1..3]).unwrap())
|
|
||||||
.unwrap();
|
|
||||||
buffer.edit(vec![4..5], "e");
|
buffer.edit(vec![4..5], "e");
|
||||||
buffer.end_transaction_at(Some(set_id), now).unwrap();
|
buffer.end_transaction_at(now).unwrap();
|
||||||
assert_eq!(buffer.text(), "12cde6");
|
assert_eq!(buffer.text(), "12cde6");
|
||||||
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
|
|
||||||
|
|
||||||
now += buffer.history.group_interval + Duration::from_millis(1);
|
now += buffer.history.group_interval + Duration::from_millis(1);
|
||||||
buffer.start_transaction_at(Some(set_id), now);
|
buffer.start_transaction_at(now);
|
||||||
buffer
|
|
||||||
.update_selection_set(set_id, &buffer.selections_from_ranges(vec![2..2]).unwrap())
|
|
||||||
.unwrap();
|
|
||||||
buffer.edit(vec![0..1], "a");
|
buffer.edit(vec![0..1], "a");
|
||||||
buffer.edit(vec![1..1], "b");
|
buffer.edit(vec![1..1], "b");
|
||||||
buffer.end_transaction_at(Some(set_id), now).unwrap();
|
buffer.end_transaction_at(now).unwrap();
|
||||||
assert_eq!(buffer.text(), "ab2cde6");
|
assert_eq!(buffer.text(), "ab2cde6");
|
||||||
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
|
|
||||||
|
|
||||||
// Last transaction happened past the group interval, undo it on its
|
// Last transaction happened past the group interval, undo it on its own.
|
||||||
// own.
|
|
||||||
buffer.undo();
|
buffer.undo();
|
||||||
assert_eq!(buffer.text(), "12cde6");
|
assert_eq!(buffer.text(), "12cde6");
|
||||||
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
|
|
||||||
|
|
||||||
// First two transactions happened within the group interval, undo them
|
// First two transactions happened within the group interval, undo them together.
|
||||||
// together.
|
|
||||||
buffer.undo();
|
buffer.undo();
|
||||||
assert_eq!(buffer.text(), "123456");
|
assert_eq!(buffer.text(), "123456");
|
||||||
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
|
|
||||||
|
|
||||||
// Redo the first two transactions together.
|
// Redo the first two transactions together.
|
||||||
buffer.redo();
|
buffer.redo();
|
||||||
assert_eq!(buffer.text(), "12cde6");
|
assert_eq!(buffer.text(), "12cde6");
|
||||||
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
|
|
||||||
|
|
||||||
// Redo the last transaction on its own.
|
// Redo the last transaction on its own.
|
||||||
buffer.redo();
|
buffer.redo();
|
||||||
assert_eq!(buffer.text(), "ab2cde6");
|
assert_eq!(buffer.text(), "ab2cde6");
|
||||||
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
|
|
||||||
|
|
||||||
buffer.start_transaction_at(None, now);
|
buffer.start_transaction_at(now);
|
||||||
assert!(buffer.end_transaction_at(None, now).is_none());
|
assert!(buffer.end_transaction_at(now).is_none());
|
||||||
buffer.undo();
|
buffer.undo();
|
||||||
assert_eq!(buffer.text(), "12cde6");
|
assert_eq!(buffer.text(), "12cde6");
|
||||||
}
|
}
|
||||||
|
@ -582,8 +560,8 @@ fn test_random_concurrent_edits(mut rng: StdRng) {
|
||||||
let buffer = &mut buffers[replica_index];
|
let buffer = &mut buffers[replica_index];
|
||||||
match rng.gen_range(0..=100) {
|
match rng.gen_range(0..=100) {
|
||||||
0..=50 if mutation_count != 0 => {
|
0..=50 if mutation_count != 0 => {
|
||||||
let ops = buffer.randomly_mutate(&mut rng);
|
let op = buffer.randomly_edit(&mut rng, 5).2;
|
||||||
network.broadcast(buffer.replica_id, ops);
|
network.broadcast(buffer.replica_id, vec!(op));
|
||||||
log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text());
|
log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text());
|
||||||
mutation_count -= 1;
|
mutation_count -= 1;
|
||||||
}
|
}
|
||||||
|
@ -620,18 +598,6 @@ fn test_random_concurrent_edits(mut rng: StdRng) {
|
||||||
"Replica {} text != Replica 0 text",
|
"Replica {} text != Replica 0 text",
|
||||||
buffer.replica_id
|
buffer.replica_id
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
buffer.selection_sets().collect::<HashMap<_, _>>(),
|
|
||||||
first_buffer.selection_sets().collect::<HashMap<_, _>>()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
buffer
|
|
||||||
.all_selection_ranges::<usize>()
|
|
||||||
.collect::<HashMap<_, _>>(),
|
|
||||||
first_buffer
|
|
||||||
.all_selection_ranges::<usize>()
|
|
||||||
.collect::<HashMap<_, _>>()
|
|
||||||
);
|
|
||||||
buffer.check_invariants();
|
buffer.check_invariants();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub mod subscription;
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
pub use anchor::*;
|
pub use anchor::*;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::Result;
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use locator::Locator;
|
use locator::Locator;
|
||||||
|
@ -71,17 +71,11 @@ pub struct Transaction {
|
||||||
end: clock::Global,
|
end: clock::Global,
|
||||||
edits: Vec<clock::Local>,
|
edits: Vec<clock::Local>,
|
||||||
ranges: Vec<Range<FullOffset>>,
|
ranges: Vec<Range<FullOffset>>,
|
||||||
selections_before: HashMap<SelectionSetId, Arc<[Selection<Anchor>]>>,
|
|
||||||
selections_after: HashMap<SelectionSetId, Arc<[Selection<Anchor>]>>,
|
|
||||||
first_edit_at: Instant,
|
first_edit_at: Instant,
|
||||||
last_edit_at: Instant,
|
last_edit_at: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
pub fn starting_selection_set_ids<'a>(&'a self) -> impl Iterator<Item = SelectionSetId> + 'a {
|
|
||||||
self.selections_before.keys().copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_edit(&mut self, edit: &EditOperation) {
|
fn push_edit(&mut self, edit: &EditOperation) {
|
||||||
self.edits.push(edit.timestamp.local());
|
self.edits.push(edit.timestamp.local());
|
||||||
self.end.observe(edit.timestamp.local());
|
self.end.observe(edit.timestamp.local());
|
||||||
|
@ -158,12 +152,7 @@ impl History {
|
||||||
self.ops.insert(op.timestamp.local(), op);
|
self.ops.insert(op.timestamp.local(), op);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_transaction(
|
fn start_transaction(&mut self, start: clock::Global, now: Instant) -> Option<TransactionId> {
|
||||||
&mut self,
|
|
||||||
start: clock::Global,
|
|
||||||
selections_before: HashMap<SelectionSetId, Arc<[Selection<Anchor>]>>,
|
|
||||||
now: Instant,
|
|
||||||
) -> Option<TransactionId> {
|
|
||||||
self.transaction_depth += 1;
|
self.transaction_depth += 1;
|
||||||
if self.transaction_depth == 1 {
|
if self.transaction_depth == 1 {
|
||||||
let id = self.next_transaction_id;
|
let id = self.next_transaction_id;
|
||||||
|
@ -174,8 +163,6 @@ impl History {
|
||||||
end: start,
|
end: start,
|
||||||
edits: Vec::new(),
|
edits: Vec::new(),
|
||||||
ranges: Vec::new(),
|
ranges: Vec::new(),
|
||||||
selections_before,
|
|
||||||
selections_after: Default::default(),
|
|
||||||
first_edit_at: now,
|
first_edit_at: now,
|
||||||
last_edit_at: now,
|
last_edit_at: now,
|
||||||
});
|
});
|
||||||
|
@ -185,11 +172,7 @@ impl History {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_transaction(
|
fn end_transaction(&mut self, now: Instant) -> Option<&Transaction> {
|
||||||
&mut self,
|
|
||||||
selections_after: HashMap<SelectionSetId, Arc<[Selection<Anchor>]>>,
|
|
||||||
now: Instant,
|
|
||||||
) -> Option<&Transaction> {
|
|
||||||
assert_ne!(self.transaction_depth, 0);
|
assert_ne!(self.transaction_depth, 0);
|
||||||
self.transaction_depth -= 1;
|
self.transaction_depth -= 1;
|
||||||
if self.transaction_depth == 0 {
|
if self.transaction_depth == 0 {
|
||||||
|
@ -198,7 +181,6 @@ impl History {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let transaction = self.undo_stack.last_mut().unwrap();
|
let transaction = self.undo_stack.last_mut().unwrap();
|
||||||
transaction.selections_after = selections_after;
|
|
||||||
transaction.last_edit_at = now;
|
transaction.last_edit_at = now;
|
||||||
Some(transaction)
|
Some(transaction)
|
||||||
}
|
}
|
||||||
|
@ -234,9 +216,6 @@ impl History {
|
||||||
|
|
||||||
if let Some(transaction) = transactions_to_merge.last_mut() {
|
if let Some(transaction) = transactions_to_merge.last_mut() {
|
||||||
last_transaction.last_edit_at = transaction.last_edit_at;
|
last_transaction.last_edit_at = transaction.last_edit_at;
|
||||||
last_transaction
|
|
||||||
.selections_after
|
|
||||||
.extend(transaction.selections_after.drain());
|
|
||||||
last_transaction.end = transaction.end.clone();
|
last_transaction.end = transaction.end.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -558,7 +537,7 @@ impl Buffer {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
self.start_transaction(None);
|
self.start_transaction();
|
||||||
let timestamp = InsertionTimestamp {
|
let timestamp = InsertionTimestamp {
|
||||||
replica_id: self.replica_id,
|
replica_id: self.replica_id,
|
||||||
local: self.local_clock.tick().value,
|
local: self.local_clock.tick().value,
|
||||||
|
@ -570,7 +549,7 @@ impl Buffer {
|
||||||
self.history.push_undo(edit.timestamp.local());
|
self.history.push_undo(edit.timestamp.local());
|
||||||
self.last_edit = edit.timestamp.local();
|
self.last_edit = edit.timestamp.local();
|
||||||
self.snapshot.version.observe(edit.timestamp.local());
|
self.snapshot.version.observe(edit.timestamp.local());
|
||||||
self.end_transaction(None);
|
self.end_transaction();
|
||||||
edit
|
edit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1149,56 +1128,20 @@ impl Buffer {
|
||||||
self.history.undo_stack.last()
|
self.history.undo_stack.last()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_transaction(
|
pub fn start_transaction(&mut self) -> Option<TransactionId> {
|
||||||
&mut self,
|
self.start_transaction_at(Instant::now())
|
||||||
selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
|
|
||||||
) -> Option<TransactionId> {
|
|
||||||
self.start_transaction_at(selection_set_ids, Instant::now())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_transaction_at(
|
pub fn start_transaction_at(&mut self, now: Instant) -> Option<TransactionId> {
|
||||||
&mut self,
|
self.history.start_transaction(self.version.clone(), now)
|
||||||
selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
|
|
||||||
now: Instant,
|
|
||||||
) -> Option<TransactionId> {
|
|
||||||
let selections = selection_set_ids
|
|
||||||
.into_iter()
|
|
||||||
.map(|set_id| {
|
|
||||||
let set = self
|
|
||||||
.selection_sets
|
|
||||||
.get(&set_id)
|
|
||||||
.expect("invalid selection set id");
|
|
||||||
(set_id, set.selections.clone())
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
self.history
|
|
||||||
.start_transaction(self.version.clone(), selections, now)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end_transaction(
|
pub fn end_transaction(&mut self) -> Option<(TransactionId, clock::Global)> {
|
||||||
&mut self,
|
self.end_transaction_at(Instant::now())
|
||||||
selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
|
|
||||||
) -> Option<(TransactionId, clock::Global)> {
|
|
||||||
self.end_transaction_at(selection_set_ids, Instant::now())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end_transaction_at(
|
pub fn end_transaction_at(&mut self, now: Instant) -> Option<(TransactionId, clock::Global)> {
|
||||||
&mut self,
|
if let Some(transaction) = self.history.end_transaction(now) {
|
||||||
selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
|
|
||||||
now: Instant,
|
|
||||||
) -> Option<(TransactionId, clock::Global)> {
|
|
||||||
let selections = selection_set_ids
|
|
||||||
.into_iter()
|
|
||||||
.map(|set_id| {
|
|
||||||
let set = self
|
|
||||||
.selection_sets
|
|
||||||
.get(&set_id)
|
|
||||||
.expect("invalid selection set id");
|
|
||||||
(set_id, set.selections.clone())
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if let Some(transaction) = self.history.end_transaction(selections, now) {
|
|
||||||
let id = transaction.id;
|
let id = transaction.id;
|
||||||
let since = transaction.start.clone();
|
let since = transaction.start.clone();
|
||||||
self.history.group();
|
self.history.group();
|
||||||
|
@ -1221,31 +1164,21 @@ impl Buffer {
|
||||||
self.history.ops.values()
|
self.history.ops.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn undo(&mut self) -> Option<(TransactionId, Vec<Operation>)> {
|
pub fn undo(&mut self) -> Option<(TransactionId, Operation)> {
|
||||||
if let Some(transaction) = self.history.pop_undo().cloned() {
|
if let Some(transaction) = self.history.pop_undo().cloned() {
|
||||||
let transaction_id = transaction.id;
|
let transaction_id = transaction.id;
|
||||||
let selections = transaction.selections_before.clone();
|
let op = self.undo_or_redo(transaction).unwrap();
|
||||||
let mut ops = Vec::new();
|
Some((transaction_id, op))
|
||||||
ops.push(self.undo_or_redo(transaction).unwrap());
|
|
||||||
for (set_id, selections) in selections {
|
|
||||||
ops.extend(self.restore_selection_set(set_id, selections));
|
|
||||||
}
|
|
||||||
Some((transaction_id, ops))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redo(&mut self) -> Option<(TransactionId, Vec<Operation>)> {
|
pub fn redo(&mut self) -> Option<(TransactionId, Operation)> {
|
||||||
if let Some(transaction) = self.history.pop_redo().cloned() {
|
if let Some(transaction) = self.history.pop_redo().cloned() {
|
||||||
let transaction_id = transaction.id;
|
let transaction_id = transaction.id;
|
||||||
let selections = transaction.selections_after.clone();
|
let op = self.undo_or_redo(transaction).unwrap();
|
||||||
let mut ops = Vec::new();
|
Some((transaction_id, op))
|
||||||
ops.push(self.undo_or_redo(transaction).unwrap());
|
|
||||||
for (set_id, selections) in selections {
|
|
||||||
ops.extend(self.restore_selection_set(set_id, selections));
|
|
||||||
}
|
|
||||||
Some((transaction_id, ops))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -1275,125 +1208,6 @@ impl Buffer {
|
||||||
pub fn subscribe(&mut self) -> Subscription {
|
pub fn subscribe(&mut self) -> Subscription {
|
||||||
self.subscriptions.subscribe()
|
self.subscriptions.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selection_set(&self, set_id: SelectionSetId) -> Result<&SelectionSet> {
|
|
||||||
self.selection_sets
|
|
||||||
.get(&set_id)
|
|
||||||
.ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn selection_sets(&self) -> impl Iterator<Item = (&SelectionSetId, &SelectionSet)> {
|
|
||||||
self.selection_sets.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_anchor_selection_set<T: ToOffset>(
|
|
||||||
&self,
|
|
||||||
selections: &[Selection<T>],
|
|
||||||
) -> Arc<[Selection<Anchor>]> {
|
|
||||||
Arc::from(
|
|
||||||
selections
|
|
||||||
.iter()
|
|
||||||
.map(|selection| Selection {
|
|
||||||
id: selection.id,
|
|
||||||
start: self.anchor_before(&selection.start),
|
|
||||||
end: self.anchor_before(&selection.end),
|
|
||||||
reversed: selection.reversed,
|
|
||||||
goal: selection.goal,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_selection_set<T: ToOffset>(
|
|
||||||
&mut self,
|
|
||||||
set_id: SelectionSetId,
|
|
||||||
selections: &[Selection<T>],
|
|
||||||
) -> Result<Operation> {
|
|
||||||
let selections = self.build_anchor_selection_set(selections);
|
|
||||||
let set = self
|
|
||||||
.selection_sets
|
|
||||||
.get_mut(&set_id)
|
|
||||||
.ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))?;
|
|
||||||
set.selections = selections.clone();
|
|
||||||
Ok(Operation::UpdateSelections {
|
|
||||||
set_id,
|
|
||||||
selections,
|
|
||||||
lamport_timestamp: self.lamport_clock.tick(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restore_selection_set(
|
|
||||||
&mut self,
|
|
||||||
set_id: SelectionSetId,
|
|
||||||
selections: Arc<[Selection<Anchor>]>,
|
|
||||||
) -> Result<Operation> {
|
|
||||||
let set = self
|
|
||||||
.selection_sets
|
|
||||||
.get_mut(&set_id)
|
|
||||||
.ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))?;
|
|
||||||
set.selections = selections.clone();
|
|
||||||
Ok(Operation::UpdateSelections {
|
|
||||||
set_id,
|
|
||||||
selections,
|
|
||||||
lamport_timestamp: self.lamport_clock.tick(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_selection_set<T: ToOffset>(&mut self, selections: &[Selection<T>]) -> Operation {
|
|
||||||
let selections = self.build_anchor_selection_set(selections);
|
|
||||||
let set_id = self.lamport_clock.tick();
|
|
||||||
self.selection_sets.insert(
|
|
||||||
set_id,
|
|
||||||
SelectionSet {
|
|
||||||
id: set_id,
|
|
||||||
selections: selections.clone(),
|
|
||||||
active: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Operation::UpdateSelections {
|
|
||||||
set_id,
|
|
||||||
selections,
|
|
||||||
lamport_timestamp: set_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_raw_selection_set(&mut self, id: SelectionSetId, selections: SelectionSet) {
|
|
||||||
self.selection_sets.insert(id, selections);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_active_selection_set(
|
|
||||||
&mut self,
|
|
||||||
set_id: Option<SelectionSetId>,
|
|
||||||
) -> Result<Operation> {
|
|
||||||
if let Some(set_id) = set_id {
|
|
||||||
assert_eq!(set_id.replica_id, self.replica_id());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (id, set) in &mut self.selection_sets {
|
|
||||||
if id.replica_id == self.local_clock.replica_id {
|
|
||||||
if Some(*id) == set_id {
|
|
||||||
set.active = true;
|
|
||||||
} else {
|
|
||||||
set.active = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Operation::SetActiveSelections {
|
|
||||||
set_id,
|
|
||||||
lamport_timestamp: self.lamport_clock.tick(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_selection_set(&mut self, set_id: SelectionSetId) -> Result<Operation> {
|
|
||||||
self.selection_sets
|
|
||||||
.remove(&set_id)
|
|
||||||
.ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))?;
|
|
||||||
Ok(Operation::RemoveSelections {
|
|
||||||
set_id,
|
|
||||||
lamport_timestamp: self.lamport_clock.tick(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
@ -1434,42 +1248,6 @@ impl Buffer {
|
||||||
(old_ranges, new_text, Operation::Edit(op))
|
(old_ranges, new_text, Operation::Edit(op))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn randomly_mutate<T>(&mut self, rng: &mut T) -> Vec<Operation>
|
|
||||||
where
|
|
||||||
T: rand::Rng,
|
|
||||||
{
|
|
||||||
use rand::prelude::*;
|
|
||||||
|
|
||||||
let mut ops = vec![self.randomly_edit(rng, 5).2];
|
|
||||||
|
|
||||||
// Randomly add, remove or mutate selection sets.
|
|
||||||
let replica_selection_sets = &self
|
|
||||||
.selection_sets()
|
|
||||||
.map(|(set_id, _)| *set_id)
|
|
||||||
.filter(|set_id| self.replica_id == set_id.replica_id)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let set_id = replica_selection_sets.choose(rng);
|
|
||||||
if set_id.is_some() && rng.gen_bool(1.0 / 6.0) {
|
|
||||||
ops.push(self.remove_selection_set(*set_id.unwrap()).unwrap());
|
|
||||||
} else {
|
|
||||||
let mut ranges = Vec::new();
|
|
||||||
for _ in 0..5 {
|
|
||||||
ranges.push(self.random_byte_range(0, rng));
|
|
||||||
}
|
|
||||||
let new_selections = self.selections_from_ranges(ranges).unwrap();
|
|
||||||
|
|
||||||
let op = if set_id.is_none() || rng.gen_bool(1.0 / 5.0) {
|
|
||||||
self.add_selection_set(&new_selections)
|
|
||||||
} else {
|
|
||||||
self.update_selection_set(*set_id.unwrap(), &new_selections)
|
|
||||||
.unwrap()
|
|
||||||
};
|
|
||||||
ops.push(op);
|
|
||||||
}
|
|
||||||
|
|
||||||
ops
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng) -> Vec<Operation> {
|
pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng) -> Vec<Operation> {
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
|
||||||
|
@ -1486,73 +1264,6 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
ops
|
ops
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selections_from_ranges<I>(&self, ranges: I) -> Result<Vec<Selection<usize>>>
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = Range<usize>>,
|
|
||||||
{
|
|
||||||
use std::sync::atomic::{self, AtomicUsize};
|
|
||||||
|
|
||||||
static NEXT_SELECTION_ID: AtomicUsize = AtomicUsize::new(0);
|
|
||||||
|
|
||||||
let mut ranges = ranges.into_iter().collect::<Vec<_>>();
|
|
||||||
ranges.sort_unstable_by_key(|range| range.start);
|
|
||||||
|
|
||||||
let mut selections = Vec::<Selection<usize>>::with_capacity(ranges.len());
|
|
||||||
for mut range in ranges {
|
|
||||||
let mut reversed = false;
|
|
||||||
if range.start > range.end {
|
|
||||||
reversed = true;
|
|
||||||
std::mem::swap(&mut range.start, &mut range.end);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(selection) = selections.last_mut() {
|
|
||||||
if selection.end >= range.start {
|
|
||||||
selection.end = range.end;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selections.push(Selection {
|
|
||||||
id: NEXT_SELECTION_ID.fetch_add(1, atomic::Ordering::SeqCst),
|
|
||||||
start: range.start,
|
|
||||||
end: range.end,
|
|
||||||
reversed,
|
|
||||||
goal: SelectionGoal::None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(selections)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn selection_ranges<'a, D>(&'a self, set_id: SelectionSetId) -> Result<Vec<Range<D>>>
|
|
||||||
where
|
|
||||||
D: TextDimension,
|
|
||||||
{
|
|
||||||
Ok(self
|
|
||||||
.selection_set(set_id)?
|
|
||||||
.selections(self)
|
|
||||||
.map(move |selection| {
|
|
||||||
if selection.reversed {
|
|
||||||
selection.end..selection.start
|
|
||||||
} else {
|
|
||||||
selection.start..selection.end
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn all_selection_ranges<'a, D>(
|
|
||||||
&'a self,
|
|
||||||
) -> impl 'a + Iterator<Item = (SelectionSetId, Vec<Range<usize>>)>
|
|
||||||
where
|
|
||||||
D: TextDimension,
|
|
||||||
{
|
|
||||||
self.selection_sets
|
|
||||||
.keys()
|
|
||||||
.map(move |set_id| (*set_id, self.selection_ranges(*set_id).unwrap()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Buffer {
|
impl Deref for Buffer {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue