Move selection helpers to SelectionCollection, add update_anchor_selections, add a number of invariant preserving mutation functions to the MutableSelectionCollection
This commit is contained in:
parent
61b4a4202f
commit
c9dcfff607
22 changed files with 1891 additions and 1467 deletions
|
@ -37,7 +37,7 @@ impl Breadcrumbs {
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Option<(ModelHandle<Buffer>, Vec<OutlineItem<Anchor>>)> {
|
) -> Option<(ModelHandle<Buffer>, Vec<OutlineItem<Anchor>>)> {
|
||||||
let editor = self.editor.as_ref()?.read(cx);
|
let editor = self.editor.as_ref()?.read(cx);
|
||||||
let cursor = editor.newest_anchor_selection().head();
|
let cursor = editor.selections.newest_anchor().head();
|
||||||
let multibuffer = &editor.buffer().read(cx);
|
let multibuffer = &editor.buffer().read(cx);
|
||||||
let (buffer_id, symbols) = multibuffer
|
let (buffer_id, symbols) = multibuffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
|
|
|
@ -3001,7 +3001,7 @@ mod tests {
|
||||||
|
|
||||||
// Type a completion trigger character as the guest.
|
// Type a completion trigger character as the guest.
|
||||||
editor_b.update(cx_b, |editor, cx| {
|
editor_b.update(cx_b, |editor, cx| {
|
||||||
editor.select_ranges([13..13], None, cx);
|
editor.change_selections(true, cx, |s| s.select_ranges([13..13], None));
|
||||||
editor.handle_input(&Input(".".into()), cx);
|
editor.handle_input(&Input(".".into()), cx);
|
||||||
cx.focus(&editor_b);
|
cx.focus(&editor_b);
|
||||||
});
|
});
|
||||||
|
@ -4213,7 +4213,9 @@ mod tests {
|
||||||
|
|
||||||
// Move cursor to a location that contains code actions.
|
// Move cursor to a location that contains code actions.
|
||||||
editor_b.update(cx_b, |editor, cx| {
|
editor_b.update(cx_b, |editor, cx| {
|
||||||
editor.select_ranges([Point::new(1, 31)..Point::new(1, 31)], None, cx);
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_ranges([Point::new(1, 31)..Point::new(1, 31)], None)
|
||||||
|
});
|
||||||
cx.focus(&editor_b);
|
cx.focus(&editor_b);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -4450,7 +4452,7 @@ mod tests {
|
||||||
|
|
||||||
// Move cursor to a location that can be renamed.
|
// Move cursor to a location that can be renamed.
|
||||||
let prepare_rename = editor_b.update(cx_b, |editor, cx| {
|
let prepare_rename = editor_b.update(cx_b, |editor, cx| {
|
||||||
editor.select_ranges([7..7], None, cx);
|
editor.change_selections(true, cx, |s| s.select_ranges([7..7], None));
|
||||||
editor.rename(&Rename, cx).unwrap()
|
editor.rename(&Rename, cx).unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5470,8 +5472,12 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
// When client B starts following client A, all visible view states are replicated to client B.
|
// When client B starts following client A, all visible view states are replicated to client B.
|
||||||
editor_a1.update(cx_a, |editor, cx| editor.select_ranges([0..1], None, cx));
|
editor_a1.update(cx_a, |editor, cx| {
|
||||||
editor_a2.update(cx_a, |editor, cx| editor.select_ranges([2..3], None, cx));
|
editor.change_selections(true, cx, |s| s.select_ranges([0..1], None))
|
||||||
|
});
|
||||||
|
editor_a2.update(cx_a, |editor, cx| {
|
||||||
|
editor.change_selections(true, cx, |s| s.select_ranges([2..3], None))
|
||||||
|
});
|
||||||
workspace_b
|
workspace_b
|
||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
workspace
|
workspace
|
||||||
|
@ -5536,7 +5542,7 @@ mod tests {
|
||||||
|
|
||||||
// Changes to client A's editor are reflected on client B.
|
// Changes to client A's editor are reflected on client B.
|
||||||
editor_a1.update(cx_a, |editor, cx| {
|
editor_a1.update(cx_a, |editor, cx| {
|
||||||
editor.select_ranges([1..1, 2..2], None, cx);
|
editor.change_selections(true, cx, |s| s.select_ranges([1..1, 2..2], None));
|
||||||
});
|
});
|
||||||
editor_b1
|
editor_b1
|
||||||
.condition(cx_b, |editor, cx| {
|
.condition(cx_b, |editor, cx| {
|
||||||
|
@ -5550,7 +5556,7 @@ mod tests {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
editor_a1.update(cx_a, |editor, cx| {
|
editor_a1.update(cx_a, |editor, cx| {
|
||||||
editor.select_ranges([3..3], None, cx);
|
editor.change_selections(true, cx, |s| s.select_ranges([3..3], None));
|
||||||
editor.set_scroll_position(vec2f(0., 100.), cx);
|
editor.set_scroll_position(vec2f(0., 100.), cx);
|
||||||
});
|
});
|
||||||
editor_b1
|
editor_b1
|
||||||
|
|
|
@ -417,8 +417,11 @@ impl ProjectDiagnosticsEditor {
|
||||||
}];
|
}];
|
||||||
} else {
|
} else {
|
||||||
groups = self.path_states.get(path_ix)?.diagnostic_groups.as_slice();
|
groups = self.path_states.get(path_ix)?.diagnostic_groups.as_slice();
|
||||||
new_excerpt_ids_by_selection_id = editor.refresh_selections(cx);
|
new_excerpt_ids_by_selection_id =
|
||||||
selections = editor.local_selections::<usize>(cx);
|
editor.change_selections(true, cx, |s| s.refresh());
|
||||||
|
selections = editor
|
||||||
|
.selections
|
||||||
|
.interleaved::<usize>(&editor.buffer().read(cx).read(cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any selection has lost its position, move it to start of the next primary diagnostic.
|
// If any selection has lost its position, move it to start of the next primary diagnostic.
|
||||||
|
@ -441,7 +444,9 @@ impl ProjectDiagnosticsEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
editor.update_selections(selections, None, cx);
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select(selections, None);
|
||||||
|
});
|
||||||
Some(())
|
Some(())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -58,9 +58,7 @@ impl DiagnosticIndicator {
|
||||||
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 buffer = editor.buffer().read(cx);
|
let buffer = editor.buffer().read(cx);
|
||||||
let cursor_position = editor
|
let cursor_position = editor.selections.newest::<usize>(&buffer.read(cx)).head();
|
||||||
.newest_selection_with_snapshot::<usize>(&buffer.read(cx))
|
|
||||||
.head();
|
|
||||||
let new_diagnostic = buffer
|
let new_diagnostic = buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false)
|
.diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -957,8 +957,9 @@ impl Element for EditorElement {
|
||||||
selections.extend(remote_selections);
|
selections.extend(remote_selections);
|
||||||
|
|
||||||
if view.show_local_selections {
|
if view.show_local_selections {
|
||||||
let local_selections =
|
let local_selections = view
|
||||||
view.local_selections_in_range(start_anchor..end_anchor, &display_map);
|
.selections
|
||||||
|
.interleaved_in_range(start_anchor..end_anchor, &display_map.buffer_snapshot);
|
||||||
for selection in &local_selections {
|
for selection in &local_selections {
|
||||||
let is_empty = selection.start == selection.end;
|
let is_empty = selection.start == selection.end;
|
||||||
let selection_start = snapshot.prev_line_boundary(selection.start).1;
|
let selection_start = snapshot.prev_line_boundary(selection.start).1;
|
||||||
|
@ -1041,7 +1042,8 @@ impl Element for EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
let newest_selection_head = view
|
let newest_selection_head = view
|
||||||
.newest_selection_with_snapshot::<usize>(&snapshot.buffer_snapshot)
|
.selections
|
||||||
|
.newest::<usize>(&snapshot.buffer_snapshot)
|
||||||
.head()
|
.head()
|
||||||
.to_display_point(&snapshot);
|
.to_display_point(&snapshot);
|
||||||
|
|
||||||
|
|
|
@ -102,7 +102,7 @@ impl FollowableItem for Editor {
|
||||||
} else {
|
} else {
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
if self.focused {
|
if self.focused {
|
||||||
buffer.set_active_selections(&self.selections, cx);
|
buffer.set_active_selections(&self.selections.disjoint_anchors(), cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,12 @@ impl FollowableItem for Editor {
|
||||||
)),
|
)),
|
||||||
scroll_x: self.scroll_position.x(),
|
scroll_x: self.scroll_position.x(),
|
||||||
scroll_y: self.scroll_position.y(),
|
scroll_y: self.scroll_position.y(),
|
||||||
selections: self.selections.iter().map(serialize_selection).collect(),
|
selections: self
|
||||||
|
.selections
|
||||||
|
.disjoint_anchors()
|
||||||
|
.iter()
|
||||||
|
.map(serialize_selection)
|
||||||
|
.collect(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,8 +149,9 @@ impl FollowableItem for Editor {
|
||||||
Event::SelectionsChanged { .. } => {
|
Event::SelectionsChanged { .. } => {
|
||||||
update.selections = self
|
update.selections = self
|
||||||
.selections
|
.selections
|
||||||
|
.disjoint_anchors()
|
||||||
.iter()
|
.iter()
|
||||||
.chain(self.pending_selection.as_ref().map(|p| &p.selection))
|
.chain(self.selections.pending_anchor().as_ref())
|
||||||
.map(serialize_selection)
|
.map(serialize_selection)
|
||||||
.collect();
|
.collect();
|
||||||
true
|
true
|
||||||
|
@ -252,7 +258,7 @@ impl Item for Editor {
|
||||||
} else {
|
} else {
|
||||||
buffer.clip_point(data.cursor_position, Bias::Left)
|
buffer.clip_point(data.cursor_position, Bias::Left)
|
||||||
};
|
};
|
||||||
let newest_selection = self.newest_selection_with_snapshot::<Point>(&buffer);
|
let newest_selection = self.selections.newest::<Point>(&buffer);
|
||||||
|
|
||||||
let scroll_top_anchor = if buffer.can_resolve(&data.scroll_top_anchor) {
|
let scroll_top_anchor = if buffer.can_resolve(&data.scroll_top_anchor) {
|
||||||
data.scroll_top_anchor
|
data.scroll_top_anchor
|
||||||
|
@ -270,7 +276,9 @@ impl Item for Editor {
|
||||||
let nav_history = self.nav_history.take();
|
let nav_history = self.nav_history.take();
|
||||||
self.scroll_position = data.scroll_position;
|
self.scroll_position = data.scroll_position;
|
||||||
self.scroll_top_anchor = scroll_top_anchor;
|
self.scroll_top_anchor = scroll_top_anchor;
|
||||||
self.select_ranges([offset..offset], Some(Autoscroll::Fit), cx);
|
self.change_selections(true, cx, |s| {
|
||||||
|
s.select_ranges([offset..offset], Some(Autoscroll::Fit))
|
||||||
|
});
|
||||||
self.nav_history = nav_history;
|
self.nav_history = nav_history;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -307,7 +315,7 @@ impl Item for Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
let selection = self.newest_anchor_selection();
|
let selection = self.selections.newest_anchor();
|
||||||
self.push_to_nav_history(selection.head(), None, cx);
|
self.push_to_nav_history(selection.head(), None, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -457,7 +465,7 @@ impl CursorPosition {
|
||||||
|
|
||||||
self.selected_count = 0;
|
self.selected_count = 0;
|
||||||
let mut last_selection: Option<Selection<usize>> = None;
|
let mut last_selection: Option<Selection<usize>> = None;
|
||||||
for selection in editor.local_selections::<usize>(cx) {
|
for selection in editor.selections.interleaved::<usize>(&buffer) {
|
||||||
self.selected_count += selection.end - selection.start;
|
self.selected_count += selection.end - selection.start;
|
||||||
if last_selection
|
if last_selection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
703
crates/editor/src/selections_collection.rs
Normal file
703
crates/editor/src/selections_collection.rs
Normal file
|
@ -0,0 +1,703 @@
|
||||||
|
use std::{
|
||||||
|
iter, mem,
|
||||||
|
ops::{Deref, Range, Sub},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use collections::HashMap;
|
||||||
|
use gpui::{ModelHandle, MutableAppContext};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use language::{rope::TextDimension, Bias, Point, Selection, SelectionGoal, ToPoint};
|
||||||
|
use util::post_inc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
|
||||||
|
Anchor, Autoscroll, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode,
|
||||||
|
ToOffset,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PendingSelection {
|
||||||
|
pub selection: Selection<Anchor>,
|
||||||
|
pub mode: SelectMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SelectionsCollection {
|
||||||
|
pub next_selection_id: usize,
|
||||||
|
disjoint: Arc<[Selection<Anchor>]>,
|
||||||
|
pending: Option<PendingSelection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectionsCollection {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
next_selection_id: 1,
|
||||||
|
disjoint: Arc::from([]),
|
||||||
|
pending: Some(PendingSelection {
|
||||||
|
selection: Selection {
|
||||||
|
id: 0,
|
||||||
|
start: Anchor::min(),
|
||||||
|
end: Anchor::min(),
|
||||||
|
reversed: false,
|
||||||
|
goal: SelectionGoal::None,
|
||||||
|
},
|
||||||
|
mode: SelectMode::Character,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count<'a>(&self) -> usize {
|
||||||
|
let mut count = self.disjoint.len();
|
||||||
|
if self.pending.is_some() {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disjoint_anchors(&self) -> Arc<[Selection<Anchor>]> {
|
||||||
|
self.disjoint.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pending_anchor(&self) -> Option<Selection<Anchor>> {
|
||||||
|
self.pending
|
||||||
|
.as_ref()
|
||||||
|
.map(|pending| pending.selection.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pending<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||||
|
&self,
|
||||||
|
snapshot: &MultiBufferSnapshot,
|
||||||
|
) -> Option<Selection<D>> {
|
||||||
|
self.pending_anchor()
|
||||||
|
.as_ref()
|
||||||
|
.map(|pending| pending.map(|p| p.summary::<D>(&snapshot)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pending_mode(&self) -> Option<SelectMode> {
|
||||||
|
self.pending.as_ref().map(|pending| pending.mode.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interleaved<'a, D>(&self, buffer: &MultiBufferSnapshot) -> Vec<Selection<D>>
|
||||||
|
where
|
||||||
|
D: 'a + TextDimension + Ord + Sub<D, Output = D>,
|
||||||
|
{
|
||||||
|
let anchor_disjoint = &self.disjoint;
|
||||||
|
let mut disjoint = resolve_multiple::<D, _>(anchor_disjoint.iter(), &buffer).peekable();
|
||||||
|
|
||||||
|
let mut pending_opt = self.pending::<D>(&buffer);
|
||||||
|
|
||||||
|
iter::from_fn(move || {
|
||||||
|
if let Some(pending) = pending_opt.as_mut() {
|
||||||
|
while let Some(next_selection) = disjoint.peek() {
|
||||||
|
if pending.start <= next_selection.end && pending.end >= next_selection.start {
|
||||||
|
let next_selection = disjoint.next().unwrap();
|
||||||
|
if next_selection.start < pending.start {
|
||||||
|
pending.start = next_selection.start;
|
||||||
|
}
|
||||||
|
if next_selection.end > pending.end {
|
||||||
|
pending.end = next_selection.end;
|
||||||
|
}
|
||||||
|
} else if next_selection.end < pending.start {
|
||||||
|
return disjoint.next();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pending_opt.take()
|
||||||
|
} else {
|
||||||
|
disjoint.next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interleaved_in_range<'a>(
|
||||||
|
&self,
|
||||||
|
range: Range<Anchor>,
|
||||||
|
buffer: &MultiBufferSnapshot,
|
||||||
|
) -> Vec<Selection<Point>> {
|
||||||
|
let start_ix = match self
|
||||||
|
.disjoint
|
||||||
|
.binary_search_by(|probe| probe.end.cmp(&range.start, &buffer))
|
||||||
|
{
|
||||||
|
Ok(ix) | Err(ix) => ix,
|
||||||
|
};
|
||||||
|
let end_ix = match self
|
||||||
|
.disjoint
|
||||||
|
.binary_search_by(|probe| probe.start.cmp(&range.end, &buffer))
|
||||||
|
{
|
||||||
|
Ok(ix) => ix + 1,
|
||||||
|
Err(ix) => ix,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn point_selection(
|
||||||
|
selection: &Selection<Anchor>,
|
||||||
|
buffer: &MultiBufferSnapshot,
|
||||||
|
) -> Selection<Point> {
|
||||||
|
let start = crate::ToPoint::to_point(&selection.start, &buffer);
|
||||||
|
let end = crate::ToPoint::to_point(&selection.end, &buffer);
|
||||||
|
Selection {
|
||||||
|
id: selection.id,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
reversed: selection.reversed,
|
||||||
|
goal: selection.goal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.disjoint[start_ix..end_ix]
|
||||||
|
.iter()
|
||||||
|
.chain(self.pending.as_ref().map(|pending| &pending.selection))
|
||||||
|
.map(|s| point_selection(s, &buffer))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newest_anchor(&self) -> &Selection<Anchor> {
|
||||||
|
self.pending
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| &s.selection)
|
||||||
|
.or_else(|| self.disjoint.iter().max_by_key(|s| s.id))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newest<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||||
|
&self,
|
||||||
|
snapshot: &MultiBufferSnapshot,
|
||||||
|
) -> Selection<D> {
|
||||||
|
resolve(self.newest_anchor(), snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn oldest_anchor(&self) -> &Selection<Anchor> {
|
||||||
|
self.disjoint
|
||||||
|
.iter()
|
||||||
|
.min_by_key(|s| s.id)
|
||||||
|
.or_else(|| self.pending.as_ref().map(|p| &p.selection))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn oldest<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||||
|
&self,
|
||||||
|
snapshot: &MultiBufferSnapshot,
|
||||||
|
) -> Selection<D> {
|
||||||
|
resolve(self.oldest_anchor(), snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn first<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||||
|
&self,
|
||||||
|
snapshot: &MultiBufferSnapshot,
|
||||||
|
) -> Selection<D> {
|
||||||
|
self.interleaved(&snapshot).first().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn last<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||||
|
&self,
|
||||||
|
snapshot: &MultiBufferSnapshot,
|
||||||
|
) -> Selection<D> {
|
||||||
|
self.interleaved(&snapshot).last().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE do not use. This should only be called from Editor::change_selections.
|
||||||
|
#[deprecated]
|
||||||
|
pub fn change_with<R>(
|
||||||
|
&mut self,
|
||||||
|
display_map: ModelHandle<DisplayMap>,
|
||||||
|
buffer: ModelHandle<MultiBuffer>,
|
||||||
|
cx: &mut MutableAppContext,
|
||||||
|
change: impl FnOnce(&mut MutableSelectionsCollection) -> R,
|
||||||
|
) -> (Option<Autoscroll>, R) {
|
||||||
|
let mut mutable_collection = MutableSelectionsCollection {
|
||||||
|
collection: self,
|
||||||
|
autoscroll: None,
|
||||||
|
display_map,
|
||||||
|
buffer,
|
||||||
|
cx,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = change(&mut mutable_collection);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!mutable_collection.disjoint.is_empty() || mutable_collection.pending.is_some(),
|
||||||
|
"There must be at least one selection"
|
||||||
|
);
|
||||||
|
(mutable_collection.autoscroll, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MutableSelectionsCollection<'a> {
|
||||||
|
collection: &'a mut SelectionsCollection,
|
||||||
|
pub autoscroll: Option<Autoscroll>,
|
||||||
|
buffer: ModelHandle<MultiBuffer>,
|
||||||
|
display_map: ModelHandle<DisplayMap>,
|
||||||
|
cx: &'a mut MutableAppContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MutableSelectionsCollection<'a> {
|
||||||
|
pub fn clear_disjoint(&mut self) {
|
||||||
|
self.collection.disjoint = Arc::from([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(&mut self, selection_id: usize) {
|
||||||
|
self.collection.disjoint = self
|
||||||
|
.disjoint
|
||||||
|
.into_iter()
|
||||||
|
.filter(|selection| selection.id != selection_id)
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_pending(&mut self) {
|
||||||
|
self.collection.pending = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_pending_range(&mut self, range: Range<Anchor>, mode: SelectMode) {
|
||||||
|
self.collection.pending = Some(PendingSelection {
|
||||||
|
selection: Selection {
|
||||||
|
id: post_inc(&mut self.collection.next_selection_id),
|
||||||
|
start: range.start,
|
||||||
|
end: range.end,
|
||||||
|
reversed: false,
|
||||||
|
goal: SelectionGoal::None,
|
||||||
|
},
|
||||||
|
mode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn pending_mut(&mut self) -> &mut Option<PendingSelection> {
|
||||||
|
&mut self.collection.pending
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_cancel(&mut self) -> bool {
|
||||||
|
let buffer = self.buffer.read(self.cx).snapshot(self.cx);
|
||||||
|
if let Some(pending) = self.collection.pending.take() {
|
||||||
|
if self.disjoint.is_empty() {
|
||||||
|
self.collection.disjoint = Arc::from([pending.selection]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut oldest = self.oldest_anchor().clone();
|
||||||
|
if self.count() > 1 {
|
||||||
|
self.collection.disjoint = Arc::from([oldest]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !oldest.start.cmp(&oldest.end, &buffer).is_eq() {
|
||||||
|
let head = oldest.head();
|
||||||
|
oldest.start = head.clone();
|
||||||
|
oldest.end = head;
|
||||||
|
self.collection.disjoint = Arc::from([oldest]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_biases(&mut self) {
|
||||||
|
let buffer = self.buffer.read(self.cx).snapshot(self.cx);
|
||||||
|
self.collection.disjoint = self
|
||||||
|
.collection
|
||||||
|
.disjoint
|
||||||
|
.into_iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|selection| reset_biases(selection, &buffer))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if let Some(pending) = self.collection.pending.as_mut() {
|
||||||
|
pending.selection = reset_biases(pending.selection.clone(), &buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_range<T>(&mut self, range: Range<T>, autoscroll: Option<Autoscroll>)
|
||||||
|
where
|
||||||
|
T: 'a
|
||||||
|
+ ToOffset
|
||||||
|
+ ToPoint
|
||||||
|
+ TextDimension
|
||||||
|
+ Ord
|
||||||
|
+ Sub<T, Output = T>
|
||||||
|
+ std::marker::Copy
|
||||||
|
+ std::fmt::Debug,
|
||||||
|
{
|
||||||
|
let buffer = self.buffer.read(self.cx).snapshot(self.cx);
|
||||||
|
let mut selections = self.interleaved(&buffer);
|
||||||
|
let mut start = range.start.to_offset(&buffer);
|
||||||
|
let mut end = range.end.to_offset(&buffer);
|
||||||
|
let reversed = if start > end {
|
||||||
|
mem::swap(&mut start, &mut end);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
selections.push(Selection {
|
||||||
|
id: post_inc(&mut self.collection.next_selection_id),
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
reversed,
|
||||||
|
goal: SelectionGoal::None,
|
||||||
|
});
|
||||||
|
self.select(selections, autoscroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select<T>(&mut self, mut selections: Vec<Selection<T>>, autoscroll: Option<Autoscroll>)
|
||||||
|
where
|
||||||
|
T: ToOffset + ToPoint + Ord + std::marker::Copy + std::fmt::Debug,
|
||||||
|
{
|
||||||
|
let buffer = self.buffer.read(self.cx).snapshot(self.cx);
|
||||||
|
selections.sort_unstable_by_key(|s| s.start);
|
||||||
|
// Merge overlapping selections.
|
||||||
|
let mut i = 1;
|
||||||
|
while i < selections.len() {
|
||||||
|
if selections[i - 1].end >= selections[i].start {
|
||||||
|
let removed = selections.remove(i);
|
||||||
|
if removed.start < selections[i - 1].start {
|
||||||
|
selections[i - 1].start = removed.start;
|
||||||
|
}
|
||||||
|
if removed.end > selections[i - 1].end {
|
||||||
|
selections[i - 1].end = removed.end;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.autoscroll = autoscroll.or(self.autoscroll.take());
|
||||||
|
|
||||||
|
self.collection.disjoint = Arc::from_iter(selections.into_iter().map(|selection| {
|
||||||
|
let end_bias = if selection.end > selection.start {
|
||||||
|
Bias::Left
|
||||||
|
} else {
|
||||||
|
Bias::Right
|
||||||
|
};
|
||||||
|
Selection {
|
||||||
|
id: selection.id,
|
||||||
|
start: buffer.anchor_after(selection.start),
|
||||||
|
end: buffer.anchor_at(selection.end, end_bias),
|
||||||
|
reversed: selection.reversed,
|
||||||
|
goal: selection.goal,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.collection.pending = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_anchors(
|
||||||
|
&mut self,
|
||||||
|
mut selections: Vec<Selection<Anchor>>,
|
||||||
|
autoscroll: Option<Autoscroll>,
|
||||||
|
) {
|
||||||
|
let buffer = self.buffer.read(self.cx).snapshot(self.cx);
|
||||||
|
selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, &buffer));
|
||||||
|
|
||||||
|
// Merge overlapping selections.
|
||||||
|
let mut i = 1;
|
||||||
|
while i < selections.len() {
|
||||||
|
if selections[i - 1]
|
||||||
|
.end
|
||||||
|
.cmp(&selections[i].start, &buffer)
|
||||||
|
.is_ge()
|
||||||
|
{
|
||||||
|
let removed = selections.remove(i);
|
||||||
|
if removed.start.cmp(&selections[i - 1].start, &buffer).is_lt() {
|
||||||
|
selections[i - 1].start = removed.start;
|
||||||
|
}
|
||||||
|
if removed.end.cmp(&selections[i - 1].end, &buffer).is_gt() {
|
||||||
|
selections[i - 1].end = removed.end;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.autoscroll = autoscroll.or(self.autoscroll.take());
|
||||||
|
|
||||||
|
self.collection.disjoint = Arc::from_iter(
|
||||||
|
selections
|
||||||
|
.into_iter()
|
||||||
|
.map(|selection| reset_biases(selection, &buffer)),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.collection.pending = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_ranges<I, T>(&mut self, ranges: I, autoscroll: Option<Autoscroll>)
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Range<T>>,
|
||||||
|
T: ToOffset,
|
||||||
|
{
|
||||||
|
let buffer = self.buffer.read(self.cx).snapshot(self.cx);
|
||||||
|
let selections = ranges
|
||||||
|
.into_iter()
|
||||||
|
.map(|range| {
|
||||||
|
let mut start = range.start.to_offset(&buffer);
|
||||||
|
let mut end = range.end.to_offset(&buffer);
|
||||||
|
let reversed = if start > end {
|
||||||
|
mem::swap(&mut start, &mut end);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
Selection {
|
||||||
|
id: post_inc(&mut self.collection.next_selection_id),
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
reversed,
|
||||||
|
goal: SelectionGoal::None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
self.select(selections, autoscroll)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_anchor_ranges<I: IntoIterator<Item = Range<Anchor>>>(
|
||||||
|
&mut self,
|
||||||
|
ranges: I,
|
||||||
|
autoscroll: Option<Autoscroll>,
|
||||||
|
) {
|
||||||
|
let buffer = self.buffer.read(self.cx).snapshot(self.cx);
|
||||||
|
let selections = ranges
|
||||||
|
.into_iter()
|
||||||
|
.map(|range| {
|
||||||
|
let mut start = range.start;
|
||||||
|
let mut end = range.end;
|
||||||
|
let reversed = if start.cmp(&end, &buffer).is_gt() {
|
||||||
|
mem::swap(&mut start, &mut end);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
Selection {
|
||||||
|
id: post_inc(&mut self.collection.next_selection_id),
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
reversed,
|
||||||
|
goal: SelectionGoal::None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
self.select_anchors(selections, autoscroll)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
pub fn select_display_ranges<T>(&mut self, ranges: T)
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = Range<DisplayPoint>>,
|
||||||
|
{
|
||||||
|
let display_map = self.display_map.update(self.cx, |map, cx| map.snapshot(cx));
|
||||||
|
let selections = ranges
|
||||||
|
.into_iter()
|
||||||
|
.map(|range| {
|
||||||
|
let mut start = range.start;
|
||||||
|
let mut end = range.end;
|
||||||
|
let reversed = if start > end {
|
||||||
|
mem::swap(&mut start, &mut end);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
Selection {
|
||||||
|
id: post_inc(&mut self.collection.next_selection_id),
|
||||||
|
start: start.to_point(&display_map),
|
||||||
|
end: end.to_point(&display_map),
|
||||||
|
reversed,
|
||||||
|
goal: SelectionGoal::None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
self.select(selections, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_with(
|
||||||
|
&mut self,
|
||||||
|
mut move_selection: impl FnMut(&DisplaySnapshot, &mut Selection<DisplayPoint>),
|
||||||
|
) {
|
||||||
|
let display_map = self.display_map.update(self.cx, |map, cx| map.snapshot(cx));
|
||||||
|
let selections = self
|
||||||
|
.interleaved::<Point>(&display_map.buffer_snapshot)
|
||||||
|
.into_iter()
|
||||||
|
.map(|selection| {
|
||||||
|
let mut selection = selection.map(|point| point.to_display_point(&display_map));
|
||||||
|
move_selection(&display_map, &mut selection);
|
||||||
|
selection.map(|display_point| display_point.to_point(&display_map))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
self.select(selections, Some(Autoscroll::Fit))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_heads_with(
|
||||||
|
&mut self,
|
||||||
|
mut update_head: impl FnMut(
|
||||||
|
&DisplaySnapshot,
|
||||||
|
DisplayPoint,
|
||||||
|
SelectionGoal,
|
||||||
|
) -> (DisplayPoint, SelectionGoal),
|
||||||
|
) {
|
||||||
|
self.move_with(|map, selection| {
|
||||||
|
let (new_head, new_goal) = update_head(map, selection.head(), selection.goal);
|
||||||
|
selection.set_head(new_head, new_goal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_cursors_with(
|
||||||
|
&mut self,
|
||||||
|
mut update_cursor_position: impl FnMut(
|
||||||
|
&DisplaySnapshot,
|
||||||
|
DisplayPoint,
|
||||||
|
SelectionGoal,
|
||||||
|
) -> (DisplayPoint, SelectionGoal),
|
||||||
|
) {
|
||||||
|
self.move_with(|map, selection| {
|
||||||
|
let (cursor, new_goal) = update_cursor_position(map, selection.head(), selection.goal);
|
||||||
|
selection.collapse_to(cursor, new_goal)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace_cursors_with(
|
||||||
|
&mut self,
|
||||||
|
mut find_replacement_cursors: impl FnMut(&DisplaySnapshot) -> Vec<DisplayPoint>,
|
||||||
|
) {
|
||||||
|
let display_map = self.display_map.update(self.cx, |map, cx| map.snapshot(cx));
|
||||||
|
let new_selections = find_replacement_cursors(&display_map)
|
||||||
|
.into_iter()
|
||||||
|
.map(|cursor| {
|
||||||
|
let cursor_point = cursor.to_point(&display_map);
|
||||||
|
Selection {
|
||||||
|
id: post_inc(&mut self.collection.next_selection_id),
|
||||||
|
start: cursor_point,
|
||||||
|
end: cursor_point,
|
||||||
|
reversed: false,
|
||||||
|
goal: SelectionGoal::None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
self.select(new_selections, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute new ranges for any selections that were located in excerpts that have
|
||||||
|
/// since been removed.
|
||||||
|
///
|
||||||
|
/// Returns a `HashMap` indicating which selections whose former head position
|
||||||
|
/// was no longer present. The keys of the map are selection ids. The values are
|
||||||
|
/// the id of the new excerpt where the head of the selection has been moved.
|
||||||
|
pub fn refresh(&mut self) -> HashMap<usize, ExcerptId> {
|
||||||
|
// TODO: Pull disjoint constraint out of update_selections so we don't have to
|
||||||
|
// store the pending_selection here.
|
||||||
|
let buffer = self.buffer.read(self.cx).snapshot(self.cx);
|
||||||
|
let mut pending = self.collection.pending.take();
|
||||||
|
let mut selections_with_lost_position = HashMap::default();
|
||||||
|
|
||||||
|
let anchors_with_status = buffer.refresh_anchors(
|
||||||
|
self.disjoint
|
||||||
|
.iter()
|
||||||
|
.flat_map(|selection| [&selection.start, &selection.end]),
|
||||||
|
);
|
||||||
|
let adjusted_disjoint: Vec<_> = anchors_with_status
|
||||||
|
.chunks(2)
|
||||||
|
.map(|selection_anchors| {
|
||||||
|
let (anchor_ix, start, kept_start) = selection_anchors[0].clone();
|
||||||
|
let (_, end, kept_end) = selection_anchors[1].clone();
|
||||||
|
let selection = &self.disjoint[anchor_ix / 2];
|
||||||
|
let kept_head = if selection.reversed {
|
||||||
|
kept_start
|
||||||
|
} else {
|
||||||
|
kept_end
|
||||||
|
};
|
||||||
|
if !kept_head {
|
||||||
|
selections_with_lost_position
|
||||||
|
.insert(selection.id, selection.head().excerpt_id.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Selection {
|
||||||
|
id: selection.id,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
reversed: selection.reversed,
|
||||||
|
goal: selection.goal,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !adjusted_disjoint.is_empty() {
|
||||||
|
self.select::<usize>(
|
||||||
|
resolve_multiple(adjusted_disjoint.iter(), &buffer).collect(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pending) = pending.as_mut() {
|
||||||
|
let anchors =
|
||||||
|
buffer.refresh_anchors([&pending.selection.start, &pending.selection.end]);
|
||||||
|
let (_, start, kept_start) = anchors[0].clone();
|
||||||
|
let (_, end, kept_end) = anchors[1].clone();
|
||||||
|
let kept_head = if pending.selection.reversed {
|
||||||
|
kept_start
|
||||||
|
} else {
|
||||||
|
kept_end
|
||||||
|
};
|
||||||
|
if !kept_head {
|
||||||
|
selections_with_lost_position.insert(
|
||||||
|
pending.selection.id,
|
||||||
|
pending.selection.head().excerpt_id.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pending.selection.start = start;
|
||||||
|
pending.selection.end = end;
|
||||||
|
}
|
||||||
|
self.collection.pending = pending;
|
||||||
|
|
||||||
|
selections_with_lost_position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deref for MutableSelectionsCollection<'a> {
|
||||||
|
type Target = SelectionsCollection;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.collection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panics if passed selections are not in order
|
||||||
|
pub fn resolve_multiple<'a, D, I>(
|
||||||
|
selections: I,
|
||||||
|
snapshot: &MultiBufferSnapshot,
|
||||||
|
) -> impl 'a + Iterator<Item = Selection<D>>
|
||||||
|
where
|
||||||
|
D: TextDimension + Ord + Sub<D, Output = D>,
|
||||||
|
I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
|
||||||
|
{
|
||||||
|
let (to_summarize, selections) = selections.into_iter().tee();
|
||||||
|
let mut summaries = snapshot
|
||||||
|
.summaries_for_anchors::<D, _>(to_summarize.flat_map(|s| [&s.start, &s.end]))
|
||||||
|
.into_iter();
|
||||||
|
selections.map(move |s| Selection {
|
||||||
|
id: s.id,
|
||||||
|
start: summaries.next().unwrap(),
|
||||||
|
end: summaries.next().unwrap(),
|
||||||
|
reversed: s.reversed,
|
||||||
|
goal: s.goal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||||
|
selection: &Selection<Anchor>,
|
||||||
|
buffer: &MultiBufferSnapshot,
|
||||||
|
) -> Selection<D> {
|
||||||
|
selection.map(|p| p.summary::<D>(&buffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_biases(
|
||||||
|
mut selection: Selection<Anchor>,
|
||||||
|
buffer: &MultiBufferSnapshot,
|
||||||
|
) -> Selection<Anchor> {
|
||||||
|
let end_bias = if selection.end.cmp(&selection.start, buffer).is_gt() {
|
||||||
|
Bias::Left
|
||||||
|
} else {
|
||||||
|
Bias::Right
|
||||||
|
};
|
||||||
|
selection.start = buffer.anchor_after(selection.start);
|
||||||
|
selection.end = buffer.anchor_at(selection.end, end_bias);
|
||||||
|
selection
|
||||||
|
}
|
|
@ -43,7 +43,7 @@ pub fn marked_display_snapshot(
|
||||||
pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) {
|
pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) {
|
||||||
let (umarked_text, text_ranges) = marked_text_ranges(marked_text);
|
let (umarked_text, text_ranges) = marked_text_ranges(marked_text);
|
||||||
assert_eq!(editor.text(cx), umarked_text);
|
assert_eq!(editor.text(cx), umarked_text);
|
||||||
editor.select_ranges(text_ranges, None, cx);
|
editor.change_selections(true, cx, |s| s.select_ranges(text_ranges, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn assert_text_with_selections(
|
pub fn assert_text_with_selections(
|
||||||
|
|
|
@ -43,7 +43,7 @@ impl GoToLine {
|
||||||
let buffer = editor.buffer().read(cx).read(cx);
|
let buffer = editor.buffer().read(cx).read(cx);
|
||||||
(
|
(
|
||||||
Some(scroll_position),
|
Some(scroll_position),
|
||||||
editor.newest_selection_with_snapshot(&buffer).head(),
|
editor.selections.newest(&buffer).head(),
|
||||||
buffer.max_point(),
|
buffer.max_point(),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -80,7 +80,9 @@ impl GoToLine {
|
||||||
if let Some(rows) = active_editor.highlighted_rows() {
|
if let Some(rows) = active_editor.highlighted_rows() {
|
||||||
let snapshot = active_editor.snapshot(cx).display_snapshot;
|
let snapshot = active_editor.snapshot(cx).display_snapshot;
|
||||||
let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot);
|
let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot);
|
||||||
active_editor.select_ranges([position..position], Some(Autoscroll::Center), cx);
|
active_editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_ranges([position..position], Some(Autoscroll::Center))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cx.emit(Event::Dismissed);
|
cx.emit(Event::Dismissed);
|
||||||
|
|
|
@ -57,7 +57,9 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||||
if let Some(editor) = item.downcast::<Editor>() {
|
if let Some(editor) = item.downcast::<Editor>() {
|
||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
let len = editor.buffer().read(cx).read(cx).len();
|
let len = editor.buffer().read(cx).read(cx).len();
|
||||||
editor.select_ranges([len..len], Some(Autoscroll::Center), cx);
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_ranges([len..len], Some(Autoscroll::Center))
|
||||||
|
});
|
||||||
if len > 0 {
|
if len > 0 {
|
||||||
editor.insert("\n\n", cx);
|
editor.insert("\n\n", cx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,9 +172,7 @@ impl PickerDelegate for OutlineView {
|
||||||
|
|
||||||
let editor = self.active_editor.read(cx);
|
let editor = self.active_editor.read(cx);
|
||||||
let buffer = editor.buffer().read(cx).read(cx);
|
let buffer = editor.buffer().read(cx).read(cx);
|
||||||
let cursor_offset = editor
|
let cursor_offset = editor.selections.newest::<usize>(&buffer).head();
|
||||||
.newest_selection_with_snapshot::<usize>(&buffer)
|
|
||||||
.head();
|
|
||||||
selected_index = self
|
selected_index = self
|
||||||
.outline
|
.outline
|
||||||
.items
|
.items
|
||||||
|
@ -217,7 +215,9 @@ impl PickerDelegate for OutlineView {
|
||||||
if let Some(rows) = active_editor.highlighted_rows() {
|
if let Some(rows) = active_editor.highlighted_rows() {
|
||||||
let snapshot = active_editor.snapshot(cx).display_snapshot;
|
let snapshot = active_editor.snapshot(cx).display_snapshot;
|
||||||
let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot);
|
let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot);
|
||||||
active_editor.select_ranges([position..position], Some(Autoscroll::Center), cx);
|
active_editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_ranges([position..position], Some(Autoscroll::Center))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cx.emit(Event::Dismissed);
|
cx.emit(Event::Dismissed);
|
||||||
|
|
|
@ -145,11 +145,9 @@ impl ProjectSymbolsView {
|
||||||
|
|
||||||
let editor = workspace.open_project_item::<Editor>(buffer, cx);
|
let editor = workspace.open_project_item::<Editor>(buffer, cx);
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.select_ranges(
|
editor.change_selections(true, cx, |s| {
|
||||||
[position..position],
|
s.select_ranges([position..position], Some(Autoscroll::Center))
|
||||||
Some(Autoscroll::Center),
|
});
|
||||||
cx,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
|
|
|
@ -227,7 +227,8 @@ impl BufferSearchBar {
|
||||||
.display_snapshot;
|
.display_snapshot;
|
||||||
let selection = editor
|
let selection = editor
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.newest_selection_with_snapshot::<usize>(&display_map.buffer_snapshot);
|
.selections
|
||||||
|
.newest::<usize>(&display_map.buffer_snapshot);
|
||||||
|
|
||||||
let mut text: String;
|
let mut text: String;
|
||||||
if selection.start == selection.end {
|
if selection.start == selection.end {
|
||||||
|
@ -387,14 +388,16 @@ impl BufferSearchBar {
|
||||||
if let Some(ranges) = self.editors_with_matches.get(&cx.weak_handle()) {
|
if let Some(ranges) = self.editors_with_matches.get(&cx.weak_handle()) {
|
||||||
let new_index = match_index_for_direction(
|
let new_index = match_index_for_direction(
|
||||||
ranges,
|
ranges,
|
||||||
&editor.newest_anchor_selection().head(),
|
&editor.selections.newest_anchor().head(),
|
||||||
index,
|
index,
|
||||||
direction,
|
direction,
|
||||||
&editor.buffer().read(cx).read(cx),
|
&editor.buffer().read(cx).read(cx),
|
||||||
);
|
);
|
||||||
let range_to_select = ranges[new_index].clone();
|
let range_to_select = ranges[new_index].clone();
|
||||||
editor.unfold_ranges([range_to_select.clone()], false, cx);
|
editor.unfold_ranges([range_to_select.clone()], false, cx);
|
||||||
editor.select_ranges([range_to_select], Some(Autoscroll::Fit), cx);
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_ranges([range_to_select], Some(Autoscroll::Fit))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -535,11 +538,12 @@ impl BufferSearchBar {
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
if select_closest_match {
|
if select_closest_match {
|
||||||
if let Some(match_ix) = this.active_match_index {
|
if let Some(match_ix) = this.active_match_index {
|
||||||
editor.select_ranges(
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_ranges(
|
||||||
[ranges[match_ix].clone()],
|
[ranges[match_ix].clone()],
|
||||||
Some(Autoscroll::Fit),
|
Some(Autoscroll::Fit),
|
||||||
cx,
|
)
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -564,7 +568,7 @@ impl BufferSearchBar {
|
||||||
let editor = editor.read(cx);
|
let editor = editor.read(cx);
|
||||||
active_match_index(
|
active_match_index(
|
||||||
&ranges,
|
&ranges,
|
||||||
&editor.newest_anchor_selection().head(),
|
&editor.selections.newest_anchor().head(),
|
||||||
&editor.buffer().read(cx).read(cx),
|
&editor.buffer().read(cx).read(cx),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -721,7 +725,9 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.select_display_ranges(&[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)], cx);
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)])
|
||||||
|
});
|
||||||
});
|
});
|
||||||
search_bar.update(cx, |search_bar, cx| {
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
assert_eq!(search_bar.active_match_index, Some(0));
|
assert_eq!(search_bar.active_match_index, Some(0));
|
||||||
|
@ -804,7 +810,9 @@ mod tests {
|
||||||
// Park the cursor in between matches and ensure that going to the previous match selects
|
// Park the cursor in between matches and ensure that going to the previous match selects
|
||||||
// the closest match to the left.
|
// the closest match to the left.
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx);
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
|
||||||
|
});
|
||||||
});
|
});
|
||||||
search_bar.update(cx, |search_bar, cx| {
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
assert_eq!(search_bar.active_match_index, Some(1));
|
assert_eq!(search_bar.active_match_index, Some(1));
|
||||||
|
@ -821,7 +829,9 @@ mod tests {
|
||||||
// Park the cursor in between matches and ensure that going to the next match selects the
|
// Park the cursor in between matches and ensure that going to the next match selects the
|
||||||
// closest match to the right.
|
// closest match to the right.
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx);
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
|
||||||
|
});
|
||||||
});
|
});
|
||||||
search_bar.update(cx, |search_bar, cx| {
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
assert_eq!(search_bar.active_match_index, Some(1));
|
assert_eq!(search_bar.active_match_index, Some(1));
|
||||||
|
@ -838,7 +848,9 @@ mod tests {
|
||||||
// Park the cursor after the last match and ensure that going to the previous match selects
|
// Park the cursor after the last match and ensure that going to the previous match selects
|
||||||
// the last match.
|
// the last match.
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.select_display_ranges(&[DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)], cx);
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_display_ranges([DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)])
|
||||||
|
});
|
||||||
});
|
});
|
||||||
search_bar.update(cx, |search_bar, cx| {
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
assert_eq!(search_bar.active_match_index, Some(2));
|
assert_eq!(search_bar.active_match_index, Some(2));
|
||||||
|
@ -855,7 +867,9 @@ mod tests {
|
||||||
// Park the cursor after the last match and ensure that going to the next match selects the
|
// Park the cursor after the last match and ensure that going to the next match selects the
|
||||||
// first match.
|
// first match.
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.select_display_ranges(&[DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)], cx);
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_display_ranges([DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)])
|
||||||
|
});
|
||||||
});
|
});
|
||||||
search_bar.update(cx, |search_bar, cx| {
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
assert_eq!(search_bar.active_match_index, Some(2));
|
assert_eq!(search_bar.active_match_index, Some(2));
|
||||||
|
@ -872,7 +886,9 @@ mod tests {
|
||||||
// Park the cursor before the first match and ensure that going to the previous match
|
// Park the cursor before the first match and ensure that going to the previous match
|
||||||
// selects the last match.
|
// selects the last match.
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.select_display_ranges(&[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)], cx);
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)])
|
||||||
|
});
|
||||||
});
|
});
|
||||||
search_bar.update(cx, |search_bar, cx| {
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
assert_eq!(search_bar.active_match_index, Some(0));
|
assert_eq!(search_bar.active_match_index, Some(0));
|
||||||
|
|
|
@ -454,7 +454,7 @@ impl ProjectSearchView {
|
||||||
let results_editor = self.results_editor.read(cx);
|
let results_editor = self.results_editor.read(cx);
|
||||||
let new_index = match_index_for_direction(
|
let new_index = match_index_for_direction(
|
||||||
&model.match_ranges,
|
&model.match_ranges,
|
||||||
&results_editor.newest_anchor_selection().head(),
|
&results_editor.selections.newest_anchor().head(),
|
||||||
index,
|
index,
|
||||||
direction,
|
direction,
|
||||||
&results_editor.buffer().read(cx).read(cx),
|
&results_editor.buffer().read(cx).read(cx),
|
||||||
|
@ -462,7 +462,9 @@ impl ProjectSearchView {
|
||||||
let range_to_select = model.match_ranges[new_index].clone();
|
let range_to_select = model.match_ranges[new_index].clone();
|
||||||
self.results_editor.update(cx, |editor, cx| {
|
self.results_editor.update(cx, |editor, cx| {
|
||||||
editor.unfold_ranges([range_to_select.clone()], false, cx);
|
editor.unfold_ranges([range_to_select.clone()], false, cx);
|
||||||
editor.select_ranges([range_to_select], Some(Autoscroll::Fit), cx);
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_ranges([range_to_select], Some(Autoscroll::Fit))
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -476,8 +478,10 @@ impl ProjectSearchView {
|
||||||
|
|
||||||
fn focus_results_editor(&self, cx: &mut ViewContext<Self>) {
|
fn focus_results_editor(&self, cx: &mut ViewContext<Self>) {
|
||||||
self.query_editor.update(cx, |query_editor, cx| {
|
self.query_editor.update(cx, |query_editor, cx| {
|
||||||
let cursor = query_editor.newest_anchor_selection().head();
|
let cursor = query_editor.selections.newest_anchor().head();
|
||||||
query_editor.select_ranges([cursor.clone()..cursor], None, cx);
|
query_editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_ranges([cursor.clone()..cursor], None)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
cx.focus(&self.results_editor);
|
cx.focus(&self.results_editor);
|
||||||
}
|
}
|
||||||
|
@ -489,7 +493,9 @@ impl ProjectSearchView {
|
||||||
} else {
|
} else {
|
||||||
self.results_editor.update(cx, |editor, cx| {
|
self.results_editor.update(cx, |editor, cx| {
|
||||||
if reset_selections {
|
if reset_selections {
|
||||||
editor.select_ranges(match_ranges.first().cloned(), Some(Autoscroll::Fit), cx);
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_ranges(match_ranges.first().cloned(), Some(Autoscroll::Fit))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
editor.highlight_background::<Self>(
|
editor.highlight_background::<Self>(
|
||||||
match_ranges,
|
match_ranges,
|
||||||
|
@ -510,7 +516,7 @@ impl ProjectSearchView {
|
||||||
let results_editor = self.results_editor.read(cx);
|
let results_editor = self.results_editor.read(cx);
|
||||||
let new_index = active_match_index(
|
let new_index = active_match_index(
|
||||||
&self.model.read(cx).match_ranges,
|
&self.model.read(cx).match_ranges,
|
||||||
&results_editor.newest_anchor_selection().head(),
|
&results_editor.selections.newest_anchor().head(),
|
||||||
&results_editor.buffer().read(cx).read(cx),
|
&results_editor.buffer().read(cx).read(cx),
|
||||||
);
|
);
|
||||||
if self.active_match_index != new_index {
|
if self.active_match_index != new_index {
|
||||||
|
|
|
@ -13,11 +13,13 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
fn normal_before(_: &mut Workspace, _: &NormalBefore, cx: &mut ViewContext<Workspace>) {
|
fn normal_before(_: &mut Workspace, _: &NormalBefore, cx: &mut ViewContext<Workspace>) {
|
||||||
Vim::update(cx, |state, cx| {
|
Vim::update(cx, |state, cx| {
|
||||||
state.update_active_editor(cx, |editor, cx| {
|
state.update_active_editor(cx, |editor, cx| {
|
||||||
editor.move_cursors(cx, |map, mut cursor, _| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_cursors_with(|map, mut cursor, _| {
|
||||||
*cursor.column_mut() = cursor.column().saturating_sub(1);
|
*cursor.column_mut() = cursor.column().saturating_sub(1);
|
||||||
(map.clip_point(cursor, Bias::Left), SelectionGoal::None)
|
(map.clip_point(cursor, Bias::Left), SelectionGoal::None)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
state.switch_mode(Mode::Normal, cx);
|
state.switch_mode(Mode::Normal, cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,9 @@ pub fn normal_motion(motion: Motion, cx: &mut MutableAppContext) {
|
||||||
|
|
||||||
fn move_cursor(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
|
fn move_cursor(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.move_cursors(cx, |map, cursor, goal| motion.move_point(map, cursor, goal))
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_cursors_with(|map, cursor, goal| motion.move_point(map, cursor, goal))
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,11 +86,13 @@ fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspa
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.switch_mode(Mode::Insert, cx);
|
vim.switch_mode(Mode::Insert, cx);
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.move_cursors(cx, |map, cursor, goal| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_cursors_with(|map, cursor, goal| {
|
||||||
Motion::Right.move_point(map, cursor, goal)
|
Motion::Right.move_point(map, cursor, goal)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_first_non_whitespace(
|
fn insert_first_non_whitespace(
|
||||||
|
@ -99,22 +103,26 @@ fn insert_first_non_whitespace(
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.switch_mode(Mode::Insert, cx);
|
vim.switch_mode(Mode::Insert, cx);
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.move_cursors(cx, |map, cursor, goal| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_cursors_with(|map, cursor, goal| {
|
||||||
Motion::FirstNonWhitespace.move_point(map, cursor, goal)
|
Motion::FirstNonWhitespace.move_point(map, cursor, goal)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewContext<Workspace>) {
|
fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewContext<Workspace>) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.switch_mode(Mode::Insert, cx);
|
vim.switch_mode(Mode::Insert, cx);
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.move_cursors(cx, |map, cursor, goal| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_cursors_with(|map, cursor, goal| {
|
||||||
Motion::EndOfLine.move_point(map, cursor, goal)
|
Motion::EndOfLine.move_point(map, cursor, goal)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContext<Workspace>) {
|
fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContext<Workspace>) {
|
||||||
|
@ -137,7 +145,8 @@ fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContex
|
||||||
(start_of_line..start_of_line, new_text)
|
(start_of_line..start_of_line, new_text)
|
||||||
});
|
});
|
||||||
editor.edit_with_autoindent(edits, cx);
|
editor.edit_with_autoindent(edits, cx);
|
||||||
editor.move_cursors(cx, |map, mut cursor, _| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_cursors_with(|map, mut cursor, _| {
|
||||||
*cursor.row_mut() -= 1;
|
*cursor.row_mut() -= 1;
|
||||||
*cursor.column_mut() = map.line_len(cursor.row());
|
*cursor.column_mut() = map.line_len(cursor.row());
|
||||||
(map.clip_point(cursor, Bias::Left), SelectionGoal::None)
|
(map.clip_point(cursor, Bias::Left), SelectionGoal::None)
|
||||||
|
@ -145,6 +154,7 @@ fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContex
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext<Workspace>) {
|
fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext<Workspace>) {
|
||||||
|
@ -166,9 +176,11 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex
|
||||||
new_text.push_str(&" ".repeat(indent as usize));
|
new_text.push_str(&" ".repeat(indent as usize));
|
||||||
(end_of_line..end_of_line, new_text)
|
(end_of_line..end_of_line, new_text)
|
||||||
});
|
});
|
||||||
editor.move_cursors(cx, |map, cursor, goal| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_cursors_with(|map, cursor, goal| {
|
||||||
Motion::EndOfLine.move_point(map, cursor, goal)
|
Motion::EndOfLine.move_point(map, cursor, goal)
|
||||||
});
|
});
|
||||||
|
});
|
||||||
editor.edit_with_autoindent(edits, cx);
|
editor.edit_with_autoindent(edits, cx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,9 +22,11 @@ pub fn change_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
editor.move_selections(cx, |map, selection| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
motion.expand_selection(map, selection, false);
|
motion.expand_selection(map, selection, false);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
editor.insert(&"", cx);
|
editor.insert(&"", cx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -46,18 +48,23 @@ fn change_word(
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
editor.move_selections(cx, |map, selection| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
if selection.end.column() == map.line_len(selection.end.row()) {
|
if selection.end.column() == map.line_len(selection.end.row()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.end = movement::find_boundary(map, selection.end, |left, right| {
|
selection.end =
|
||||||
let left_kind = char_kind(left).coerce_punctuation(ignore_punctuation);
|
movement::find_boundary(map, selection.end, |left, right| {
|
||||||
let right_kind = char_kind(right).coerce_punctuation(ignore_punctuation);
|
let left_kind =
|
||||||
|
char_kind(left).coerce_punctuation(ignore_punctuation);
|
||||||
|
let right_kind =
|
||||||
|
char_kind(right).coerce_punctuation(ignore_punctuation);
|
||||||
|
|
||||||
left_kind != right_kind || left == '\n' || right == '\n'
|
left_kind != right_kind || left == '\n' || right == '\n'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
editor.insert(&"", cx);
|
editor.insert(&"", cx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,16 +8,19 @@ pub fn delete_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
let mut original_columns: HashMap<_, _> = Default::default();
|
let mut original_columns: HashMap<_, _> = Default::default();
|
||||||
editor.move_selections(cx, |map, selection| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
let original_head = selection.head();
|
let original_head = selection.head();
|
||||||
motion.expand_selection(map, selection, true);
|
motion.expand_selection(map, selection, true);
|
||||||
original_columns.insert(selection.id, original_head.column());
|
original_columns.insert(selection.id, original_head.column());
|
||||||
});
|
});
|
||||||
|
});
|
||||||
editor.insert(&"", cx);
|
editor.insert(&"", cx);
|
||||||
|
|
||||||
// Fixup cursor position after the deletion
|
// Fixup cursor position after the deletion
|
||||||
editor.set_clip_at_line_ends(true, cx);
|
editor.set_clip_at_line_ends(true, cx);
|
||||||
editor.move_selections(cx, |map, selection| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
let mut cursor = selection.head();
|
let mut cursor = selection.head();
|
||||||
if motion.linewise() {
|
if motion.linewise() {
|
||||||
if let Some(column) = original_columns.get(&selection.id) {
|
if let Some(column) = original_columns.get(&selection.id) {
|
||||||
|
@ -29,6 +32,7 @@ pub fn delete_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -128,7 +128,9 @@ impl<'a> VimTestContext<'a> {
|
||||||
let (unmarked_text, markers) = marked_text(&text);
|
let (unmarked_text, markers) = marked_text(&text);
|
||||||
editor.set_text(unmarked_text, cx);
|
editor.set_text(unmarked_text, cx);
|
||||||
let cursor_offset = markers[0];
|
let cursor_offset = markers[0];
|
||||||
editor.replace_selections_with(cx, |map| cursor_offset.to_display_point(map));
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.replace_cursors_with(|map| vec![cursor_offset.to_display_point(map)])
|
||||||
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +199,8 @@ impl<'a> VimTestContext<'a> {
|
||||||
let (empty_selections, reverse_selections, forward_selections) =
|
let (empty_selections, reverse_selections, forward_selections) =
|
||||||
self.editor.read_with(self.cx, |editor, cx| {
|
self.editor.read_with(self.cx, |editor, cx| {
|
||||||
let (empty_selections, non_empty_selections): (Vec<_>, Vec<_>) = editor
|
let (empty_selections, non_empty_selections): (Vec<_>, Vec<_>) = editor
|
||||||
.local_selections::<usize>(cx)
|
.selections
|
||||||
|
.interleaved::<usize>(&editor.buffer().read(cx).read(cx))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.partition_map(|selection| {
|
.partition_map(|selection| {
|
||||||
if selection.is_empty() {
|
if selection.is_empty() {
|
||||||
|
|
|
@ -14,7 +14,8 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
pub fn visual_motion(motion: Motion, cx: &mut MutableAppContext) {
|
pub fn visual_motion(motion: Motion, cx: &mut MutableAppContext) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.move_selections(cx, |map, selection| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
let (new_head, goal) = motion.move_point(map, selection.head(), selection.goal);
|
let (new_head, goal) = motion.move_point(map, selection.head(), selection.goal);
|
||||||
let new_head = map.clip_at_line_end(new_head);
|
let new_head = map.clip_at_line_end(new_head);
|
||||||
let was_reversed = selection.reversed;
|
let was_reversed = selection.reversed;
|
||||||
|
@ -34,13 +35,15 @@ pub fn visual_motion(motion: Motion, cx: &mut MutableAppContext) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspace>) {
|
pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspace>) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
editor.move_selections(cx, |map, selection| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
if !selection.reversed {
|
if !selection.reversed {
|
||||||
// Head was at the end of the selection, and now is at the start. We need to move the end
|
// Head was at the end of the selection, and now is at the start. We need to move the end
|
||||||
// forward by one if possible in order to compensate for this change.
|
// forward by one if possible in order to compensate for this change.
|
||||||
|
@ -48,6 +51,7 @@ pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspac
|
||||||
selection.end = map.clip_point(selection.end, Bias::Left);
|
selection.end = map.clip_point(selection.end, Bias::Left);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
editor.insert("", cx);
|
editor.insert("", cx);
|
||||||
});
|
});
|
||||||
vim.switch_mode(Mode::Insert, cx);
|
vim.switch_mode(Mode::Insert, cx);
|
||||||
|
@ -59,7 +63,8 @@ pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspac
|
||||||
vim.switch_mode(Mode::Normal, cx);
|
vim.switch_mode(Mode::Normal, cx);
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
editor.move_selections(cx, |map, selection| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
if !selection.reversed {
|
if !selection.reversed {
|
||||||
// Head was at the end of the selection, and now is at the start. We need to move the end
|
// Head was at the end of the selection, and now is at the start. We need to move the end
|
||||||
// forward by one if possible in order to compensate for this change.
|
// forward by one if possible in order to compensate for this change.
|
||||||
|
@ -67,17 +72,20 @@ pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspac
|
||||||
selection.end = map.clip_point(selection.end, Bias::Left);
|
selection.end = map.clip_point(selection.end, Bias::Left);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
editor.insert("", cx);
|
editor.insert("", cx);
|
||||||
|
|
||||||
// Fixup cursor position after the deletion
|
// Fixup cursor position after the deletion
|
||||||
editor.set_clip_at_line_ends(true, cx);
|
editor.set_clip_at_line_ends(true, cx);
|
||||||
editor.move_selections(cx, |map, selection| {
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
let mut cursor = selection.head();
|
let mut cursor = selection.head();
|
||||||
cursor = map.clip_point(cursor, Bias::Left);
|
cursor = map.clip_point(cursor, Bias::Left);
|
||||||
selection.collapse_to(cursor, selection.goal)
|
selection.collapse_to(cursor, selection.goal)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -962,7 +962,9 @@ mod tests {
|
||||||
.downcast::<Editor>()
|
.downcast::<Editor>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
editor1.update(cx, |editor, cx| {
|
editor1.update(cx, |editor, cx| {
|
||||||
editor.select_display_ranges(&[DisplayPoint::new(10, 0)..DisplayPoint::new(10, 0)], cx);
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_display_ranges([DisplayPoint::new(10, 0)..DisplayPoint::new(10, 0)])
|
||||||
|
});
|
||||||
});
|
});
|
||||||
let editor2 = workspace
|
let editor2 = workspace
|
||||||
.update(cx, |w, cx| w.open_path(file2.clone(), true, cx))
|
.update(cx, |w, cx| w.open_path(file2.clone(), true, cx))
|
||||||
|
@ -979,10 +981,9 @@ mod tests {
|
||||||
|
|
||||||
editor3
|
editor3
|
||||||
.update(cx, |editor, cx| {
|
.update(cx, |editor, cx| {
|
||||||
editor.select_display_ranges(
|
editor.change_selections(true, cx, |s| {
|
||||||
&[DisplayPoint::new(12, 0)..DisplayPoint::new(12, 0)],
|
s.select_display_ranges([DisplayPoint::new(12, 0)..DisplayPoint::new(12, 0)])
|
||||||
cx,
|
});
|
||||||
);
|
|
||||||
editor.newline(&Default::default(), cx);
|
editor.newline(&Default::default(), cx);
|
||||||
editor.newline(&Default::default(), cx);
|
editor.newline(&Default::default(), cx);
|
||||||
editor.move_down(&Default::default(), cx);
|
editor.move_down(&Default::default(), cx);
|
||||||
|
@ -1123,34 +1124,37 @@ mod tests {
|
||||||
// Modify file to collapse multiple nav history entries into the same location.
|
// Modify file to collapse multiple nav history entries into the same location.
|
||||||
// Ensure we don't visit the same location twice when navigating.
|
// Ensure we don't visit the same location twice when navigating.
|
||||||
editor1.update(cx, |editor, cx| {
|
editor1.update(cx, |editor, cx| {
|
||||||
editor.select_display_ranges(&[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)], cx)
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_display_ranges([DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)])
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
editor1.update(cx, |editor, cx| {
|
editor1.update(cx, |editor, cx| {
|
||||||
editor
|
editor.change_selections(true, cx, |s| {
|
||||||
.select_display_ranges(&[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)], cx);
|
s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
|
||||||
|
});
|
||||||
});
|
});
|
||||||
editor1.update(cx, |editor, cx| {
|
editor1.update(cx, |editor, cx| {
|
||||||
editor.select_display_ranges(
|
editor.change_selections(true, cx, |s| {
|
||||||
&[DisplayPoint::new(13, 0)..DisplayPoint::new(13, 0)],
|
s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 0)])
|
||||||
cx,
|
})
|
||||||
)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
editor1.update(cx, |editor, cx| {
|
editor1.update(cx, |editor, cx| {
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
editor.select_display_ranges(
|
editor.change_selections(true, cx, |s| {
|
||||||
&[DisplayPoint::new(2, 0)..DisplayPoint::new(14, 0)],
|
s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(14, 0)])
|
||||||
cx,
|
});
|
||||||
);
|
|
||||||
editor.insert("", cx);
|
editor.insert("", cx);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
editor1.update(cx, |editor, cx| {
|
editor1.update(cx, |editor, cx| {
|
||||||
editor.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx)
|
editor.change_selections(true, cx, |s| {
|
||||||
|
s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
|
||||||
|
})
|
||||||
});
|
});
|
||||||
workspace
|
workspace
|
||||||
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue