Stop unnecessary repeat cursor movements in Vim mode (#7641)

Fixes: #7605

When repeating some cursor movements in Vim mode (e.g. `99999999 w`),
Zed tries to repeat the movement that many times, even if further
actions don't have any effect. This causes Zed to hang.

This commit makes those movements like other actions (like moving the
cursor left/right), stopping the repeat movement if a boundary of the
text is reached/the cursor can't move anymore.

Release Notes:

- Fixed [#7605](https://github.com/zed-industries/zed/issues/7605).
This commit is contained in:
Colin Cai 2024-02-10 00:50:38 -05:00 committed by GitHub
parent 0304edd8ab
commit e2a3e89318
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -617,6 +617,9 @@ fn left(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> Display
fn backspace(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint { fn backspace(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
for _ in 0..times { for _ in 0..times {
point = movement::left(map, point); point = movement::left(map, point);
if point.is_zero() {
break;
}
} }
point point
} }
@ -624,6 +627,9 @@ fn backspace(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> Di
fn space(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint { fn space(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
for _ in 0..times { for _ in 0..times {
point = wrapping_right(map, point); point = wrapping_right(map, point);
if point == map.max_point() {
break;
}
} }
point point
} }
@ -768,7 +774,7 @@ pub(crate) fn next_word_start(
let scope = map.buffer_snapshot.language_scope_at(point.to_point(map)); let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
for _ in 0..times { for _ in 0..times {
let mut crossed_newline = false; let mut crossed_newline = false;
point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| { let new_point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| {
let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation); let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation); let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
let at_newline = right == '\n'; let at_newline = right == '\n';
@ -779,7 +785,11 @@ pub(crate) fn next_word_start(
crossed_newline |= at_newline; crossed_newline |= at_newline;
found found
}) });
if point == new_point {
break;
}
point = new_point;
} }
point point
} }
@ -792,21 +802,30 @@ fn next_word_end(
) -> DisplayPoint { ) -> DisplayPoint {
let scope = map.buffer_snapshot.language_scope_at(point.to_point(map)); let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
for _ in 0..times { for _ in 0..times {
if point.column() < map.line_len(point.row()) { let mut new_point = point;
*point.column_mut() += 1; if new_point.column() < map.line_len(new_point.row()) {
} else if point.row() < map.max_buffer_row() { *new_point.column_mut() += 1;
*point.row_mut() += 1; } else if new_point.row() < map.max_buffer_row() {
*point.column_mut() = 0; *new_point.row_mut() += 1;
*new_point.column_mut() = 0;
} }
point = let new_point = movement::find_boundary_exclusive(
movement::find_boundary_exclusive(map, point, FindRange::MultiLine, |left, right| { map,
new_point,
FindRange::MultiLine,
|left, right| {
let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation); let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation); let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
left_kind != right_kind && left_kind != CharKind::Whitespace left_kind != right_kind && left_kind != CharKind::Whitespace
}); },
point = map.clip_point(point, Bias::Left); );
let new_point = map.clip_point(new_point, Bias::Left);
if point == new_point {
break;
}
point = new_point;
} }
point point
} }
@ -821,13 +840,17 @@ fn previous_word_start(
for _ in 0..times { for _ in 0..times {
// This works even though find_preceding_boundary is called for every character in the line containing // This works even though find_preceding_boundary is called for every character in the line containing
// cursor because the newline is checked only once. // cursor because the newline is checked only once.
point = let new_point =
movement::find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| { movement::find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| {
let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation); let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation); let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
(left_kind != right_kind && !right.is_whitespace()) || left == '\n' (left_kind != right_kind && !right.is_whitespace()) || left == '\n'
}); });
if point == new_point {
break;
}
point = new_point;
} }
point point
} }
@ -967,10 +990,14 @@ fn find_forward(
for _ in 0..times { for _ in 0..times {
found = false; found = false;
to = find_boundary(map, to, FindRange::SingleLine, |_, right| { let new_to = find_boundary(map, to, FindRange::SingleLine, |_, right| {
found = right == target; found = right == target;
found found
}); });
if to == new_to {
break;
}
to = new_to;
} }
if found { if found {
@ -995,7 +1022,12 @@ fn find_backward(
let mut to = from; let mut to = from;
for _ in 0..times { for _ in 0..times {
to = find_preceding_boundary(map, to, FindRange::SingleLine, |_, right| right == target); let new_to =
find_preceding_boundary(map, to, FindRange::SingleLine, |_, right| right == target);
if to == new_to {
break;
}
to = new_to;
} }
if map.buffer_snapshot.chars_at(to.to_point(map)).next() == Some(target) { if map.buffer_snapshot.chars_at(to.to_point(map)).next() == Some(target) {