use unicode_segmentation::UnicodeSegmentation;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Class {
Whitespace,
Word,
Punctuation,
}
fn class(grapheme: &str) -> Class {
let Some(c) = grapheme.chars().next() else {
return Class::Whitespace;
};
if c.is_whitespace() {
Class::Whitespace
} else if c.is_alphanumeric() || c == '_' {
Class::Word
} else {
Class::Punctuation
}
}
fn classes(line: &str) -> Vec<Class> {
line.graphemes(true).map(class).collect()
}
pub fn next_word_boundary(line: &str, col: usize) -> usize {
let classes = classes(line);
let len = classes.len();
let mut i = col.min(len);
while i < len && classes[i] == Class::Whitespace {
i += 1;
}
if i >= len {
return len;
}
let run = classes[i];
while i < len && classes[i] == run {
i += 1;
}
i
}
pub fn previous_word_boundary(line: &str, col: usize) -> usize {
let classes = classes(line);
let mut i = col.min(classes.len());
while i > 0 && classes[i - 1] == Class::Whitespace {
i -= 1;
}
if i == 0 {
return 0;
}
let run = classes[i - 1];
while i > 0 && classes[i - 1] == run {
i -= 1;
}
i
}
pub fn word_at(line: &str, col: usize) -> Option<(usize, usize)> {
let classes = classes(line);
let len = classes.len();
if len == 0 {
return None;
}
let probe = if col < len { col } else { len - 1 };
let target = classes[probe];
if target == Class::Whitespace {
return None;
}
let mut start = probe;
while start > 0 && classes[start - 1] == target {
start -= 1;
}
let mut end = probe;
while end < len && classes[end] == target {
end += 1;
}
Some((start, end))
}