Allow moving by subword

pull/8147/head
Jefta 1 year ago
parent c145999bff
commit de97e9ebef

@ -197,13 +197,31 @@ pub fn move_prev_long_word_end(slice: RopeSlice, range: Range, count: usize) ->
word_move(slice, range, count, WordMotionTarget::PrevLongWordEnd) word_move(slice, range, count, WordMotionTarget::PrevLongWordEnd)
} }
pub fn move_next_sub_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
word_move(slice, range, count, WordMotionTarget::NextSubWordStart)
}
pub fn move_next_sub_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
word_move(slice, range, count, WordMotionTarget::NextSubWordEnd)
}
pub fn move_prev_sub_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
word_move(slice, range, count, WordMotionTarget::PrevSubWordStart)
}
pub fn move_prev_sub_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
word_move(slice, range, count, WordMotionTarget::PrevSubWordEnd)
}
fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range { fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
let is_prev = matches!( let is_prev = matches!(
target, target,
WordMotionTarget::PrevWordStart WordMotionTarget::PrevWordStart
| WordMotionTarget::PrevLongWordStart | WordMotionTarget::PrevLongWordStart
| WordMotionTarget::PrevSubWordStart
| WordMotionTarget::PrevWordEnd | WordMotionTarget::PrevWordEnd
| WordMotionTarget::PrevLongWordEnd | WordMotionTarget::PrevLongWordEnd
| WordMotionTarget::PrevSubWordEnd
); );
// Special-case early-out. // Special-case early-out.
@ -383,6 +401,12 @@ pub enum WordMotionTarget {
NextLongWordEnd, NextLongWordEnd,
PrevLongWordStart, PrevLongWordStart,
PrevLongWordEnd, PrevLongWordEnd,
// A sub word is similar to a regular word, except it is also delimited by
// underscores and transitions from lowercase to uppercase.
NextSubWordStart,
NextSubWordEnd,
PrevSubWordStart,
PrevSubWordEnd,
} }
pub trait CharHelpers { pub trait CharHelpers {
@ -398,8 +422,10 @@ impl CharHelpers for Chars<'_> {
target, target,
WordMotionTarget::PrevWordStart WordMotionTarget::PrevWordStart
| WordMotionTarget::PrevLongWordStart | WordMotionTarget::PrevLongWordStart
| WordMotionTarget::PrevSubWordStart
| WordMotionTarget::PrevWordEnd | WordMotionTarget::PrevWordEnd
| WordMotionTarget::PrevLongWordEnd | WordMotionTarget::PrevLongWordEnd
| WordMotionTarget::PrevSubWordEnd
); );
// Reverse the iterator if needed for the motion direction. // Reverse the iterator if needed for the motion direction.
@ -476,6 +502,25 @@ fn is_long_word_boundary(a: char, b: char) -> bool {
} }
} }
fn is_sub_word_boundary(a: char, b: char, dir: Direction) -> bool {
match (categorize_char(a), categorize_char(b)) {
(CharCategory::Word, CharCategory::Word) => {
if (a == '_') != (b == '_') {
return true;
}
// Subword boundaries are directional: in 'fooBar', there is a
// boundary between 'o' and 'B', but not between 'B' and 'a'.
match dir {
Direction::Forward => a.is_lowercase() && b.is_uppercase(),
Direction::Backward => a.is_uppercase() && b.is_lowercase(),
}
}
(a, b) if a != b => true,
_ => false,
}
}
fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> bool { fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> bool {
match target { match target {
WordMotionTarget::NextWordStart | WordMotionTarget::PrevWordEnd => { WordMotionTarget::NextWordStart | WordMotionTarget::PrevWordEnd => {
@ -494,6 +539,22 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
is_long_word_boundary(prev_ch, next_ch) is_long_word_boundary(prev_ch, next_ch)
&& (!prev_ch.is_whitespace() || char_is_line_ending(next_ch)) && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
} }
WordMotionTarget::NextSubWordStart => {
is_sub_word_boundary(prev_ch, next_ch, Direction::Forward)
&& (char_is_line_ending(next_ch) || !(next_ch.is_whitespace() || next_ch == '_'))
}
WordMotionTarget::PrevSubWordEnd => {
is_sub_word_boundary(prev_ch, next_ch, Direction::Backward)
&& (char_is_line_ending(next_ch) || !(next_ch.is_whitespace() || next_ch == '_'))
}
WordMotionTarget::NextSubWordEnd => {
is_sub_word_boundary(prev_ch, next_ch, Direction::Forward)
&& (!(prev_ch.is_whitespace() || prev_ch == '_') || char_is_line_ending(next_ch))
}
WordMotionTarget::PrevSubWordStart => {
is_sub_word_boundary(prev_ch, next_ch, Direction::Backward)
&& (!(prev_ch.is_whitespace() || prev_ch == '_') || char_is_line_ending(next_ch))
}
} }
} }

@ -249,6 +249,10 @@ impl MappableCommand {
move_prev_long_word_start, "Move to start of previous long word", move_prev_long_word_start, "Move to start of previous long word",
move_next_long_word_end, "Move to end of next long word", move_next_long_word_end, "Move to end of next long word",
move_prev_long_word_end, "Move to end of previous long word", move_prev_long_word_end, "Move to end of previous long word",
move_next_sub_word_start, "Move to start of next sub word",
move_prev_sub_word_start, "Move to start of previous sub word",
move_next_sub_word_end, "Move to end of next sub word",
move_prev_sub_word_end, "Move to end of previous sub word",
move_parent_node_end, "Move to end of the parent node", move_parent_node_end, "Move to end of the parent node",
move_parent_node_start, "Move to beginning of the parent node", move_parent_node_start, "Move to beginning of the parent node",
extend_next_word_start, "Extend to start of next word", extend_next_word_start, "Extend to start of next word",
@ -259,6 +263,10 @@ impl MappableCommand {
extend_prev_long_word_start, "Extend to start of previous long word", extend_prev_long_word_start, "Extend to start of previous long word",
extend_next_long_word_end, "Extend to end of next long word", extend_next_long_word_end, "Extend to end of next long word",
extend_prev_long_word_end, "Extend to end of prev long word", extend_prev_long_word_end, "Extend to end of prev long word",
extend_next_sub_word_start, "Extend to start of next sub word",
extend_prev_sub_word_start, "Extend to start of previous sub word",
extend_next_sub_word_end, "Extend to end of next sub word",
extend_prev_sub_word_end, "Extend to end of prev sub word",
extend_parent_node_end, "Extend to end of the parent node", extend_parent_node_end, "Extend to end of the parent node",
extend_parent_node_start, "Extend to beginning of the parent node", extend_parent_node_start, "Extend to beginning of the parent node",
find_till_char, "Move till next occurrence of char", find_till_char, "Move till next occurrence of char",
@ -1094,6 +1102,22 @@ fn move_next_long_word_end(cx: &mut Context) {
move_word_impl(cx, movement::move_next_long_word_end) move_word_impl(cx, movement::move_next_long_word_end)
} }
fn move_next_sub_word_start(cx: &mut Context) {
move_word_impl(cx, movement::move_next_sub_word_start)
}
fn move_prev_sub_word_start(cx: &mut Context) {
move_word_impl(cx, movement::move_prev_sub_word_start)
}
fn move_prev_sub_word_end(cx: &mut Context) {
move_word_impl(cx, movement::move_prev_sub_word_end)
}
fn move_next_sub_word_end(cx: &mut Context) {
move_word_impl(cx, movement::move_next_sub_word_end)
}
fn goto_para_impl<F>(cx: &mut Context, move_fn: F) fn goto_para_impl<F>(cx: &mut Context, move_fn: F)
where where
F: Fn(RopeSlice, Range, usize, Movement) -> Range + 'static, F: Fn(RopeSlice, Range, usize, Movement) -> Range + 'static,
@ -1307,6 +1331,22 @@ fn extend_next_long_word_end(cx: &mut Context) {
extend_word_impl(cx, movement::move_next_long_word_end) extend_word_impl(cx, movement::move_next_long_word_end)
} }
fn extend_next_sub_word_start(cx: &mut Context) {
extend_word_impl(cx, movement::move_next_sub_word_start)
}
fn extend_prev_sub_word_start(cx: &mut Context) {
extend_word_impl(cx, movement::move_prev_sub_word_start)
}
fn extend_prev_sub_word_end(cx: &mut Context) {
extend_word_impl(cx, movement::move_prev_sub_word_end)
}
fn extend_next_sub_word_end(cx: &mut Context) {
extend_word_impl(cx, movement::move_next_sub_word_end)
}
/// Separate branch to find_char designed only for `<ret>` char. /// Separate branch to find_char designed only for `<ret>` char.
// //
// This is necessary because the one document can have different line endings inside. And we // This is necessary because the one document can have different line endings inside. And we

Loading…
Cancel
Save