vim: Handle exclusive-linewise edgecase correctly (#27786)
Before this change we didn't explicitly handle vim's exclusive-linewise edgecase (https://neovim.io/doc/user/motion.html#exclusive). Instead we had hard-coded workarounds in a few places to make our tests pass. The most pernicious of these workarounds was that we represented a visual line selection as including the trailing newline (or leading newline for files that end with no newline), which other code had to undo to get back to what the user indended. Closes #21440 Updates #6900 Release Notes: - vim: Fixed `d]}` to not delete the closing brace - vim: Fixed `d}` from the start of the line to not delete the paragraph separator - vim: Fixed `d}` from the middle of the line to not delete the final newline
This commit is contained in:
parent
e1e8c1786e
commit
fc269dfaf9
27 changed files with 471 additions and 482 deletions
|
@ -261,6 +261,7 @@
|
||||||
"o": "vim::OtherEndRowAware",
|
"o": "vim::OtherEndRowAware",
|
||||||
"d": "vim::VisualDelete",
|
"d": "vim::VisualDelete",
|
||||||
"x": "vim::VisualDelete",
|
"x": "vim::VisualDelete",
|
||||||
|
"delete": "vim::VisualDelete",
|
||||||
"shift-d": "vim::VisualDeleteLine",
|
"shift-d": "vim::VisualDeleteLine",
|
||||||
"shift-x": "vim::VisualDeleteLine",
|
"shift-x": "vim::VisualDeleteLine",
|
||||||
"y": "vim::VisualYank",
|
"y": "vim::VisualYank",
|
||||||
|
|
|
@ -752,27 +752,11 @@ impl DisplaySnapshot {
|
||||||
|
|
||||||
// used by line_mode selections and tries to match vim behavior
|
// used by line_mode selections and tries to match vim behavior
|
||||||
pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
|
pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
|
||||||
let max_row = self.buffer_snapshot.max_row().0;
|
let new_start = MultiBufferPoint::new(range.start.row, 0);
|
||||||
let new_start = if range.start.row == 0 {
|
let new_end = MultiBufferPoint::new(
|
||||||
MultiBufferPoint::new(0, 0)
|
range.end.row,
|
||||||
} else if range.start.row == max_row || (range.end.column > 0 && range.end.row == max_row) {
|
self.buffer_snapshot.line_len(MultiBufferRow(range.end.row)),
|
||||||
MultiBufferPoint::new(
|
);
|
||||||
range.start.row - 1,
|
|
||||||
self.buffer_snapshot
|
|
||||||
.line_len(MultiBufferRow(range.start.row - 1)),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
self.prev_line_boundary(range.start).0
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_end = if range.end.column == 0 {
|
|
||||||
range.end
|
|
||||||
} else if range.end.row < max_row {
|
|
||||||
self.buffer_snapshot
|
|
||||||
.clip_point(MultiBufferPoint::new(range.end.row + 1, 0), Bias::Left)
|
|
||||||
} else {
|
|
||||||
self.buffer_snapshot.max_point()
|
|
||||||
};
|
|
||||||
|
|
||||||
new_start..new_end
|
new_start..new_end
|
||||||
}
|
}
|
||||||
|
|
|
@ -7891,40 +7891,37 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut selections = this.selections.all::<MultiBufferPoint>(cx);
|
let mut selections = this.selections.all::<MultiBufferPoint>(cx);
|
||||||
if !this.selections.line_mode {
|
let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
|
for selection in &mut selections {
|
||||||
for selection in &mut selections {
|
if selection.is_empty() {
|
||||||
if selection.is_empty() {
|
let old_head = selection.head();
|
||||||
let old_head = selection.head();
|
let mut new_head =
|
||||||
let mut new_head =
|
movement::left(&display_map, old_head.to_display_point(&display_map))
|
||||||
movement::left(&display_map, old_head.to_display_point(&display_map))
|
.to_point(&display_map);
|
||||||
.to_point(&display_map);
|
if let Some((buffer, line_buffer_range)) = display_map
|
||||||
if let Some((buffer, line_buffer_range)) = display_map
|
.buffer_snapshot
|
||||||
.buffer_snapshot
|
.buffer_line_for_row(MultiBufferRow(old_head.row))
|
||||||
.buffer_line_for_row(MultiBufferRow(old_head.row))
|
{
|
||||||
{
|
let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
|
||||||
let indent_size =
|
let indent_len = match indent_size.kind {
|
||||||
buffer.indent_size_for_line(line_buffer_range.start.row);
|
IndentKind::Space => {
|
||||||
let indent_len = match indent_size.kind {
|
buffer.settings_at(line_buffer_range.start, cx).tab_size
|
||||||
IndentKind::Space => {
|
|
||||||
buffer.settings_at(line_buffer_range.start, cx).tab_size
|
|
||||||
}
|
|
||||||
IndentKind::Tab => NonZeroU32::new(1).unwrap(),
|
|
||||||
};
|
|
||||||
if old_head.column <= indent_size.len && old_head.column > 0 {
|
|
||||||
let indent_len = indent_len.get();
|
|
||||||
new_head = cmp::min(
|
|
||||||
new_head,
|
|
||||||
MultiBufferPoint::new(
|
|
||||||
old_head.row,
|
|
||||||
((old_head.column - 1) / indent_len) * indent_len,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
IndentKind::Tab => NonZeroU32::new(1).unwrap(),
|
||||||
|
};
|
||||||
|
if old_head.column <= indent_size.len && old_head.column > 0 {
|
||||||
|
let indent_len = indent_len.get();
|
||||||
|
new_head = cmp::min(
|
||||||
|
new_head,
|
||||||
|
MultiBufferPoint::new(
|
||||||
|
old_head.row,
|
||||||
|
((old_head.column - 1) / indent_len) * indent_len,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.set_head(new_head, SelectionGoal::None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selection.set_head(new_head, SelectionGoal::None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7968,9 +7965,8 @@ impl Editor {
|
||||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
|
self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
|
||||||
self.transact(window, cx, |this, window, cx| {
|
self.transact(window, cx, |this, window, cx| {
|
||||||
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
let line_mode = s.line_mode;
|
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if selection.is_empty() && !line_mode {
|
if selection.is_empty() {
|
||||||
let cursor = movement::right(map, selection.head());
|
let cursor = movement::right(map, selection.head());
|
||||||
selection.end = cursor;
|
selection.end = cursor;
|
||||||
selection.reversed = true;
|
selection.reversed = true;
|
||||||
|
@ -9419,9 +9415,8 @@ impl Editor {
|
||||||
self.transact(window, cx, |this, window, cx| {
|
self.transact(window, cx, |this, window, cx| {
|
||||||
let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
let mut edits: Vec<(Range<usize>, String)> = Default::default();
|
let mut edits: Vec<(Range<usize>, String)> = Default::default();
|
||||||
let line_mode = s.line_mode;
|
|
||||||
s.move_with(|display_map, selection| {
|
s.move_with(|display_map, selection| {
|
||||||
if !selection.is_empty() || line_mode {
|
if !selection.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9994,9 +9989,8 @@ impl Editor {
|
||||||
pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
|
self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
|
||||||
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
let line_mode = s.line_mode;
|
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let cursor = if selection.is_empty() && !line_mode {
|
let cursor = if selection.is_empty() {
|
||||||
movement::left(map, selection.start)
|
movement::left(map, selection.start)
|
||||||
} else {
|
} else {
|
||||||
selection.start
|
selection.start
|
||||||
|
@ -10016,9 +10010,8 @@ impl Editor {
|
||||||
pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
|
self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
|
||||||
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
let line_mode = s.line_mode;
|
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let cursor = if selection.is_empty() && !line_mode {
|
let cursor = if selection.is_empty() {
|
||||||
movement::right(map, selection.end)
|
movement::right(map, selection.end)
|
||||||
} else {
|
} else {
|
||||||
selection.end
|
selection.end
|
||||||
|
@ -10052,9 +10045,8 @@ impl Editor {
|
||||||
let first_selection = self.selections.first_anchor();
|
let first_selection = self.selections.first_anchor();
|
||||||
|
|
||||||
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
let line_mode = s.line_mode;
|
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if !selection.is_empty() && !line_mode {
|
if !selection.is_empty() {
|
||||||
selection.goal = SelectionGoal::None;
|
selection.goal = SelectionGoal::None;
|
||||||
}
|
}
|
||||||
let (cursor, goal) = movement::up(
|
let (cursor, goal) = movement::up(
|
||||||
|
@ -10094,9 +10086,8 @@ impl Editor {
|
||||||
let text_layout_details = &self.text_layout_details(window);
|
let text_layout_details = &self.text_layout_details(window);
|
||||||
|
|
||||||
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
let line_mode = s.line_mode;
|
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if !selection.is_empty() && !line_mode {
|
if !selection.is_empty() {
|
||||||
selection.goal = SelectionGoal::None;
|
selection.goal = SelectionGoal::None;
|
||||||
}
|
}
|
||||||
let (cursor, goal) = movement::up_by_rows(
|
let (cursor, goal) = movement::up_by_rows(
|
||||||
|
@ -10132,9 +10123,8 @@ impl Editor {
|
||||||
let text_layout_details = &self.text_layout_details(window);
|
let text_layout_details = &self.text_layout_details(window);
|
||||||
|
|
||||||
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
let line_mode = s.line_mode;
|
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if !selection.is_empty() && !line_mode {
|
if !selection.is_empty() {
|
||||||
selection.goal = SelectionGoal::None;
|
selection.goal = SelectionGoal::None;
|
||||||
}
|
}
|
||||||
let (cursor, goal) = movement::down_by_rows(
|
let (cursor, goal) = movement::down_by_rows(
|
||||||
|
@ -10241,9 +10231,8 @@ impl Editor {
|
||||||
let text_layout_details = &self.text_layout_details(window);
|
let text_layout_details = &self.text_layout_details(window);
|
||||||
|
|
||||||
self.change_selections(Some(autoscroll), window, cx, |s| {
|
self.change_selections(Some(autoscroll), window, cx, |s| {
|
||||||
let line_mode = s.line_mode;
|
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if !selection.is_empty() && !line_mode {
|
if !selection.is_empty() {
|
||||||
selection.goal = SelectionGoal::None;
|
selection.goal = SelectionGoal::None;
|
||||||
}
|
}
|
||||||
let (cursor, goal) = movement::up_by_rows(
|
let (cursor, goal) = movement::up_by_rows(
|
||||||
|
@ -10284,9 +10273,8 @@ impl Editor {
|
||||||
let first_selection = self.selections.first_anchor();
|
let first_selection = self.selections.first_anchor();
|
||||||
|
|
||||||
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
let line_mode = s.line_mode;
|
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if !selection.is_empty() && !line_mode {
|
if !selection.is_empty() {
|
||||||
selection.goal = SelectionGoal::None;
|
selection.goal = SelectionGoal::None;
|
||||||
}
|
}
|
||||||
let (cursor, goal) = movement::down(
|
let (cursor, goal) = movement::down(
|
||||||
|
@ -10366,9 +10354,8 @@ impl Editor {
|
||||||
|
|
||||||
let text_layout_details = &self.text_layout_details(window);
|
let text_layout_details = &self.text_layout_details(window);
|
||||||
self.change_selections(Some(autoscroll), window, cx, |s| {
|
self.change_selections(Some(autoscroll), window, cx, |s| {
|
||||||
let line_mode = s.line_mode;
|
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if !selection.is_empty() && !line_mode {
|
if !selection.is_empty() {
|
||||||
selection.goal = SelectionGoal::None;
|
selection.goal = SelectionGoal::None;
|
||||||
}
|
}
|
||||||
let (cursor, goal) = movement::down_by_rows(
|
let (cursor, goal) = movement::down_by_rows(
|
||||||
|
@ -10516,9 +10503,8 @@ impl Editor {
|
||||||
self.transact(window, cx, |this, window, cx| {
|
self.transact(window, cx, |this, window, cx| {
|
||||||
this.select_autoclose_pair(window, cx);
|
this.select_autoclose_pair(window, cx);
|
||||||
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
let line_mode = s.line_mode;
|
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if selection.is_empty() && !line_mode {
|
if selection.is_empty() {
|
||||||
let cursor = if action.ignore_newlines {
|
let cursor = if action.ignore_newlines {
|
||||||
movement::previous_word_start(map, selection.head())
|
movement::previous_word_start(map, selection.head())
|
||||||
} else {
|
} else {
|
||||||
|
@ -10542,9 +10528,8 @@ impl Editor {
|
||||||
self.transact(window, cx, |this, window, cx| {
|
self.transact(window, cx, |this, window, cx| {
|
||||||
this.select_autoclose_pair(window, cx);
|
this.select_autoclose_pair(window, cx);
|
||||||
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
let line_mode = s.line_mode;
|
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if selection.is_empty() && !line_mode {
|
if selection.is_empty() {
|
||||||
let cursor = movement::previous_subword_start(map, selection.head());
|
let cursor = movement::previous_subword_start(map, selection.head());
|
||||||
selection.set_head(cursor, SelectionGoal::None);
|
selection.set_head(cursor, SelectionGoal::None);
|
||||||
}
|
}
|
||||||
|
@ -10619,9 +10604,8 @@ impl Editor {
|
||||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
|
self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
|
||||||
self.transact(window, cx, |this, window, cx| {
|
self.transact(window, cx, |this, window, cx| {
|
||||||
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
let line_mode = s.line_mode;
|
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if selection.is_empty() && !line_mode {
|
if selection.is_empty() {
|
||||||
let cursor = if action.ignore_newlines {
|
let cursor = if action.ignore_newlines {
|
||||||
movement::next_word_end(map, selection.head())
|
movement::next_word_end(map, selection.head())
|
||||||
} else {
|
} else {
|
||||||
|
@ -14745,25 +14729,11 @@ impl Editor {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let selections = self.selections.all::<Point>(cx);
|
let selections = self.selections.all_adjusted(cx);
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
let line_mode = self.selections.line_mode;
|
|
||||||
let ranges = selections
|
let ranges = selections
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| {
|
.map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
|
||||||
if line_mode {
|
|
||||||
let start = Point::new(s.start.row, 0);
|
|
||||||
let end = Point::new(
|
|
||||||
s.end.row,
|
|
||||||
display_map
|
|
||||||
.buffer_snapshot
|
|
||||||
.line_len(MultiBufferRow(s.end.row)),
|
|
||||||
);
|
|
||||||
Crease::simple(start..end, display_map.fold_placeholder.clone())
|
|
||||||
} else {
|
|
||||||
Crease::simple(s.start..s.end, display_map.fold_placeholder.clone())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
self.fold_creases(ranges, true, window, cx);
|
self.fold_creases(ranges, true, window, cx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3269,18 +3269,6 @@ async fn test_backspace(cx: &mut TestAppContext) {
|
||||||
ˇtwo
|
ˇtwo
|
||||||
ˇ threeˇ four
|
ˇ threeˇ four
|
||||||
"});
|
"});
|
||||||
|
|
||||||
// Test backspace with line_mode set to true
|
|
||||||
cx.update_editor(|e, _, _| e.selections.line_mode = true);
|
|
||||||
cx.set_state(indoc! {"
|
|
||||||
The ˇquick ˇbrown
|
|
||||||
fox jumps over
|
|
||||||
the lazy dog
|
|
||||||
ˇThe qu«ick bˇ»rown"});
|
|
||||||
cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
|
|
||||||
cx.assert_editor_state(indoc! {"
|
|
||||||
ˇfox jumps over
|
|
||||||
the lazy dogˇ"});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -3300,16 +3288,6 @@ async fn test_delete(cx: &mut TestAppContext) {
|
||||||
fouˇ five six
|
fouˇ five six
|
||||||
seven ˇten
|
seven ˇten
|
||||||
"});
|
"});
|
||||||
|
|
||||||
// Test backspace with line_mode set to true
|
|
||||||
cx.update_editor(|e, _, _| e.selections.line_mode = true);
|
|
||||||
cx.set_state(indoc! {"
|
|
||||||
The ˇquick ˇbrown
|
|
||||||
fox «ˇjum»ps over
|
|
||||||
the lazy dog
|
|
||||||
ˇThe qu«ick bˇ»rown"});
|
|
||||||
cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
|
|
||||||
cx.assert_editor_state("ˇthe lazy dogˇ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -4928,7 +4906,7 @@ async fn test_copy_trim(cx: &mut TestAppContext) {
|
||||||
r#" «for selection in selections.iter() {
|
r#" «for selection in selections.iter() {
|
||||||
let mut start = selection.start;
|
let mut start = selection.start;
|
||||||
let mut end = selection.end;
|
let mut end = selection.end;
|
||||||
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
let is_entire_line = selection.is_empty();
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
start = Point::new(start.row, 0);ˇ»
|
start = Point::new(start.row, 0);ˇ»
|
||||||
end = cmp::min(max_point, Point::new(end.row + 1, 0));
|
end = cmp::min(max_point, Point::new(end.row + 1, 0));
|
||||||
|
@ -4943,7 +4921,7 @@ async fn test_copy_trim(cx: &mut TestAppContext) {
|
||||||
"for selection in selections.iter() {
|
"for selection in selections.iter() {
|
||||||
let mut start = selection.start;
|
let mut start = selection.start;
|
||||||
let mut end = selection.end;
|
let mut end = selection.end;
|
||||||
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
let is_entire_line = selection.is_empty();
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
start = Point::new(start.row, 0);"
|
start = Point::new(start.row, 0);"
|
||||||
.to_string()
|
.to_string()
|
||||||
|
@ -4958,7 +4936,7 @@ async fn test_copy_trim(cx: &mut TestAppContext) {
|
||||||
"for selection in selections.iter() {
|
"for selection in selections.iter() {
|
||||||
let mut start = selection.start;
|
let mut start = selection.start;
|
||||||
let mut end = selection.end;
|
let mut end = selection.end;
|
||||||
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
let is_entire_line = selection.is_empty();
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
start = Point::new(start.row, 0);"
|
start = Point::new(start.row, 0);"
|
||||||
.to_string()
|
.to_string()
|
||||||
|
@ -4970,7 +4948,7 @@ if is_entire_line {
|
||||||
r#" « for selection in selections.iter() {
|
r#" « for selection in selections.iter() {
|
||||||
let mut start = selection.start;
|
let mut start = selection.start;
|
||||||
let mut end = selection.end;
|
let mut end = selection.end;
|
||||||
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
let is_entire_line = selection.is_empty();
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
start = Point::new(start.row, 0);ˇ»
|
start = Point::new(start.row, 0);ˇ»
|
||||||
end = cmp::min(max_point, Point::new(end.row + 1, 0));
|
end = cmp::min(max_point, Point::new(end.row + 1, 0));
|
||||||
|
@ -4985,7 +4963,7 @@ if is_entire_line {
|
||||||
" for selection in selections.iter() {
|
" for selection in selections.iter() {
|
||||||
let mut start = selection.start;
|
let mut start = selection.start;
|
||||||
let mut end = selection.end;
|
let mut end = selection.end;
|
||||||
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
let is_entire_line = selection.is_empty();
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
start = Point::new(start.row, 0);"
|
start = Point::new(start.row, 0);"
|
||||||
.to_string()
|
.to_string()
|
||||||
|
@ -5000,7 +4978,7 @@ if is_entire_line {
|
||||||
"for selection in selections.iter() {
|
"for selection in selections.iter() {
|
||||||
let mut start = selection.start;
|
let mut start = selection.start;
|
||||||
let mut end = selection.end;
|
let mut end = selection.end;
|
||||||
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
let is_entire_line = selection.is_empty();
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
start = Point::new(start.row, 0);"
|
start = Point::new(start.row, 0);"
|
||||||
.to_string()
|
.to_string()
|
||||||
|
@ -5012,7 +4990,7 @@ if is_entire_line {
|
||||||
r#" «ˇ for selection in selections.iter() {
|
r#" «ˇ for selection in selections.iter() {
|
||||||
let mut start = selection.start;
|
let mut start = selection.start;
|
||||||
let mut end = selection.end;
|
let mut end = selection.end;
|
||||||
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
let is_entire_line = selection.is_empty();
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
start = Point::new(start.row, 0);»
|
start = Point::new(start.row, 0);»
|
||||||
end = cmp::min(max_point, Point::new(end.row + 1, 0));
|
end = cmp::min(max_point, Point::new(end.row + 1, 0));
|
||||||
|
@ -5027,7 +5005,7 @@ if is_entire_line {
|
||||||
" for selection in selections.iter() {
|
" for selection in selections.iter() {
|
||||||
let mut start = selection.start;
|
let mut start = selection.start;
|
||||||
let mut end = selection.end;
|
let mut end = selection.end;
|
||||||
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
let is_entire_line = selection.is_empty();
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
start = Point::new(start.row, 0);"
|
start = Point::new(start.row, 0);"
|
||||||
.to_string()
|
.to_string()
|
||||||
|
@ -5042,7 +5020,7 @@ if is_entire_line {
|
||||||
"for selection in selections.iter() {
|
"for selection in selections.iter() {
|
||||||
let mut start = selection.start;
|
let mut start = selection.start;
|
||||||
let mut end = selection.end;
|
let mut end = selection.end;
|
||||||
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
let is_entire_line = selection.is_empty();
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
start = Point::new(start.row, 0);"
|
start = Point::new(start.row, 0);"
|
||||||
.to_string()
|
.to_string()
|
||||||
|
@ -5054,7 +5032,7 @@ if is_entire_line {
|
||||||
r#" for selection «in selections.iter() {
|
r#" for selection «in selections.iter() {
|
||||||
let mut start = selection.start;
|
let mut start = selection.start;
|
||||||
let mut end = selection.end;
|
let mut end = selection.end;
|
||||||
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
let is_entire_line = selection.is_empty();
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
start = Point::new(start.row, 0);ˇ»
|
start = Point::new(start.row, 0);ˇ»
|
||||||
end = cmp::min(max_point, Point::new(end.row + 1, 0));
|
end = cmp::min(max_point, Point::new(end.row + 1, 0));
|
||||||
|
@ -5069,7 +5047,7 @@ if is_entire_line {
|
||||||
"in selections.iter() {
|
"in selections.iter() {
|
||||||
let mut start = selection.start;
|
let mut start = selection.start;
|
||||||
let mut end = selection.end;
|
let mut end = selection.end;
|
||||||
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
let is_entire_line = selection.is_empty();
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
start = Point::new(start.row, 0);"
|
start = Point::new(start.row, 0);"
|
||||||
.to_string()
|
.to_string()
|
||||||
|
@ -5084,7 +5062,7 @@ if is_entire_line {
|
||||||
"in selections.iter() {
|
"in selections.iter() {
|
||||||
let mut start = selection.start;
|
let mut start = selection.start;
|
||||||
let mut end = selection.end;
|
let mut end = selection.end;
|
||||||
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
let is_entire_line = selection.is_empty();
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
start = Point::new(start.row, 0);"
|
start = Point::new(start.row, 0);"
|
||||||
.to_string()
|
.to_string()
|
||||||
|
|
|
@ -31,7 +31,7 @@ use workspace::{notifications::NotifyResultExt, SaveIntent};
|
||||||
use zed_actions::RevealTarget;
|
use zed_actions::RevealTarget;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
motion::{EndOfDocument, Motion, StartOfDocument},
|
motion::{EndOfDocument, Motion, MotionKind, StartOfDocument},
|
||||||
normal::{
|
normal::{
|
||||||
search::{FindCommand, ReplaceCommand, Replacement},
|
search::{FindCommand, ReplaceCommand, Replacement},
|
||||||
JoinLines,
|
JoinLines,
|
||||||
|
@ -281,7 +281,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||||
};
|
};
|
||||||
vim.copy_ranges(
|
vim.copy_ranges(
|
||||||
editor,
|
editor,
|
||||||
true,
|
MotionKind::Linewise,
|
||||||
true,
|
true,
|
||||||
vec![Point::new(range.start.0, 0)..end],
|
vec![Point::new(range.start.0, 0)..end],
|
||||||
window,
|
window,
|
||||||
|
@ -1328,9 +1328,9 @@ impl Vim {
|
||||||
let snapshot = editor.snapshot(window, cx);
|
let snapshot = editor.snapshot(window, cx);
|
||||||
let start = editor.selections.newest_display(cx);
|
let start = editor.selections.newest_display(cx);
|
||||||
let text_layout_details = editor.text_layout_details(window);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
let mut range = motion
|
let (mut range, _) = motion
|
||||||
.range(&snapshot, start.clone(), times, false, &text_layout_details)
|
.range(&snapshot, start.clone(), times, &text_layout_details)
|
||||||
.unwrap_or(start.range());
|
.unwrap_or((start.range(), MotionKind::Exclusive));
|
||||||
if range.start != start.start {
|
if range.start != start.start {
|
||||||
editor.change_selections(None, window, cx, |s| {
|
editor.change_selections(None, window, cx, |s| {
|
||||||
s.select_ranges([
|
s.select_ranges([
|
||||||
|
|
|
@ -3,6 +3,7 @@ use gpui::{actions, Action};
|
||||||
use gpui::{Context, Window};
|
use gpui::{Context, Window};
|
||||||
use language::{CharClassifier, CharKind};
|
use language::{CharClassifier, CharKind};
|
||||||
|
|
||||||
|
use crate::motion::MotionKind;
|
||||||
use crate::{motion::Motion, state::Mode, Vim};
|
use crate::{motion::Motion, state::Mode, Vim};
|
||||||
|
|
||||||
actions!(vim, [HelixNormalAfter, HelixDelete]);
|
actions!(vim, [HelixNormalAfter, HelixDelete]);
|
||||||
|
@ -254,7 +255,7 @@ impl Vim {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
vim.copy_selections_content(editor, false, window, cx);
|
vim.copy_selections_content(editor, MotionKind::Exclusive, window, cx);
|
||||||
editor.insert("", window, cx);
|
editor.insert("", window, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ impl Vim {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
|
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
|
||||||
selection_starts.insert(selection.id, anchor);
|
selection_starts.insert(selection.id, anchor);
|
||||||
motion.expand_selection(map, selection, times, false, &text_layout_details);
|
motion.expand_selection(map, selection, times, &text_layout_details);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
match dir {
|
match dir {
|
||||||
|
|
|
@ -21,6 +21,26 @@ use crate::{
|
||||||
Vim,
|
Vim,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub(crate) enum MotionKind {
|
||||||
|
Linewise,
|
||||||
|
Exclusive,
|
||||||
|
Inclusive,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MotionKind {
|
||||||
|
pub(crate) fn for_mode(mode: Mode) -> Self {
|
||||||
|
match mode {
|
||||||
|
Mode::VisualLine => MotionKind::Linewise,
|
||||||
|
_ => MotionKind::Exclusive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn linewise(&self) -> bool {
|
||||||
|
matches!(self, MotionKind::Linewise)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum Motion {
|
pub enum Motion {
|
||||||
Left,
|
Left,
|
||||||
|
@ -622,7 +642,7 @@ impl Vim {
|
||||||
// Motion handling is specified here:
|
// Motion handling is specified here:
|
||||||
// https://github.com/vim/vim/blob/master/runtime/doc/motion.txt
|
// https://github.com/vim/vim/blob/master/runtime/doc/motion.txt
|
||||||
impl Motion {
|
impl Motion {
|
||||||
pub fn linewise(&self) -> bool {
|
fn default_kind(&self) -> MotionKind {
|
||||||
use Motion::*;
|
use Motion::*;
|
||||||
match self {
|
match self {
|
||||||
Down { .. }
|
Down { .. }
|
||||||
|
@ -633,8 +653,6 @@ impl Motion {
|
||||||
| NextLineStart
|
| NextLineStart
|
||||||
| PreviousLineStart
|
| PreviousLineStart
|
||||||
| StartOfLineDownward
|
| StartOfLineDownward
|
||||||
| StartOfParagraph
|
|
||||||
| EndOfParagraph
|
|
||||||
| WindowTop
|
| WindowTop
|
||||||
| WindowMiddle
|
| WindowMiddle
|
||||||
| WindowBottom
|
| WindowBottom
|
||||||
|
@ -649,37 +667,47 @@ impl Motion {
|
||||||
| NextComment
|
| NextComment
|
||||||
| PreviousComment
|
| PreviousComment
|
||||||
| GoToPercentage
|
| GoToPercentage
|
||||||
| Jump { line: true, .. } => true,
|
| Jump { line: true, .. } => MotionKind::Linewise,
|
||||||
EndOfLine { .. }
|
EndOfLine { .. }
|
||||||
|
| EndOfLineDownward
|
||||||
| Matching
|
| Matching
|
||||||
| UnmatchedForward { .. }
|
|
||||||
| UnmatchedBackward { .. }
|
|
||||||
| FindForward { .. }
|
| FindForward { .. }
|
||||||
| Left
|
| NextWordEnd { .. }
|
||||||
|
| PreviousWordEnd { .. }
|
||||||
|
| NextSubwordEnd { .. }
|
||||||
|
| PreviousSubwordEnd { .. } => MotionKind::Inclusive,
|
||||||
|
Left
|
||||||
| WrappingLeft
|
| WrappingLeft
|
||||||
| Right
|
| Right
|
||||||
| SentenceBackward
|
|
||||||
| SentenceForward
|
|
||||||
| WrappingRight
|
| WrappingRight
|
||||||
| StartOfLine { .. }
|
| StartOfLine { .. }
|
||||||
| EndOfLineDownward
|
| StartOfParagraph
|
||||||
|
| EndOfParagraph
|
||||||
|
| SentenceBackward
|
||||||
|
| SentenceForward
|
||||||
| GoToColumn
|
| GoToColumn
|
||||||
|
| UnmatchedForward { .. }
|
||||||
|
| UnmatchedBackward { .. }
|
||||||
| NextWordStart { .. }
|
| NextWordStart { .. }
|
||||||
| NextWordEnd { .. }
|
|
||||||
| PreviousWordStart { .. }
|
| PreviousWordStart { .. }
|
||||||
| PreviousWordEnd { .. }
|
|
||||||
| NextSubwordStart { .. }
|
| NextSubwordStart { .. }
|
||||||
| NextSubwordEnd { .. }
|
|
||||||
| PreviousSubwordStart { .. }
|
| PreviousSubwordStart { .. }
|
||||||
| PreviousSubwordEnd { .. }
|
|
||||||
| FirstNonWhitespace { .. }
|
| FirstNonWhitespace { .. }
|
||||||
| FindBackward { .. }
|
| FindBackward { .. }
|
||||||
| Sneak { .. }
|
| Sneak { .. }
|
||||||
| SneakBackward { .. }
|
| SneakBackward { .. }
|
||||||
| RepeatFind { .. }
|
| Jump { .. }
|
||||||
| RepeatFindReversed { .. }
|
| ZedSearchResult { .. } => MotionKind::Exclusive,
|
||||||
| Jump { line: false, .. }
|
RepeatFind { last_find: motion } | RepeatFindReversed { last_find: motion } => {
|
||||||
| ZedSearchResult { .. } => false,
|
motion.default_kind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_exclusive_special_case(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Motion::WrappingLeft | Motion::WrappingRight => true,
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -741,67 +769,6 @@ impl Motion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inclusive(&self) -> bool {
|
|
||||||
use Motion::*;
|
|
||||||
match self {
|
|
||||||
Down { .. }
|
|
||||||
| Up { .. }
|
|
||||||
| StartOfDocument
|
|
||||||
| EndOfDocument
|
|
||||||
| CurrentLine
|
|
||||||
| EndOfLine { .. }
|
|
||||||
| EndOfLineDownward
|
|
||||||
| Matching
|
|
||||||
| GoToPercentage
|
|
||||||
| UnmatchedForward { .. }
|
|
||||||
| UnmatchedBackward { .. }
|
|
||||||
| FindForward { .. }
|
|
||||||
| WindowTop
|
|
||||||
| WindowMiddle
|
|
||||||
| WindowBottom
|
|
||||||
| NextWordEnd { .. }
|
|
||||||
| PreviousWordEnd { .. }
|
|
||||||
| NextSubwordEnd { .. }
|
|
||||||
| PreviousSubwordEnd { .. }
|
|
||||||
| NextLineStart
|
|
||||||
| PreviousLineStart => true,
|
|
||||||
Left
|
|
||||||
| WrappingLeft
|
|
||||||
| Right
|
|
||||||
| WrappingRight
|
|
||||||
| StartOfLine { .. }
|
|
||||||
| StartOfLineDownward
|
|
||||||
| StartOfParagraph
|
|
||||||
| EndOfParagraph
|
|
||||||
| SentenceBackward
|
|
||||||
| SentenceForward
|
|
||||||
| GoToColumn
|
|
||||||
| NextWordStart { .. }
|
|
||||||
| PreviousWordStart { .. }
|
|
||||||
| NextSubwordStart { .. }
|
|
||||||
| PreviousSubwordStart { .. }
|
|
||||||
| FirstNonWhitespace { .. }
|
|
||||||
| FindBackward { .. }
|
|
||||||
| Sneak { .. }
|
|
||||||
| SneakBackward { .. }
|
|
||||||
| Jump { .. }
|
|
||||||
| NextSectionStart
|
|
||||||
| NextSectionEnd
|
|
||||||
| PreviousSectionStart
|
|
||||||
| PreviousSectionEnd
|
|
||||||
| NextMethodStart
|
|
||||||
| NextMethodEnd
|
|
||||||
| PreviousMethodStart
|
|
||||||
| PreviousMethodEnd
|
|
||||||
| NextComment
|
|
||||||
| PreviousComment
|
|
||||||
| ZedSearchResult { .. } => false,
|
|
||||||
RepeatFind { last_find: motion } | RepeatFindReversed { last_find: motion } => {
|
|
||||||
motion.inclusive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_point(
|
pub fn move_point(
|
||||||
&self,
|
&self,
|
||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
|
@ -1153,9 +1120,8 @@ impl Motion {
|
||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
selection: Selection<DisplayPoint>,
|
selection: Selection<DisplayPoint>,
|
||||||
times: Option<usize>,
|
times: Option<usize>,
|
||||||
expand_to_surrounding_newline: bool,
|
|
||||||
text_layout_details: &TextLayoutDetails,
|
text_layout_details: &TextLayoutDetails,
|
||||||
) -> Option<Range<DisplayPoint>> {
|
) -> Option<(Range<DisplayPoint>, MotionKind)> {
|
||||||
if let Motion::ZedSearchResult {
|
if let Motion::ZedSearchResult {
|
||||||
prior_selections,
|
prior_selections,
|
||||||
new_selections,
|
new_selections,
|
||||||
|
@ -1174,89 +1140,88 @@ impl Motion {
|
||||||
.max(prior_selection.end.to_display_point(map));
|
.max(prior_selection.end.to_display_point(map));
|
||||||
|
|
||||||
if start < end {
|
if start < end {
|
||||||
return Some(start..end);
|
return Some((start..end, MotionKind::Exclusive));
|
||||||
} else {
|
} else {
|
||||||
return Some(end..start);
|
return Some((end..start, MotionKind::Exclusive));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((new_head, goal)) = self.move_point(
|
let (new_head, goal) = self.move_point(
|
||||||
map,
|
map,
|
||||||
selection.head(),
|
selection.head(),
|
||||||
selection.goal,
|
selection.goal,
|
||||||
times,
|
times,
|
||||||
text_layout_details,
|
text_layout_details,
|
||||||
) {
|
)?;
|
||||||
let mut selection = selection.clone();
|
let mut selection = selection.clone();
|
||||||
selection.set_head(new_head, goal);
|
selection.set_head(new_head, goal);
|
||||||
|
|
||||||
if self.linewise() {
|
let mut kind = self.default_kind();
|
||||||
selection.start = map.prev_line_boundary(selection.start.to_point(map)).1;
|
|
||||||
|
|
||||||
if expand_to_surrounding_newline {
|
if let Motion::NextWordStart {
|
||||||
if selection.end.row() < map.max_point().row() {
|
ignore_punctuation: _,
|
||||||
*selection.end.row_mut() += 1;
|
} = self
|
||||||
*selection.end.column_mut() = 0;
|
{
|
||||||
selection.end = map.clip_point(selection.end, Bias::Right);
|
// Another special case: When using the "w" motion in combination with an
|
||||||
// Don't reset the end here
|
// operator and the last word moved over is at the end of a line, the end of
|
||||||
return Some(selection.start..selection.end);
|
// that word becomes the end of the operated text, not the first word in the
|
||||||
} else if selection.start.row().0 > 0 {
|
// next line.
|
||||||
*selection.start.row_mut() -= 1;
|
let start = selection.start.to_point(map);
|
||||||
*selection.start.column_mut() = map.line_len(selection.start.row());
|
let end = selection.end.to_point(map);
|
||||||
selection.start = map.clip_point(selection.start, Bias::Left);
|
let start_row = MultiBufferRow(selection.start.to_point(map).row);
|
||||||
}
|
if end.row > start.row {
|
||||||
|
selection.end = Point::new(start_row.0, map.buffer_snapshot.line_len(start_row))
|
||||||
|
.to_display_point(map);
|
||||||
|
|
||||||
|
// a bit of a hack, we need `cw` on a blank line to not delete the newline,
|
||||||
|
// but dw on a blank line should. The `Linewise` returned from this method
|
||||||
|
// causes the `d` operator to include the trailing newline.
|
||||||
|
if selection.start == selection.end {
|
||||||
|
return Some((selection.start..selection.end, MotionKind::Linewise));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else if kind == MotionKind::Exclusive && !self.skip_exclusive_special_case() {
|
||||||
|
let start_point = selection.start.to_point(map);
|
||||||
|
let mut end_point = selection.end.to_point(map);
|
||||||
|
|
||||||
selection.end = map.next_line_boundary(selection.end.to_point(map)).1;
|
if end_point.row > start_point.row {
|
||||||
} else {
|
let first_non_blank_of_start_row = map
|
||||||
// Another special case: When using the "w" motion in combination with an
|
.line_indent_for_buffer_row(MultiBufferRow(start_point.row))
|
||||||
// operator and the last word moved over is at the end of a line, the end of
|
.raw_len();
|
||||||
// that word becomes the end of the operated text, not the first word in the
|
// https://github.com/neovim/neovim/blob/ee143aaf65a0e662c42c636aa4a959682858b3e7/src/nvim/ops.c#L6178-L6203
|
||||||
// next line.
|
if end_point.column == 0 {
|
||||||
if let Motion::NextWordStart {
|
// If the motion is exclusive and the end of the motion is in column 1, the
|
||||||
ignore_punctuation: _,
|
// end of the motion is moved to the end of the previous line and the motion
|
||||||
} = self
|
// becomes inclusive. Example: "}" moves to the first line after a paragraph,
|
||||||
{
|
// but "d}" will not include that line.
|
||||||
let start_row = MultiBufferRow(selection.start.to_point(map).row);
|
//
|
||||||
if selection.end.to_point(map).row > start_row.0 {
|
// If the motion is exclusive, the end of the motion is in column 1 and the
|
||||||
selection.end =
|
// start of the motion was at or before the first non-blank in the line, the
|
||||||
Point::new(start_row.0, map.buffer_snapshot.line_len(start_row))
|
// motion becomes linewise. Example: If a paragraph begins with some blanks
|
||||||
.to_display_point(map)
|
// and you do "d}" while standing on the first non-blank, all the lines of
|
||||||
|
// the paragraph are deleted, including the blanks.
|
||||||
|
if start_point.column <= first_non_blank_of_start_row {
|
||||||
|
kind = MotionKind::Linewise;
|
||||||
|
} else {
|
||||||
|
kind = MotionKind::Inclusive;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If the motion is exclusive and the end of the motion is in column 1, the
|
|
||||||
// end of the motion is moved to the end of the previous line and the motion
|
|
||||||
// becomes inclusive. Example: "}" moves to the first line after a paragraph,
|
|
||||||
// but "d}" will not include that line.
|
|
||||||
let mut inclusive = self.inclusive();
|
|
||||||
let start_point = selection.start.to_point(map);
|
|
||||||
let mut end_point = selection.end.to_point(map);
|
|
||||||
|
|
||||||
// DisplayPoint
|
|
||||||
|
|
||||||
if !inclusive
|
|
||||||
&& self != &Motion::WrappingLeft
|
|
||||||
&& end_point.row > start_point.row
|
|
||||||
&& end_point.column == 0
|
|
||||||
{
|
|
||||||
inclusive = true;
|
|
||||||
end_point.row -= 1;
|
end_point.row -= 1;
|
||||||
end_point.column = 0;
|
end_point.column = 0;
|
||||||
selection.end = map.clip_point(map.next_line_boundary(end_point).1, Bias::Left);
|
selection.end = map.clip_point(map.next_line_boundary(end_point).1, Bias::Left);
|
||||||
}
|
}
|
||||||
|
|
||||||
if inclusive && selection.end.column() < map.line_len(selection.end.row()) {
|
|
||||||
selection.end = movement::saturating_right(map, selection.end)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(selection.start..selection.end)
|
} else if kind == MotionKind::Inclusive {
|
||||||
} else {
|
selection.end = movement::saturating_right(map, selection.end)
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if kind == MotionKind::Linewise {
|
||||||
|
selection.start = map.prev_line_boundary(selection.start.to_point(map)).1;
|
||||||
|
selection.end = map.next_line_boundary(selection.end.to_point(map)).1;
|
||||||
|
}
|
||||||
|
Some((selection.start..selection.end, kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expands a selection using self for an operator
|
// Expands a selection using self for an operator
|
||||||
|
@ -1265,22 +1230,12 @@ impl Motion {
|
||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
selection: &mut Selection<DisplayPoint>,
|
selection: &mut Selection<DisplayPoint>,
|
||||||
times: Option<usize>,
|
times: Option<usize>,
|
||||||
expand_to_surrounding_newline: bool,
|
|
||||||
text_layout_details: &TextLayoutDetails,
|
text_layout_details: &TextLayoutDetails,
|
||||||
) -> bool {
|
) -> Option<MotionKind> {
|
||||||
if let Some(range) = self.range(
|
let (range, kind) = self.range(map, selection.clone(), times, text_layout_details)?;
|
||||||
map,
|
selection.start = range.start;
|
||||||
selection.clone(),
|
selection.end = range.end;
|
||||||
times,
|
Some(kind)
|
||||||
expand_to_surrounding_newline,
|
|
||||||
text_layout_details,
|
|
||||||
) {
|
|
||||||
selection.start = range.start;
|
|
||||||
selection.end = range.end;
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ impl Vim {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let anchor = map.display_point_to_anchor(selection.head(), Bias::Left);
|
let anchor = map.display_point_to_anchor(selection.head(), Bias::Left);
|
||||||
selection_starts.insert(selection.id, anchor);
|
selection_starts.insert(selection.id, anchor);
|
||||||
motion.expand_selection(map, selection, times, false, &text_layout_details);
|
motion.expand_selection(map, selection, times, &text_layout_details);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
match mode {
|
match mode {
|
||||||
|
@ -146,18 +146,9 @@ impl Vim {
|
||||||
let mut ranges = Vec::new();
|
let mut ranges = Vec::new();
|
||||||
let mut cursor_positions = Vec::new();
|
let mut cursor_positions = Vec::new();
|
||||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
for selection in editor.selections.all::<Point>(cx) {
|
for selection in editor.selections.all_adjusted(cx) {
|
||||||
match vim.mode {
|
match vim.mode {
|
||||||
Mode::VisualLine => {
|
Mode::Visual | Mode::VisualLine => {
|
||||||
let start = Point::new(selection.start.row, 0);
|
|
||||||
let end = Point::new(
|
|
||||||
selection.end.row,
|
|
||||||
snapshot.line_len(MultiBufferRow(selection.end.row)),
|
|
||||||
);
|
|
||||||
ranges.push(start..end);
|
|
||||||
cursor_positions.push(start..start);
|
|
||||||
}
|
|
||||||
Mode::Visual => {
|
|
||||||
ranges.push(selection.start..selection.end);
|
ranges.push(selection.start..selection.end);
|
||||||
cursor_positions.push(selection.start..selection.start);
|
cursor_positions.push(selection.start..selection.start);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
motion::{self, Motion},
|
motion::{self, Motion, MotionKind},
|
||||||
object::Object,
|
object::Object,
|
||||||
state::Mode,
|
state::Mode,
|
||||||
Vim,
|
Vim,
|
||||||
|
@ -22,14 +22,18 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
// Some motions ignore failure when switching to normal mode
|
// Some motions ignore failure when switching to normal mode
|
||||||
let mut motion_succeeded = matches!(
|
let mut motion_kind = if matches!(
|
||||||
motion,
|
motion,
|
||||||
Motion::Left
|
Motion::Left
|
||||||
| Motion::Right
|
| Motion::Right
|
||||||
| Motion::EndOfLine { .. }
|
| Motion::EndOfLine { .. }
|
||||||
| Motion::WrappingLeft
|
| Motion::WrappingLeft
|
||||||
| Motion::StartOfLine { .. }
|
| Motion::StartOfLine { .. }
|
||||||
);
|
) {
|
||||||
|
Some(MotionKind::Exclusive)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||||
let text_layout_details = editor.text_layout_details(window);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
|
@ -37,7 +41,7 @@ impl Vim {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
motion_succeeded |= match motion {
|
let kind = match motion {
|
||||||
Motion::NextWordStart { ignore_punctuation }
|
Motion::NextWordStart { ignore_punctuation }
|
||||||
| Motion::NextSubwordStart { ignore_punctuation } => {
|
| Motion::NextSubwordStart { ignore_punctuation } => {
|
||||||
expand_changed_word_selection(
|
expand_changed_word_selection(
|
||||||
|
@ -50,11 +54,10 @@ impl Vim {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let result = motion.expand_selection(
|
let kind = motion.expand_selection(
|
||||||
map,
|
map,
|
||||||
selection,
|
selection,
|
||||||
times,
|
times,
|
||||||
false,
|
|
||||||
&text_layout_details,
|
&text_layout_details,
|
||||||
);
|
);
|
||||||
if let Motion::CurrentLine = motion {
|
if let Motion::CurrentLine = motion {
|
||||||
|
@ -71,18 +74,23 @@ impl Vim {
|
||||||
}
|
}
|
||||||
selection.start = start_offset.to_display_point(map);
|
selection.start = start_offset.to_display_point(map);
|
||||||
}
|
}
|
||||||
result
|
kind
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
if let Some(kind) = kind {
|
||||||
|
motion_kind.get_or_insert(kind);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
vim.copy_selections_content(editor, motion.linewise(), window, cx);
|
if let Some(kind) = motion_kind {
|
||||||
editor.insert("", window, cx);
|
vim.copy_selections_content(editor, kind, window, cx);
|
||||||
editor.refresh_inline_completion(true, false, window, cx);
|
editor.insert("", window, cx);
|
||||||
|
editor.refresh_inline_completion(true, false, window, cx);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if motion_succeeded {
|
if motion_kind.is_some() {
|
||||||
self.switch_mode(Mode::Insert, false, window, cx)
|
self.switch_mode(Mode::Insert, false, window, cx)
|
||||||
} else {
|
} else {
|
||||||
self.switch_mode(Mode::Normal, false, window, cx)
|
self.switch_mode(Mode::Normal, false, window, cx)
|
||||||
|
@ -107,7 +115,7 @@ impl Vim {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if objects_found {
|
if objects_found {
|
||||||
vim.copy_selections_content(editor, false, window, cx);
|
vim.copy_selections_content(editor, MotionKind::Exclusive, window, cx);
|
||||||
editor.insert("", window, cx);
|
editor.insert("", window, cx);
|
||||||
editor.refresh_inline_completion(true, false, window, cx);
|
editor.refresh_inline_completion(true, false, window, cx);
|
||||||
}
|
}
|
||||||
|
@ -135,7 +143,7 @@ fn expand_changed_word_selection(
|
||||||
ignore_punctuation: bool,
|
ignore_punctuation: bool,
|
||||||
text_layout_details: &TextLayoutDetails,
|
text_layout_details: &TextLayoutDetails,
|
||||||
use_subword: bool,
|
use_subword: bool,
|
||||||
) -> bool {
|
) -> Option<MotionKind> {
|
||||||
let is_in_word = || {
|
let is_in_word = || {
|
||||||
let classifier = map
|
let classifier = map
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
|
@ -166,14 +174,14 @@ fn expand_changed_word_selection(
|
||||||
selection.end = motion::next_char(map, selection.end, false);
|
selection.end = motion::next_char(map, selection.end, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
Some(MotionKind::Inclusive)
|
||||||
} else {
|
} else {
|
||||||
let motion = if use_subword {
|
let motion = if use_subword {
|
||||||
Motion::NextSubwordStart { ignore_punctuation }
|
Motion::NextSubwordStart { ignore_punctuation }
|
||||||
} else {
|
} else {
|
||||||
Motion::NextWordStart { ignore_punctuation }
|
Motion::NextWordStart { ignore_punctuation }
|
||||||
};
|
};
|
||||||
motion.expand_selection(map, selection, times, false, text_layout_details)
|
motion.expand_selection(map, selection, times, text_layout_details)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
use crate::{motion::Motion, object::Object, Vim};
|
use crate::{
|
||||||
|
motion::{Motion, MotionKind},
|
||||||
|
object::Object,
|
||||||
|
Vim,
|
||||||
|
};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use editor::{
|
use editor::{
|
||||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||||
|
@ -23,44 +27,41 @@ impl Vim {
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, 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();
|
||||||
|
let mut motion_kind = None;
|
||||||
|
let mut ranges_to_copy = Vec::new();
|
||||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let original_head = selection.head();
|
let original_head = selection.head();
|
||||||
original_columns.insert(selection.id, original_head.column());
|
original_columns.insert(selection.id, original_head.column());
|
||||||
motion.expand_selection(map, selection, times, true, &text_layout_details);
|
let kind =
|
||||||
|
motion.expand_selection(map, selection, times, &text_layout_details);
|
||||||
|
|
||||||
let start_point = selection.start.to_point(map);
|
ranges_to_copy
|
||||||
let next_line = map
|
.push(selection.start.to_point(map)..selection.end.to_point(map));
|
||||||
.buffer_snapshot
|
|
||||||
.clip_point(Point::new(start_point.row + 1, 0), Bias::Left)
|
// When deleting line-wise, we always want to delete a newline.
|
||||||
.to_display_point(map);
|
// If there is one after the current line, it goes; otherwise we
|
||||||
match motion {
|
// pick the one before.
|
||||||
// Motion::NextWordStart on an empty line should delete it.
|
if kind == Some(MotionKind::Linewise) {
|
||||||
Motion::NextWordStart { .. }
|
let start = selection.start.to_point(map);
|
||||||
if selection.is_empty()
|
let end = selection.end.to_point(map);
|
||||||
&& map
|
if end.row < map.buffer_snapshot.max_point().row {
|
||||||
.buffer_snapshot
|
selection.end = Point::new(end.row + 1, 0).to_display_point(map)
|
||||||
.line_len(MultiBufferRow(start_point.row))
|
} else if start.row > 0 {
|
||||||
== 0 =>
|
selection.start = Point::new(
|
||||||
{
|
start.row - 1,
|
||||||
selection.end = next_line
|
map.buffer_snapshot.line_len(MultiBufferRow(start.row - 1)),
|
||||||
|
)
|
||||||
|
.to_display_point(map)
|
||||||
}
|
}
|
||||||
// Sentence motions, when done from start of line, include the newline
|
}
|
||||||
Motion::SentenceForward | Motion::SentenceBackward
|
if let Some(kind) = kind {
|
||||||
if selection.start.column() == 0 =>
|
motion_kind.get_or_insert(kind);
|
||||||
{
|
|
||||||
selection.end = next_line
|
|
||||||
}
|
|
||||||
Motion::EndOfDocument {} if times.is_none() => {
|
|
||||||
// Deleting until the end of the document includes the last line, including
|
|
||||||
// soft-wrapped lines.
|
|
||||||
selection.end = map.max_point()
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
vim.copy_selections_content(editor, motion.linewise(), window, cx);
|
let Some(kind) = motion_kind else { return };
|
||||||
|
vim.copy_ranges(editor, kind, false, ranges_to_copy, window, cx);
|
||||||
editor.insert("", window, cx);
|
editor.insert("", window, cx);
|
||||||
|
|
||||||
// Fixup cursor position after the deletion
|
// Fixup cursor position after the deletion
|
||||||
|
@ -68,7 +69,7 @@ impl Vim {
|
||||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let mut cursor = selection.head();
|
let mut cursor = selection.head();
|
||||||
if motion.linewise() {
|
if kind.linewise() {
|
||||||
if let Some(column) = original_columns.get(&selection.id) {
|
if let Some(column) = original_columns.get(&selection.id) {
|
||||||
*cursor.column_mut() = *column
|
*cursor.column_mut() = *column
|
||||||
}
|
}
|
||||||
|
@ -148,7 +149,7 @@ impl Vim {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
vim.copy_selections_content(editor, false, window, cx);
|
vim.copy_selections_content(editor, MotionKind::Exclusive, window, cx);
|
||||||
editor.insert("", window, cx);
|
editor.insert("", window, cx);
|
||||||
|
|
||||||
// Fixup cursor position after the deletion
|
// Fixup cursor position after the deletion
|
||||||
|
@ -654,36 +655,36 @@ mod test {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_delete_sentence(cx: &mut gpui::TestAppContext) {
|
async fn test_delete_sentence(cx: &mut gpui::TestAppContext) {
|
||||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
cx.simulate(
|
// cx.simulate(
|
||||||
"d )",
|
// "d )",
|
||||||
indoc! {"
|
// indoc! {"
|
||||||
Fiˇrst. Second. Third.
|
// Fiˇrst. Second. Third.
|
||||||
Fourth.
|
// Fourth.
|
||||||
"},
|
// "},
|
||||||
)
|
// )
|
||||||
.await
|
// .await
|
||||||
.assert_matches();
|
// .assert_matches();
|
||||||
|
|
||||||
cx.simulate(
|
// cx.simulate(
|
||||||
"d )",
|
// "d )",
|
||||||
indoc! {"
|
// indoc! {"
|
||||||
First. Secˇond. Third.
|
// First. Secˇond. Third.
|
||||||
Fourth.
|
// Fourth.
|
||||||
"},
|
// "},
|
||||||
)
|
// )
|
||||||
.await
|
// .await
|
||||||
.assert_matches();
|
// .assert_matches();
|
||||||
|
|
||||||
// Two deletes
|
// // Two deletes
|
||||||
cx.simulate(
|
// cx.simulate(
|
||||||
"d ) d )",
|
// "d ) d )",
|
||||||
indoc! {"
|
// indoc! {"
|
||||||
First. Second. Thirˇd.
|
// First. Second. Thirˇd.
|
||||||
Fourth.
|
// Fourth.
|
||||||
"},
|
// "},
|
||||||
)
|
// )
|
||||||
.await
|
// .await
|
||||||
.assert_matches();
|
// .assert_matches();
|
||||||
|
|
||||||
// Should delete whole line if done on first column
|
// Should delete whole line if done on first column
|
||||||
cx.simulate(
|
cx.simulate(
|
||||||
|
|
|
@ -6,7 +6,7 @@ use serde::Deserialize;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
motion::Motion,
|
motion::{Motion, MotionKind},
|
||||||
object::Object,
|
object::Object,
|
||||||
state::{Mode, Register},
|
state::{Mode, Register},
|
||||||
Vim,
|
Vim,
|
||||||
|
@ -50,7 +50,7 @@ impl Vim {
|
||||||
.filter(|sel| sel.len() > 1 && vim.mode != Mode::VisualLine);
|
.filter(|sel| sel.len() > 1 && vim.mode != Mode::VisualLine);
|
||||||
|
|
||||||
if !action.preserve_clipboard && vim.mode.is_visual() {
|
if !action.preserve_clipboard && vim.mode.is_visual() {
|
||||||
vim.copy_selections_content(editor, vim.mode == Mode::VisualLine, window, cx);
|
vim.copy_selections_content(editor, MotionKind::for_mode(vim.mode), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (display_map, current_selections) = editor.selections.all_adjusted_display(cx);
|
let (display_map, current_selections) = editor.selections.all_adjusted_display(cx);
|
||||||
|
@ -118,8 +118,8 @@ impl Vim {
|
||||||
} else {
|
} else {
|
||||||
to_insert = "\n".to_owned() + &to_insert;
|
to_insert = "\n".to_owned() + &to_insert;
|
||||||
}
|
}
|
||||||
} else if !line_mode && vim.mode == Mode::VisualLine {
|
} else if line_mode && vim.mode == Mode::VisualLine {
|
||||||
to_insert += "\n";
|
to_insert.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
let display_range = if !selection.is_empty() {
|
let display_range = if !selection.is_empty() {
|
||||||
|
@ -257,7 +257,7 @@ impl Vim {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
editor.change_selections(None, window, cx, |s| {
|
editor.change_selections(None, window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
motion.expand_selection(map, selection, times, false, &text_layout_details);
|
motion.expand_selection(map, selection, times, &text_layout_details);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -537,6 +537,7 @@ mod test {
|
||||||
cx.shared_state().await.assert_eq(indoc! {"
|
cx.shared_state().await.assert_eq(indoc! {"
|
||||||
The quick brown
|
The quick brown
|
||||||
the laˇzy dog"});
|
the laˇzy dog"});
|
||||||
|
cx.shared_clipboard().await.assert_eq("fox jumps over\n");
|
||||||
// paste in visual line mode
|
// paste in visual line mode
|
||||||
cx.simulate_shared_keystrokes("k shift-v p").await;
|
cx.simulate_shared_keystrokes("k shift-v p").await;
|
||||||
cx.shared_state().await.assert_eq(indoc! {"
|
cx.shared_state().await.assert_eq(indoc! {"
|
||||||
|
|
|
@ -2,7 +2,10 @@ use editor::{movement, Editor};
|
||||||
use gpui::{actions, Context, Window};
|
use gpui::{actions, Context, Window};
|
||||||
use language::Point;
|
use language::Point;
|
||||||
|
|
||||||
use crate::{motion::Motion, Mode, Vim};
|
use crate::{
|
||||||
|
motion::{Motion, MotionKind},
|
||||||
|
Mode, Vim,
|
||||||
|
};
|
||||||
|
|
||||||
actions!(vim, [Substitute, SubstituteLine]);
|
actions!(vim, [Substitute, SubstituteLine]);
|
||||||
|
|
||||||
|
@ -43,7 +46,6 @@ impl Vim {
|
||||||
map,
|
map,
|
||||||
selection,
|
selection,
|
||||||
count,
|
count,
|
||||||
true,
|
|
||||||
&text_layout_details,
|
&text_layout_details,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -57,7 +59,6 @@ impl Vim {
|
||||||
map,
|
map,
|
||||||
selection,
|
selection,
|
||||||
None,
|
None,
|
||||||
false,
|
|
||||||
&text_layout_details,
|
&text_layout_details,
|
||||||
);
|
);
|
||||||
if let Some((point, _)) = (Motion::FirstNonWhitespace {
|
if let Some((point, _)) = (Motion::FirstNonWhitespace {
|
||||||
|
@ -75,7 +76,12 @@ impl Vim {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
vim.copy_selections_content(editor, line_mode, window, cx);
|
let kind = if line_mode {
|
||||||
|
MotionKind::Linewise
|
||||||
|
} else {
|
||||||
|
MotionKind::Exclusive
|
||||||
|
};
|
||||||
|
vim.copy_selections_content(editor, kind, window, cx);
|
||||||
let selections = editor.selections.all::<Point>(cx).into_iter();
|
let selections = editor.selections.all::<Point>(cx).into_iter();
|
||||||
let edits = selections.map(|selection| (selection.start..selection.end, ""));
|
let edits = selections.map(|selection| (selection.start..selection.end, ""));
|
||||||
editor.edit(edits, cx);
|
editor.edit(edits, cx);
|
||||||
|
|
|
@ -21,7 +21,7 @@ impl Vim {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
|
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
|
||||||
selection_starts.insert(selection.id, anchor);
|
selection_starts.insert(selection.id, anchor);
|
||||||
motion.expand_selection(map, selection, times, false, &text_layout_details);
|
motion.expand_selection(map, selection, times, &text_layout_details);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
editor.toggle_comments(&Default::default(), window, cx);
|
editor.toggle_comments(&Default::default(), window, cx);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{ops::Range, time::Duration};
|
use std::{ops::Range, time::Duration};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
motion::Motion,
|
motion::{Motion, MotionKind},
|
||||||
object::Object,
|
object::Object,
|
||||||
state::{Mode, Register},
|
state::{Mode, Register},
|
||||||
Vim, VimSettings,
|
Vim, VimSettings,
|
||||||
|
@ -29,14 +29,16 @@ impl Vim {
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
let mut original_positions: HashMap<_, _> = Default::default();
|
let mut original_positions: HashMap<_, _> = Default::default();
|
||||||
|
let mut kind = None;
|
||||||
editor.change_selections(None, window, cx, |s| {
|
editor.change_selections(None, window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let original_position = (selection.head(), selection.goal);
|
let original_position = (selection.head(), selection.goal);
|
||||||
original_positions.insert(selection.id, original_position);
|
original_positions.insert(selection.id, original_position);
|
||||||
motion.expand_selection(map, selection, times, true, &text_layout_details);
|
kind = motion.expand_selection(map, selection, times, &text_layout_details);
|
||||||
});
|
})
|
||||||
});
|
});
|
||||||
vim.yank_selections_content(editor, motion.linewise(), window, cx);
|
let Some(kind) = kind else { return };
|
||||||
|
vim.yank_selections_content(editor, kind, window, cx);
|
||||||
editor.change_selections(None, window, cx, |s| {
|
editor.change_selections(None, window, cx, |s| {
|
||||||
s.move_with(|_, selection| {
|
s.move_with(|_, selection| {
|
||||||
let (head, goal) = original_positions.remove(&selection.id).unwrap();
|
let (head, goal) = original_positions.remove(&selection.id).unwrap();
|
||||||
|
@ -66,7 +68,7 @@ impl Vim {
|
||||||
start_positions.insert(selection.id, start_position);
|
start_positions.insert(selection.id, start_position);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
vim.yank_selections_content(editor, false, window, cx);
|
vim.yank_selections_content(editor, MotionKind::Exclusive, window, cx);
|
||||||
editor.change_selections(None, window, cx, |s| {
|
editor.change_selections(None, window, cx, |s| {
|
||||||
s.move_with(|_, selection| {
|
s.move_with(|_, selection| {
|
||||||
let (head, goal) = start_positions.remove(&selection.id).unwrap();
|
let (head, goal) = start_positions.remove(&selection.id).unwrap();
|
||||||
|
@ -81,13 +83,13 @@ impl Vim {
|
||||||
pub fn yank_selections_content(
|
pub fn yank_selections_content(
|
||||||
&mut self,
|
&mut self,
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
linewise: bool,
|
kind: MotionKind,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) {
|
) {
|
||||||
self.copy_ranges(
|
self.copy_ranges(
|
||||||
editor,
|
editor,
|
||||||
linewise,
|
kind,
|
||||||
true,
|
true,
|
||||||
editor
|
editor
|
||||||
.selections
|
.selections
|
||||||
|
@ -103,13 +105,13 @@ impl Vim {
|
||||||
pub fn copy_selections_content(
|
pub fn copy_selections_content(
|
||||||
&mut self,
|
&mut self,
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
linewise: bool,
|
kind: MotionKind,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) {
|
) {
|
||||||
self.copy_ranges(
|
self.copy_ranges(
|
||||||
editor,
|
editor,
|
||||||
linewise,
|
kind,
|
||||||
false,
|
false,
|
||||||
editor
|
editor
|
||||||
.selections
|
.selections
|
||||||
|
@ -125,7 +127,7 @@ impl Vim {
|
||||||
pub(crate) fn copy_ranges(
|
pub(crate) fn copy_ranges(
|
||||||
&mut self,
|
&mut self,
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
linewise: bool,
|
kind: MotionKind,
|
||||||
is_yank: bool,
|
is_yank: bool,
|
||||||
selections: Vec<Range<Point>>,
|
selections: Vec<Range<Point>>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
|
@ -160,7 +162,7 @@ impl Vim {
|
||||||
{
|
{
|
||||||
let mut is_first = true;
|
let mut is_first = true;
|
||||||
for selection in selections.iter() {
|
for selection in selections.iter() {
|
||||||
let mut start = selection.start;
|
let start = selection.start;
|
||||||
let end = selection.end;
|
let end = selection.end;
|
||||||
if is_first {
|
if is_first {
|
||||||
is_first = false;
|
is_first = false;
|
||||||
|
@ -169,23 +171,6 @@ impl Vim {
|
||||||
}
|
}
|
||||||
let initial_len = text.len();
|
let initial_len = text.len();
|
||||||
|
|
||||||
// if the file does not end with \n, and our line-mode selection ends on
|
|
||||||
// that line, we will have expanded the start of the selection to ensure it
|
|
||||||
// contains a newline (so that delete works as expected). We undo that change
|
|
||||||
// here.
|
|
||||||
let max_point = buffer.max_point();
|
|
||||||
let should_adjust_start = linewise
|
|
||||||
&& end.row == max_point.row
|
|
||||||
&& max_point.column > 0
|
|
||||||
&& start.row < max_point.row
|
|
||||||
&& start == Point::new(start.row, buffer.line_len(MultiBufferRow(start.row)));
|
|
||||||
let should_add_newline =
|
|
||||||
should_adjust_start || (end == max_point && max_point.column > 0 && linewise);
|
|
||||||
|
|
||||||
if should_adjust_start {
|
|
||||||
start = Point::new(start.row + 1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let start_anchor = buffer.anchor_after(start);
|
let start_anchor = buffer.anchor_after(start);
|
||||||
let end_anchor = buffer.anchor_before(end);
|
let end_anchor = buffer.anchor_before(end);
|
||||||
ranges_to_highlight.push(start_anchor..end_anchor);
|
ranges_to_highlight.push(start_anchor..end_anchor);
|
||||||
|
@ -193,12 +178,12 @@ impl Vim {
|
||||||
for chunk in buffer.text_for_range(start..end) {
|
for chunk in buffer.text_for_range(start..end) {
|
||||||
text.push_str(chunk);
|
text.push_str(chunk);
|
||||||
}
|
}
|
||||||
if should_add_newline {
|
if kind.linewise() {
|
||||||
text.push('\n');
|
text.push('\n');
|
||||||
}
|
}
|
||||||
clipboard_selections.push(ClipboardSelection {
|
clipboard_selections.push(ClipboardSelection {
|
||||||
len: text.len() - initial_len,
|
len: text.len() - initial_len,
|
||||||
is_entire_line: linewise,
|
is_entire_line: kind.linewise(),
|
||||||
first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
|
first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -213,7 +198,7 @@ impl Vim {
|
||||||
},
|
},
|
||||||
selected_register,
|
selected_register,
|
||||||
is_yank,
|
is_yank,
|
||||||
linewise,
|
kind,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
|
@ -188,13 +188,7 @@ impl Vim {
|
||||||
let text_layout_details = editor.text_layout_details(window);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
let mut selection = editor.selections.newest_display(cx);
|
let mut selection = editor.selections.newest_display(cx);
|
||||||
let snapshot = editor.snapshot(window, cx);
|
let snapshot = editor.snapshot(window, cx);
|
||||||
motion.expand_selection(
|
motion.expand_selection(&snapshot, &mut selection, times, &text_layout_details);
|
||||||
&snapshot,
|
|
||||||
&mut selection,
|
|
||||||
times,
|
|
||||||
false,
|
|
||||||
&text_layout_details,
|
|
||||||
);
|
|
||||||
let start = snapshot
|
let start = snapshot
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.anchor_before(selection.start.to_point(&snapshot));
|
.anchor_before(selection.start.to_point(&snapshot));
|
||||||
|
|
|
@ -55,7 +55,7 @@ impl Vim {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
|
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
|
||||||
selection_starts.insert(selection.id, anchor);
|
selection_starts.insert(selection.id, anchor);
|
||||||
motion.expand_selection(map, selection, times, false, &text_layout_details);
|
motion.expand_selection(map, selection, times, &text_layout_details);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
editor.rewrap_impl(
|
editor.rewrap_impl(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::command::command_interceptor;
|
use crate::command::command_interceptor;
|
||||||
|
use crate::motion::MotionKind;
|
||||||
use crate::normal::repeat::Replayer;
|
use crate::normal::repeat::Replayer;
|
||||||
use crate::surrounds::SurroundsType;
|
use crate::surrounds::SurroundsType;
|
||||||
use crate::{motion::Motion, object::Object};
|
use crate::{motion::Motion, object::Object};
|
||||||
|
@ -695,7 +696,7 @@ impl VimGlobals {
|
||||||
content: Register,
|
content: Register,
|
||||||
register: Option<char>,
|
register: Option<char>,
|
||||||
is_yank: bool,
|
is_yank: bool,
|
||||||
linewise: bool,
|
kind: MotionKind,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) {
|
) {
|
||||||
if let Some(register) = register {
|
if let Some(register) = register {
|
||||||
|
@ -752,7 +753,7 @@ impl VimGlobals {
|
||||||
if !contains_newline {
|
if !contains_newline {
|
||||||
self.registers.insert('-', content.clone());
|
self.registers.insert('-', content.clone());
|
||||||
}
|
}
|
||||||
if linewise || contains_newline {
|
if kind.linewise() || contains_newline {
|
||||||
let mut content = content;
|
let mut content = content;
|
||||||
for i in '1'..'8' {
|
for i in '1'..'8' {
|
||||||
if let Some(moved) = self.registers.insert(i, content) {
|
if let Some(moved) = self.registers.insert(i, content) {
|
||||||
|
|
|
@ -55,14 +55,8 @@ impl Vim {
|
||||||
}
|
}
|
||||||
SurroundsType::Motion(motion) => {
|
SurroundsType::Motion(motion) => {
|
||||||
motion
|
motion
|
||||||
.range(
|
.range(&display_map, selection.clone(), count, &text_layout_details)
|
||||||
&display_map,
|
.map(|(mut range, _)| {
|
||||||
selection.clone(),
|
|
||||||
count,
|
|
||||||
true,
|
|
||||||
&text_layout_details,
|
|
||||||
)
|
|
||||||
.map(|mut range| {
|
|
||||||
// The Motion::CurrentLine operation will contain the newline of the current line and leading/trailing whitespace
|
// The Motion::CurrentLine operation will contain the newline of the current line and leading/trailing whitespace
|
||||||
if let Motion::CurrentLine = motion {
|
if let Motion::CurrentLine = motion {
|
||||||
range.start = motion::first_non_whitespace(
|
range.start = motion::first_non_whitespace(
|
||||||
|
@ -72,11 +66,7 @@ impl Vim {
|
||||||
);
|
);
|
||||||
range.end = movement::saturating_right(
|
range.end = movement::saturating_right(
|
||||||
&display_map,
|
&display_map,
|
||||||
motion::last_non_whitespace(
|
motion::last_non_whitespace(&display_map, range.end, 1),
|
||||||
&display_map,
|
|
||||||
movement::left(&display_map, range.end),
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
range
|
range
|
||||||
|
@ -89,7 +79,7 @@ impl Vim {
|
||||||
let start = range.start.to_offset(&display_map, Bias::Right);
|
let start = range.start.to_offset(&display_map, Bias::Right);
|
||||||
let end = range.end.to_offset(&display_map, Bias::Left);
|
let end = range.end.to_offset(&display_map, Bias::Left);
|
||||||
let (start_cursor_str, end_cursor_str) = if mode == Mode::VisualLine {
|
let (start_cursor_str, end_cursor_str) = if mode == Mode::VisualLine {
|
||||||
(format!("{}\n", pair.start), format!("{}\n", pair.end))
|
(format!("{}\n", pair.start), format!("\n{}", pair.end))
|
||||||
} else {
|
} else {
|
||||||
let maybe_space = if surround { " " } else { "" };
|
let maybe_space = if surround { " " } else { "" };
|
||||||
(
|
(
|
||||||
|
|
|
@ -1902,3 +1902,71 @@ async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
|
||||||
"
|
"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_delete_paragraph_motion(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"ˇhello world.
|
||||||
|
|
||||||
|
hello world.
|
||||||
|
"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes("y }").await;
|
||||||
|
cx.shared_clipboard().await.assert_eq("hello world.\n");
|
||||||
|
cx.simulate_shared_keystrokes("d }").await;
|
||||||
|
cx.shared_state().await.assert_eq("ˇ\nhello world.\n");
|
||||||
|
cx.shared_clipboard().await.assert_eq("hello world.\n");
|
||||||
|
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"helˇlo world.
|
||||||
|
|
||||||
|
hello world.
|
||||||
|
"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes("y }").await;
|
||||||
|
cx.shared_clipboard().await.assert_eq("lo world.");
|
||||||
|
cx.simulate_shared_keystrokes("d }").await;
|
||||||
|
cx.shared_state().await.assert_eq("heˇl\n\nhello world.\n");
|
||||||
|
cx.shared_clipboard().await.assert_eq("lo world.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_delete_unmatched_brace(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"fn o(wow: i32) {
|
||||||
|
dbgˇ!(wow)
|
||||||
|
dbg!(wow)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes("d ] }").await;
|
||||||
|
cx.shared_state().await.assert_eq(indoc! {
|
||||||
|
"fn o(wow: i32) {
|
||||||
|
dbˇg
|
||||||
|
}
|
||||||
|
"
|
||||||
|
});
|
||||||
|
cx.shared_clipboard().await.assert_eq("!(wow)\n dbg!(wow)");
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"fn o(wow: i32) {
|
||||||
|
ˇdbg!(wow)
|
||||||
|
dbg!(wow)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes("d ] }").await;
|
||||||
|
cx.shared_state().await.assert_eq(indoc! {
|
||||||
|
"fn o(wow: i32) {
|
||||||
|
ˇ}
|
||||||
|
"
|
||||||
|
});
|
||||||
|
cx.shared_clipboard()
|
||||||
|
.await
|
||||||
|
.assert_eq(" dbg!(wow)\n dbg!(wow)\n");
|
||||||
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@ impl SharedClipboard {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = if expected == self.neovim {
|
let message = if expected != self.neovim {
|
||||||
"Test is incorrect (currently expected != neovim_state)"
|
"Test is incorrect (currently expected != neovim_state)"
|
||||||
} else {
|
} else {
|
||||||
"Editor does not match nvim behavior"
|
"Editor does not match nvim behavior"
|
||||||
|
@ -119,12 +119,9 @@ impl SharedClipboard {
|
||||||
{}
|
{}
|
||||||
# keystrokes:
|
# keystrokes:
|
||||||
{}
|
{}
|
||||||
# currently expected:
|
# currently expected: {:?}
|
||||||
{}
|
# neovim register \"{}: {:?}
|
||||||
# neovim register \"{}:
|
# zed register \"{}: {:?}"},
|
||||||
{}
|
|
||||||
# zed register \"{}:
|
|
||||||
{}"},
|
|
||||||
message,
|
message,
|
||||||
self.state.initial,
|
self.state.initial,
|
||||||
self.state.recent_keystrokes,
|
self.state.recent_keystrokes,
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::{
|
use editor::{
|
||||||
display_map::{DisplayRow, DisplaySnapshot, ToDisplayPoint},
|
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||||
movement,
|
movement,
|
||||||
scroll::Autoscroll,
|
scroll::Autoscroll,
|
||||||
Bias, DisplayPoint, Editor, ToOffset,
|
Bias, DisplayPoint, Editor, ToOffset,
|
||||||
|
@ -15,7 +15,7 @@ use util::ResultExt;
|
||||||
use workspace::searchable::Direction;
|
use workspace::searchable::Direction;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
motion::{first_non_whitespace, next_line_end, start_of_line, Motion},
|
motion::{first_non_whitespace, next_line_end, start_of_line, Motion, MotionKind},
|
||||||
object::Object,
|
object::Object,
|
||||||
state::{Mark, Mode, Operator},
|
state::{Mark, Mode, Operator},
|
||||||
Vim,
|
Vim,
|
||||||
|
@ -503,6 +503,7 @@ impl Vim {
|
||||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||||
let mut original_columns: HashMap<_, _> = Default::default();
|
let mut original_columns: HashMap<_, _> = Default::default();
|
||||||
let line_mode = line_mode || editor.selections.line_mode;
|
let line_mode = line_mode || editor.selections.line_mode;
|
||||||
|
editor.selections.line_mode = false;
|
||||||
|
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
|
@ -515,28 +516,49 @@ impl Vim {
|
||||||
original_columns.insert(selection.id, position.to_point(map).column);
|
original_columns.insert(selection.id, position.to_point(map).column);
|
||||||
if vim.mode == Mode::VisualBlock {
|
if vim.mode == Mode::VisualBlock {
|
||||||
*selection.end.column_mut() = map.line_len(selection.end.row())
|
*selection.end.column_mut() = map.line_len(selection.end.row())
|
||||||
} else if vim.mode != Mode::VisualLine {
|
} else {
|
||||||
selection.start = DisplayPoint::new(selection.start.row(), 0);
|
let start = selection.start.to_point(map);
|
||||||
selection.end =
|
let end = selection.end.to_point(map);
|
||||||
map.next_line_boundary(selection.end.to_point(map)).1;
|
selection.start = map.prev_line_boundary(start).1;
|
||||||
if selection.end.row() == map.max_point().row() {
|
if end.column == 0 && end > start {
|
||||||
selection.end = map.max_point();
|
let row = end.row.saturating_sub(1);
|
||||||
if selection.start == selection.end {
|
selection.end = Point::new(
|
||||||
let prev_row =
|
row,
|
||||||
DisplayRow(selection.start.row().0.saturating_sub(1));
|
map.buffer_snapshot.line_len(MultiBufferRow(row)),
|
||||||
selection.start =
|
)
|
||||||
DisplayPoint::new(prev_row, map.line_len(prev_row));
|
.to_display_point(map)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
*selection.end.row_mut() += 1;
|
selection.end = map.next_line_boundary(end).1;
|
||||||
*selection.end.column_mut() = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
selection.goal = SelectionGoal::None;
|
selection.goal = SelectionGoal::None;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
vim.copy_selections_content(editor, line_mode, window, cx);
|
let kind = if line_mode {
|
||||||
|
MotionKind::Linewise
|
||||||
|
} else {
|
||||||
|
MotionKind::Exclusive
|
||||||
|
};
|
||||||
|
vim.copy_selections_content(editor, kind, window, cx);
|
||||||
|
|
||||||
|
if line_mode && vim.mode != Mode::VisualBlock {
|
||||||
|
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
|
let end = selection.end.to_point(map);
|
||||||
|
let start = selection.start.to_point(map);
|
||||||
|
if end.row < map.buffer_snapshot.max_point().row {
|
||||||
|
selection.end = Point::new(end.row + 1, 0).to_display_point(map)
|
||||||
|
} else if start.row > 0 {
|
||||||
|
selection.start = Point::new(
|
||||||
|
start.row - 1,
|
||||||
|
map.buffer_snapshot.line_len(MultiBufferRow(start.row - 1)),
|
||||||
|
)
|
||||||
|
.to_display_point(map)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
editor.insert("", window, cx);
|
editor.insert("", window, cx);
|
||||||
|
|
||||||
// Fixup cursor position after the deletion
|
// Fixup cursor position after the deletion
|
||||||
|
@ -565,7 +587,12 @@ impl Vim {
|
||||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||||
let line_mode = line_mode || editor.selections.line_mode;
|
let line_mode = line_mode || editor.selections.line_mode;
|
||||||
editor.selections.line_mode = line_mode;
|
editor.selections.line_mode = line_mode;
|
||||||
vim.yank_selections_content(editor, line_mode, window, cx);
|
let kind = if line_mode {
|
||||||
|
MotionKind::Linewise
|
||||||
|
} else {
|
||||||
|
MotionKind::Exclusive
|
||||||
|
};
|
||||||
|
vim.yank_selections_content(editor, kind, window, cx);
|
||||||
editor.change_selections(None, window, cx, |s| {
|
editor.change_selections(None, window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if line_mode {
|
if line_mode {
|
||||||
|
|
14
crates/vim/test_data/test_delete_paragraph.json
Normal file
14
crates/vim/test_data/test_delete_paragraph.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{"Put":{"state":"helˇlo world.\n\nhello world.\n"}}
|
||||||
|
{"Key":"y"}
|
||||||
|
{"Key":"}"}
|
||||||
|
{"Key":"d"}
|
||||||
|
{"Key":"}"}
|
||||||
|
{"Get":{"state":"heˇl\n\nhello world.\n","mode":"Normal"}}
|
||||||
|
{"ReadRegister":{"name":"\"","value":"lo world."}}
|
||||||
|
{"Get":{"state":"heˇl\n\nhello world.\n","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"ˇhello world.\n\nhello world.\n"}}
|
||||||
|
{"Key":"y"}
|
||||||
|
{"Key":"}"}
|
||||||
|
{"Key":"d"}
|
||||||
|
{"Key":"}"}
|
||||||
|
{"Get":{"state":"ˇ\nhello world.\n","mode":"Normal"}}
|
18
crates/vim/test_data/test_delete_paragraph_motion.json
Normal file
18
crates/vim/test_data/test_delete_paragraph_motion.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{"Put":{"state":"ˇhello world.\n\nhello world.\n"}}
|
||||||
|
{"Key":"y"}
|
||||||
|
{"Key":"}"}
|
||||||
|
{"Get":{"state":"ˇhello world.\n\nhello world.\n","mode":"Normal"}}
|
||||||
|
{"ReadRegister":{"name":"\"","value":"hello world.\n"}}
|
||||||
|
{"Key":"d"}
|
||||||
|
{"Key":"}"}
|
||||||
|
{"Get":{"state":"ˇ\nhello world.\n","mode":"Normal"}}
|
||||||
|
{"ReadRegister":{"name":"\"","value":"hello world.\n"}}
|
||||||
|
{"Put":{"state":"helˇlo world.\n\nhello world.\n"}}
|
||||||
|
{"Key":"y"}
|
||||||
|
{"Key":"}"}
|
||||||
|
{"Get":{"state":"helˇlo world.\n\nhello world.\n","mode":"Normal"}}
|
||||||
|
{"ReadRegister":{"name":"\"","value":"lo world."}}
|
||||||
|
{"Key":"d"}
|
||||||
|
{"Key":"}"}
|
||||||
|
{"Get":{"state":"heˇl\n\nhello world.\n","mode":"Normal"}}
|
||||||
|
{"ReadRegister":{"name":"\"","value":"lo world."}}
|
|
@ -1,17 +1,3 @@
|
||||||
{"Put":{"state":"Fiˇrst. Second. Third.\nFourth.\n"}}
|
|
||||||
{"Key":"d"}
|
|
||||||
{"Key":")"}
|
|
||||||
{"Get":{"state":"FiˇSecond. Third.\nFourth.\n","mode":"Normal"}}
|
|
||||||
{"Put":{"state":"First. Secˇond. Third.\nFourth.\n"}}
|
|
||||||
{"Key":"d"}
|
|
||||||
{"Key":")"}
|
|
||||||
{"Get":{"state":"First. SecˇThird.\nFourth.\n","mode":"Normal"}}
|
|
||||||
{"Put":{"state":"First. Second. Thirˇd.\nFourth.\n"}}
|
|
||||||
{"Key":"d"}
|
|
||||||
{"Key":")"}
|
|
||||||
{"Key":"d"}
|
|
||||||
{"Key":")"}
|
|
||||||
{"Get":{"state":"First. Second. Thˇi\n","mode":"Normal"}}
|
|
||||||
{"Put":{"state":"ˇFirst.\nFourth.\n"}}
|
{"Put":{"state":"ˇFirst.\nFourth.\n"}}
|
||||||
{"Key":"d"}
|
{"Key":"d"}
|
||||||
{"Key":")"}
|
{"Key":")"}
|
||||||
|
|
12
crates/vim/test_data/test_delete_unmatched_brace.json
Normal file
12
crates/vim/test_data/test_delete_unmatched_brace.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{"Put":{"state":"fn o(wow: i32) {\n dbgˇ!(wow)\n dbg!(wow)\n}\n"}}
|
||||||
|
{"Key":"d"}
|
||||||
|
{"Key":"]"}
|
||||||
|
{"Key":"}"}
|
||||||
|
{"Get":{"state":"fn o(wow: i32) {\n dbˇg\n}\n","mode":"Normal"}}
|
||||||
|
{"ReadRegister":{"name":"\"","value":"!(wow)\n dbg!(wow)"}}
|
||||||
|
{"Put":{"state":"fn o(wow: i32) {\n ˇdbg!(wow)\n dbg!(wow)\n}\n"}}
|
||||||
|
{"Key":"d"}
|
||||||
|
{"Key":"]"}
|
||||||
|
{"Key":"}"}
|
||||||
|
{"Get":{"state":"fn o(wow: i32) {\nˇ}\n","mode":"Normal"}}
|
||||||
|
{"ReadRegister":{"name":"\"","value":" dbg!(wow)\n dbg!(wow)\n"}}
|
|
@ -35,6 +35,7 @@
|
||||||
{"Key":"shift-v"}
|
{"Key":"shift-v"}
|
||||||
{"Key":"d"}
|
{"Key":"d"}
|
||||||
{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
|
{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
|
||||||
|
{"ReadRegister":{"name":"\"","value":"fox jumps over\n"}}
|
||||||
{"Key":"k"}
|
{"Key":"k"}
|
||||||
{"Key":"shift-v"}
|
{"Key":"shift-v"}
|
||||||
{"Key":"p"}
|
{"Key":"p"}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue