@@ -1715,18 +1715,14 @@ pub(crate) fn next_word_start(
point
}
-pub(crate) fn next_word_end(
+fn next_end_impl(
map: &DisplaySnapshot,
mut point: DisplayPoint,
- ignore_punctuation: bool,
times: usize,
allow_cross_newline: bool,
always_advance: bool,
+ mut is_boundary: impl FnMut(char, char) -> bool,
) -> DisplayPoint {
- let classifier = map
- .buffer_snapshot()
- .char_classifier_at(point.to_point(map))
- .ignore_punctuation(ignore_punctuation);
for _ in 0..times {
let mut need_next_char = false;
let new_point = if always_advance {
@@ -1739,8 +1735,6 @@ pub(crate) fn next_word_end(
new_point,
FindRange::MultiLine,
|left, right| {
- let left_kind = classifier.kind(left);
- let right_kind = classifier.kind(right);
let at_newline = right == '\n';
if !allow_cross_newline && at_newline {
@@ -1748,7 +1742,7 @@ pub(crate) fn next_word_end(
return true;
}
- left_kind != right_kind && left_kind != CharKind::Whitespace
+ is_boundary(left, right)
},
);
let new_point = if need_next_char {
@@ -1765,6 +1759,64 @@ pub(crate) fn next_word_end(
point
}
+pub(crate) fn next_word_end(
+ map: &DisplaySnapshot,
+ point: DisplayPoint,
+ ignore_punctuation: bool,
+ times: usize,
+ allow_cross_newline: bool,
+ always_advance: bool,
+) -> DisplayPoint {
+ let classifier = map
+ .buffer_snapshot()
+ .char_classifier_at(point.to_point(map))
+ .ignore_punctuation(ignore_punctuation);
+
+ next_end_impl(
+ map,
+ point,
+ times,
+ allow_cross_newline,
+ always_advance,
+ |left, right| {
+ let left_kind = classifier.kind(left);
+ let right_kind = classifier.kind(right);
+ left_kind != right_kind && left_kind != CharKind::Whitespace
+ },
+ )
+}
+
+pub(crate) fn next_subword_end(
+ map: &DisplaySnapshot,
+ point: DisplayPoint,
+ ignore_punctuation: bool,
+ times: usize,
+ allow_cross_newline: bool,
+) -> DisplayPoint {
+ let classifier = map
+ .buffer_snapshot()
+ .char_classifier_at(point.to_point(map))
+ .ignore_punctuation(ignore_punctuation);
+
+ next_end_impl(
+ map,
+ point,
+ times,
+ allow_cross_newline,
+ true,
+ |left, right| {
+ let left_kind = classifier.kind(left);
+ let right_kind = classifier.kind(right);
+ let is_stopping_punct = |c: char| ".\"'{}[]()<>".contains(c);
+ let found_subword_end = is_subword_end(left, right, "_-");
+ let is_word_end = (left_kind != right_kind)
+ && (!left.is_ascii_punctuation() || is_stopping_punct(left));
+
+ !left.is_whitespace() && (is_word_end || found_subword_end)
+ },
+ )
+}
+
fn previous_word_start(
map: &DisplaySnapshot,
mut point: DisplayPoint,
@@ -1870,11 +1922,10 @@ fn next_subword_start(
let left_kind = classifier.kind(left);
let right_kind = classifier.kind(right);
let at_newline = right == '\n';
-
let is_stopping_punct = |c: char| "\"'{}[]()<>".contains(c);
+ let found_subword_start = is_subword_start(left, right, "._-");
let is_word_start = (left_kind != right_kind)
&& (!right.is_ascii_punctuation() || is_stopping_punct(right));
- let found_subword_start = is_subword_start(left, right, "._-");
let found = (!right.is_whitespace() && (is_word_start || found_subword_start))
|| at_newline && crossed_newline
|| at_newline && left == '\n'; // Prevents skipping repeated empty lines
@@ -1890,60 +1941,6 @@ fn next_subword_start(
point
}
-pub(crate) fn next_subword_end(
- map: &DisplaySnapshot,
- mut point: DisplayPoint,
- ignore_punctuation: bool,
- times: usize,
- allow_cross_newline: bool,
-) -> DisplayPoint {
- let classifier = map
- .buffer_snapshot()
- .char_classifier_at(point.to_point(map))
- .ignore_punctuation(ignore_punctuation);
- for _ in 0..times {
- let new_point = next_char(map, point, allow_cross_newline);
-
- let mut crossed_newline = false;
- let mut need_backtrack = false;
- let new_point =
- movement::find_boundary(map, new_point, FindRange::MultiLine, |left, right| {
- let left_kind = classifier.kind(left);
- let right_kind = classifier.kind(right);
- let at_newline = right == '\n';
-
- if !allow_cross_newline && at_newline {
- return true;
- }
-
- let is_stopping_punct = |c: char| ".\"'{}[]()<>".contains(c);
- let is_word_end = (left_kind != right_kind)
- && (!left.is_ascii_punctuation() || is_stopping_punct(left));
- let found_subword_end = is_subword_end(left, right, "_-");
-
- let found =
- !left.is_whitespace() && !at_newline && (is_word_end || found_subword_end);
-
- if found {
- need_backtrack = true;
- }
-
- crossed_newline |= at_newline;
- found
- });
- let mut new_point = map.clip_point(new_point, Bias::Left);
- if need_backtrack {
- *new_point.column_mut() -= 1;
- }
- let new_point = map.clip_point(new_point, Bias::Left);
- if point == new_point {
- break;
- }
- point = new_point;
- }
- point
-}
-
fn previous_subword_start(
map: &DisplaySnapshot,
mut point: DisplayPoint,
@@ -1950,6 +1950,19 @@ mod test {
cx.assert_binding_normal("e", indoc! {"ˇassert_binding"}, indoc! {"asserˇt_binding"});
+ // Subword end should stop at EOL
+ cx.assert_binding_normal("e", indoc! {"foo_bˇar\nbaz"}, indoc! {"foo_baˇr\nbaz"});
+
+ // Already at subword end, should move to next subword on next line
+ cx.assert_binding_normal(
+ "e",
+ indoc! {"foo_barˇ\nbaz_qux"},
+ indoc! {"foo_bar\nbaˇz_qux"},
+ );
+
+ // CamelCase at EOL
+ cx.assert_binding_normal("e", indoc! {"fooˇBar\nbaz"}, indoc! {"fooBaˇr\nbaz"});
+
cx.assert_binding_normal("b", indoc! {"assert_ˇbinding"}, indoc! {"ˇassert_binding"});
cx.assert_binding_normal(