Add editor selection utilities

Also improved test that was failing during above to use marker text approach as a drive by
This commit is contained in:
Keith Simmons 2022-03-22 16:05:01 -07:00
parent 308bfa3486
commit 6a1c197c1b
2 changed files with 326 additions and 384 deletions

View file

@ -88,8 +88,8 @@ action!(MoveUp);
action!(MoveDown); action!(MoveDown);
action!(MoveLeft); action!(MoveLeft);
action!(MoveRight); action!(MoveRight);
action!(MoveToPreviousWordBoundary); action!(MoveToPreviousWordStart);
action!(MoveToNextWordBoundary); action!(MoveToNextWordEnd);
action!(MoveToBeginningOfLine); action!(MoveToBeginningOfLine);
action!(MoveToEndOfLine); action!(MoveToEndOfLine);
action!(MoveToBeginning); action!(MoveToBeginning);
@ -200,10 +200,10 @@ pub fn init(cx: &mut MutableAppContext) {
Binding::new("ctrl-n", MoveDown, Some("Editor")), Binding::new("ctrl-n", MoveDown, Some("Editor")),
Binding::new("ctrl-b", MoveLeft, Some("Editor")), Binding::new("ctrl-b", MoveLeft, Some("Editor")),
Binding::new("ctrl-f", MoveRight, Some("Editor")), Binding::new("ctrl-f", MoveRight, Some("Editor")),
Binding::new("alt-left", MoveToPreviousWordBoundary, Some("Editor")), Binding::new("alt-left", MoveToPreviousWordStart, Some("Editor")),
Binding::new("alt-b", MoveToPreviousWordBoundary, Some("Editor")), Binding::new("alt-b", MoveToPreviousWordStart, Some("Editor")),
Binding::new("alt-right", MoveToNextWordBoundary, Some("Editor")), Binding::new("alt-right", MoveToNextWordEnd, Some("Editor")),
Binding::new("alt-f", MoveToNextWordBoundary, Some("Editor")), Binding::new("alt-f", MoveToNextWordEnd, Some("Editor")),
Binding::new("cmd-left", MoveToBeginningOfLine, Some("Editor")), Binding::new("cmd-left", MoveToBeginningOfLine, Some("Editor")),
Binding::new("ctrl-a", MoveToBeginningOfLine, Some("Editor")), Binding::new("ctrl-a", MoveToBeginningOfLine, Some("Editor")),
Binding::new("cmd-right", MoveToEndOfLine, Some("Editor")), Binding::new("cmd-right", MoveToEndOfLine, Some("Editor")),
@ -281,8 +281,8 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Editor::tab); cx.add_action(Editor::tab);
cx.add_action(Editor::outdent); cx.add_action(Editor::outdent);
cx.add_action(Editor::delete_line); cx.add_action(Editor::delete_line);
cx.add_action(Editor::delete_to_previous_word_boundary); cx.add_action(Editor::delete_to_previous_word_start);
cx.add_action(Editor::delete_to_next_word_boundary); cx.add_action(Editor::delete_to_next_word_end);
cx.add_action(Editor::delete_to_beginning_of_line); cx.add_action(Editor::delete_to_beginning_of_line);
cx.add_action(Editor::delete_to_end_of_line); cx.add_action(Editor::delete_to_end_of_line);
cx.add_action(Editor::cut_to_end_of_line); cx.add_action(Editor::cut_to_end_of_line);
@ -298,8 +298,8 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Editor::move_down); cx.add_action(Editor::move_down);
cx.add_action(Editor::move_left); cx.add_action(Editor::move_left);
cx.add_action(Editor::move_right); cx.add_action(Editor::move_right);
cx.add_action(Editor::move_to_previous_word_boundary); cx.add_action(Editor::move_to_previous_word_start);
cx.add_action(Editor::move_to_next_word_boundary); cx.add_action(Editor::move_to_next_word_end);
cx.add_action(Editor::move_to_beginning_of_line); cx.add_action(Editor::move_to_beginning_of_line);
cx.add_action(Editor::move_to_end_of_line); cx.add_action(Editor::move_to_end_of_line);
cx.add_action(Editor::move_to_beginning); cx.add_action(Editor::move_to_beginning);
@ -308,8 +308,8 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Editor::select_down); cx.add_action(Editor::select_down);
cx.add_action(Editor::select_left); cx.add_action(Editor::select_left);
cx.add_action(Editor::select_right); cx.add_action(Editor::select_right);
cx.add_action(Editor::select_to_previous_word_boundary); cx.add_action(Editor::select_to_previous_word_start);
cx.add_action(Editor::select_to_next_word_boundary); cx.add_action(Editor::select_to_next_word_end);
cx.add_action(Editor::select_to_beginning_of_line); cx.add_action(Editor::select_to_beginning_of_line);
cx.add_action(Editor::select_to_end_of_line); cx.add_action(Editor::select_to_end_of_line);
cx.add_action(Editor::select_to_beginning); cx.add_action(Editor::select_to_beginning);
@ -345,14 +345,6 @@ pub fn init(cx: &mut MutableAppContext) {
}); });
} }
trait SelectionExt {
fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range<usize>;
fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point>;
fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
fn spanned_rows(&self, include_end_if_at_line_start: bool, map: &DisplaySnapshot)
-> Range<u32>;
}
trait InvalidationRegion { trait InvalidationRegion {
fn ranges(&self) -> &[Range<Anchor>]; fn ranges(&self) -> &[Range<Anchor>];
} }
@ -1202,6 +1194,66 @@ impl Editor {
} }
} }
pub fn move_selections(
&mut self,
cx: &mut ViewContext<Self>,
move_selection: impl Fn(&DisplaySnapshot, &mut Selection<DisplayPoint>),
) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self
.local_selections::<Point>(cx)
.into_iter()
.map(|selection| {
let mut selection = Selection {
id: selection.id,
start: selection.start.to_display_point(&display_map),
end: selection.end.to_display_point(&display_map),
reversed: selection.reversed,
goal: selection.goal,
};
move_selection(&display_map, &mut selection);
Selection {
id: selection.id,
start: selection.start.to_point(&display_map),
end: selection.end.to_point(&display_map),
reversed: selection.reversed,
goal: selection.goal,
}
})
.collect();
self.update_selections(selections, Some(Autoscroll::Fit), cx);
}
pub fn move_selection_heads(
&mut self,
cx: &mut ViewContext<Self>,
update_head: impl Fn(
&DisplaySnapshot,
DisplayPoint,
SelectionGoal,
) -> (DisplayPoint, SelectionGoal),
) {
self.move_selections(cx, |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(
&mut self,
cx: &mut ViewContext<Self>,
update_cursor_position: impl Fn(
&DisplaySnapshot,
DisplayPoint,
SelectionGoal,
) -> (DisplayPoint, SelectionGoal),
) {
self.move_selections(cx, |map, selection| {
let (cursor, new_goal) = update_cursor_position(map, selection.head(), selection.goal);
selection.collapse_to(cursor, new_goal)
});
}
fn select(&mut self, Select(phase): &Select, cx: &mut ViewContext<Self>) { fn select(&mut self, Select(phase): &Select, cx: &mut ViewContext<Self>) {
self.hide_context_menu(cx); self.hide_context_menu(cx);
@ -2678,8 +2730,7 @@ impl Editor {
} }
} }
selection.set_head(new_head); selection.set_head(new_head, SelectionGoal::None);
selection.goal = SelectionGoal::None;
} }
} }
self.update_selections(selections, Some(Autoscroll::Fit), cx); self.update_selections(selections, Some(Autoscroll::Fit), cx);
@ -2688,20 +2739,15 @@ impl Editor {
} }
pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) { pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
self.start_transaction(cx); self.transact(cx, |this, cx| {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); this.move_selections(cx, |map, selection| {
let mut selections = self.local_selections::<Point>(cx); if selection.is_empty() {
for selection in &mut selections { let cursor = movement::right(map, selection.head());
if selection.is_empty() { selection.set_head(cursor, SelectionGoal::None);
let head = selection.head().to_display_point(&display_map); }
let cursor = movement::right(&display_map, head).to_point(&display_map); });
selection.set_head(cursor); this.insert(&"", cx);
selection.goal = SelectionGoal::None; });
}
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
self.insert(&"", cx);
self.end_transaction(cx);
} }
pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) { pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
@ -3303,67 +3349,37 @@ impl Editor {
} }
pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) { pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_selections(cx, |map, selection| {
let mut selections = self.local_selections::<Point>(cx); let cursor = if selection.is_empty() {
for selection in &mut selections { movement::left(map, selection.start)
let start = selection.start.to_display_point(&display_map);
let end = selection.end.to_display_point(&display_map);
if start != end {
selection.end = selection.start.clone();
} else { } else {
let cursor = movement::left(&display_map, start).to_point(&display_map); selection.start
selection.start = cursor.clone(); };
selection.end = cursor; selection.collapse_to(cursor, SelectionGoal::None);
} });
selection.reversed = false;
selection.goal = SelectionGoal::None;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) { pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_selection_heads(cx, |map, head, _| {
let mut selections = self.local_selections::<Point>(cx); (movement::left(map, head), SelectionGoal::None)
for selection in &mut selections { });
let head = selection.head().to_display_point(&display_map);
let cursor = movement::left(&display_map, head).to_point(&display_map);
selection.set_head(cursor);
selection.goal = SelectionGoal::None;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext<Self>) { pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_selections(cx, |map, selection| {
let mut selections = self.local_selections::<Point>(cx); let cursor = if selection.is_empty() {
for selection in &mut selections { movement::right(map, selection.end)
let start = selection.start.to_display_point(&display_map);
let end = selection.end.to_display_point(&display_map);
if start != end {
selection.start = selection.end.clone();
} else { } else {
let cursor = movement::right(&display_map, end).to_point(&display_map); selection.end
selection.start = cursor; };
selection.end = cursor; selection.collapse_to(cursor, SelectionGoal::None)
} });
selection.reversed = false;
selection.goal = SelectionGoal::None;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) { pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_selection_heads(cx, |map, head, _| {
let mut selections = self.local_selections::<Point>(cx); (movement::right(map, head), SelectionGoal::None)
for selection in &mut selections { });
let head = selection.head().to_display_point(&display_map);
let cursor = movement::right(&display_map, head).to_point(&display_map);
selection.set_head(cursor);
selection.goal = SelectionGoal::None;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) { pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
@ -3382,36 +3398,17 @@ impl Editor {
return; return;
} }
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_selections(cx, |map, selection| {
let mut selections = self.local_selections::<Point>(cx); if !selection.is_empty() {
for selection in &mut selections {
let start = selection.start.to_display_point(&display_map);
let end = selection.end.to_display_point(&display_map);
if start != end {
selection.goal = SelectionGoal::None; selection.goal = SelectionGoal::None;
} }
let (cursor, goal) = movement::up(&map, selection.start, selection.goal);
let (start, goal) = movement::up(&display_map, start, selection.goal); selection.collapse_to(cursor, goal);
let cursor = start.to_point(&display_map); });
selection.start = cursor;
selection.end = cursor;
selection.goal = goal;
selection.reversed = false;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) { pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_selection_heads(cx, movement::up)
let mut selections = self.local_selections::<Point>(cx);
for selection in &mut selections {
let head = selection.head().to_display_point(&display_map);
let (head, goal) = movement::up(&display_map, head, selection.goal);
let cursor = head.to_point(&display_map);
selection.set_head(cursor);
selection.goal = goal;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) { pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
@ -3428,147 +3425,91 @@ impl Editor {
return; return;
} }
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_selections(cx, |map, selection| {
let mut selections = self.local_selections::<Point>(cx); if !selection.is_empty() {
for selection in &mut selections {
let start = selection.start.to_display_point(&display_map);
let end = selection.end.to_display_point(&display_map);
if start != end {
selection.goal = SelectionGoal::None; selection.goal = SelectionGoal::None;
} }
let (cursor, goal) = movement::down(&map, selection.end, selection.goal);
let (start, goal) = movement::down(&display_map, end, selection.goal); selection.collapse_to(cursor, goal);
let cursor = start.to_point(&display_map); });
selection.start = cursor;
selection.end = cursor;
selection.goal = goal;
selection.reversed = false;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) { pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_selection_heads(cx, movement::down)
let mut selections = self.local_selections::<Point>(cx);
for selection in &mut selections {
let head = selection.head().to_display_point(&display_map);
let (head, goal) = movement::down(&display_map, head, selection.goal);
let cursor = head.to_point(&display_map);
selection.set_head(cursor);
selection.goal = goal;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn move_to_previous_word_boundary( pub fn move_to_previous_word_start(
&mut self, &mut self,
_: &MoveToPreviousWordBoundary, _: &MoveToPreviousWordStart,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_cursors(cx, |map, head, _| {
let mut selections = self.local_selections::<Point>(cx); (
for selection in &mut selections { movement::previous_word_start(map, head),
let head = selection.head().to_display_point(&display_map); SelectionGoal::None,
let cursor = movement::previous_word_start(&display_map, head).to_point(&display_map); )
selection.start = cursor.clone(); });
selection.end = cursor;
selection.reversed = false;
selection.goal = SelectionGoal::None;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn select_to_previous_word_boundary( pub fn select_to_previous_word_start(
&mut self, &mut self,
_: &SelectToPreviousWordBoundary, _: &SelectToPreviousWordBoundary,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_selection_heads(cx, |map, head, _| {
let mut selections = self.local_selections::<Point>(cx); (
for selection in &mut selections { movement::previous_word_start(map, head),
let head = selection.head().to_display_point(&display_map); SelectionGoal::None,
let cursor = movement::previous_word_start(&display_map, head).to_point(&display_map); )
selection.set_head(cursor); });
selection.goal = SelectionGoal::None;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn delete_to_previous_word_boundary( pub fn delete_to_previous_word_start(
&mut self, &mut self,
_: &DeleteToPreviousWordBoundary, _: &DeleteToPreviousWordBoundary,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.start_transaction(cx); self.transact(cx, |this, cx| {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); this.move_selections(cx, |map, selection| {
let mut selections = self.local_selections::<Point>(cx); if selection.is_empty() {
for selection in &mut selections { let cursor = movement::previous_word_start(map, selection.head());
if selection.is_empty() { selection.set_head(cursor, SelectionGoal::None);
let head = selection.head().to_display_point(&display_map); }
let cursor = });
movement::previous_word_start(&display_map, head).to_point(&display_map); this.insert("", cx);
selection.set_head(cursor); });
selection.goal = SelectionGoal::None;
}
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
self.insert("", cx);
self.end_transaction(cx);
} }
pub fn move_to_next_word_boundary( pub fn move_to_next_word_end(&mut self, _: &MoveToNextWordEnd, cx: &mut ViewContext<Self>) {
&mut self, self.move_cursors(cx, |map, head, _| {
_: &MoveToNextWordBoundary, (movement::next_word_end(map, head), SelectionGoal::None)
cx: &mut ViewContext<Self>, });
) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.local_selections::<Point>(cx);
for selection in &mut selections {
let head = selection.head().to_display_point(&display_map);
let cursor = movement::next_word_end(&display_map, head).to_point(&display_map);
selection.start = cursor;
selection.end = cursor;
selection.reversed = false;
selection.goal = SelectionGoal::None;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn select_to_next_word_boundary( pub fn select_to_next_word_end(
&mut self, &mut self,
_: &SelectToNextWordBoundary, _: &SelectToNextWordBoundary,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_selection_heads(cx, |map, head, _| {
let mut selections = self.local_selections::<Point>(cx); (movement::next_word_end(map, head), SelectionGoal::None)
for selection in &mut selections { });
let head = selection.head().to_display_point(&display_map);
let cursor = movement::next_word_end(&display_map, head).to_point(&display_map);
selection.set_head(cursor);
selection.goal = SelectionGoal::None;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn delete_to_next_word_boundary( pub fn delete_to_next_word_end(
&mut self, &mut self,
_: &DeleteToNextWordBoundary, _: &DeleteToNextWordBoundary,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.start_transaction(cx); self.transact(cx, |this, cx| {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); this.move_selections(cx, |map, selection| {
let mut selections = self.local_selections::<Point>(cx); if selection.is_empty() {
for selection in &mut selections { let cursor = movement::next_word_end(map, selection.head());
if selection.is_empty() { selection.set_head(cursor, SelectionGoal::None);
let head = selection.head().to_display_point(&display_map); }
let cursor = movement::next_word_end(&display_map, head).to_point(&display_map); });
selection.set_head(cursor); this.insert("", cx);
selection.goal = SelectionGoal::None; });
}
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
self.insert("", cx);
self.end_transaction(cx);
} }
pub fn move_to_beginning_of_line( pub fn move_to_beginning_of_line(
@ -3576,18 +3517,12 @@ impl Editor {
_: &MoveToBeginningOfLine, _: &MoveToBeginningOfLine,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_cursors(cx, |map, head, _| {
let mut selections = self.local_selections::<Point>(cx); (
for selection in &mut selections { movement::line_beginning(map, head, true),
let head = selection.head().to_display_point(&display_map); SelectionGoal::None,
let new_head = movement::line_beginning(&display_map, head, true); )
let cursor = new_head.to_point(&display_map); });
selection.start = cursor;
selection.end = cursor;
selection.reversed = false;
selection.goal = SelectionGoal::None;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn select_to_beginning_of_line( pub fn select_to_beginning_of_line(
@ -3595,15 +3530,12 @@ impl Editor {
SelectToBeginningOfLine(stop_at_soft_boundaries): &SelectToBeginningOfLine, SelectToBeginningOfLine(stop_at_soft_boundaries): &SelectToBeginningOfLine,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_selection_heads(cx, |map, head, _| {
let mut selections = self.local_selections::<Point>(cx); (
for selection in &mut selections { movement::line_beginning(map, head, *stop_at_soft_boundaries),
let head = selection.head().to_display_point(&display_map); SelectionGoal::None,
let new_head = movement::line_beginning(&display_map, head, *stop_at_soft_boundaries); )
selection.set_head(new_head.to_point(&display_map)); });
selection.goal = SelectionGoal::None;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn delete_to_beginning_of_line( pub fn delete_to_beginning_of_line(
@ -3611,27 +3543,16 @@ impl Editor {
_: &DeleteToBeginningOfLine, _: &DeleteToBeginningOfLine,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.start_transaction(cx); self.transact(cx, |this, cx| {
self.select_to_beginning_of_line(&SelectToBeginningOfLine(false), cx); this.select_to_beginning_of_line(&SelectToBeginningOfLine(false), cx);
self.backspace(&Backspace, cx); this.backspace(&Backspace, cx);
self.end_transaction(cx); });
} }
pub fn move_to_end_of_line(&mut self, _: &MoveToEndOfLine, cx: &mut ViewContext<Self>) { pub fn move_to_end_of_line(&mut self, _: &MoveToEndOfLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_cursors(cx, |map, head, _| {
let mut selections = self.local_selections::<Point>(cx); (movement::line_end(map, head, true), SelectionGoal::None)
{ });
for selection in &mut selections {
let head = selection.head().to_display_point(&display_map);
let new_head = movement::line_end(&display_map, head, true);
let anchor = new_head.to_point(&display_map);
selection.start = anchor.clone();
selection.end = anchor;
selection.reversed = false;
selection.goal = SelectionGoal::None;
}
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn select_to_end_of_line( pub fn select_to_end_of_line(
@ -3639,29 +3560,26 @@ impl Editor {
SelectToEndOfLine(stop_at_soft_boundaries): &SelectToEndOfLine, SelectToEndOfLine(stop_at_soft_boundaries): &SelectToEndOfLine,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); self.move_selection_heads(cx, |map, head, _| {
let mut selections = self.local_selections::<Point>(cx); (
for selection in &mut selections { movement::line_end(map, head, *stop_at_soft_boundaries),
let head = selection.head().to_display_point(&display_map); SelectionGoal::None,
let new_head = movement::line_end(&display_map, head, *stop_at_soft_boundaries); )
selection.set_head(new_head.to_point(&display_map)); });
selection.goal = SelectionGoal::None;
}
self.update_selections(selections, Some(Autoscroll::Fit), cx);
} }
pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext<Self>) { pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext<Self>) {
self.start_transaction(cx); self.transact(cx, |this, cx| {
self.select_to_end_of_line(&SelectToEndOfLine(false), cx); this.select_to_end_of_line(&SelectToEndOfLine(false), cx);
self.delete(&Delete, cx); this.delete(&Delete, cx);
self.end_transaction(cx); });
} }
pub fn cut_to_end_of_line(&mut self, _: &CutToEndOfLine, cx: &mut ViewContext<Self>) { pub fn cut_to_end_of_line(&mut self, _: &CutToEndOfLine, cx: &mut ViewContext<Self>) {
self.start_transaction(cx); self.transact(cx, |this, cx| {
self.select_to_end_of_line(&SelectToEndOfLine(false), cx); this.select_to_end_of_line(&SelectToEndOfLine(false), cx);
self.cut(&Cut, cx); this.cut(&Cut, cx);
self.end_transaction(cx); });
} }
pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) { pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
@ -3682,7 +3600,7 @@ impl Editor {
pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext<Self>) { pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext<Self>) {
let mut selection = self.local_selections::<Point>(cx).last().unwrap().clone(); let mut selection = self.local_selections::<Point>(cx).last().unwrap().clone();
selection.set_head(Point::zero()); selection.set_head(Point::zero(), SelectionGoal::None);
self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); self.update_selections(vec![selection], Some(Autoscroll::Fit), cx);
} }
@ -3739,7 +3657,7 @@ impl Editor {
pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) { pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
let mut selection = self.local_selections::<usize>(cx).first().unwrap().clone(); let mut selection = self.local_selections::<usize>(cx).first().unwrap().clone();
selection.set_head(self.buffer.read(cx).read(cx).len()); selection.set_head(self.buffer.read(cx).read(cx).len(), SelectionGoal::None);
self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); self.update_selections(vec![selection], Some(Autoscroll::Fit), cx);
} }
@ -5082,6 +5000,16 @@ impl Editor {
cx.notify(); cx.notify();
} }
fn transact(
&mut self,
cx: &mut ViewContext<Self>,
update: impl FnOnce(&mut Self, &mut ViewContext<Self>),
) {
self.start_transaction(cx);
update(self, cx);
self.end_transaction(cx);
}
fn start_transaction(&mut self, cx: &mut ViewContext<Self>) { fn start_transaction(&mut self, cx: &mut ViewContext<Self>) {
self.start_transaction_at(Instant::now(), cx); self.start_transaction_at(Instant::now(), cx);
} }
@ -5748,6 +5676,14 @@ fn build_style(
style style
} }
trait SelectionExt {
fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range<usize>;
fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point>;
fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
fn spanned_rows(&self, include_end_if_at_line_start: bool, map: &DisplaySnapshot)
-> Range<u32>;
}
impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> { impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point> { fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point> {
let start = self.start.to_point(buffer); let start = self.start.to_point(buffer);
@ -6056,6 +5992,8 @@ pub fn styled_runs_for_code_label<'a>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::test::marked_text_by;
use super::*; use super::*;
use language::{LanguageConfig, LanguageServerConfig}; use language::{LanguageConfig, LanguageServerConfig};
use lsp::FakeLanguageServer; use lsp::FakeLanguageServer;
@ -6789,127 +6727,94 @@ mod tests {
], ],
cx, cx,
); );
});
view.update(cx, |view, cx| { view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx); assert_selection_ranges(
assert_eq!( "use std::<>str::{foo, bar}\n\n {[]baz.qux()}",
view.selected_display_ranges(cx), vec![('<', '>'), ('[', ']')],
&[ view,
DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9), cx,
DisplayPoint::new(2, 3)..DisplayPoint::new(2, 3),
]
); );
});
view.update(cx, |view, cx| { view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx); assert_selection_ranges(
assert_eq!( "use std<>::str::{foo, bar}\n\n []{baz.qux()}",
view.selected_display_ranges(cx), vec![('<', '>'), ('[', ']')],
&[ view,
DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7), cx,
DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2),
]
); );
});
view.update(cx, |view, cx| { view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx); assert_selection_ranges(
assert_eq!( "use <>std::str::{foo, bar}\n\n[] {baz.qux()}",
view.selected_display_ranges(cx), vec![('<', '>'), ('[', ']')],
&[ view,
DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4), cx,
DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
]
); );
});
view.update(cx, |view, cx| { view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx); assert_selection_ranges(
assert_eq!( "<>use std::str::{foo, bar}\n[]\n {baz.qux()}",
view.selected_display_ranges(cx), vec![('<', '>'), ('[', ']')],
&[ view,
DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), cx,
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
]
); );
});
view.update(cx, |view, cx| { view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx); assert_selection_ranges(
assert_eq!( "<>use std::str::{foo, bar[]}\n\n {baz.qux()}",
view.selected_display_ranges(cx), vec![('<', '>'), ('[', ']')],
&[ view,
DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), cx,
DisplayPoint::new(0, 23)..DisplayPoint::new(0, 23),
]
); );
});
view.update(cx, |view, cx| { view.move_to_next_word_end(&MoveToNextWordEnd, cx);
view.move_to_next_word_boundary(&MoveToNextWordBoundary, cx); assert_selection_ranges(
assert_eq!( "use<> std::str::{foo, bar}[]\n\n {baz.qux()}",
view.selected_display_ranges(cx), vec![('<', '>'), ('[', ']')],
&[ view,
DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), cx,
DisplayPoint::new(0, 24)..DisplayPoint::new(0, 24),
]
); );
});
view.update(cx, |view, cx| { view.move_to_next_word_end(&MoveToNextWordEnd, cx);
view.move_to_next_word_boundary(&MoveToNextWordBoundary, cx); assert_selection_ranges(
assert_eq!( "use std<>::str::{foo, bar}\n[]\n {baz.qux()}",
view.selected_display_ranges(cx), vec![('<', '>'), ('[', ']')],
&[ view,
DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7), cx,
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
]
); );
});
view.update(cx, |view, cx| { view.move_to_next_word_end(&MoveToNextWordEnd, cx);
view.move_to_next_word_boundary(&MoveToNextWordBoundary, cx); assert_selection_ranges(
assert_eq!( "use std::<>str::{foo, bar}\n\n {[]baz.qux()}",
view.selected_display_ranges(cx), vec![('<', '>'), ('[', ']')],
&[ view,
DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9), cx,
DisplayPoint::new(2, 3)..DisplayPoint::new(2, 3),
]
); );
});
view.update(cx, |view, cx| {
view.move_right(&MoveRight, cx); view.move_right(&MoveRight, cx);
view.select_to_previous_word_boundary(&SelectToPreviousWordBoundary, cx); view.select_to_previous_word_start(&SelectToPreviousWordBoundary, cx);
assert_eq!( assert_selection_ranges(
view.selected_display_ranges(cx), "use std::>s<tr::{foo, bar}\n\n {]b[az.qux()}",
&[ vec![('<', '>'), ('[', ']')],
DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9), view,
DisplayPoint::new(2, 4)..DisplayPoint::new(2, 3), cx,
]
); );
});
view.update(cx, |view, cx| { view.select_to_previous_word_start(&SelectToPreviousWordBoundary, cx);
view.select_to_previous_word_boundary(&SelectToPreviousWordBoundary, cx); assert_selection_ranges(
assert_eq!( "use std>::s<tr::{foo, bar}\n\n ]{b[az.qux()}",
view.selected_display_ranges(cx), vec![('<', '>'), ('[', ']')],
&[ view,
DisplayPoint::new(0, 10)..DisplayPoint::new(0, 7), cx,
DisplayPoint::new(2, 4)..DisplayPoint::new(2, 2),
]
); );
});
view.update(cx, |view, cx| { view.select_to_next_word_end(&SelectToNextWordBoundary, cx);
view.select_to_next_word_boundary(&SelectToNextWordBoundary, cx); assert_selection_ranges(
assert_eq!( "use std::>s<tr::{foo, bar}\n\n {]b[az.qux()}",
view.selected_display_ranges(cx), vec![('<', '>'), ('[', ']')],
&[ view,
DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9), cx,
DisplayPoint::new(2, 4)..DisplayPoint::new(2, 3),
]
); );
}); });
} }
@ -6929,37 +6834,37 @@ mod tests {
view.select_display_ranges(&[DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)], cx); view.select_display_ranges(&[DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)], cx);
view.move_to_next_word_boundary(&MoveToNextWordBoundary, cx); view.move_to_next_word_end(&MoveToNextWordEnd, cx);
assert_eq!( assert_eq!(
view.selected_display_ranges(cx), view.selected_display_ranges(cx),
&[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)] &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
); );
view.move_to_next_word_boundary(&MoveToNextWordBoundary, cx); view.move_to_next_word_end(&MoveToNextWordEnd, cx);
assert_eq!( assert_eq!(
view.selected_display_ranges(cx), view.selected_display_ranges(cx),
&[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)] &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
); );
view.move_to_next_word_boundary(&MoveToNextWordBoundary, cx); view.move_to_next_word_end(&MoveToNextWordEnd, cx);
assert_eq!( assert_eq!(
view.selected_display_ranges(cx), view.selected_display_ranges(cx),
&[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)] &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
); );
view.move_to_next_word_boundary(&MoveToNextWordBoundary, cx); view.move_to_next_word_end(&MoveToNextWordEnd, cx);
assert_eq!( assert_eq!(
view.selected_display_ranges(cx), view.selected_display_ranges(cx),
&[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)] &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
); );
view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx); view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
assert_eq!( assert_eq!(
view.selected_display_ranges(cx), view.selected_display_ranges(cx),
&[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)] &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
); );
view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx); view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
assert_eq!( assert_eq!(
view.selected_display_ranges(cx), view.selected_display_ranges(cx),
&[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)] &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
@ -6983,7 +6888,7 @@ mod tests {
], ],
cx, cx,
); );
view.delete_to_previous_word_boundary(&DeleteToPreviousWordBoundary, cx); view.delete_to_previous_word_start(&DeleteToPreviousWordBoundary, cx);
}); });
assert_eq!(buffer.read(cx).read(cx).text(), "e two te four"); assert_eq!(buffer.read(cx).read(cx).text(), "e two te four");
@ -6998,7 +6903,7 @@ mod tests {
], ],
cx, cx,
); );
view.delete_to_next_word_boundary(&DeleteToNextWordBoundary, cx); view.delete_to_next_word_end(&DeleteToNextWordBoundary, cx);
}); });
assert_eq!(buffer.read(cx).read(cx).text(), "e t te our"); assert_eq!(buffer.read(cx).read(cx).text(), "e t te our");
@ -9080,6 +8985,35 @@ mod tests {
let settings = Settings::test(cx); let settings = Settings::test(cx);
cx.set_global(settings); cx.set_global(settings);
} }
fn assert_selection_ranges(
marked_text: &str,
selection_marker_pairs: Vec<(char, char)>,
view: &mut Editor,
cx: &mut ViewContext<Editor>,
) {
let snapshot = view.snapshot(cx).display_snapshot;
let mut marker_chars = Vec::new();
for (start, end) in selection_marker_pairs.iter() {
marker_chars.push(*start);
marker_chars.push(*end);
}
let (_, markers) = marked_text_by(marked_text, marker_chars);
let asserted_ranges: Vec<Range<DisplayPoint>> = selection_marker_pairs
.iter()
.map(|(start, end)| {
let start = markers.get(start).unwrap()[0].to_display_point(&snapshot);
let end = markers.get(end).unwrap()[0].to_display_point(&snapshot);
start..end
})
.collect();
assert_eq!(
view.selected_display_ranges(cx),
&asserted_ranges[..],
"Assert selections are {}",
marked_text
);
}
} }
trait RangeExt<T> { trait RangeExt<T> {

View file

@ -1,5 +1,5 @@
use crate::Anchor; use crate::Anchor;
use crate::{rope::TextDimension, BufferSnapshot, ToOffset, ToPoint}; use crate::{rope::TextDimension, BufferSnapshot};
use std::cmp::Ordering; use std::cmp::Ordering;
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
@ -36,12 +36,12 @@ impl<T: Clone> Selection<T> {
} }
} }
impl<T: ToOffset + ToPoint + Copy + Ord> Selection<T> { impl<T: Copy + Ord> Selection<T> {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.start == self.end self.start == self.end
} }
pub fn set_head(&mut self, head: T) { pub fn set_head(&mut self, head: T, new_goal: SelectionGoal) {
if head.cmp(&self.tail()) < Ordering::Equal { if head.cmp(&self.tail()) < Ordering::Equal {
if !self.reversed { if !self.reversed {
self.end = self.start; self.end = self.start;
@ -55,6 +55,14 @@ impl<T: ToOffset + ToPoint + Copy + Ord> Selection<T> {
} }
self.end = head; self.end = head;
} }
self.goal = new_goal;
}
pub fn collapse_to(&mut self, point: T, new_goal: SelectionGoal) {
self.start = point;
self.end = point;
self.goal = new_goal;
self.reversed = false;
} }
} }