diff options
Diffstat (limited to 'helix-core')
-rw-r--r-- | helix-core/src/movement.rs | 47 |
1 files changed, 43 insertions, 4 deletions
diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index bfceb4ef..c73b218c 100644 --- a/helix-core/src/movement.rs +++ b/helix-core/src/movement.rs @@ -104,6 +104,18 @@ pub fn move_prev_word_start(slice: RopeSlice, range: Range, count: usize) -> Ran word_move(slice, range, count, WordMotionTarget::PrevWordStart) } +pub fn move_next_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range { + word_move(slice, range, count, WordMotionTarget::NextLongWordStart) +} + +pub fn move_next_long_word_end(slice: RopeSlice, range: Range, count: usize) -> Range { + word_move(slice, range, count, WordMotionTarget::NextLongWordEnd) +} + +pub fn move_prev_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range { + word_move(slice, range, count, WordMotionTarget::PrevLongWordStart) +} + fn word_move(slice: RopeSlice, mut range: Range, count: usize, target: WordMotionTarget) -> Range { (0..count).fold(range, |range, _| { slice.chars_at(range.head).range_to_target(target, range) @@ -150,6 +162,12 @@ pub enum WordMotionTarget { NextWordStart, NextWordEnd, PrevWordStart, + // A "Long word" (also known as a WORD in vim/kakoune) is strictly + // delimited by whitespace, and can consist of punctuation as well + // as alphanumerics. + NextLongWordStart, + NextLongWordEnd, + PrevLongWordStart, } pub trait CharHelpers { @@ -167,7 +185,7 @@ impl CharHelpers for Chars<'_> { let range = origin; // Characters are iterated forward or backwards depending on the motion direction. let characters: Box<dyn Iterator<Item = char>> = match target { - WordMotionTarget::PrevWordStart => { + WordMotionTarget::PrevWordStart | WordMotionTarget::PrevLongWordStart => { self.next(); Box::new(from_fn(|| self.prev())) } @@ -176,7 +194,7 @@ impl CharHelpers for Chars<'_> { // Index advancement also depends on the direction. let advance: &dyn Fn(&mut usize) = match target { - WordMotionTarget::PrevWordStart => &|u| *u = u.saturating_sub(1), + WordMotionTarget::PrevWordStart | WordMotionTarget::PrevLongWordStart => &|u| *u = u.saturating_sub(1), _ => &|u| *u += 1, }; @@ -229,6 +247,19 @@ impl CharHelpers for Chars<'_> { } } +fn is_word_boundary(a: char, b: char) -> bool { + categorize_char(a) != categorize_char(b) +} + +fn is_long_word_boundary(a: char, b: char) -> bool { + match (categorize_char(a), categorize_char(b)) { + (CharCategory::Word, CharCategory::Punctuation) + | (CharCategory::Punctuation, CharCategory::Word) => false, + (a, b) if a != b => true, + _ => false + } +} + fn reached_target(target: WordMotionTarget, peek: char, next_peek: Option<&char>) -> bool { let next_peek = match next_peek { Some(next_peek) => next_peek, @@ -237,11 +268,19 @@ fn reached_target(target: WordMotionTarget, peek: char, next_peek: Option<&char> match target { WordMotionTarget::NextWordStart => { - ((categorize_char(peek) != categorize_char(*next_peek)) + (is_word_boundary(peek, *next_peek) && (char_is_line_ending(*next_peek) || !next_peek.is_whitespace())) } WordMotionTarget::NextWordEnd | WordMotionTarget::PrevWordStart => { - ((categorize_char(peek) != categorize_char(*next_peek)) + (is_word_boundary(peek, *next_peek) + && (!peek.is_whitespace() || char_is_line_ending(*next_peek))) + } + WordMotionTarget::NextLongWordStart => { + (is_long_word_boundary(peek, *next_peek) + && (char_is_line_ending(*next_peek) || !next_peek.is_whitespace())) + } + WordMotionTarget::NextLongWordEnd | WordMotionTarget::PrevLongWordStart => { + (is_long_word_boundary(peek, *next_peek) && (!peek.is_whitespace() || char_is_line_ending(*next_peek))) } } |