#[derive(PartialEq, Eq, Clone, Copy)]
enum CharKind {
Space,
Punct,
Other,
}
impl CharKind {
fn classify(c: char) -> Self {
if c.is_whitespace() {
Self::Space
} else if c.is_ascii_punctuation() {
Self::Punct
} else {
Self::Other
}
}
}
pub fn erase_word_backward(s: &mut String) {
let mut iter = s.char_indices().rev();
let Some((_, last)) = iter.next() else {
return;
};
let mut cur = CharKind::classify(last);
for (byte_idx, c) in iter {
let next = CharKind::classify(c);
if cur != CharKind::Space && next != cur {
s.truncate(byte_idx + c.len_utf8());
return;
}
cur = next;
}
s.clear();
}
#[cfg(test)]
mod tests {
use super::erase_word_backward;
fn erase(s: &str) -> String {
let mut s = s.to_string();
erase_word_backward(&mut s);
s
}
#[test]
fn respects_punctuation_boundary() {
assert_eq!(erase("something/another"), "something/");
assert_eq!(erase("abc.def"), "abc.");
assert_eq!(erase("foo-bar-baz"), "foo-bar-");
}
#[test]
fn deletes_punctuation_run_when_at_end() {
assert_eq!(erase("something/"), "something");
assert_eq!(erase("abc.."), "abc");
assert_eq!(erase("a---"), "a");
}
#[test]
fn absorbs_trailing_whitespace_into_word() {
assert_eq!(erase("foo bar"), "foo ");
assert_eq!(erase("foo "), "");
assert_eq!(erase("foo "), "");
}
#[test]
fn handles_edges() {
assert_eq!(erase(""), "");
assert_eq!(erase("a"), "");
assert_eq!(erase(" "), "");
assert_eq!(erase("abc"), "");
}
#[test]
fn handles_unicode() {
assert_eq!(erase("naïve/résumé"), "naïve/");
assert_eq!(erase("café"), "");
}
}