diff --git a/CHANGELOG.md b/CHANGELOG.md index cb8a83a..3584594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,16 @@ Unreleased ========== -- feat: add `.` command to repeat the last change (dot-repeat) + +- fix: count hyphen as punctuation Released -------- +0.11.5 - 2026-07-18 +=================== + +- feat: add `.` command to repeat the last change (dot-repeat) + 0.11.4 - 2026-07-18 =================== - feat: add bindings for delete word and delete big word @acerv diff --git a/src/actions/motion.rs b/src/actions/motion.rs index e88a61b..6992cfd 100644 --- a/src/actions/motion.rs +++ b/src/actions/motion.rs @@ -474,7 +474,8 @@ pub(crate) enum CharacterClass { impl From<&char> for CharacterClass { fn from(value: &char) -> Self { - if value.is_ascii_alphanumeric() { + // Underscore counts as a word character (matching Vim's `iskeyword`), + if value.is_ascii_alphanumeric() || *value == '_' { return Self::Alphanumeric; } if value.is_ascii_punctuation() { diff --git a/src/actions/select.rs b/src/actions/select.rs index fb0ba42..8f32be1 100644 --- a/src/actions/select.rs +++ b/src/actions/select.rs @@ -527,6 +527,32 @@ mod tests { assert_eq!(state.mode, EditorMode::Normal); } + #[test] + fn test_select_inner_word_includes_underscores() { + // Underscore is a word character, so `viw` on the `B` of `BY` selects + // the whole identifier (matching Vim/nvim). + let mut state = EditorState::new(Lines::from("ORDER_BY_FIELD")); + state.cursor = Index2::new(0, 6); + + SelectInnerWord.execute(&mut state); + + let want = Selection::new(Index2::new(0, 0), Index2::new(0, 13)); + assert_eq!(state.selection.unwrap(), want); + } + + #[test] + fn test_select_inner_word_stops_at_hyphen() { + // A hyphen is punctuation, so `viw` on the `B` of `BY` selects only + // `BY` (matching Vim/nvim). + let mut state = EditorState::new(Lines::from("ORDER-BY")); + state.cursor = Index2::new(0, 6); + + SelectInnerWord.execute(&mut state); + + let want = Selection::new(Index2::new(0, 6), Index2::new(0, 7)); + assert_eq!(state.selection.unwrap(), want); + } + #[test] fn test_select_inner_big_word() { let mut state = EditorState::new(Lines::from("foo.bar baz"));