#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorMove {
Forward,
Back,
Up,
Down,
Head,
End,
Top,
Bottom,
WordForward,
WordBack,
UpBy(usize),
DownBy(usize),
#[cfg(test)]
Jump(usize, usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CharClass {
Space,
Word,
Punctuation,
}
fn class_of(c: char) -> CharClass {
if c.is_whitespace() {
CharClass::Space
} else if c.is_alphanumeric() || c == '_' {
CharClass::Word
} else {
CharClass::Punctuation
}
}
pub(super) fn next_word_start(chars: &[char], col: usize) -> usize {
let len = chars.len();
let mut i = col.min(len);
if i == len {
return len;
}
let start_class = class_of(chars[i]);
if start_class != CharClass::Space {
while i < len && class_of(chars[i]) == start_class {
i += 1;
}
}
while i < len && class_of(chars[i]) == CharClass::Space {
i += 1;
}
i
}
pub(super) fn prev_word_start(chars: &[char], col: usize) -> usize {
let mut i = col.min(chars.len());
while i > 0 && class_of(chars[i - 1]) == CharClass::Space {
i -= 1;
}
if i == 0 {
return 0;
}
let class = class_of(chars[i - 1]);
while i > 0 && class_of(chars[i - 1]) == class {
i -= 1;
}
i
}
#[cfg(test)]
mod tests {
use super::*;
fn chars(s: &str) -> Vec<char> {
s.chars().collect()
}
#[test]
fn next_word_start_skips_run_then_whitespace() {
let c = chars("hello world");
assert_eq!(next_word_start(&c, 0), 6);
assert_eq!(next_word_start(&c, 3), 6);
assert_eq!(next_word_start(&c, 6), 11);
assert_eq!(next_word_start(&c, 11), 11);
}
#[test]
fn next_word_start_treats_punctuation_as_its_own_run() {
let c = chars("a == b");
assert_eq!(next_word_start(&c, 0), 2);
assert_eq!(next_word_start(&c, 2), 5);
}
#[test]
fn prev_word_start_walks_back_over_whitespace() {
let c = chars("hello world");
assert_eq!(prev_word_start(&c, 11), 6);
assert_eq!(prev_word_start(&c, 6), 0);
assert_eq!(prev_word_start(&c, 0), 0);
assert_eq!(prev_word_start(&c, 8), 6);
}
#[test]
fn word_moves_handle_empty_lines() {
let c: Vec<char> = Vec::new();
assert_eq!(next_word_start(&c, 0), 0);
assert_eq!(prev_word_start(&c, 0), 0);
}
#[test]
fn word_moves_are_unicode_aware() {
let c = chars("héllo wörld");
assert_eq!(next_word_start(&c, 0), 6);
assert_eq!(prev_word_start(&c, 11), 6);
}
}