mirror of
https://github.com/helix-editor/helix
synced 2024-05-29 18:56:05 +02:00
Allow moving by subword
This commit is contained in:
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
let is_prev = matches!(
|
||||
target,
|
||||
WordMotionTarget::PrevWordStart
|
||||
| WordMotionTarget::PrevLongWordStart
|
||||
| WordMotionTarget::PrevSubWordStart
|
||||
| WordMotionTarget::PrevWordEnd
|
||||
| WordMotionTarget::PrevLongWordEnd
|
||||
| WordMotionTarget::PrevSubWordEnd
|
||||
);
|
||||
|
||||
// Special-case early-out.
|
||||
|
@ -383,6 +401,12 @@ pub enum WordMotionTarget {
|
|||
NextLongWordEnd,
|
||||
PrevLongWordStart,
|
||||
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 {
|
||||
|
@ -398,8 +422,10 @@ fn range_to_target(&mut self, target: WordMotionTarget, origin: Range) -> Range
|
|||
target,
|
||||
WordMotionTarget::PrevWordStart
|
||||
| WordMotionTarget::PrevLongWordStart
|
||||
| WordMotionTarget::PrevSubWordStart
|
||||
| WordMotionTarget::PrevWordEnd
|
||||
| WordMotionTarget::PrevLongWordEnd
|
||||
| WordMotionTarget::PrevSubWordEnd
|
||||
);
|
||||
|
||||
// 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 {
|
||||
match target {
|
||||
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)
|
||||
&& (!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 @@ pub fn doc(&self) -> &str {
|
|||
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_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_start, "Move to beginning of the parent node",
|
||||
extend_next_word_start, "Extend to start of next word",
|
||||
|
@ -259,6 +263,10 @@ pub fn doc(&self) -> &str {
|
|||
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_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_start, "Extend to beginning of the parent node",
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
where
|
||||
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)
|
||||
}
|
||||
|
||||
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.
|
||||
//
|
||||
// This is necessary because the one document can have different line endings inside. And we
|
||||
|
|
Loading…
Reference in New Issue